use crate::language::Token;
use crate::parsers::SyntaxKind;
use crate::parsers::cst::Parser;
use rowan::Checkpoint;
#[derive(Debug, Clone, Copy)]
enum ExprParseFrame {
ParsePrefix {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
InfixLoop {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
FinishBinary {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
FinishUnary {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
FinishTypeOf {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
FinishParenthesized {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
ParsePostfix {
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
},
StartArgumentList {
min_bp: BindingPower,
call_checkpoint: Checkpoint,
},
NextArgument {
min_bp: BindingPower,
call_checkpoint: Checkpoint,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct BindingPower(u8);
impl BindingPower {
pub(super) const NONE: BindingPower = BindingPower(0);
pub(super) const IMP: BindingPower = BindingPower(10);
pub(super) const EQV: BindingPower = BindingPower(20);
pub(super) const XOR: BindingPower = BindingPower(30);
pub(super) const OR: BindingPower = BindingPower(40);
pub(super) const AND: BindingPower = BindingPower(50);
pub(super) const NOT: BindingPower = BindingPower(60);
pub(super) const COMPARISON: BindingPower = BindingPower(70);
pub(super) const CONCATENATION: BindingPower = BindingPower(80);
pub(super) const ADDITION: BindingPower = BindingPower(90);
pub(super) const MODULO: BindingPower = BindingPower(100);
pub(super) const INT_DIVISION: BindingPower = BindingPower(110);
pub(super) const MULTIPLICATION: BindingPower = BindingPower(120);
pub(super) const UNARY: BindingPower = BindingPower(130);
pub(super) const EXPONENTIATION: BindingPower = BindingPower(140);
}
impl Parser<'_> {
pub(crate) fn parse_expression(&mut self) {
self.parse_expression_with_binding_power(BindingPower::NONE);
}
pub(crate) fn parse_lvalue(&mut self) {
self.parse_expression_with_binding_power(BindingPower(75));
}
pub(crate) fn parse_expression_with_binding_power(&mut self, min_bp: BindingPower) {
let mut frame_stack: Vec<ExprParseFrame> = Vec::new();
self.consume_whitespace();
let initial_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp,
lhs_checkpoint: initial_checkpoint,
});
while let Some(frame) = frame_stack.pop() {
self.dispatch_expression_frame(frame, &mut frame_stack);
}
}
fn dispatch_expression_frame(
&mut self,
frame: ExprParseFrame,
frame_stack: &mut Vec<ExprParseFrame>,
) {
match frame {
ExprParseFrame::ParsePrefix {
min_bp,
lhs_checkpoint,
} => self.handle_prefix_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::InfixLoop {
min_bp,
lhs_checkpoint,
} => self.handle_infix_loop_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::FinishBinary {
min_bp,
lhs_checkpoint,
} => self.handle_finish_binary_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::FinishTypeOf {
min_bp,
lhs_checkpoint,
} => self.handle_finish_typeof_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::FinishUnary {
min_bp,
lhs_checkpoint,
} => self.handle_finish_unary_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::FinishParenthesized {
min_bp,
lhs_checkpoint,
} => self.handle_finish_parenthesized_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint,
} => self.handle_postfix_frame(frame_stack, min_bp, lhs_checkpoint),
ExprParseFrame::StartArgumentList {
min_bp,
call_checkpoint,
} => self.handle_argument_list_start_frame(frame_stack, min_bp, call_checkpoint),
ExprParseFrame::NextArgument {
min_bp,
call_checkpoint,
} => self.handle_argument_next_frame(frame_stack, min_bp, call_checkpoint),
}
}
fn handle_prefix_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
let (pushes_frames, prefix_checkpoint) =
self.parse_prefix_expression_frame(frame_stack, min_bp, lhs_checkpoint);
if !pushes_frames {
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint: prefix_checkpoint,
});
}
}
fn handle_infix_loop_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
let Some((left_bp, right_bp)) = self.peek_infix_binding_power_after_trivia() else {
return;
};
if left_bp < min_bp {
return;
}
self.consume_whitespace();
self.builder
.start_node_at(lhs_checkpoint, SyntaxKind::BinaryExpression.to_raw());
self.consume_token();
self.consume_whitespace();
let rhs_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::FinishBinary {
min_bp,
lhs_checkpoint,
});
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp: right_bp,
lhs_checkpoint: rhs_checkpoint,
});
}
fn handle_finish_binary_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
self.builder.finish_node();
frame_stack.push(ExprParseFrame::InfixLoop {
min_bp,
lhs_checkpoint,
});
}
fn handle_finish_unary_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
self.builder.finish_node();
frame_stack.push(ExprParseFrame::InfixLoop {
min_bp,
lhs_checkpoint,
});
}
fn handle_finish_typeof_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
self.consume_whitespace();
if self.at_token(Token::IsKeyword) {
self.consume_token(); }
self.consume_whitespace();
if self.is_identifier() || self.at_keyword() {
self.consume_token();
}
self.builder.finish_node();
frame_stack.push(ExprParseFrame::InfixLoop {
min_bp,
lhs_checkpoint,
});
}
fn handle_finish_parenthesized_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
self.consume_whitespace();
if self.at_token(Token::Comma) {
self.consume_token();
self.consume_whitespace();
if !self.at_token(Token::RightParenthesis) && !self.is_at_end() {
frame_stack.push(ExprParseFrame::FinishParenthesized {
min_bp,
lhs_checkpoint,
});
let inner_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp: BindingPower::NONE,
lhs_checkpoint: inner_checkpoint,
});
return;
}
}
if self.at_token(Token::RightParenthesis) {
self.consume_token();
}
self.builder.finish_node();
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint,
});
}
fn handle_argument_list_start_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
call_checkpoint: Checkpoint,
) {
self.builder.start_node(SyntaxKind::ArgumentList.to_raw());
self.consume_whitespace();
if self.at_token(Token::RightParenthesis) {
self.builder.finish_node(); self.consume_token(); self.builder.finish_node();
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint: call_checkpoint,
});
return;
}
if self.at_token(Token::Comma) {
self.builder.start_node(SyntaxKind::Argument.to_raw());
self.builder.finish_node(); self.consume_token(); self.consume_whitespace();
self.parse_argument_after_separator(frame_stack, min_bp, call_checkpoint);
return;
}
self.parse_argument_expression(frame_stack, min_bp, call_checkpoint);
}
fn parse_argument_after_separator(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
call_checkpoint: Checkpoint,
) {
loop {
if self.at_token(Token::RightParenthesis) {
self.builder.start_node(SyntaxKind::Argument.to_raw());
self.builder.finish_node();
self.builder.finish_node(); self.consume_token(); self.builder.finish_node();
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint: call_checkpoint,
});
return;
}
if self.at_token(Token::Comma) {
self.builder.start_node(SyntaxKind::Argument.to_raw());
self.builder.finish_node(); self.consume_token(); self.consume_whitespace();
continue;
}
self.parse_argument_expression(frame_stack, min_bp, call_checkpoint);
return;
}
}
fn parse_argument_expression(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
call_checkpoint: Checkpoint,
) {
self.builder.start_node(SyntaxKind::Argument.to_raw());
let _ = self.try_consume_named_argument_prefix();
frame_stack.push(ExprParseFrame::NextArgument {
min_bp,
call_checkpoint,
});
let arg_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp: BindingPower::NONE,
lhs_checkpoint: arg_checkpoint,
});
}
fn try_consume_named_argument_prefix(&mut self) -> bool {
let mut idx = self.pos;
let Some((_, first_token)) = self.tokens.get(idx) else {
return false;
};
if !(*first_token == Token::Identifier || first_token.is_keyword()) {
return false;
}
idx += 1;
while let Some((_, Token::Whitespace)) = self.tokens.get(idx) {
idx += 1;
}
if self.tokens.get(idx).map(|(_, token)| *token) != Some(Token::ColonOperator) {
return false;
}
idx += 1;
while let Some((_, Token::Whitespace)) = self.tokens.get(idx) {
idx += 1;
}
if self.tokens.get(idx).map(|(_, token)| *token) != Some(Token::EqualityOperator) {
return false;
}
while self.pos <= idx {
self.consume_token();
}
self.consume_whitespace();
true
}
fn handle_argument_next_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
call_checkpoint: Checkpoint,
) {
self.builder.finish_node();
self.consume_whitespace();
if self.at_token(Token::Comma) {
self.consume_token();
self.consume_whitespace();
self.parse_argument_after_separator(frame_stack, min_bp, call_checkpoint);
return;
}
self.builder.finish_node();
if self.at_token(Token::RightParenthesis) {
self.consume_token();
}
self.builder.finish_node();
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint: call_checkpoint,
});
}
fn skip_expression_trivia(&mut self) {
loop {
match self.current_token() {
Some(Token::Whitespace) => {
self.pos += 1;
}
Some(Token::Underscore) => {
let mut lookahead = 1;
let mut is_continuation = false;
while let Some((_, token)) = self.tokens.get(self.pos + lookahead) {
if *token == Token::Whitespace {
lookahead += 1;
} else if *token == Token::Newline {
is_continuation = true;
break;
} else {
break;
}
}
if is_continuation {
self.pos += lookahead + 1;
} else {
break;
}
}
_ => break,
}
}
}
fn has_postfix_operator_ahead(&mut self) -> bool {
let saved_pos = self.pos;
self.skip_expression_trivia();
let found_postfix = matches!(
self.current_token(),
Some(Token::PeriodOperator | Token::LeftParenthesis | Token::ExclamationMark)
);
self.pos = saved_pos;
found_postfix
}
fn peek_infix_binding_power_after_trivia(&mut self) -> Option<(BindingPower, BindingPower)> {
let saved_pos = self.pos;
self.skip_expression_trivia();
if self.is_at_end() || self.is_at_expression_delimiter() {
self.pos = saved_pos;
return None;
}
let binding_power = self.get_infix_binding_power();
self.pos = saved_pos;
binding_power
}
fn handle_postfix_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
let found_postfix = self.has_postfix_operator_ahead();
if found_postfix {
self.consume_whitespace();
match self.current_token() {
Some(Token::PeriodOperator) => {
self.builder
.start_node_at(lhs_checkpoint, SyntaxKind::MemberAccessExpression.to_raw());
self.parse_member_access_content();
self.builder.finish_node();
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint,
});
}
Some(Token::LeftParenthesis) => {
self.builder
.start_node_at(lhs_checkpoint, SyntaxKind::CallExpression.to_raw());
self.consume_token();
frame_stack.push(ExprParseFrame::StartArgumentList {
min_bp,
call_checkpoint: lhs_checkpoint,
});
}
Some(Token::ExclamationMark) => {
self.builder
.start_node_at(lhs_checkpoint, SyntaxKind::MemberAccessExpression.to_raw());
self.parse_dictionary_access_content();
self.builder.finish_node();
frame_stack.push(ExprParseFrame::ParsePostfix {
min_bp,
lhs_checkpoint,
});
}
_ => {
frame_stack.push(ExprParseFrame::InfixLoop {
min_bp,
lhs_checkpoint,
});
}
}
} else {
frame_stack.push(ExprParseFrame::InfixLoop {
min_bp,
lhs_checkpoint,
});
}
}
fn push_unary_prefix_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
operand_min_bp: BindingPower,
) {
self.builder
.start_node(SyntaxKind::UnaryExpression.to_raw());
self.consume_token();
self.consume_whitespace();
frame_stack.push(ExprParseFrame::FinishUnary {
min_bp,
lhs_checkpoint,
});
let operand_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp: operand_min_bp,
lhs_checkpoint: operand_checkpoint,
});
}
fn push_typeof_prefix_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
self.builder
.start_node(SyntaxKind::TypeOfExpression.to_raw());
self.consume_token(); self.consume_whitespace();
frame_stack.push(ExprParseFrame::FinishTypeOf {
min_bp,
lhs_checkpoint,
});
let operand_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp: BindingPower::CONCATENATION,
lhs_checkpoint: operand_checkpoint,
});
}
fn push_parenthesized_prefix_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) {
self.builder
.start_node(SyntaxKind::ParenthesizedExpression.to_raw());
self.consume_token();
self.consume_whitespace();
frame_stack.push(ExprParseFrame::FinishParenthesized {
min_bp,
lhs_checkpoint,
});
let inner_checkpoint = self.builder.checkpoint();
frame_stack.push(ExprParseFrame::ParsePrefix {
min_bp: BindingPower::NONE,
lhs_checkpoint: inner_checkpoint,
});
}
fn try_push_prefix_frames(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) -> bool {
match self.current_token() {
Some(Token::NotKeyword) => {
self.push_unary_prefix_frame(
frame_stack,
min_bp,
lhs_checkpoint,
BindingPower::NOT,
);
true
}
Some(Token::ByValKeyword | Token::ByRefKeyword | Token::SubtractionOperator) => {
self.push_unary_prefix_frame(
frame_stack,
min_bp,
lhs_checkpoint,
BindingPower::UNARY,
);
true
}
Some(Token::LeftParenthesis) => {
self.push_parenthesized_prefix_frame(frame_stack, min_bp, lhs_checkpoint);
true
}
_ => false,
}
}
fn parse_prefix_expression_frame(
&mut self,
frame_stack: &mut Vec<ExprParseFrame>,
min_bp: BindingPower,
lhs_checkpoint: Checkpoint,
) -> (bool, Checkpoint) {
self.consume_whitespace();
let checkpoint = self.builder.checkpoint();
let mut is_identifier = false;
if self.try_push_prefix_frames(frame_stack, min_bp, lhs_checkpoint) {
return (true, checkpoint);
}
match self.current_token() {
Some(Token::AddressOfKeyword) => {
self.parse_addressof_expression();
}
Some(Token::NewKeyword) => {
self.parse_new_expression();
}
Some(
Token::IntegerLiteral
| Token::LongLiteral
| Token::SingleLiteral
| Token::DoubleLiteral
| Token::DecimalLiteral,
) => {
self.parse_numeric_literal();
}
Some(Token::StringLiteral) => {
self.parse_string_literal();
}
Some(Token::TrueKeyword | Token::FalseKeyword) => {
self.parse_boolean_literal();
}
Some(Token::NullKeyword | Token::EmptyKeyword) => {
self.parse_special_literal();
}
Some(Token::DateTimeLiteral) => {
self.parse_date_literal();
}
Some(Token::Octothorpe) => {
self.parse_file_number_reference();
is_identifier = true;
}
Some(Token::TypeOfKeyword) => {
self.push_typeof_prefix_frame(frame_stack, min_bp, lhs_checkpoint);
return (true, checkpoint);
}
Some(Token::PeriodOperator) => {
self.parse_with_member_expression();
is_identifier = true;
}
_ => {
self.parse_identifier_or_call_expression();
is_identifier = true;
}
}
if is_identifier {
self.parse_bare_identifier(checkpoint);
}
(false, checkpoint)
}
fn parse_bare_identifier(&mut self, checkpoint: Checkpoint) {
let has_postfix = self.has_postfix_operator_ahead();
if !has_postfix {
self.builder
.start_node_at(checkpoint, SyntaxKind::IdentifierExpression.to_raw());
self.builder.finish_node();
}
}
fn parse_identifier_or_call_expression(&mut self) {
if self.at_token(Token::LeftSquareBracket) {
self.parse_bracketed_identifier();
return;
}
if self.is_identifier() || self.at_keyword() {
if self.at_keyword_dollar() {
self.consume_keyword_dollar_as_identifier();
} else {
self.consume_token();
if matches!(
self.current_token(),
Some(
Token::DollarSign
| Token::Percent
| Token::Ampersand
| Token::Octothorpe
| Token::AtSign
)
) {
self.consume_token();
}
}
} else {
self.consume_token();
}
}
fn parse_bracketed_identifier(&mut self) {
if let Some((text, _)) = self.tokens.get(self.pos) {
self.builder
.token(SyntaxKind::LeftSquareBracket.to_raw(), text);
self.pos += 1;
}
let content_start = self.pos;
while !self.is_at_end() && !self.at_token(Token::RightSquareBracket) {
self.pos += 1;
}
let content_end = self.pos;
if let Some((start_offset, end_offset)) =
self.tokens_span_offsets(content_start, content_end)
{
let content = &self.source_content[start_offset..end_offset];
self.builder.token(SyntaxKind::Identifier.to_raw(), content);
} else {
let mut merged_identifier = String::new();
for idx in content_start..content_end {
if let Some((text, _)) = self.tokens.get(idx) {
merged_identifier.push_str(text);
}
}
self.builder
.token(SyntaxKind::Identifier.to_raw(), &merged_identifier);
}
if let Some((text, _)) = self.tokens.get(self.pos)
&& self.at_token(Token::RightSquareBracket)
{
self.builder
.token(SyntaxKind::RightSquareBracket.to_raw(), text);
self.pos += 1;
}
if matches!(
self.current_token(),
Some(
Token::DollarSign
| Token::Percent
| Token::Ampersand
| Token::Octothorpe
| Token::AtSign
)
) {
self.consume_token();
}
}
fn parse_file_number_reference(&mut self) {
self.consume_token();
self.consume_whitespace();
if self.at_token(Token::PeriodOperator) {
self.parse_with_member_expression();
return;
}
if self.is_number() {
self.consume_token();
return;
}
if self.is_identifier() || self.at_keyword() {
self.consume_token_as_identifier();
}
}
fn parse_addressof_expression(&mut self) {
self.builder
.start_node(SyntaxKind::AddressOfExpression.to_raw());
self.consume_token();
self.consume_whitespace();
if self.is_identifier() || self.at_keyword() {
self.consume_token();
}
self.builder.finish_node();
}
fn parse_new_expression(&mut self) {
self.builder.start_node(SyntaxKind::NewExpression.to_raw());
self.consume_token();
self.consume_whitespace();
if self.is_identifier() || self.at_keyword() {
self.consume_token();
}
self.builder.finish_node();
}
fn parse_numeric_literal(&mut self) {
self.builder
.start_node(SyntaxKind::NumericLiteralExpression.to_raw());
self.consume_token();
self.builder.finish_node();
}
fn parse_string_literal(&mut self) {
self.builder
.start_node(SyntaxKind::StringLiteralExpression.to_raw());
self.consume_token();
self.builder.finish_node();
}
fn parse_boolean_literal(&mut self) {
self.builder
.start_node(SyntaxKind::BooleanLiteralExpression.to_raw());
self.consume_token();
self.builder.finish_node();
}
fn parse_special_literal(&mut self) {
self.builder
.start_node(SyntaxKind::LiteralExpression.to_raw());
self.consume_token();
self.builder.finish_node();
}
fn parse_date_literal(&mut self) {
self.builder
.start_node(SyntaxKind::LiteralExpression.to_raw());
self.consume_token();
self.builder.finish_node();
}
fn parse_with_member_expression(&mut self) {
self.consume_token();
self.consume_whitespace();
if self.is_identifier() || self.at_keyword() {
self.consume_token_as_identifier();
if matches!(
self.current_token(),
Some(
Token::DollarSign
| Token::Percent
| Token::Ampersand
| Token::ExclamationMark
| Token::Octothorpe
| Token::AtSign
)
) {
self.consume_token();
}
}
}
fn parse_member_access_content(&mut self) {
self.consume_token();
self.consume_whitespace();
if self.is_identifier() || self.at_keyword() {
self.consume_token();
if matches!(
self.current_token(),
Some(
Token::DollarSign
| Token::Percent
| Token::Ampersand
| Token::ExclamationMark
| Token::Octothorpe
| Token::AtSign
)
) {
self.consume_token();
}
}
}
fn parse_dictionary_access_content(&mut self) {
self.consume_token();
self.consume_whitespace();
if self.is_identifier() || self.at_keyword() || self.at_token(Token::StringLiteral) {
self.consume_token();
}
}
fn get_infix_binding_power(&self) -> Option<(BindingPower, BindingPower)> {
let token = self.current_token()?;
let (left_bp, right_bp) = match token {
Token::ExponentiationOperator => {
(BindingPower::EXPONENTIATION, BindingPower::EXPONENTIATION)
}
Token::MultiplicationOperator | Token::DivisionOperator => {
let bp = BindingPower::MULTIPLICATION;
(bp, BindingPower(bp.0 + 1))
}
Token::BackwardSlashOperator => {
let bp = BindingPower::INT_DIVISION;
(bp, BindingPower(bp.0 + 1))
}
Token::ModKeyword => {
let bp = BindingPower::MODULO;
(bp, BindingPower(bp.0 + 1))
}
Token::AdditionOperator | Token::SubtractionOperator => {
let bp = BindingPower::ADDITION;
(bp, BindingPower(bp.0 + 1))
}
Token::Ampersand => {
let bp = BindingPower::CONCATENATION;
(bp, BindingPower(bp.0 + 1))
}
Token::EqualityOperator
| Token::InequalityOperator
| Token::LessThanOrEqualOperator
| Token::GreaterThanOrEqualOperator
| Token::LessThanOperator
| Token::GreaterThanOperator
| Token::LikeKeyword
| Token::IsKeyword => {
let bp = BindingPower::COMPARISON;
(bp, BindingPower(bp.0 + 1))
}
Token::AndKeyword => {
let bp = BindingPower::AND;
(bp, BindingPower(bp.0 + 1))
}
Token::OrKeyword => {
let bp = BindingPower::OR;
(bp, BindingPower(bp.0 + 1))
}
Token::XorKeyword => {
let bp = BindingPower::XOR;
(bp, BindingPower(bp.0 + 1))
}
Token::EqvKeyword => {
let bp = BindingPower::EQV;
(bp, BindingPower(bp.0 + 1))
}
Token::ImpKeyword => {
let bp = BindingPower::IMP;
(bp, BindingPower(bp.0 + 1))
}
_ => return None,
};
Some((left_bp, right_bp))
}
fn is_at_expression_delimiter(&self) -> bool {
matches!(
self.current_token(),
Some(
Token::Newline
| Token::ThenKeyword
| Token::ToKeyword
| Token::StepKeyword
| Token::ColonOperator
| Token::EndOfLineComment
| Token::RemComment
| Token::Comma
| Token::RightParenthesis
)
)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn numeric_literal() {
let source = "x = 42\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn numeric_literal_with_type_suffix() {
let source = "x = 42%\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn string_literal() {
let source = "x = \"Hello, World!\"\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn boolean_literal_true() {
let source = "x = True\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn boolean_literal_false() {
let source = "x = False\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn identifier_expression() {
let source = "x = myVariable\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn octothorpe_file_number_reference() {
let source = "x = #1\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn simple_addition() {
let source = "x = 2 + 3\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn simple_subtraction() {
let source = "x = 10 - 5\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn simple_multiplication() {
let source = "x = 4 * 5\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn simple_division() {
let source = "x = 20 / 4\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn operator_precedence_multiplication_before_addition() {
let source = "x = 2 + 3 * 4\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn operator_precedence_with_line_continuation() {
let source = "x = 2 + _\n 3 * 4\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn operator_precedence_left_associativity() {
let source = "x = 10 - 5 - 2\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn unary_negation() {
let source = "x = -5\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn logical_not() {
let source = "x = Not True\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn logical_and() {
let source = "x = True And False\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn logical_or() {
let source = "x = True Or False\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn comparison_equal() {
let source = "x = a = b\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn comparison_less_than() {
let source = "x = a < b\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn comparison_greater_than() {
let source = "x = a > b\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn parenthesized_expression() {
let source = "x = (5 + 3)\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn parenthesized_changes_precedence() {
let source = "x = (2 + 3) * 4\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn member_access() {
let source = "x = obj.property\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn chained_member_access() {
let source = "x = obj.prop1.prop2\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn function_call_no_args() {
let source = "x = MyFunction()\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn function_call_one_arg() {
let source = "x = MyFunction(42)\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn function_call_multiple_args() {
let source = "x = MyFunction(1, 2, 3)\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn method_call() {
let source = "x = obj.Method(arg)\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn new_expression() {
let source = "Set x = New MyClass\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn addressof_expression() {
let source = "x = AddressOf MyProc\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn string_concatenation() {
let source = "x = \"Hello\" & \" \" & \"World\"\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn modulo_operator() {
let source = "x = 10 Mod 3\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn integer_division() {
let source = "x = 10 \\ 3\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn exponentiation() {
let source = "x = 2 ^ 8\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn complex_arithmetic() {
let source = "x = (a + b) * c - d / e\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn complex_logical() {
let source = "x = Not a And b Or c\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn nothing_literal() {
let source = "Set x = Nothing\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn null_literal() {
let source = "x = Null\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn empty_literal() {
let source = "x = Empty\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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn dollar_sign_functions_merged() {
let source = r#"
x = Chr$(65)
y = UCase$("hello")
z = Left$("test", 2)
"#;
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("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/syntax/expressions");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
}