use std::collections::HashMap;
use std::num::NonZeroUsize;
use crate::ParseResult;
use crate::errors::{ErrorDetails, ErrorKind, FormError, ModuleError, Severity, Span};
use crate::files::common::{
Creatable, Exposed, FileAttributes, FileFormatVersion, NameSpace, ObjectReference,
PreDeclaredID, Properties,
};
use crate::io::{SourceFile, SourceStream};
use crate::language::{
CheckBoxProperties, ComboBoxProperties, CommandButtonProperties, Control, ControlKind,
DataProperties, DirListBoxProperties, DriveListBoxProperties, FileListBoxProperties, Font,
Form, FormProperties, FormRoot, FrameProperties, LabelProperties, ListBoxProperties, MDIForm,
MDIFormProperties, MenuControl, MenuProperties, OptionButtonProperties, PictureBoxProperties,
PropertyGroup, TextBoxProperties, Token,
};
use crate::lexer::{TokenStream, tokenize};
use crate::parsers::SyntaxKind;
use either::Either;
use rowan::{GreenNode, GreenNodeBuilder, Language};
use serde::Serialize;
mod assignment;
mod attribute_statements;
mod declarations;
mod deftype_statements;
mod enum_statements;
mod for_statements;
mod function_statements;
mod helpers;
mod if_statements;
mod label_statements;
mod loop_statements;
mod navigation;
mod option_statements;
mod parameters;
mod properties;
mod property_statements;
mod select_statements;
mod sub_statements;
mod type_statements;
pub mod visitor;
pub use navigation::CstNode;
pub use visitor::{Visitor, VisitorMut, walk_node, walk_node_mut};
const MAX_PROPERTY_GROUP_DEPTH: usize = 100;
const MAX_CONTROL_DEPTH: usize = 1000;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, Hash)]
pub struct SerializableTree {
pub root: CstNode,
}
pub(crate) fn serialize_cst<S>(cst: &ConcreteSyntaxTree, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
cst.to_serializable().serialize(serializer)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VB6Language {}
impl Language for VB6Language {
type Kind = SyntaxKind;
fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
SyntaxKind::from_raw(raw)
}
fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
kind.to_raw()
}
}
fn extract_property_groups(groups: &[PropertyGroup]) -> ExtractedGroups {
let mut font = None;
for group in groups
.iter()
.filter(|g| g.name.eq_ignore_ascii_case("Font"))
{
if let Ok(f) = Font::try_from(group) {
font = Some(f);
}
}
ExtractedGroups { font }
}
struct ExtractedGroups {
font: Option<Font>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConcreteSyntaxTree {
root: GreenNode,
}
impl ConcreteSyntaxTree {
fn new(root: GreenNode) -> Self {
Self { root }
}
#[must_use]
pub fn from_source(source_file: &SourceFile) -> ParseResult<'_, Self> {
Self::from_text(
source_file.file_name().to_string(),
source_file.source_stream().contents,
)
}
pub fn from_text<S>(file_name: S, contents: &str) -> ParseResult<'_, Self>
where
S: Into<String>,
{
let mut source_stream = SourceStream::new(file_name.into(), contents);
let token_stream_result = tokenize(&mut source_stream);
let (token_stream_opt, mut failures) = token_stream_result.unpack();
let Some(token_stream) = token_stream_opt else {
return ParseResult::new(None, failures);
};
let mut parser = Parser::new(token_stream);
parser.set_source_content(contents);
let (cst, mut parser_failures, recovery_events) = parser.parse_root();
failures.append(&mut parser_failures);
ParseResult::new(Some(cst), failures).with_recovery_events(recovery_events)
}
#[must_use]
pub fn root_kind(&self) -> SyntaxKind {
SyntaxKind::from_raw(self.root.kind())
}
#[must_use]
pub fn to_serializable(&self) -> SerializableTree {
SerializableTree {
root: self.to_root_node(),
}
}
#[must_use]
pub fn to_root_node(&self) -> CstNode {
CstNode::new(SyntaxKind::Root, self.text(), false, self.children())
}
#[must_use]
pub fn error_recovery_ranges(&self) -> Vec<NodeRange> {
let syntax_node = rowan::SyntaxNode::<VB6Language>::new_root(self.root.clone());
syntax_node
.descendants()
.filter(|node| node.kind() == SyntaxKind::ErrorRecovery)
.map(|node| {
let range = node.text_range();
NodeRange {
start: range.start().into(),
end: range.end().into(),
}
})
.collect()
}
#[must_use]
pub fn without_kinds(&self, kinds_to_remove: &[SyntaxKind]) -> Self {
if kinds_to_remove.is_empty() {
return self.clone();
}
let mut remove_lookup = vec![false; SyntaxKind::ErrorRecovery as usize + 1];
for kind in kinds_to_remove {
remove_lookup[*kind as usize] = true;
}
let is_removed_kind =
|kind: SyntaxKind| remove_lookup.get(kind as usize).copied().unwrap_or(false);
let syntax_node = rowan::SyntaxNode::<VB6Language>::new_root(self.root.clone());
let has_matching_root_child = syntax_node.children_with_tokens().any(|child| {
let child_kind = match &child {
rowan::NodeOrToken::Node(node) => node.kind(),
rowan::NodeOrToken::Token(token) => token.kind(),
};
is_removed_kind(child_kind)
});
if !has_matching_root_child {
return self.clone();
}
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::Root.to_raw());
for child in syntax_node.children_with_tokens() {
let child_kind = match &child {
rowan::NodeOrToken::Node(node) => node.kind(),
rowan::NodeOrToken::Token(token) => token.kind(),
};
if is_removed_kind(child_kind) {
continue;
}
Self::clone_node_or_token(&mut builder, child);
}
builder.finish_node();
let new_root = builder.finish();
Self::new(new_root)
}
fn clone_node_or_token(
builder: &mut GreenNodeBuilder<'static>,
node_or_token: rowan::NodeOrToken<
rowan::SyntaxNode<VB6Language>,
rowan::SyntaxToken<VB6Language>,
>,
) {
match node_or_token {
rowan::NodeOrToken::Node(node) => {
builder.start_node(node.kind().to_raw());
for child in node.children_with_tokens() {
Self::clone_node_or_token(builder, child);
}
builder.finish_node();
}
rowan::NodeOrToken::Token(token) => {
builder.token(token.kind().to_raw(), token.text());
}
}
}
}
#[must_use]
pub fn parse(tokens: TokenStream) -> ConcreteSyntaxTree {
let parser = Parser::new(tokens);
parser.parse_root().0
}
#[derive(Debug)]
struct ControlParseFrame {
control_type: String,
control_name: String,
properties: Properties,
child_controls: Vec<Control>,
menus: Vec<MenuControl>,
property_groups: Vec<PropertyGroup>,
}
impl ControlParseFrame {
fn new(control_type: String, control_name: String) -> Self {
Self {
control_type,
control_name,
properties: Properties::new(),
child_controls: Vec::new(),
menus: Vec::new(),
property_groups: Vec::new(),
}
}
fn into_control(self) -> Control {
let tag = self.properties.get("Tag").cloned().unwrap_or_default();
let index = self
.properties
.get("Index")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let kind = Parser::build_control_kind(
&self.control_type,
self.properties,
self.child_controls,
self.menus,
self.property_groups,
);
Control::new(self.control_name, tag, index, kind)
}
}
#[derive(Debug)]
struct PropertyGroupFrame {
name: String,
guid: Option<uuid::Uuid>,
properties: HashMap<String, Either<String, PropertyGroup>>,
}
impl PropertyGroupFrame {
fn new(name: String, guid: Option<uuid::Uuid>) -> Self {
Self {
name,
guid,
properties: HashMap::new(),
}
}
fn into_property_group(self) -> PropertyGroup {
PropertyGroup {
name: self.name,
guid: self.guid,
properties: self.properties,
}
}
}
const MAX_STATEMENT_DEPTH: usize = 500;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ControlFlowFrameType {
StatementList,
IfStatement,
ForStatement,
SelectCase,
WhileStatement,
DoStatement,
WithStatement,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecoveryStrategy {
SingleToken,
ToNewline,
ProcedureTerminator,
}
#[derive(Debug, Clone)]
pub struct RecoveryEvent {
pub id: usize,
pub expected: Vec<String>,
pub found: Vec<Token>,
pub strategy: RecoveryStrategy,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeRange {
pub start: u32,
pub end: u32,
}
enum ControlFlowFrame {
StatementList {
depth: usize,
context: StatementListContext,
started: bool,
},
IfStatement { phase: IfPhase, depth: usize },
ForStatement {
phase: ForPhase,
is_for_each: bool,
depth: usize,
},
SelectCase { phase: SelectPhase, depth: usize },
WhileStatement { phase: WhilePhase, depth: usize },
DoStatement { phase: DoPhase, depth: usize },
WithStatement { phase: WithPhase, depth: usize },
}
#[derive(Copy, Clone)]
enum StatementListContext {
TopLevel,
IfThenBody,
ElseIfBody,
ElseBody,
ForBody,
SelectCaseBody,
WhileBody,
DoBody,
WithBody,
}
#[derive(Copy, Clone)]
enum IfPhase {
Start,
ThenBody,
CheckElseIf,
ElseIfBody,
CheckElse,
ElseBody,
Finish,
}
#[derive(Copy, Clone)]
enum ForPhase {
Start,
Body,
Finish,
}
#[derive(Copy, Clone)]
enum SelectPhase {
Start,
CaseClause,
CaseBody,
CheckNextCase,
CaseElseBody,
Finish,
}
#[derive(Copy, Clone)]
enum WhilePhase {
Start,
Body,
Finish,
}
#[derive(Copy, Clone)]
enum DoPhase {
Start,
Body,
Finish,
}
#[derive(Copy, Clone)]
enum WithPhase {
Start,
Body,
Finish,
}
impl ControlFlowFrame {
fn frame_type(&self) -> ControlFlowFrameType {
match self {
ControlFlowFrame::StatementList { .. } => ControlFlowFrameType::StatementList,
ControlFlowFrame::IfStatement { .. } => ControlFlowFrameType::IfStatement,
ControlFlowFrame::ForStatement { .. } => ControlFlowFrameType::ForStatement,
ControlFlowFrame::SelectCase { .. } => ControlFlowFrameType::SelectCase,
ControlFlowFrame::WhileStatement { .. } => ControlFlowFrameType::WhileStatement,
ControlFlowFrame::DoStatement { .. } => ControlFlowFrameType::DoStatement,
ControlFlowFrame::WithStatement { .. } => ControlFlowFrameType::WithStatement,
}
}
}
pub(crate) struct Parser<'a> {
pub(crate) tokens: Vec<(&'a str, Token)>,
pub(crate) pos: usize,
pub(crate) builder: GreenNodeBuilder<'static>,
pub(crate) parsing_header: bool,
pub(crate) source_name: String,
pub(crate) source_content: &'a str,
pub(crate) failures: Vec<ErrorDetails<'a>>,
pub(crate) recovery_events: Vec<RecoveryEvent>,
pub(crate) next_recovery_event_id: usize,
}
impl<'a> Parser<'a> {
fn new(token_stream: TokenStream<'a>) -> Self {
let source_name = token_stream.file_name().to_string();
Parser {
tokens: token_stream.into_tokens(),
pos: 0,
builder: GreenNodeBuilder::new(),
parsing_header: true,
source_name,
source_content: "",
failures: Vec::new(),
recovery_events: Vec::new(),
next_recovery_event_id: 1,
}
}
fn set_source_content(&mut self, source_content: &'a str) {
self.source_content = source_content;
}
pub(crate) fn new_direct_extraction(tokens: Vec<(&'a str, Token)>, pos: usize) -> Self {
Parser {
tokens,
pos,
builder: GreenNodeBuilder::new(),
parsing_header: true,
source_name: String::new(),
source_content: "",
failures: Vec::new(),
recovery_events: Vec::new(),
next_recovery_event_id: 1,
}
}
fn current_span(&self) -> Span {
let mut offset: u32 = 0;
let mut line: u32 = 1;
for (text, token) in self.tokens.iter().take(self.pos) {
let text_len = u32::try_from(text.len()).unwrap_or(u32::MAX);
offset = offset.saturating_add(text_len);
if *token == Token::Newline {
line = line.saturating_add(1);
}
}
let (length, line_end) = if let Some((text, token)) = self.tokens.get(self.pos) {
let text_len = u32::try_from(text.len().max(1)).unwrap_or(u32::MAX);
if *token == Token::Newline {
(text_len, line.saturating_add(1))
} else {
(text_len, line)
}
} else {
(1, line)
};
Span::new(offset, line, line_end, length)
}
pub(crate) fn report_error<E>(&mut self, kind: E)
where
E: Into<ErrorKind>,
{
let span = self.current_span();
self.failures.push(ErrorDetails::basic(
self.source_name.clone().into_boxed_str(),
self.source_content,
span.offset,
span.line_start,
span.line_end,
kind,
Severity::Error,
));
}
pub(crate) fn into_tokens(self) -> Vec<(&'a str, Token)> {
self.tokens[self.pos..].to_vec()
}
pub(crate) fn skip_whitespace(&mut self) {
while self.at_token(Token::Whitespace) {
self.pos += 1;
}
}
pub(crate) fn skip_whitespace_and_newlines(&mut self) {
while self.at_token(Token::Whitespace) || self.at_token(Token::Newline) {
self.pos += 1;
}
}
pub(crate) fn consume_advance(&mut self) -> Option<(&'a str, Token)> {
if self.pos < self.tokens.len() {
let token = self.tokens[self.pos];
self.pos += 1;
Some(token)
} else {
None
}
}
pub(crate) fn parse_version_direct(&mut self) -> ParseResult<'a, FileFormatVersion> {
self.skip_whitespace();
if !self.at_token(Token::VersionKeyword) {
return ParseResult::new(None, Vec::new());
}
self.consume_advance(); self.skip_whitespace();
let version_result = if let Some((text, token)) = self.tokens.get(self.pos) {
match token {
Token::SingleLiteral | Token::DoubleLiteral | Token::IntegerLiteral => {
let version_str = text.trim();
self.consume_advance();
let parts: Vec<&str> = version_str.split('.').collect();
if parts.len() == 2 {
if let (Ok(major), Ok(minor)) =
(parts[0].parse::<u8>(), parts[1].parse::<u8>())
{
Some(FileFormatVersion { major, minor })
} else {
None
}
} else {
None
}
}
_ => None,
}
} else {
None
};
self.skip_whitespace();
if self.at_token(Token::ClassKeyword) {
self.consume_advance();
}
self.skip_whitespace_and_newlines();
ParseResult::new(version_result, Vec::new())
}
fn is_begin_property(&self) -> bool {
if let Some((text, token)) = self.tokens.get(self.pos) {
*token == Token::Identifier && text.eq_ignore_ascii_case("BeginProperty")
} else {
false
}
}
fn is_identifier_text(&self, target: &str) -> bool {
if let Some((text, token)) = self.tokens.get(self.pos) {
*token == Token::Identifier && text.eq_ignore_ascii_case(target)
} else {
false
}
}
fn control_to_menu(control: Control) -> MenuControl {
let (name, tag, index, kind) = control.into_parts();
if let ControlKind::Menu {
properties,
sub_menus,
} = kind
{
MenuControl::new(name, tag, index, properties, sub_menus)
} else {
MenuControl::new(name, tag, index, MenuProperties::default(), Vec::new())
}
}
fn parse_control_type_direct(&mut self) -> String {
let mut span_start: Option<usize> = None;
let mut span_end = self.pos;
if (self.is_identifier() || self.at_keyword()) && self.tokens.get(self.pos).is_some() {
span_start = Some(self.pos);
self.consume_advance();
span_end = self.pos;
}
while self.at_token(Token::PeriodOperator) {
self.consume_advance(); if self.is_identifier() || self.at_keyword() {
if self.tokens.get(self.pos).is_some() {
self.consume_advance();
span_end = self.pos;
}
} else {
break;
}
}
let Some(start) = span_start else {
return String::new();
};
if let Some((start_offset, end_offset)) = self.tokens_span_offsets(start, span_end) {
return self.source_content[start_offset..end_offset].to_string();
}
let mut value = String::new();
for idx in start..span_end {
if let Some((text, _)) = self.tokens.get(idx) {
value.push_str(text);
}
}
value
}
fn parse_control_name_direct(&mut self) -> String {
if (self.is_identifier() || self.at_keyword()) && self.tokens.get(self.pos).is_some() {
let start = self.pos;
self.consume_advance();
if let Some((start_offset, end_offset)) = self.tokens_span_offsets(start, self.pos) {
return self.source_content[start_offset..end_offset].to_string();
}
if let Some((text, _)) = self.tokens.get(start) {
return text.to_string();
}
}
String::new()
}
fn parse_property_direct(&mut self) -> Option<(String, String)> {
let key = if self.is_identifier() || self.at_keyword() {
if self.tokens.get(self.pos).is_some() {
let key_start = self.pos;
self.consume_advance();
if let Some((start_offset, end_offset)) =
self.tokens_span_offsets(key_start, self.pos)
{
self.source_content[start_offset..end_offset].to_string()
} else if let Some((text, _)) = self.tokens.get(key_start) {
text.to_string()
} else {
return None;
}
} else {
return None;
}
} else {
return None;
};
self.skip_whitespace();
if !self.at_token(Token::EqualityOperator) {
return None;
}
self.consume_advance();
self.skip_whitespace();
let value_start = self.pos;
let mut in_resource_reference = false;
while !self.is_at_end() && !self.at_token(Token::Newline) {
if let Some((_, token)) = self.tokens.get(self.pos) {
let token_copy = *token;
if token_copy == Token::DollarSign {
self.consume_advance();
}
else if token_copy == Token::StringLiteral {
self.consume_advance();
if let Some((_, next_token)) = self.tokens.get(self.pos)
&& *next_token == Token::ColonOperator
{
in_resource_reference = true;
}
}
else if in_resource_reference && token_copy == Token::ColonOperator {
self.consume_advance();
}
else if in_resource_reference
&& (token_copy == Token::IntegerLiteral || token_copy == Token::LongLiteral)
{
self.consume_advance();
break; }
else if token_copy == Token::ColonOperator {
break;
}
else {
self.consume_advance();
}
} else {
break;
}
}
self.skip_whitespace_and_newlines();
let value_end = self.pos;
let value = if let Some((start_offset, end_offset)) =
self.tokens_span_offsets(value_start, value_end)
{
self.source_content[start_offset..end_offset]
.trim()
.to_string()
} else {
let mut value_text = String::new();
for idx in value_start..value_end {
if let Some((text, _)) = self.tokens.get(idx) {
value_text.push_str(text);
}
}
value_text.trim().to_string()
};
Some((key, value))
}
fn parse_property_group_direct(&mut self) -> Option<PropertyGroup> {
if !self.is_identifier_text("BeginProperty") {
return None;
}
self.consume_advance(); self.skip_whitespace();
let (name, guid) = self.parse_property_group_name_direct();
self.skip_whitespace_and_newlines();
let mut stack: Vec<PropertyGroupFrame> = Vec::new();
stack.push(PropertyGroupFrame::new(name, guid));
while let Some(current_frame) = stack.last_mut() {
self.skip_whitespace();
if self.is_at_end() || self.is_identifier_text("EndProperty") {
if self.is_identifier_text("EndProperty") {
self.consume_advance();
self.skip_whitespace_and_newlines();
}
let finished_frame = stack.pop().unwrap();
let prop_group = finished_frame.into_property_group();
if let Some(parent_frame) = stack.last_mut() {
parent_frame
.properties
.insert(prop_group.name.clone(), Either::Right(prop_group));
} else {
return Some(prop_group);
}
continue;
}
if self.is_identifier_text("BeginProperty") {
self.consume_advance(); self.skip_whitespace();
let (nested_name, nested_guid) = self.parse_property_group_name_direct();
self.skip_whitespace_and_newlines();
if stack.len() >= MAX_PROPERTY_GROUP_DEPTH {
let mut depth = 1;
while !self.is_at_end() && depth > 0 {
if self.is_identifier_text("BeginProperty") {
depth += 1;
self.consume_advance();
} else if self.is_identifier_text("EndProperty") {
depth -= 1;
self.consume_advance();
} else {
self.consume_advance();
}
}
continue;
}
stack.push(PropertyGroupFrame::new(nested_name, nested_guid));
continue;
}
if self.is_identifier() || self.at_keyword() {
if let Some((key, value)) = self.parse_property_direct() {
current_frame.properties.insert(key, Either::Left(value));
}
} else {
self.consume_advance();
}
}
None
}
fn parse_property_group_name_direct(&mut self) -> (String, Option<uuid::Uuid>) {
let mut name_parts: Vec<&str> = Vec::new();
let mut guid_parts: Vec<&str> = Vec::new();
let mut in_guid = false;
while !self.is_at_end() && !self.at_token(Token::Newline) {
if let Some((text, token)) = self.tokens.get(self.pos) {
if *token == Token::LeftCurlyBrace {
in_guid = true;
} else if *token == Token::RightCurlyBrace {
in_guid = false;
} else if *token != Token::Whitespace && *token != Token::EndOfLineComment {
if in_guid {
guid_parts.push(*text);
} else {
name_parts.push(*text);
}
}
}
self.consume_advance();
}
let name = name_parts.concat();
let guid = if guid_parts.is_empty() {
None
} else {
let guid_str = guid_parts.concat();
uuid::Uuid::parse_str(&guid_str).ok()
};
(name, guid)
}
pub(crate) fn parse_objects_direct(&mut self) -> Vec<ObjectReference> {
let mut objects = Vec::new();
self.skip_whitespace_and_newlines();
while self.at_token(Token::ObjectKeyword) {
if let Some(obj_ref) = self.parse_single_object_direct() {
objects.push(obj_ref);
}
self.skip_whitespace_and_newlines();
}
objects
}
fn parse_single_object_direct(&mut self) -> Option<ObjectReference> {
if !self.at_token(Token::ObjectKeyword) {
return None;
}
self.consume_advance(); self.skip_whitespace();
if !self.at_token(Token::EqualityOperator) {
return None;
}
self.consume_advance(); self.skip_whitespace();
let mut _is_embedded = false;
if self.at_token(Token::MultiplicationOperator) {
self.consume_advance(); if let Some((_text, token)) = self.tokens.get(self.pos)
&& *token == Token::BackwardSlashOperator
{
self.consume_advance(); if let Some((text2, token2)) = self.tokens.get(self.pos)
&& *token2 == Token::Identifier
&& text2.eq_ignore_ascii_case("G")
{
self.consume_advance(); _is_embedded = true;
}
}
}
self.skip_whitespace();
let uuid_part = if let Some((text, token)) = self.tokens.get(self.pos) {
if *token == Token::StringLiteral {
let s = text.trim_matches('"').to_string();
self.consume_advance();
s
} else if *token == Token::LeftCurlyBrace {
let mut parts: Vec<&str> = Vec::new();
while !self.is_at_end() && !self.at_token(Token::Semicolon) {
if let Some((text, token)) = self.tokens.get(self.pos) {
if *token != Token::Whitespace {
parts.push(text);
}
self.consume_advance();
} else {
break;
}
}
parts.concat()
} else {
return None;
}
} else {
return None;
};
self.skip_whitespace();
if !self.at_token(Token::Semicolon) {
return None;
}
self.consume_advance(); self.skip_whitespace();
let file_name = if let Some((text, token)) = self.tokens.get(self.pos) {
if *token == Token::StringLiteral {
let s = text.trim_matches('"').to_string();
self.consume_advance();
s
} else {
return None;
}
} else {
return None;
};
let parts: Vec<&str> = uuid_part.split('#').collect();
if parts.len() >= 3 {
let uuid_str = parts[0].trim_matches(|c| c == '{' || c == '}');
if let Ok(uuid) = uuid::Uuid::parse_str(uuid_str) {
let version = parts[1].to_string();
let unknown1 = parts[2].to_string();
return Some(ObjectReference::Compiled {
uuid,
version,
unknown1,
file_name,
});
}
}
None
}
pub(crate) fn parse_attributes_direct(&mut self) -> FileAttributes {
let mut name = String::new();
let mut global_name_space = NameSpace::Local;
let mut creatable = Creatable::True;
let mut predeclared_id = PreDeclaredID::False;
let mut exposed = Exposed::False;
let mut description: Option<String> = None;
let mut ext_key: HashMap<String, String> = HashMap::new();
self.skip_whitespace_and_newlines();
while self.at_token(Token::AttributeKeyword) {
if let Some((key, value)) = self.parse_single_attribute_direct() {
match key.as_str() {
"VB_Name" => {
name = value;
}
"VB_GlobalNameSpace" => {
global_name_space = if value == "True" || value == "-1" {
NameSpace::Global
} else {
NameSpace::Local
};
}
"VB_Creatable" => {
creatable = if value == "True" || value == "-1" {
Creatable::True
} else {
Creatable::False
};
}
"VB_PredeclaredId" => {
predeclared_id = if value == "True" || value == "-1" {
PreDeclaredID::True
} else {
PreDeclaredID::False
};
}
"VB_Exposed" => {
exposed = if value == "True" || value == "-1" {
Exposed::True
} else {
Exposed::False
};
}
"VB_Description" => {
description = Some(value);
}
_ => {
ext_key.insert(key, value);
}
}
}
self.skip_whitespace_and_newlines();
}
FileAttributes {
name,
global_name_space,
creatable,
predeclared_id,
exposed,
description,
ext_key,
}
}
fn parse_single_attribute_direct(&mut self) -> Option<(String, String)> {
if !self.at_token(Token::AttributeKeyword) {
return None;
}
self.consume_advance(); self.skip_whitespace();
let key = if let Some((text, token)) = self.tokens.get(self.pos) {
if *token == Token::Identifier {
let k = text.to_string();
self.consume_advance();
k
} else {
return None;
}
} else {
return None;
};
self.skip_whitespace();
if !self.at_token(Token::EqualityOperator) {
return None;
}
self.consume_advance(); self.skip_whitespace();
let mut value = String::new();
let mut found_value = false;
if self.at_token(Token::SubtractionOperator) {
value.push('-');
self.consume_advance();
self.skip_whitespace();
}
if let Some((text, token)) = self.tokens.get(self.pos) {
match token {
Token::StringLiteral => {
value.push_str(text.trim().trim_matches('"'));
self.consume_advance();
found_value = true;
}
Token::TrueKeyword => {
value.push_str("True");
self.consume_advance();
found_value = true;
}
Token::FalseKeyword => {
value.push_str("False");
self.consume_advance();
found_value = true;
}
Token::IntegerLiteral | Token::LongLiteral => {
value.push_str(text.trim());
self.consume_advance();
found_value = true;
}
_ => {}
}
}
while self.pos < self.tokens.len() {
if let Some((_, token)) = self.tokens.get(self.pos) {
if *token == Token::Newline {
break;
}
self.consume_advance();
} else {
break;
}
}
if found_value {
Some((key, value))
} else {
None
}
}
fn build_form_root(
control_type: &str,
control_name: String,
tag: String,
index: i32,
properties: Properties,
groups: &[PropertyGroup],
child_controls: Vec<Control>,
menus: Vec<MenuControl>,
) -> Result<FormRoot, ErrorKind> {
match control_type {
"VB.Form" => {
let mut form_properties: FormProperties = properties.into();
let extracted_groups = extract_property_groups(groups);
if let Some(font) = extracted_groups.font {
form_properties.font = Some(font);
}
Ok(FormRoot::Form(Form {
name: control_name,
tag,
index,
properties: form_properties,
controls: child_controls,
menus,
}))
}
"VB.MDIForm" => {
let mut mdi_form_properties: MDIFormProperties = properties.into();
let extracted_groups = extract_property_groups(groups);
if let Some(font) = extracted_groups.font {
mdi_form_properties.font = Some(font);
}
Ok(FormRoot::MDIForm(MDIForm {
name: control_name,
tag,
index,
properties: mdi_form_properties,
controls: child_controls,
menus,
}))
}
_ => Err(ErrorKind::Form(FormError::InvalidTopLevelControl {
control_type: control_type.to_string(),
})),
}
}
#[allow(clippy::too_many_lines)]
fn build_control_kind(
control_type: &str,
properties: Properties,
child_controls: Vec<Control>,
menus: Vec<MenuControl>,
property_groups: Vec<PropertyGroup>,
) -> ControlKind {
use ControlKind;
let groups = extract_property_groups(&property_groups);
match control_type {
"VB.CommandButton" => {
let mut props: CommandButtonProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::CommandButton { properties: props }
}
"VB.Data" => {
let mut props: DataProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::Data { properties: props }
}
"VB.TextBox" => {
let mut props: TextBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::TextBox { properties: props }
}
"VB.Label" => {
let mut props: LabelProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::Label { properties: props }
}
"VB.CheckBox" => {
let mut props: CheckBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::CheckBox { properties: props }
}
"VB.Line" => ControlKind::Line {
properties: properties.into(),
},
"VB.Shape" => ControlKind::Shape {
properties: properties.into(),
},
"VB.ListBox" => {
let mut props: ListBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::ListBox { properties: props }
}
"VB.ComboBox" => {
let mut props: ComboBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::ComboBox { properties: props }
}
"VB.Timer" => ControlKind::Timer {
properties: properties.into(),
},
"VB.HScrollBar" => ControlKind::HScrollBar {
properties: properties.into(),
},
"VB.VScrollBar" => ControlKind::VScrollBar {
properties: properties.into(),
},
"VB.Frame" => {
let mut props: FrameProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::Frame {
properties: props,
controls: child_controls,
}
}
"VB.PictureBox" => {
let mut props: PictureBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::PictureBox {
properties: props,
controls: child_controls,
}
}
"VB.FileListBox" => {
let mut props: FileListBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::FileListBox { properties: props }
}
"VB.DirListBox" => {
let mut props: DirListBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::DirListBox { properties: props }
}
"VB.DriveListBox" => {
let mut props: DriveListBoxProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::DriveListBox { properties: props }
}
"VB.Image" => ControlKind::Image {
properties: properties.into(),
},
"VB.OptionButton" => {
let mut props: OptionButtonProperties = properties.into();
if let Some(font) = groups.font {
props.font = Some(font);
}
ControlKind::OptionButton { properties: props }
}
"VB.OLE" => ControlKind::Ole {
properties: properties.into(),
},
"VB.Menu" => ControlKind::Menu {
properties: properties.into(),
sub_menus: menus,
},
_ => ControlKind::Custom {
properties: properties.into(),
property_groups,
},
}
}
pub(crate) fn parse_properties_block_to_control(&mut self) -> ParseResult<'a, Control> {
self.skip_whitespace();
if !self.at_token(Token::BeginKeyword) {
return ParseResult::new(None, Vec::new());
}
self.consume_advance(); self.skip_whitespace();
let control_type = self.parse_control_type_direct();
self.skip_whitespace();
let control_name = self.parse_control_name_direct();
self.skip_whitespace_and_newlines();
let mut stack: Vec<ControlParseFrame> = Vec::new();
let failures = Vec::new();
stack.push(ControlParseFrame::new(control_type, control_name));
while let Some(current_frame) = stack.last_mut() {
self.skip_whitespace();
if self.is_at_end() || self.at_token(Token::EndKeyword) {
if self.at_token(Token::EndKeyword) {
self.consume_advance();
self.skip_whitespace_and_newlines();
}
let finished_frame = stack.pop().unwrap();
let control = finished_frame.into_control();
if let Some(parent_frame) = stack.last_mut() {
if matches!(control.kind(), ControlKind::Menu { .. }) {
parent_frame.menus.push(Self::control_to_menu(control));
} else {
parent_frame.child_controls.push(control);
}
} else {
return ParseResult::new(Some(control), failures);
}
continue;
}
if self.at_token(Token::BeginKeyword) {
self.consume_advance(); self.skip_whitespace();
let nested_type = self.parse_control_type_direct();
self.skip_whitespace();
let nested_name = self.parse_control_name_direct();
self.skip_whitespace_and_newlines();
if stack.len() >= MAX_CONTROL_DEPTH {
let mut depth = 1;
while !self.is_at_end() && depth > 0 {
if self.at_token(Token::BeginKeyword) {
depth += 1;
} else if self.at_token(Token::EndKeyword) {
depth -= 1;
}
self.consume_advance();
}
continue;
}
stack.push(ControlParseFrame::new(nested_type, nested_name));
continue;
}
if self.is_begin_property() {
if let Some(group) = self.parse_property_group_direct() {
current_frame.property_groups.push(group);
}
} else if self.is_identifier() || self.at_keyword() {
if let Some((key, value)) = self.parse_property_direct() {
let is_resource_reference = value.contains(':')
&& value
.split(':')
.next_back()
.is_some_and(|part| part.chars().all(|c| c.is_ascii_digit()));
let cleaned_value = if !is_resource_reference
&& value.starts_with('"')
&& value.ends_with('"')
&& value.len() >= 2
{
&value[1..value.len() - 1]
} else {
&value
};
current_frame.properties.insert(&key, cleaned_value);
}
} else {
self.consume_advance();
}
}
ParseResult::new(None, failures)
}
pub(crate) fn parse_properties_block_to_form_root(&mut self) -> ParseResult<'a, FormRoot> {
use Properties;
let mut groups = Vec::new();
self.skip_whitespace();
if !self.at_token(Token::BeginKeyword) {
return ParseResult::new(None, Vec::new());
}
self.consume_advance(); self.skip_whitespace();
let control_type = self.parse_control_type_direct();
self.skip_whitespace();
let control_name = self.parse_control_name_direct();
self.skip_whitespace_and_newlines();
let mut properties = Properties::new();
let mut child_controls = Vec::new();
let mut menus = Vec::new();
let mut failures = Vec::new();
while !self.is_at_end() && !self.at_token(Token::EndKeyword) {
self.skip_whitespace();
if self.at_token(Token::EndKeyword) {
break;
}
if self.at_token(Token::BeginKeyword) {
let child_result = self.parse_properties_block_to_control();
let (child_opt, child_failures) = child_result.unpack();
failures.extend(child_failures);
if let Some(child) = child_opt {
if matches!(child.kind(), ControlKind::Menu { .. }) {
menus.push(Self::control_to_menu(child));
} else {
child_controls.push(child);
}
}
} else if self.is_begin_property() {
if let Some(group) = self.parse_property_group_direct() {
groups.push(group);
}
} else if self.is_identifier() || self.at_keyword() {
if let Some((key, value)) = self.parse_property_direct() {
let is_resource_reference = value.contains(':')
&& value
.split(':')
.next_back()
.is_some_and(|part| part.chars().all(|c| c.is_ascii_digit()));
let cleaned_value = if !is_resource_reference
&& value.starts_with('"')
&& value.ends_with('"')
&& value.len() >= 2
{
&value[1..value.len() - 1]
} else {
&value
};
properties.insert(&key, cleaned_value);
}
} else {
self.consume_advance();
}
}
if self.at_token(Token::EndKeyword) {
self.consume_advance();
self.skip_whitespace_and_newlines();
}
let tag = properties.get("Tag").cloned().unwrap_or_default();
let index = properties
.get("Index")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
if let Ok(form_root) = Self::build_form_root(
&control_type,
control_name,
tag,
index,
properties,
&groups,
child_controls,
menus,
) {
ParseResult::new(Some(form_root), failures)
} else {
let default_form = FormRoot::Form(Form {
name: String::new(),
tag: String::new(),
index: 0,
properties: FormProperties::default(),
controls: Vec::new(),
menus: Vec::new(),
});
ParseResult::new(Some(default_form), failures)
}
}
fn parse_root(
mut self,
) -> (
ConcreteSyntaxTree,
Vec<ErrorDetails<'a>>,
Vec<RecoveryEvent>,
) {
self.builder.start_node(SyntaxKind::Root.to_raw());
if self.at_token(Token::VersionKeyword) {
self.parse_version_statement();
}
if self.at_token(Token::BeginKeyword) {
self.parse_properties_block();
}
while self.at_token(Token::AttributeKeyword) {
self.parse_attribute_statement();
}
self.parse_module_body();
self.builder.finish_node();
let root = self.builder.finish();
(
ConcreteSyntaxTree::new(root),
self.failures,
self.recovery_events,
)
}
#[allow(clippy::too_many_lines)]
fn parse_module_body(&mut self) {
while !self.is_at_end() {
match self.current_token() {
Some(Token::BeginKeyword) => {
self.parse_properties_block();
}
Some(Token::ObjectKeyword) if self.is_object_statement() => {
self.parse_object_statement();
}
Some(Token::AttributeKeyword) => {
self.parse_attribute_statement();
}
Some(Token::OptionKeyword) => {
self.parse_one_of_option_statement();
}
Some(
Token::DefBoolKeyword
| Token::DefByteKeyword
| Token::DefIntKeyword
| Token::DefLngKeyword
| Token::DefCurKeyword
| Token::DefSngKeyword
| Token::DefDblKeyword
| Token::DefDecKeyword
| Token::DefDateKeyword
| Token::DefStrKeyword
| Token::DefObjKeyword
| Token::DefVarKeyword,
) => {
self.parse_deftype_statement();
}
Some(Token::DeclareKeyword) => {
self.parse_declare_statement();
}
Some(Token::EventKeyword) => {
self.parse_event_statement();
}
Some(Token::ImplementsKeyword) => {
self.parse_implements_statement();
}
Some(Token::EnumKeyword) => {
self.parse_enum_statement();
}
Some(Token::TypeKeyword) => {
self.parse_type_statement();
}
Some(Token::SubKeyword) => {
self.parse_sub_statement();
}
Some(Token::FunctionKeyword) => {
self.parse_function_statement();
}
Some(Token::PropertyKeyword) => {
self.parse_property_statement();
}
Some(Token::DimKeyword | Token::ConstKeyword) => {
self.parse_dim();
}
Some(
Token::PrivateKeyword
| Token::PublicKeyword
| Token::FriendKeyword
| Token::StaticKeyword,
) => {
let next_keywords: Vec<_> = self
.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
.collect();
match next_keywords.as_slice() {
[Token::FunctionKeyword, ..] => self.parse_function_statement(), [Token::SubKeyword, ..] => self.parse_sub_statement(), [Token::PropertyKeyword, ..] => self.parse_property_statement(), [Token::DeclareKeyword, ..] => self.parse_declare_statement(), [Token::EnumKeyword, ..] => self.parse_enum_statement(), [Token::TypeKeyword, ..] => self.parse_type_statement(), [Token::EventKeyword, ..] => self.parse_event_statement(), [Token::ImplementsKeyword, ..] => self.parse_implements_statement(), [Token::StaticKeyword, Token::FunctionKeyword] => {
self.parse_function_statement();
}
[Token::StaticKeyword, Token::SubKeyword] => {
self.parse_sub_statement();
}
[Token::StaticKeyword, Token::PropertyKeyword] => {
self.parse_property_statement();
}
_ => self.parse_dim(),
}
}
_ => {
if matches!(
self.current_token(),
Some(
Token::Whitespace
| Token::Newline
| Token::EndOfLineComment
| Token::RemComment
)
) {
self.consume_token();
} else if self.is_at_compiler_conditional_property_statement() {
self.parse_compiler_conditional_property_statement();
} else if self.at_compiler_directive_keyword(Token::IfKeyword) {
self.parse_compiler_directive();
} else if self.is_control_flow_keyword() {
self.parse_control_flow_statement();
} else if self.is_library_statement_keyword() {
self.parse_library_statement();
} else if self.is_variable_declaration_keyword() {
self.parse_array_statement();
} else if self.is_statement_keyword() {
self.parse_statement();
} else if self.is_at_label() {
self.parse_label_statement();
} else if self.at_token(Token::LetKeyword) {
self.parse_let_statement();
} else if self.at_token(Token::PeriodOperator) {
if self.is_at_with_member_assignment() {
self.parse_assignment_statement();
} else {
self.parse_procedure_call();
}
} else if self.is_at_assignment() {
self.parse_assignment_statement();
} else if self.is_at_procedure_call() {
self.parse_procedure_call();
} else if self.is_standalone_end() {
self.parse_end_statement();
} else if self.at_token(Token::ColonOperator)
|| self.is_identifier()
|| self.at_keyword()
|| self.at_token(Token::Octothorpe)
{
self.consume_token();
} else {
self.consume_error_to_newline(vec!["statement".to_string()]);
}
}
}
}
}
fn parse_one_of_option_statement(&mut self) {
if let Some(Token::BaseKeyword) = self.peek_next_keyword() {
self.parse_option_base_statement();
} else if let Some(Token::CompareKeyword) = self.peek_next_keyword() {
self.parse_option_compare_statement();
} else if let Some(Token::PrivateKeyword) = self.peek_next_keyword() {
self.parse_option_private_statement();
} else {
self.parse_option_statement();
}
}
fn is_at_with_member_assignment(&self) -> bool {
if !self.at_token(Token::PeriodOperator) {
return false;
}
let mut paren_depth: i32 = 0;
let mut seen_other_operator = false;
for (_text, token) in self.tokens.iter().skip(self.pos + 1) {
match token {
Token::Newline | Token::EndOfLineComment | Token::RemComment => {
return false;
}
Token::LeftParenthesis => {
paren_depth += 1;
}
Token::RightParenthesis => {
paren_depth = paren_depth.saturating_sub(1);
}
Token::EqualityOperator if paren_depth == 0 => {
return !seen_other_operator;
}
Token::AndKeyword
| Token::OrKeyword
| Token::XorKeyword
| Token::EqvKeyword
| Token::ImpKeyword
| Token::ModKeyword
| Token::NotKeyword
| Token::LessThanOperator
| Token::GreaterThanOperator
| Token::LessThanOrEqualOperator
| Token::GreaterThanOrEqualOperator
| Token::InequalityOperator
| Token::AdditionOperator
| Token::SubtractionOperator
| Token::MultiplicationOperator
| Token::DivisionOperator
| Token::BackwardSlashOperator
| Token::ExponentiationOperator
| Token::Ampersand
if paren_depth == 0 =>
{
seen_other_operator = true;
}
_ => {}
}
}
false
}
fn is_control_flow_keyword(&self) -> bool {
let token = if self.at_token(Token::Whitespace) {
self.peek_next_keyword()
} else {
self.current_token().copied()
};
matches!(
token,
Some(
Token::IfKeyword
| Token::SelectKeyword
| Token::ForKeyword
| Token::DoKeyword
| Token::WhileKeyword
| Token::GotoKeyword
| Token::GoSubKeyword
| Token::ReturnKeyword
| Token::ResumeKeyword
| Token::ExitKeyword
| Token::OnKeyword
)
)
}
fn parse_control_flow_statement(&mut self) {
let token = if self.at_token(Token::Whitespace) {
self.peek_next_keyword()
} else {
self.current_token().copied()
};
match token {
Some(Token::IfKeyword) => {
self.parse_if_statement();
}
Some(Token::SelectKeyword) => {
self.parse_select_case_statement();
}
Some(Token::ForKeyword) => {
let next_kw = if self.at_token(Token::Whitespace) {
self.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
.nth(1)
} else {
self.peek_next_keyword()
};
if let Some(Token::EachKeyword) = next_kw {
self.parse_for_each_statement();
} else {
self.parse_for_statement();
}
}
Some(Token::DoKeyword) => {
self.parse_do_statement();
}
Some(Token::WhileKeyword) => {
self.parse_while_statement();
}
Some(Token::GotoKeyword) => {
self.parse_goto_statement();
}
Some(Token::GoSubKeyword) => {
self.parse_gosub_statement();
}
Some(Token::ReturnKeyword) => {
self.parse_return_statement();
}
Some(Token::ResumeKeyword) => {
self.parse_resume_statement();
}
Some(Token::ExitKeyword) => {
self.parse_exit_statement();
}
Some(Token::OnKeyword) => {
let next_kw = if self.at_token(Token::Whitespace) {
self.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
.nth(1)
} else {
self.peek_next_keyword()
};
if let Some(Token::ErrorKeyword) = next_kw {
self.parse_on_error_statement();
} else {
let peek_start = if self.at_token(Token::Whitespace) {
2
} else {
1
};
let keywords: Vec<Token> = self
.peek_next_count_keywords(NonZeroUsize::new(20).unwrap())
.skip(peek_start)
.collect();
let has_goto = keywords.contains(&Token::GotoKeyword);
let has_gosub = keywords.contains(&Token::GoSubKeyword);
if has_goto {
self.parse_on_goto_statement();
} else if has_gosub {
self.parse_on_gosub_statement();
} else {
self.parse_on_error_statement();
}
}
}
_ => {}
}
}
fn is_variable_declaration_keyword(&self) -> bool {
let token = if self.at_token(Token::Whitespace) {
self.peek_next_keyword()
} else {
self.current_token().copied()
};
matches!(token, Some(Token::ReDimKeyword | Token::EraseKeyword))
}
#[allow(clippy::needless_continue)] fn is_object_statement(&self) -> bool {
if !self.at_token(Token::ObjectKeyword) {
return false;
}
let mut found_equals = false;
for (_text, token) in self.tokens.iter().skip(self.pos + 1) {
match token {
Token::Whitespace => continue,
Token::EqualityOperator if !found_equals => {
found_equals = true;
}
Token::StringLiteral | Token::MultiplicationOperator if found_equals => {
return true;
}
_ if found_equals => return false,
Token::Newline | Token::EndOfLineComment | Token::RemComment => {
return false;
}
_ => return false,
}
}
false
}
fn parse_array_statement(&mut self) {
let token = if self.at_token(Token::Whitespace) {
self.peek_next_keyword()
} else {
self.current_token().copied()
};
match token {
Some(Token::ReDimKeyword) => {
self.parse_redim_statement();
}
Some(Token::EraseKeyword) => {
self.parse_erase_statement();
}
_ => {}
}
}
pub(crate) fn parse_statement_list<F>(&mut self, stop_conditions: F)
where
F: Fn(&Parser) -> bool,
{
let mut frame_stack: Vec<ControlFlowFrame> = Vec::new();
frame_stack.push(ControlFlowFrame::StatementList {
depth: 0,
context: StatementListContext::TopLevel,
started: false,
});
while self.statement_stack_within_limit(&frame_stack) {
let Some(frame_kind) = Self::current_frame_kind(&frame_stack) else {
break;
};
match frame_kind {
ControlFlowFrameType::StatementList => {
self.handle_statement_list_frame(&mut frame_stack, &stop_conditions);
}
_ => self.handle_non_statement_list_frame(frame_kind, &mut frame_stack),
}
}
}
fn statement_stack_within_limit(&mut self, frame_stack: &[ControlFlowFrame]) -> bool {
if frame_stack.len() <= MAX_STATEMENT_DEPTH {
return true;
}
self.report_error(ErrorKind::Module(ModuleError::NestingTooDeep {
depth: frame_stack.len(),
max_depth: MAX_STATEMENT_DEPTH,
}));
false
}
fn current_frame_kind(frame_stack: &[ControlFlowFrame]) -> Option<ControlFlowFrameType> {
frame_stack.last().map(ControlFlowFrame::frame_type)
}
fn handle_statement_list_frame<F>(
&mut self,
frame_stack: &mut Vec<ControlFlowFrame>,
stop_conditions: &F,
) where
F: Fn(&Parser) -> bool,
{
let Some((current_depth, context, started)) = Self::statement_list_frame_state(frame_stack)
else {
return;
};
if !started {
self.builder.start_node(SyntaxKind::StatementList.to_raw());
Self::mark_statement_list_started(frame_stack);
}
if self.should_stop_statement_list(context, stop_conditions) {
frame_stack.pop();
self.builder.finish_node(); return;
}
if self.try_handle_statement_list_control_flow(frame_stack, current_depth) {
return;
}
if self.try_parse_statement_list_keyword_dispatch() {
return;
}
self.parse_statement_list_fallback_item();
}
fn statement_list_frame_state(
frame_stack: &[ControlFlowFrame],
) -> Option<(usize, StatementListContext, bool)> {
if let Some(ControlFlowFrame::StatementList {
depth,
context,
started,
}) = frame_stack.last()
{
Some((*depth, *context, *started))
} else {
None
}
}
fn mark_statement_list_started(frame_stack: &mut [ControlFlowFrame]) {
if let Some(ControlFlowFrame::StatementList { started, .. }) = frame_stack.last_mut() {
*started = true;
}
}
fn should_stop_statement_list<F>(
&self,
context: StatementListContext,
stop_conditions: &F,
) -> bool
where
F: Fn(&Parser) -> bool,
{
match context {
StatementListContext::TopLevel => {
stop_conditions(self) || self.at_procedure_block_end() || self.is_at_end()
}
StatementListContext::IfThenBody | StatementListContext::ElseIfBody => {
self.at_token(Token::ElseIfKeyword)
|| self.at_compiler_directive_keyword(Token::ElseIfKeyword)
|| self.at_token(Token::ElseKeyword)
|| self.at_compiler_directive_keyword(Token::ElseKeyword)
|| self.at_if_block_end()
|| self.at_procedure_block_end()
|| self.is_at_end()
}
StatementListContext::ElseBody => {
self.at_token(Token::CaseKeyword)
|| (self.at_token(Token::EndKeyword)
&& self.peek_next_keyword() == Some(Token::SelectKeyword))
|| self.at_if_block_end()
|| self.at_procedure_block_end()
|| self.is_at_end()
}
StatementListContext::ForBody => self.at_token(Token::NextKeyword) || self.is_at_end(),
StatementListContext::SelectCaseBody => {
self.at_token(Token::CaseKeyword)
|| (self.at_token(Token::EndKeyword)
&& self.peek_next_keyword() == Some(Token::SelectKeyword))
|| self.is_at_end()
}
StatementListContext::WhileBody => {
self.at_token(Token::WendKeyword) || self.is_at_end()
}
StatementListContext::DoBody => self.at_token(Token::LoopKeyword) || self.is_at_end(),
StatementListContext::WithBody => {
(self.at_token(Token::EndKeyword)
&& self.peek_next_keyword() == Some(Token::WithKeyword))
|| self.is_at_end()
}
}
}
fn at_if_block_end(&self) -> bool {
(self.at_token(Token::EndKeyword) && self.peek_next_keyword() == Some(Token::IfKeyword))
|| self.at_compiler_end_if_directive()
}
fn at_procedure_block_end(&self) -> bool {
self.at_token(Token::EndKeyword)
&& matches!(
self.peek_next_keyword(),
Some(Token::SubKeyword | Token::FunctionKeyword | Token::PropertyKeyword)
)
}
fn at_compiler_directive_keyword(&self, keyword: Token) -> bool {
if !self.at_token(Token::Octothorpe) {
return false;
}
let mut index = self.pos + 1;
while let Some((_, token)) = self.tokens.get(index) {
if *token == Token::Whitespace {
index += 1;
continue;
}
return *token == keyword;
}
false
}
fn at_compiler_end_if_directive(&self) -> bool {
if !self.at_token(Token::Octothorpe) {
return false;
}
let mut index = self.pos + 1;
while let Some((_, token)) = self.tokens.get(index) {
if *token == Token::Whitespace {
index += 1;
continue;
}
if *token != Token::EndKeyword {
return false;
}
index += 1;
break;
}
while let Some((_, token)) = self.tokens.get(index) {
if *token == Token::Whitespace {
index += 1;
continue;
}
return *token == Token::IfKeyword;
}
false
}
fn consume_compiler_directive_prefix(&mut self) {
if self.at_token(Token::Octothorpe) {
self.consume_token();
self.consume_whitespace();
}
}
fn try_handle_statement_list_control_flow(
&mut self,
frame_stack: &mut Vec<ControlFlowFrame>,
current_depth: usize,
) -> bool {
if !self.is_control_flow_keyword() {
return false;
}
let keyword = if self.at_token(Token::Whitespace) {
self.peek_next_keyword()
} else {
self.current_token().copied()
};
match keyword {
Some(Token::IfKeyword) => {
frame_stack.push(ControlFlowFrame::IfStatement {
phase: IfPhase::Start,
depth: current_depth + 1,
});
}
Some(Token::ForKeyword) => {
let is_for_each = if self.at_token(Token::Whitespace) {
self.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
.nth(1)
== Some(Token::EachKeyword)
} else {
self.peek_next_keyword() == Some(Token::EachKeyword)
};
frame_stack.push(ControlFlowFrame::ForStatement {
phase: ForPhase::Start,
is_for_each,
depth: current_depth + 1,
});
}
Some(Token::SelectKeyword) => {
frame_stack.push(ControlFlowFrame::SelectCase {
phase: SelectPhase::Start,
depth: current_depth + 1,
});
}
Some(Token::WhileKeyword) => {
frame_stack.push(ControlFlowFrame::WhileStatement {
phase: WhilePhase::Start,
depth: current_depth + 1,
});
}
Some(Token::DoKeyword) => {
frame_stack.push(ControlFlowFrame::DoStatement {
phase: DoPhase::Start,
depth: current_depth + 1,
});
}
Some(Token::WithKeyword) => {
frame_stack.push(ControlFlowFrame::WithStatement {
phase: WithPhase::Start,
depth: current_depth + 1,
});
}
_ => {
self.parse_control_flow_statement();
}
}
true
}
fn try_parse_statement_list_keyword_dispatch(&mut self) -> bool {
if self.at_token(Token::EventKeyword) {
self.parse_event_statement();
return true;
}
if self.is_library_statement_keyword() {
self.parse_library_statement();
return true;
}
if self.is_variable_declaration_keyword() {
self.parse_array_statement();
return true;
}
if self.is_statement_keyword() {
self.parse_statement();
return true;
}
false
}
fn parse_statement_list_fallback_item(&mut self) {
match self.current_token() {
Some(
Token::Whitespace | Token::Newline | Token::EndOfLineComment | Token::RemComment,
) => {
self.consume_token();
}
Some(Token::DeclareKeyword) => {
self.parse_declare_statement();
}
Some(Token::DimKeyword | Token::ConstKeyword) => {
self.parse_dim();
}
Some(
Token::PrivateKeyword
| Token::PublicKeyword
| Token::FriendKeyword
| Token::StaticKeyword,
) => {
let next_keywords: Vec<_> = self
.peek_next_count_keywords(NonZeroUsize::new(2).unwrap())
.collect();
match next_keywords.as_slice() {
[Token::FunctionKeyword, ..] => self.parse_function_statement(),
[Token::SubKeyword, ..] => self.parse_sub_statement(),
[Token::PropertyKeyword, ..] => self.parse_property_statement(),
[Token::DeclareKeyword, ..] => self.parse_declare_statement(),
[Token::EnumKeyword, ..] => self.parse_enum_statement(),
[Token::TypeKeyword, ..] => self.parse_type_statement(),
[Token::EventKeyword, ..] => self.parse_event_statement(),
[Token::ImplementsKeyword, ..] => self.parse_implements_statement(),
[Token::StaticKeyword, Token::FunctionKeyword] => {
self.parse_function_statement();
}
[Token::StaticKeyword, Token::SubKeyword] => {
self.parse_sub_statement();
}
[Token::StaticKeyword, Token::PropertyKeyword] => {
self.parse_property_statement();
}
_ => self.parse_dim(),
}
}
_ => self.parse_statement_list_fallback_non_keyword(),
}
}
fn parse_statement_list_fallback_non_keyword(&mut self) {
if self.is_at_label() {
self.parse_label_statement();
} else if self.at_token(Token::LetKeyword)
|| (self.at_token(Token::Whitespace)
&& self.peek_next_keyword() == Some(Token::LetKeyword))
{
self.parse_let_statement();
} else if self.at_token(Token::PeriodOperator) {
if self.is_at_with_member_assignment() {
self.parse_assignment_statement();
} else {
self.parse_procedure_call();
}
} else if self.is_at_assignment() {
self.parse_assignment_statement();
} else if self.is_at_procedure_call() {
self.parse_procedure_call();
} else if self.is_standalone_end() {
self.parse_end_statement();
} else if self.is_at_compiler_conditional_property_statement() {
self.parse_compiler_conditional_property_statement();
} else if self.at_compiler_directive_keyword(Token::IfKeyword) {
self.parse_compiler_directive();
} else if self.at_token(Token::Octothorpe) {
self.consume_token();
} else if self.at_token(Token::ColonOperator) {
self.consume_token();
} else {
self.consume_error_to_newline(vec!["statement".to_string()]);
}
}
fn handle_non_statement_list_frame(
&mut self,
frame_kind: ControlFlowFrameType,
frame_stack: &mut Vec<ControlFlowFrame>,
) {
let should_continue = match frame_kind {
ControlFlowFrameType::IfStatement => self.handle_if_statement_frame(frame_stack),
ControlFlowFrameType::ForStatement => self.handle_for_statement_frame(frame_stack),
ControlFlowFrameType::SelectCase => self.handle_select_case_frame(frame_stack),
ControlFlowFrameType::WhileStatement => self.handle_while_statement_frame(frame_stack),
ControlFlowFrameType::DoStatement => self.handle_do_statement_frame(frame_stack),
ControlFlowFrameType::WithStatement => self.handle_with_statement_frame(frame_stack),
ControlFlowFrameType::StatementList => return,
};
if !should_continue {
frame_stack.pop();
}
}
fn handle_if_statement_frame(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
let Some(current_phase) = Self::current_if_phase(frame_stack) else {
return false;
};
match current_phase {
IfPhase::Start => self.handle_if_phase_start(frame_stack),
IfPhase::ThenBody => Self::handle_if_phase_then_body(frame_stack),
IfPhase::CheckElseIf => self.handle_if_phase_check_elseif(frame_stack),
IfPhase::ElseIfBody => self.handle_if_phase_elseif_body(frame_stack),
IfPhase::CheckElse => self.handle_if_phase_check_else(frame_stack),
IfPhase::ElseBody => self.handle_if_phase_else_body(frame_stack),
IfPhase::Finish => self.handle_if_phase_finish(),
}
}
fn current_if_phase(frame_stack: &[ControlFlowFrame]) -> Option<IfPhase> {
if let Some(ControlFlowFrame::IfStatement { phase, .. }) = frame_stack.last() {
Some(*phase)
} else {
None
}
}
fn set_if_phase(frame_stack: &mut [ControlFlowFrame], new_phase: IfPhase) {
if let Some(ControlFlowFrame::IfStatement { phase, .. }) = frame_stack.last_mut() {
*phase = new_phase;
}
}
fn if_frame_depth(frame_stack: &[ControlFlowFrame]) -> Option<usize> {
if let Some(ControlFlowFrame::IfStatement { depth, .. }) = frame_stack.last() {
Some(*depth)
} else {
None
}
}
fn push_if_body_statement_list(
frame_stack: &mut Vec<ControlFlowFrame>,
context: StatementListContext,
) {
if let Some(current_depth) = Self::if_frame_depth(frame_stack) {
frame_stack.push(ControlFlowFrame::StatementList {
depth: current_depth,
context,
started: false,
});
}
}
fn handle_if_phase_start(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
self.parsing_header = false;
self.builder.start_node(SyntaxKind::IfStatement.to_raw());
self.consume_whitespace();
self.consume_compiler_directive_prefix();
self.consume_token(); self.consume_whitespace();
self.parse_expression();
self.consume_whitespace();
if self.at_token(Token::ThenKeyword) {
self.consume_token();
}
self.consume_whitespace();
while self.at_token(Token::EndOfLineComment) || self.at_token(Token::RemComment) {
self.consume_token();
self.consume_whitespace();
}
let is_single_line = !self.at_token(Token::Newline) && !self.is_at_end();
if is_single_line {
self.handle_single_line_if_phase_start();
return false;
}
self.transition_if_to_then_body(frame_stack);
true
}
fn handle_single_line_if_phase_start(&mut self) {
self.parse_single_line_if_body_segment(true);
if !self.at_token(Token::Newline) && self.at_token(Token::ElseKeyword) {
self.consume_token(); self.consume_whitespace();
self.parse_single_line_if_body_segment(false);
}
if self.at_token(Token::Newline) {
self.consume_token();
}
self.builder.finish_node(); }
fn parse_single_line_if_body_segment(&mut self, stop_at_else: bool) {
loop {
if self.is_at_end() || self.at_token(Token::Newline) {
break;
}
if stop_at_else && self.at_token(Token::ElseKeyword) {
break;
}
let pos_before = self.pos;
let parsed_statement = self.parse_single_line_if_statement_item();
if parsed_statement && self.consumed_newline_in_range(pos_before, self.pos) {
break;
}
}
}
fn parse_single_line_if_statement_item(&mut self) -> bool {
if self.is_control_flow_keyword() {
self.parse_control_flow_statement();
return true;
}
if self.is_library_statement_keyword() {
self.parse_library_statement();
return true;
}
if self.is_variable_declaration_keyword() {
self.parse_array_statement();
return true;
}
if self.is_statement_keyword() {
self.parse_statement();
return true;
}
match self.current_token() {
Some(
Token::Whitespace
| Token::EndOfLineComment
| Token::RemComment
| Token::ColonOperator,
) => {
self.consume_token();
false
}
_ => {
if self.at_token(Token::LetKeyword) {
self.parse_let_statement();
true
} else if self.at_token(Token::PeriodOperator) {
if self.is_at_with_member_assignment() {
self.parse_assignment_statement();
} else {
self.parse_procedure_call();
}
true
} else if self.is_at_assignment() {
self.parse_assignment_statement();
true
} else if self.is_at_procedure_call() {
self.parse_procedure_call();
true
} else {
self.consume_token();
false
}
}
}
}
fn consumed_newline_in_range(&self, start_pos: usize, end_pos: usize) -> bool {
start_pos < end_pos
&& self.tokens[start_pos..end_pos]
.iter()
.any(|(_, t)| *t == Token::Newline)
}
fn transition_if_to_then_body(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) {
if self.at_token(Token::Newline) {
self.consume_token();
}
Self::set_if_phase(frame_stack, IfPhase::ThenBody);
Self::push_if_body_statement_list(frame_stack, StatementListContext::IfThenBody);
}
fn handle_if_phase_then_body(frame_stack: &mut [ControlFlowFrame]) -> bool {
Self::set_if_phase(frame_stack, IfPhase::CheckElseIf);
true
}
fn handle_if_phase_check_elseif(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
if self.at_compiler_directive_keyword(Token::ElseIfKeyword) {
self.consume_compiler_directive_prefix();
}
if self.at_token(Token::ElseIfKeyword) {
self.builder.start_node(SyntaxKind::ElseIfClause.to_raw());
self.consume_token(); self.consume_whitespace();
self.parse_expression();
self.consume_whitespace();
if self.at_token(Token::ThenKeyword) {
self.consume_token();
}
self.consume_whitespace();
if self.at_token(Token::Newline) {
self.consume_token();
}
Self::set_if_phase(frame_stack, IfPhase::ElseIfBody);
Self::push_if_body_statement_list(frame_stack, StatementListContext::ElseIfBody);
} else {
Self::set_if_phase(frame_stack, IfPhase::CheckElse);
}
true
}
fn handle_if_phase_elseif_body(&mut self, frame_stack: &mut [ControlFlowFrame]) -> bool {
self.builder.finish_node(); Self::set_if_phase(frame_stack, IfPhase::CheckElseIf);
true
}
fn handle_if_phase_check_else(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
if self.at_compiler_directive_keyword(Token::ElseKeyword) {
self.consume_compiler_directive_prefix();
}
if self.at_token(Token::ElseKeyword) {
self.builder.start_node(SyntaxKind::ElseClause.to_raw());
self.consume_token(); self.consume_whitespace();
if self.at_token(Token::Newline) {
self.consume_token();
}
Self::set_if_phase(frame_stack, IfPhase::ElseBody);
Self::push_if_body_statement_list(frame_stack, StatementListContext::ElseBody);
} else {
Self::set_if_phase(frame_stack, IfPhase::Finish);
}
true
}
fn handle_if_phase_else_body(&mut self, frame_stack: &mut [ControlFlowFrame]) -> bool {
self.builder.finish_node(); Self::set_if_phase(frame_stack, IfPhase::Finish);
true
}
fn handle_if_phase_finish(&mut self) -> bool {
if self.at_compiler_end_if_directive() {
self.consume_compiler_directive_prefix();
}
if self.at_token(Token::EndKeyword) {
self.consume_token();
self.consume_whitespace();
if self.at_token(Token::IfKeyword) {
self.consume_token();
}
self.consume_until_after(Token::Newline);
}
self.builder.finish_node(); false
}
fn handle_for_statement_frame(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
let (phase, is_for_each, depth) = if let Some(ControlFlowFrame::ForStatement {
phase,
is_for_each,
depth,
..
}) = frame_stack.last()
{
(*phase, *is_for_each, *depth)
} else {
return false; };
match phase {
ForPhase::Start => {
if is_for_each {
self.builder
.start_node(SyntaxKind::ForEachStatement.to_raw());
} else {
self.builder.start_node(SyntaxKind::ForStatement.to_raw());
}
self.consume_whitespace();
self.consume_token();
if is_for_each {
self.consume_whitespace();
self.consume_token(); self.consume_until_after(Token::Newline);
} else {
self.parse_lvalue();
self.consume_whitespace();
if self.at_token(Token::EqualityOperator) {
self.consume_token();
}
self.consume_whitespace();
self.parse_expression();
self.consume_whitespace();
if self.at_token(Token::ToKeyword) {
self.consume_token();
self.consume_whitespace();
self.parse_expression();
self.consume_whitespace();
if self.at_token(Token::StepKeyword) {
self.consume_token();
self.consume_whitespace();
self.parse_expression();
}
}
if self.has_inline_next_before_newline() {
self.parse_single_line_for_body_until_next();
self.builder.finish_node();
return false;
}
self.consume_until_after(Token::Newline);
}
if let Some(ControlFlowFrame::ForStatement { phase, .. }) = frame_stack.last_mut() {
*phase = ForPhase::Body;
}
true }
ForPhase::Body => {
if let Some(ControlFlowFrame::ForStatement { phase, .. }) = frame_stack.last_mut() {
*phase = ForPhase::Finish;
}
frame_stack.push(ControlFlowFrame::StatementList {
depth,
context: StatementListContext::ForBody,
started: false,
});
true }
ForPhase::Finish => {
if self.at_token(Token::NextKeyword) {
self.consume_token();
self.consume_until_after(Token::Newline);
}
self.builder.finish_node();
false }
}
}
fn handle_select_case_frame(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
let (phase, depth) =
if let Some(ControlFlowFrame::SelectCase { phase, depth, .. }) = frame_stack.last() {
(*phase, *depth)
} else {
return false; };
match phase {
SelectPhase::Start => {
self.builder
.start_node(SyntaxKind::SelectCaseStatement.to_raw());
self.consume_whitespace();
self.consume_token(); self.consume_whitespace();
if self.at_token(Token::CaseKeyword) {
self.consume_token();
}
self.consume_whitespace();
self.parse_expression();
self.consume_until_after(Token::Newline);
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut() {
*phase = SelectPhase::CaseClause;
}
true }
SelectPhase::CaseClause => self.parse_case_clause(frame_stack, depth),
SelectPhase::CaseBody => {
self.builder.finish_node();
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut() {
*phase = SelectPhase::CheckNextCase;
}
true
}
SelectPhase::CheckNextCase => {
while self.at_token(Token::Whitespace) || self.at_token(Token::Newline) {
self.consume_token();
}
if self.at_token(Token::CaseKeyword) {
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut()
{
*phase = SelectPhase::CaseClause;
}
true
} else if self.at_token(Token::EndKeyword)
&& self.peek_next_keyword() == Some(Token::SelectKeyword)
{
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut()
{
*phase = SelectPhase::Finish;
}
true
} else if self.is_at_end() {
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut()
{
*phase = SelectPhase::Finish;
}
true
} else {
self.consume_token();
true
}
}
SelectPhase::CaseElseBody => {
self.builder.finish_node();
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut() {
*phase = SelectPhase::Finish;
}
true
}
SelectPhase::Finish => {
if self.at_token(Token::EndKeyword) {
self.consume_token();
self.consume_whitespace();
if self.at_token(Token::SelectKeyword) {
self.consume_token();
}
self.consume_until_after(Token::Newline);
}
self.builder.finish_node();
false }
}
}
fn parse_case_clause(&mut self, frame_stack: &mut Vec<ControlFlowFrame>, depth: usize) -> bool {
while self.at_token(Token::Whitespace)
|| self.at_token(Token::Newline)
|| self.at_token(Token::EndOfLineComment)
|| self.at_token(Token::RemComment)
{
self.consume_token();
}
if self.at_token(Token::CaseKeyword) {
let is_case_else = self.peek_next_keyword() == Some(Token::ElseKeyword);
if is_case_else {
self.builder.start_node(SyntaxKind::CaseElseClause.to_raw());
self.consume_token(); self.consume_whitespace();
self.consume_token(); self.consume_until_after(Token::Newline);
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut() {
*phase = SelectPhase::CaseElseBody;
}
frame_stack.push(ControlFlowFrame::StatementList {
depth,
context: StatementListContext::SelectCaseBody,
started: false,
});
true } else {
self.builder.start_node(SyntaxKind::CaseClause.to_raw());
self.consume_token(); self.consume_until_after(Token::Newline);
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut() {
*phase = SelectPhase::CaseBody;
}
frame_stack.push(ControlFlowFrame::StatementList {
depth,
context: StatementListContext::SelectCaseBody,
started: false,
});
true }
} else {
if let Some(ControlFlowFrame::SelectCase { phase, .. }) = frame_stack.last_mut() {
*phase = SelectPhase::Finish;
}
true
}
}
fn handle_while_statement_frame(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
let (phase, depth) =
if let Some(ControlFlowFrame::WhileStatement { phase, depth, .. }) = frame_stack.last()
{
(*phase, *depth)
} else {
return false; };
match phase {
WhilePhase::Start => {
self.builder.start_node(SyntaxKind::WhileStatement.to_raw());
self.consume_whitespace();
self.consume_token(); self.consume_whitespace();
self.parse_expression();
if self.at_token(Token::Newline) {
self.consume_token();
}
if let Some(ControlFlowFrame::WhileStatement { phase, .. }) = frame_stack.last_mut()
{
*phase = WhilePhase::Body;
}
frame_stack.push(ControlFlowFrame::StatementList {
depth,
context: StatementListContext::WhileBody,
started: false,
});
true }
WhilePhase::Body => {
if let Some(ControlFlowFrame::WhileStatement { phase, .. }) = frame_stack.last_mut()
{
*phase = WhilePhase::Finish;
}
true
}
WhilePhase::Finish => {
if self.at_token(Token::WendKeyword) {
self.consume_token();
if self.at_token(Token::Newline) {
self.consume_token();
}
}
self.builder.finish_node();
false }
}
}
fn handle_do_statement_frame(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
let (phase, depth) =
if let Some(ControlFlowFrame::DoStatement { phase, depth, .. }) = frame_stack.last() {
(*phase, *depth)
} else {
return false; };
match phase {
DoPhase::Start => {
self.builder.start_node(SyntaxKind::DoStatement.to_raw());
self.consume_whitespace();
self.consume_token(); self.consume_whitespace();
if self.at_token(Token::WhileKeyword) || self.at_token(Token::UntilKeyword) {
self.consume_token();
self.consume_whitespace();
self.parse_expression();
}
if self.at_token(Token::Newline) {
self.consume_token();
}
if let Some(ControlFlowFrame::DoStatement { phase, .. }) = frame_stack.last_mut() {
*phase = DoPhase::Body;
}
frame_stack.push(ControlFlowFrame::StatementList {
depth,
context: StatementListContext::DoBody,
started: false,
});
true }
DoPhase::Body => {
if let Some(ControlFlowFrame::DoStatement { phase, .. }) = frame_stack.last_mut() {
*phase = DoPhase::Finish;
}
true
}
DoPhase::Finish => {
if self.at_token(Token::LoopKeyword) {
self.consume_token();
self.consume_whitespace();
if self.at_token(Token::WhileKeyword) || self.at_token(Token::UntilKeyword) {
self.consume_token();
self.consume_whitespace();
self.parse_expression();
}
if self.at_token(Token::Newline) {
self.consume_token();
}
}
self.builder.finish_node();
false }
}
}
fn handle_with_statement_frame(&mut self, frame_stack: &mut Vec<ControlFlowFrame>) -> bool {
let (phase, depth) = if let Some(ControlFlowFrame::WithStatement { phase, depth, .. }) =
frame_stack.last()
{
(*phase, *depth)
} else {
return false; };
match phase {
WithPhase::Start => {
self.builder.start_node(SyntaxKind::WithStatement.to_raw());
self.consume_whitespace();
self.consume_token(); self.consume_whitespace();
self.parse_expression();
self.consume_until_after(Token::Newline);
if let Some(ControlFlowFrame::WithStatement { phase, .. }) = frame_stack.last_mut()
{
*phase = WithPhase::Body;
}
frame_stack.push(ControlFlowFrame::StatementList {
depth,
context: StatementListContext::WithBody,
started: false,
});
true }
WithPhase::Body => {
if let Some(ControlFlowFrame::WithStatement { phase, .. }) = frame_stack.last_mut()
{
*phase = WithPhase::Finish;
}
true
}
WithPhase::Finish => {
if self.at_token(Token::EndKeyword) {
self.consume_token();
self.consume_whitespace();
if self.at_token(Token::WithKeyword) {
self.consume_token();
}
self.consume_until_after(Token::Newline);
}
self.builder.finish_node();
false }
}
}
}
#[cfg(test)]
mod tests {
use super::Parser;
use crate::parsers::cst::{
ControlKind, Creatable, Exposed, FormRoot, NameSpace, ObjectReference, PreDeclaredID,
};
use crate::*;
use assert_matches::assert_matches;
#[test]
fn parse_single_quote_comment() {
let code = "' This is a comment\nSub Main()\n";
let mut source_stream = SourceStream::new("test.bas", code);
let result = tokenize(&mut source_stream);
let (token_stream_opt, _failures) = result.unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let cst = parse(token_stream);
assert_eq!(cst.root_kind(), SyntaxKind::Root);
assert_eq!(cst.child_count(), 3); assert!(cst.text().contains("' This is a comment"));
assert!(cst.text().contains("Sub Main()"));
assert!(cst.contains_kind(SyntaxKind::EndOfLineComment));
assert!(cst.contains_kind(SyntaxKind::SubStatement));
let first = cst.first_child().expect("Expected first child");
assert_eq!(first.kind(), SyntaxKind::EndOfLineComment);
assert!(first.is_token());
}
#[test]
fn syntax_kind_conversions() {
assert_eq!(
SyntaxKind::from(Token::FunctionKeyword),
SyntaxKind::FunctionKeyword
);
assert_eq!(SyntaxKind::from(Token::IfKeyword), SyntaxKind::IfKeyword);
assert_eq!(SyntaxKind::from(Token::ForKeyword), SyntaxKind::ForKeyword);
assert_eq!(
SyntaxKind::from(Token::AdditionOperator),
SyntaxKind::AdditionOperator
);
assert_eq!(
SyntaxKind::from(Token::EqualityOperator),
SyntaxKind::EqualityOperator
);
assert_eq!(
SyntaxKind::from(Token::StringLiteral),
SyntaxKind::StringLiteral
);
assert_eq!(
SyntaxKind::from(Token::IntegerLiteral),
SyntaxKind::IntegerLiteral
);
assert_eq!(
SyntaxKind::from(Token::LongLiteral),
SyntaxKind::LongLiteral
);
assert_eq!(
SyntaxKind::from(Token::SingleLiteral),
SyntaxKind::SingleLiteral
);
assert_eq!(
SyntaxKind::from(Token::DoubleLiteral),
SyntaxKind::DoubleLiteral
);
assert_eq!(
SyntaxKind::from(Token::DateTimeLiteral),
SyntaxKind::DateLiteral
);
}
#[test]
fn parse_empty_stream() {
let source = "";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("Failed to parse source");
assert_eq!(cst.root_kind(), SyntaxKind::Root);
assert_eq!(cst.child_count(), 0);
}
#[test]
fn parse_rem_comment() {
let source = "REM This is a REM comment\nSub Test()\nEnd Sub\n";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("Failed to parse source");
assert_eq!(cst.root_kind(), SyntaxKind::Root);
assert_eq!(cst.child_count(), 3); assert!(cst.text().contains("REM This is a REM comment"));
assert!(cst.text().contains("Sub Test()"));
let debug = cst.debug_tree();
assert!(debug.contains("RemComment"));
}
#[test]
fn parse_mixed_comments() {
let source = "' Single quote comment\nREM REM comment\nSub Test()\nEnd Sub\n";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("Failed to parse source");
assert_eq!(cst.root_kind(), SyntaxKind::Root);
assert_eq!(cst.child_count(), 5);
assert!(cst.text().contains("' Single quote comment"));
assert!(cst.text().contains("REM REM comment"));
let children = cst.children();
assert_eq!(children[0].kind(), SyntaxKind::EndOfLineComment);
assert_eq!(children[1].kind(), SyntaxKind::Newline);
assert_eq!(children[2].kind(), SyntaxKind::RemComment);
assert_eq!(children[3].kind(), SyntaxKind::Newline);
assert_eq!(children[4].kind(), SyntaxKind::SubStatement);
assert!(cst.contains_kind(SyntaxKind::EndOfLineComment));
assert!(cst.contains_kind(SyntaxKind::RemComment));
}
#[test]
fn parse_with_member_assignment_with_index_expression() {
let source = r"
Sub Test()
With mstack
.item(i - 1) = aa.ObjectRef
End With
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert!(
failures.is_empty(),
"unexpected parse failures: {failures:?}"
);
let cst = cst_opt.expect("Failed to parse source");
let debug = cst.debug_tree();
assert!(
debug.contains("AssignmentStatement"),
"expected assignment statement in tree: {debug}"
);
assert!(
!debug.contains("kind: Unknown"),
"unexpected unknown token in tree: {debug}"
);
}
#[test]
fn cst_with_comments() {
let source = "' This is a comment\nSub Main()\n";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("Failed to parse source");
assert_eq!(cst.child_count(), 3);
assert!(cst.text().contains("' This is a comment"));
assert!(cst.text().contains("Sub Main()"));
}
#[test]
fn cst_serializable_tree() {
let source = "Sub Test()\nEnd Sub\n";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("Failed to parse source");
let serializable = cst.to_serializable();
assert_eq!(serializable.root.kind(), SyntaxKind::Root);
assert!(!serializable.root.is_token());
assert_eq!(serializable.root.children().len(), 1);
assert_eq!(
serializable.root.children()[0].kind(),
SyntaxKind::SubStatement
);
}
#[test]
fn cst_serializable_with_insta() {
let source = "Dim x As Integer\n";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("Failed to parse source");
let serializable = cst.to_serializable();
assert!(!serializable.root.children().is_empty());
}
#[test]
fn parser_mode_full_cst_default() {
let source = "Sub Test()\nEnd Sub\n";
let mut stream = SourceStream::new("test.bas".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let parser = Parser::new(token_stream);
assert_eq!(parser.pos, 0);
}
#[test]
fn parser_mode_direct_extraction() {
let source = "Sub Test()\nEnd Sub\n";
let mut stream = SourceStream::new("test.bas".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let parser = Parser::new_direct_extraction(tokens, 0);
assert_eq!(parser.pos, 0);
}
#[test]
fn parser_constructors_preserve_tokens() {
let source = "VERSION 5.00\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens_vec = token_stream.into_tokens();
let token_count = tokens_vec.len();
let parser = Parser::new_direct_extraction(tokens_vec, 0);
assert_eq!(parser.tokens.len(), token_count);
assert_eq!(parser.tokens[0].1, Token::VersionKeyword);
}
#[test]
fn parser_new_with_position() {
let source = "VERSION 5.00\nSub Test()\nEnd Sub\n";
let mut stream = SourceStream::new("test.bas".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let parser = Parser::new_direct_extraction(tokens, 3);
assert_eq!(parser.pos, 3);
}
#[test]
fn parse_version_direct_with_version() {
let source = "VERSION 5.00\nSub Test()\nEnd Sub\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (version_opt, failures) = parser.parse_version_direct().unpack();
assert!(version_opt.is_some());
let version = version_opt.expect("Expected version to be parsed");
assert_eq!(version.major, 5);
assert_eq!(version.minor, 0);
assert!(failures.is_empty());
}
#[test]
fn parse_version_direct_without_version() {
let source = "Sub Test()\nEnd Sub\n";
let mut stream = SourceStream::new("test.bas".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (version_opt, failures) = parser.parse_version_direct().unpack();
assert!(version_opt.is_none());
assert!(failures.is_empty());
}
#[test]
fn parse_version_direct_with_class_keyword() {
let source = "VERSION 1.0 CLASS\nSub Test()\nEnd Sub\n";
let mut stream = SourceStream::new("test.cls".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (version_opt, failures) = parser.parse_version_direct().unpack();
assert!(version_opt.is_some());
let version = version_opt.expect("Expected version to be parsed");
assert_eq!(version.major, 1);
assert_eq!(version.minor, 0);
assert!(failures.is_empty());
}
#[test]
fn parse_version_direct_version_100() {
let source = "VERSION 1.00\n";
let mut stream = SourceStream::new("test.cls".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (version_opt, _failures) = parser.parse_version_direct().unpack();
assert!(version_opt.is_some());
let version = version_opt.expect("Expected version to be parsed");
assert_eq!(version.major, 1);
assert_eq!(version.minor, 0);
}
#[test]
fn parse_version_direct_with_whitespace() {
let source = " VERSION 5.00 \nSub Test()\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (version_opt, _failures) = parser.parse_version_direct().unpack();
assert!(version_opt.is_some());
let version = version_opt.expect("Expected version to be parsed");
assert_eq!(version.major, 5);
assert_eq!(version.minor, 0);
}
#[test]
fn parse_version_direct_position_advances() {
let source = "VERSION 5.00\nBegin VB.Form Form1\nEnd\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let initial_pos = parser.pos;
let _result = parser.parse_version_direct();
assert!(parser.pos > initial_pos);
assert_eq!(parser.current_token(), Some(&Token::BeginKeyword));
}
#[test]
fn parse_version_direct_accuracy() {
let test_cases = vec![
("VERSION 5.00\n", Some((5, 0))),
("VERSION 1.0\n", Some((1, 0))),
("VERSION 6.00 CLASS\n", Some((6, 0))),
("VERSION 4.00\n", Some((4, 0))),
("Sub Test()\n", None), ];
for (source, expected) in test_cases {
let mut stream = SourceStream::new("test.vb".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (version_opt, _failures) = parser.parse_version_direct().unpack();
match expected {
Some((major, minor)) => {
assert!(version_opt.is_some(), "Expected version for: {source}");
let version = version_opt.expect("Expected version to be parsed");
assert_eq!(version.major, major, "Major mismatch for: {source}");
assert_eq!(version.minor, minor, "Minor mismatch for: {source}");
}
None => {
assert!(version_opt.is_none(), "Expected no version for: {source}");
}
}
}
}
#[test]
fn parse_control_type_direct_simple() {
let source = "VB.Form Form1\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let control_type = parser.parse_control_type_direct();
assert_eq!(control_type, "VB.Form");
}
#[test]
fn parse_control_name_direct_simple() {
let source = "Form1 \n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let control_name = parser.parse_control_name_direct();
assert_eq!(control_name, "Form1");
}
#[test]
fn parse_property_direct_simple() {
let source = "Caption = \"Hello World\"\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let property = parser.parse_property_direct();
assert!(property.is_some());
let (key, value) = property.expect("Expected property to be parsed");
assert_eq!(key, "Caption");
assert_eq!(value, "\"Hello World\"");
}
#[test]
fn parse_properties_block_to_control_simple_form() {
let source = r#"Begin VB.Form Form1
Caption = "Test Form"
ClientHeight = 3000
ClientWidth = 4000
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, failures) = parser.parse_properties_block_to_form_root().unpack();
assert!(failures.is_empty(), "Expected no failures");
assert!(control_opt.is_some(), "Expected form root to be parsed");
let form_root = control_opt.expect("Expected form root to be parsed");
assert_eq!(form_root.name(), "Form1");
assert!(form_root.is_form());
}
#[test]
fn parse_properties_block_to_control_command_button() {
let source = r#"Begin VB.CommandButton Command1
Caption = "Click Me"
Height = 495
Width = 1215
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
assert!(failures.is_empty());
assert!(control_opt.is_some());
let control = control_opt.expect("Expected control to be parsed");
assert_eq!(control.name(), "Command1");
assert_matches!(control.kind(), ControlKind::CommandButton { .. });
}
#[test]
fn parse_properties_block_to_control_textbox() {
let source = r#"Begin VB.TextBox Text1
Text = "Initial Text"
Height = 300
Width = 2000
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, _failures) = parser.parse_properties_block_to_control().unpack();
assert!(control_opt.is_some());
let control = control_opt.expect("Expected control to be parsed");
assert_eq!(control.name(), "Text1");
assert_matches!(control.kind(), ControlKind::TextBox { .. });
}
#[test]
fn parse_properties_block_without_begin() {
let source = "Caption = \"Test\"\nEnd\n";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, _failures) = parser.parse_properties_block_to_control().unpack();
assert!(control_opt.is_none());
}
#[test]
fn parse_form_with_nested_control() {
let source = r#"Begin VB.Form Form1
Caption = "Main Form"
Begin VB.CommandButton Command1
Caption = "Click Me"
Height = 400
End
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (form_root_opt, failures) = parser.parse_properties_block_to_form_root().unpack();
assert!(failures.is_empty(), "Should have no failures");
assert!(form_root_opt.is_some());
let form_root = form_root_opt.expect("Expected form root to be parsed");
assert_eq!(form_root.name(), "Form1");
if let FormRoot::Form(form) = &form_root {
assert_eq!(form.controls.len(), 1);
assert_eq!(form.controls[0].name(), "Command1");
assert_matches!(form.controls[0].kind(), ControlKind::CommandButton { .. });
} else {
panic!("Expected Form");
}
}
#[test]
fn parse_frame_with_multiple_nested_controls() {
let source = r#"Begin VB.Frame Frame1
Caption = "Options"
Begin VB.CheckBox Check1
Caption = "Option 1"
End
Begin VB.CheckBox Check2
Caption = "Option 2"
End
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
assert!(failures.is_empty());
assert!(control_opt.is_some());
let control = control_opt.expect("Expected control to be parsed");
assert_eq!(control.name(), "Frame1");
if let ControlKind::Frame { controls, .. } = control.kind() {
assert_eq!(controls.len(), 2);
assert_eq!(controls[0].name(), "Check1");
assert_eq!(controls[1].name(), "Check2");
} else {
panic!("Expected Frame control kind");
}
}
#[test]
fn parse_control_with_property_group() {
let source = r#"Begin VB.CommandButton Command1
Caption = "Button"
BeginProperty Font
Name = "Arial"
Size = 12
EndProperty
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
assert!(failures.is_empty());
assert!(control_opt.is_some());
let control = control_opt.expect("Expected control to be parsed");
assert_eq!(control.name(), "Command1");
assert_matches!(control.kind(), ControlKind::CommandButton { .. });
}
#[test]
fn parse_custom_control_with_property_group() {
let source = r#"Begin MSComctlLib.TreeView TreeView1
BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}
Name = "MS Sans Serif"
Size = 8.25
Charset = 0
EndProperty
Caption = "Tree"
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
assert!(failures.is_empty());
assert!(control_opt.is_some());
let control = control_opt.expect("Expected control to be parsed");
assert_eq!(control.name(), "TreeView1");
if let ControlKind::Custom {
property_groups, ..
} = control.kind()
{
assert_eq!(property_groups.len(), 1);
assert_eq!(property_groups[0].name, "Font");
assert!(property_groups[0].guid.is_some());
} else {
panic!("Expected Custom control kind");
}
}
#[test]
fn parse_simple_object_statement() {
let source = r#"Object = "{12345678-1234-1234-1234-123456789ABC}#1.0#0"; "MyLib.dll""#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let objects = parser.parse_objects_direct();
assert_eq!(objects.len(), 1);
match &objects[0] {
ObjectReference::Compiled {
uuid,
version,
unknown1,
file_name,
} => {
assert_eq!(
uuid.to_string().to_uppercase(),
"12345678-1234-1234-1234-123456789ABC"
);
assert_eq!(version, "1.0");
assert_eq!(unknown1, "0");
assert_eq!(file_name, "MyLib.dll");
}
ObjectReference::Project { .. } => {
panic!("Expected Compiled object reference")
}
}
}
#[test]
fn parse_multiple_object_statements() {
let source = r#"Object = "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}#1.0#0"; "Lib1.dll"
Object = "{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}#2.0#1"; "Lib2.ocx"
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let objects = parser.parse_objects_direct();
assert_eq!(objects.len(), 2);
match &objects[0] {
ObjectReference::Compiled { file_name, .. } => {
assert_eq!(file_name, "Lib1.dll");
}
ObjectReference::Project { .. } => {
panic!("Expected Compiled object reference")
}
}
match &objects[1] {
ObjectReference::Compiled { file_name, .. } => {
assert_eq!(file_name, "Lib2.ocx");
}
ObjectReference::Project { .. } => {
panic!("Expected Compiled object reference")
}
}
}
#[test]
fn parse_embedded_object_statement() {
let source = r#"Object = *\G{87654321-4321-4321-4321-CBA987654321}#3.0#5; "Embedded.ocx""#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let objects = parser.parse_objects_direct();
assert_eq!(objects.len(), 1);
match &objects[0] {
ObjectReference::Compiled {
uuid,
version,
file_name,
..
} => {
assert_eq!(
uuid.to_string().to_uppercase(),
"87654321-4321-4321-4321-CBA987654321"
);
assert_eq!(version, "3.0");
assert_eq!(file_name, "Embedded.ocx");
}
ObjectReference::Project { .. } => {
panic!("Expected Compiled object reference")
}
}
}
#[test]
fn parse_nested_property_groups() {
use either::Either;
let source = r#"Begin Custom.Control Ctrl1
BeginProperty Outer
Value1 = "Test"
BeginProperty Inner
Value2 = "Nested"
EndProperty
EndProperty
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (control_opt, failures) = parser.parse_properties_block_to_control().unpack();
assert!(failures.is_empty());
assert!(control_opt.is_some());
let control = control_opt.expect("Expected control to be parsed");
if let ControlKind::Custom {
property_groups, ..
} = control.kind()
{
assert_eq!(property_groups.len(), 1);
assert_eq!(property_groups[0].name, "Outer");
if let Some(Either::Right(inner)) = property_groups[0].properties.get("Inner") {
assert_eq!(inner.name, "Inner");
} else {
panic!("Expected nested Inner property group");
}
} else {
panic!("Expected Custom control kind");
}
}
#[test]
fn parse_deeply_nested_controls() {
let source = r#"Begin VB.Form Form1
Caption = "Outer"
Begin VB.PictureBox Picture1
Begin VB.Frame Frame1
Begin VB.Label Label1
Caption = "Deep"
End
End
End
End
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let (form_root_opt, failures) = parser.parse_properties_block_to_form_root().unpack();
assert!(failures.is_empty());
assert!(form_root_opt.is_some());
let form_root = form_root_opt.expect("Expected form root to be parsed");
if let FormRoot::Form(form) = &form_root {
assert_eq!(form.controls.len(), 1);
if let ControlKind::PictureBox { controls, .. } = form.controls[0].kind() {
assert_eq!(controls.len(), 1);
if let ControlKind::Frame { controls, .. } = controls[0].kind() {
assert_eq!(controls.len(), 1);
assert_eq!(controls[0].name(), "Label1");
} else {
panic!("Expected Frame");
}
} else {
panic!("Expected PictureBox");
}
} else {
panic!("Expected Form");
}
}
#[test]
fn parse_simple_string_attribute() {
let source = r#"Attribute VB_Name = "Form1"
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let attrs = parser.parse_attributes_direct();
assert_eq!(attrs.name, "Form1");
assert_eq!(attrs.global_name_space, NameSpace::Local);
assert_eq!(attrs.creatable, Creatable::True);
assert_eq!(attrs.predeclared_id, PreDeclaredID::False);
assert_eq!(attrs.exposed, Exposed::False);
assert_eq!(attrs.description, None);
}
#[test]
fn parse_boolean_attributes() {
let source = r"Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let attrs = parser.parse_attributes_direct();
assert_eq!(attrs.global_name_space, NameSpace::Local);
assert_eq!(attrs.creatable, Creatable::True);
assert_eq!(attrs.predeclared_id, PreDeclaredID::True);
assert_eq!(attrs.exposed, Exposed::False);
}
#[test]
fn parse_numeric_attribute() {
let source = r"Attribute VB_PredeclaredId = -1
";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let attrs = parser.parse_attributes_direct();
assert_eq!(attrs.predeclared_id, PreDeclaredID::True);
}
#[test]
fn parse_multiple_attributes() {
let source = r#"Attribute VB_Name = "MyForm"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Attribute VB_Description = "This is a test form"
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let attrs = parser.parse_attributes_direct();
assert_eq!(attrs.name, "MyForm");
assert_eq!(attrs.global_name_space, NameSpace::Local);
assert_eq!(attrs.creatable, Creatable::False);
assert_eq!(attrs.predeclared_id, PreDeclaredID::True);
assert_eq!(attrs.exposed, Exposed::False);
assert_eq!(attrs.description, Some("This is a test form".to_string()));
}
#[test]
fn parse_ext_key_attributes() {
let source = r#"Attribute VB_Name = "Form1"
Attribute VB_Ext_KEY = "CustomKey" ,"CustomValue"
Attribute VB_Description = "Test"
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let attrs = parser.parse_attributes_direct();
assert_eq!(attrs.name, "Form1");
assert_eq!(attrs.description, Some("Test".to_string()));
assert_eq!(attrs.ext_key.len(), 1);
assert!(attrs.ext_key.contains_key("VB_Ext_KEY"));
}
#[test]
fn parse_empty_attributes() {
let source = r"";
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let attrs = parser.parse_attributes_direct();
assert_eq!(attrs.name, "");
assert_eq!(attrs.global_name_space, NameSpace::Local);
assert_eq!(attrs.creatable, Creatable::True);
assert_eq!(attrs.predeclared_id, PreDeclaredID::False);
assert_eq!(attrs.exposed, Exposed::False);
assert_eq!(attrs.description, None);
assert!(attrs.ext_key.is_empty());
}
#[test]
fn parse_resource_reference_property() {
let source = r#"Caption = $"Gradient.frx":0000
"#;
let mut stream = SourceStream::new("test.frm".to_string(), source);
let (token_stream_opt, _) = tokenize(&mut stream).unpack();
let token_stream = token_stream_opt.expect("Tokenization failed");
let tokens = token_stream.into_tokens();
let mut parser = Parser::new_direct_extraction(tokens, 0);
let property = parser.parse_property_direct();
assert!(property.is_some());
let (key, value) = property.expect("Expected property to be parsed");
assert_eq!(key, "Caption");
assert_eq!(value, r#"$"Gradient.frx":0000"#);
}
}