use super::{CompletedMarker, EXPR_RECOVERY, Parser};
use crate::syntax_kind::{SyntaxKind, SyntaxKind::*};
fn infix_binding_power(op: SyntaxKind) -> Option<(u8, u8)> {
let bp = match op {
PIPE2 => (4, 5),
AMP2 => (6, 7),
EQ2 | BANG_EQ => (8, 8),
LT | LT_EQ | GT | GT_EQ => (10, 10),
PIPE => (12, 13),
CARET => (14, 15),
AMP => (16, 17),
SHL | SHR => (18, 19),
PLUS | MINUS => (20, 21),
STAR | SLASH | PERCENT => (22, 23),
STAR2 => (25, 24),
KW_AS => (26, 27),
_ => return None,
};
Some(bp)
}
fn is_comparison_op(op: SyntaxKind) -> bool {
matches!(op, EQ2 | BANG_EQ | LT | LT_EQ | GT | GT_EQ)
}
fn expected_after_comparison(bp: u8) -> &'static [&'static str] {
match bp {
8 => &["'&&'", "'||'", "'?'"], 10 => &["'&&'", "'||'", "'=='", "'!='", "'?'"], _ => &["an operator"],
}
}
fn prefix_binding_power(op: SyntaxKind) -> Option<u8> {
match op {
BANG | MINUS => Some(30),
_ => None,
}
}
fn postfix_binding_power(op: SyntaxKind) -> Option<u8> {
match op {
DOT | L_BRACKET | L_PAREN => Some(32),
_ => None,
}
}
#[derive(Default, Clone, Copy)]
pub struct ExprOpts {
pub no_struct: bool,
}
impl ExprOpts {
pub fn no_struct() -> Self {
Self { no_struct: true }
}
}
impl Parser<'_, '_> {
pub const EXPR_CONTINUATION: &'static [SyntaxKind] = &[
AMP2,
PIPE2,
AMP,
PIPE,
CARET,
EQ2,
BANG_EQ,
LT,
LT_EQ,
GT,
GT_EQ,
PLUS,
MINUS,
STAR,
SLASH,
STAR2,
PERCENT,
SHL,
SHR,
L_PAREN,
L_BRACKET,
L_BRACE,
DOT,
COLON_COLON,
QUESTION,
KW_AS,
];
pub fn parse_expr(&mut self) -> Option<CompletedMarker> {
self.parse_expr_with_opts(ExprOpts::default())
}
pub fn parse_expr_with_opts(&mut self, opts: ExprOpts) -> Option<CompletedMarker> {
self.parse_expr_bp(0, opts)
}
fn parse_expr_bp(&mut self, min_bp: u8, opts: ExprOpts) -> Option<CompletedMarker> {
let mut lhs = self.parse_prefix_expr(opts)?;
loop {
if let Some(bp) = self.current_postfix_bp() {
if bp < min_bp {
break;
}
lhs = self.parse_postfix_expr(lhs)?;
continue;
}
if self.at(QUESTION) && min_bp <= 2 {
lhs = self.parse_ternary_expr(lhs)?;
continue;
}
let op = self.current();
if let Some((l_bp, r_bp)) = infix_binding_power(op) {
if l_bp < min_bp {
break;
}
if l_bp == r_bp && l_bp == min_bp && is_comparison_op(op) {
let expected_tokens = expected_after_comparison(l_bp);
self.error_unexpected(op, expected_tokens);
break;
}
lhs = self.parse_infix_expr(lhs, op, r_bp, opts)?;
continue;
}
break;
}
Some(lhs)
}
fn current_postfix_bp(&self) -> Option<u8> {
postfix_binding_power(self.current())
}
fn parse_prefix_expr(&mut self, opts: ExprOpts) -> Option<CompletedMarker> {
self.skip_trivia();
if let Some(bp) = prefix_binding_power(self.current()) {
let m = self.start();
self.bump_any();
if self.parse_expr_bp(bp, opts).is_none() {
self.error("expected expression after unary operator");
}
return Some(m.complete(self, UNARY_EXPR));
}
self.parse_primary_expr(opts)
}
fn parse_postfix_expr(&mut self, lhs: CompletedMarker) -> Option<CompletedMarker> {
match self.current() {
DOT => self.parse_member_access(lhs),
L_BRACKET => self.parse_index_expr(lhs),
L_PAREN => self.parse_call_expr(lhs),
_ => Some(lhs),
}
}
fn parse_infix_expr(
&mut self,
lhs: CompletedMarker,
op: SyntaxKind,
r_bp: u8,
opts: ExprOpts,
) -> Option<CompletedMarker> {
let m = lhs.precede(self);
self.bump_any();
if op == KW_AS {
if self.parse_cast_type().is_none() {
let expected: Vec<&str> = Self::PRIMITIVE_TYPE_KINDS.iter().map(|k| k.user_friendly_name()).collect();
self.error_unexpected(self.current(), &expected);
}
return Some(m.complete(self, CAST_EXPR));
}
if self.parse_expr_bp(r_bp, opts).is_none() {
self.error("expected expression after operator");
}
Some(m.complete(self, BINARY_EXPR))
}
fn parse_ternary_expr(&mut self, condition: CompletedMarker) -> Option<CompletedMarker> {
let m = condition.precede(self);
self.bump_any();
if self.parse_expr().is_none() {
self.error("expected expression after '?'");
}
self.expect(COLON);
if self.parse_expr_bp(2, ExprOpts::default()).is_none() {
self.error("expected expression after ':'");
}
Some(m.complete(self, TERNARY_EXPR))
}
fn parse_member_access(&mut self, lhs: CompletedMarker) -> Option<CompletedMarker> {
let m = lhs.precede(self);
self.bump_any();
self.skip_trivia();
if self.at(INTEGER) {
self.bump_any();
return Some(m.complete(self, TUPLE_ACCESS_EXPR));
}
if self.at(IDENT) || self.current().is_keyword() {
self.bump_any();
} else {
self.error("expected field name or tuple index");
return Some(m.complete(self, FIELD_EXPR));
}
if self.at(L_PAREN) {
self.bump_any(); if !self.at(R_PAREN) {
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected argument expression", EXPR_RECOVERY);
}
while self.eat(COMMA) {
if self.at(R_PAREN) {
break;
}
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected argument expression", EXPR_RECOVERY);
}
}
}
self.expect(R_PAREN);
return Some(m.complete(self, METHOD_CALL_EXPR));
}
Some(m.complete(self, FIELD_EXPR))
}
fn parse_index_expr(&mut self, lhs: CompletedMarker) -> Option<CompletedMarker> {
let m = lhs.precede(self);
self.bump_any();
if self.parse_expr().is_none() {
self.error("expected index expression");
}
self.expect(R_BRACKET);
Some(m.complete(self, INDEX_EXPR))
}
fn parse_dynamic_call_expr(&mut self, lhs: CompletedMarker) -> Option<CompletedMarker> {
let m = lhs.precede(self);
self.bump_any(); self.expect(L_PAREN);
self.parse_expr(); if self.eat(COMMA) {
self.parse_expr(); }
self.expect(R_PAREN);
self.expect(COLON_COLON);
if self.at(IDENT) {
self.bump_any(); }
self.expect(L_PAREN);
if !self.at(R_PAREN) {
self.parse_expr();
while self.eat(COMMA) {
if self.at(R_PAREN) {
break;
}
self.parse_expr();
}
}
self.expect(R_PAREN);
Some(m.complete(self, DYNAMIC_CALL_EXPR))
}
fn parse_call_expr(&mut self, lhs: CompletedMarker) -> Option<CompletedMarker> {
let m = lhs.precede(self);
self.bump_any();
if !self.at(R_PAREN) {
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected argument expression", EXPR_RECOVERY);
}
while self.eat(COMMA) {
if self.at(R_PAREN) {
break;
}
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected argument expression", EXPR_RECOVERY);
}
}
}
self.expect(R_PAREN);
Some(m.complete(self, CALL_EXPR))
}
fn parse_primary_expr(&mut self, opts: ExprOpts) -> Option<CompletedMarker> {
self.skip_trivia();
match self.current() {
INTEGER => self.parse_integer_literal(),
STRING => self.parse_string_literal(),
ADDRESS_LIT => self.parse_address_literal(),
IDENT_LIT => self.parse_identifier_literal(),
KW_TRUE | KW_FALSE => self.parse_bool_literal(),
KW_NONE => self.parse_none_literal(),
L_PAREN => self.parse_paren_or_tuple_expr(),
L_BRACKET => self.parse_array_expr(),
IDENT | KW_FINAL_UPPER => self.parse_ident_expr(opts),
KW_SELF => self.parse_self_expr(),
KW_BLOCK => self.parse_block_access(),
KW_NETWORK => self.parse_network_access(),
KW_FINAL => self.parse_final_block_expr(),
_ => {
self.error_unexpected(self.current(), &[
"an identifier",
"a program id",
"an address literal",
"an integer literal",
"a static string",
"'!'",
"'-'",
"'('",
"'['",
"'true'",
"'false'",
"'final'",
"'block'",
"'network'",
"'self'",
]);
None
}
}
}
fn parse_integer_literal(&mut self) -> Option<CompletedMarker> {
let m = self.start();
let text = self.current_text();
let kind = if text.ends_with("field") {
LITERAL_FIELD
} else if text.ends_with("group") {
LITERAL_GROUP
} else if text.ends_with("scalar") {
LITERAL_SCALAR
} else {
LITERAL_INT
};
self.bump_any();
Some(m.complete(self, kind))
}
fn parse_string_literal(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
Some(m.complete(self, LITERAL_STRING))
}
fn parse_identifier_literal(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
Some(m.complete(self, LITERAL_IDENT))
}
fn parse_address_literal(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
Some(m.complete(self, LITERAL_ADDRESS))
}
fn parse_bool_literal(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
Some(m.complete(self, LITERAL_BOOL))
}
fn parse_none_literal(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
Some(m.complete(self, LITERAL_NONE))
}
fn parse_paren_or_tuple_expr(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
if self.eat(R_PAREN) {
return Some(m.complete(self, TUPLE_EXPR));
}
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected expression", EXPR_RECOVERY);
}
if self.eat(COMMA) {
if !self.at(R_PAREN) {
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected tuple element", EXPR_RECOVERY);
}
while self.eat(COMMA) {
if self.at(R_PAREN) {
break;
}
if self.parse_expr().is_none() && !self.at(R_PAREN) && !self.at(COMMA) {
self.error_recover("expected tuple element", EXPR_RECOVERY);
}
}
}
self.expect(R_PAREN);
return Some(m.complete(self, TUPLE_EXPR));
}
self.expect(R_PAREN);
Some(m.complete(self, PAREN_EXPR))
}
fn parse_array_expr(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
if self.eat(R_BRACKET) {
return Some(m.complete(self, ARRAY_EXPR));
}
if self.parse_expr().is_none() && !self.at(R_BRACKET) && !self.at(COMMA) && !self.at(SEMICOLON) {
self.error_recover("expected array element", EXPR_RECOVERY);
}
if self.eat(SEMICOLON) {
if self.parse_expr().is_none() && !self.at(R_BRACKET) {
self.error("expected repeat count");
}
self.expect(R_BRACKET);
return Some(m.complete(self, REPEAT_EXPR));
}
while self.eat(COMMA) {
if self.at(R_BRACKET) {
break;
}
if self.parse_expr().is_none() && !self.at(R_BRACKET) && !self.at(COMMA) {
self.error_recover("expected array element", EXPR_RECOVERY);
}
}
self.expect(R_BRACKET);
Some(m.complete(self, ARRAY_EXPR))
}
fn parse_ident_expr(&mut self, opts: ExprOpts) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
if self.at(DOT) && self.nth(1) == KW_ALEO {
self.bump_any(); self.bump_any();
let is_locator = if self.eat(COLON_COLON) {
if self.at(IDENT) {
self.bump_any();
}
true
} else {
false
};
if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
self.bump_any(); self.parse_const_generic_args_bracket();
}
if !opts.no_struct && self.at(L_BRACE) {
self.bump_any(); if !self.at(R_BRACE) {
self.parse_struct_field();
while self.eat(COMMA) {
if self.at(R_BRACE) {
break;
}
self.parse_struct_field();
}
}
self.expect(R_BRACE);
let kind = if is_locator { STRUCT_LOCATOR_EXPR } else { STRUCT_EXPR };
return Some(m.complete(self, kind));
}
if self.at(L_PAREN) {
let kind = if is_locator { PATH_LOCATOR_EXPR } else { PROGRAM_REF_EXPR };
let cm = m.complete(self, kind);
return self.parse_call_expr(cm);
}
if self.at(AT) {
let cm = m.complete(self, TYPE_LOCATOR);
return self.parse_dynamic_call_expr(cm);
}
let kind = if is_locator { PATH_LOCATOR_EXPR } else { PROGRAM_REF_EXPR };
return Some(m.complete(self, kind));
}
while self.eat(COLON_COLON) {
if self.at(L_BRACKET) {
self.parse_const_generic_args_bracket();
break;
} else if self.at(LT) {
self.parse_const_generic_args_angle();
break;
} else if self.at(IDENT) {
self.bump_any();
} else {
self.error("expected identifier after ::");
break;
}
}
if !opts.no_struct && self.at(L_BRACE) {
self.bump_any();
if !self.at(R_BRACE) {
self.parse_struct_field();
while self.eat(COMMA) {
if self.at(R_BRACE) {
break;
}
self.parse_struct_field();
}
}
self.expect(R_BRACE);
return Some(m.complete(self, STRUCT_EXPR));
}
if self.at(L_PAREN) {
let cm = m.complete(self, PATH_EXPR);
return self.parse_call_expr(cm);
}
if self.at(AT) {
let cm = m.complete(self, TYPE_PATH);
return self.parse_dynamic_call_expr(cm);
}
Some(m.complete(self, PATH_EXPR))
}
fn parse_struct_field(&mut self) {
let m = self.start();
self.skip_trivia();
if self.at(IDENT) {
self.bump_any();
if self.eat(COLON) {
if self.parse_expr().is_none() && !self.at(R_BRACE) && !self.at(COMMA) {
self.error("expected field value");
}
m.complete(self, STRUCT_FIELD_INIT);
} else {
m.complete(self, STRUCT_FIELD_SHORTHAND);
}
} else {
self.error("expected field name");
m.complete(self, STRUCT_FIELD_INIT);
}
}
fn parse_self_expr(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any();
if self.at(COLON_COLON) {
self.error("expected '.' -- found '::'");
}
Some(m.complete(self, SELF_EXPR))
}
fn parse_block_access(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any(); Some(m.complete(self, BLOCK_KW_EXPR))
}
fn parse_network_access(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any(); Some(m.complete(self, NETWORK_KW_EXPR))
}
fn parse_final_block_expr(&mut self) -> Option<CompletedMarker> {
let m = self.start();
self.bump_any(); self.skip_trivia();
if self.parse_block().is_none() {
self.error("expected block after 'final'");
}
Some(m.complete(self, FINAL_EXPR))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{lexer::lex, parser::Parse};
use expect_test::{Expect, expect};
fn check_expr(input: &str, expect: Expect) {
let (tokens, _) = lex(input);
let mut parser = Parser::new(input, &tokens);
let root = parser.start();
parser.parse_expr();
parser.skip_trivia();
root.complete(&mut parser, ROOT);
let parse: Parse = parser.finish(vec![]);
let output = format!("{:#?}", parse.syntax());
expect.assert_eq(&output);
}
#[test]
fn parse_expr_integer() {
check_expr("42", expect![[r#"
ROOT@0..2
LITERAL_INT@0..2
INTEGER@0..2 "42"
"#]]);
}
#[test]
fn parse_expr_bool_true() {
check_expr("true", expect![[r#"
ROOT@0..4
LITERAL_BOOL@0..4
KW_TRUE@0..4 "true"
"#]]);
}
#[test]
fn parse_expr_bool_false() {
check_expr("false", expect![[r#"
ROOT@0..5
LITERAL_BOOL@0..5
KW_FALSE@0..5 "false"
"#]]);
}
#[test]
fn parse_expr_none() {
check_expr("none", expect![[r#"
ROOT@0..4
LITERAL_NONE@0..4
KW_NONE@0..4 "none"
"#]]);
}
#[test]
fn parse_expr_identifier_literal() {
check_expr("'foo'", expect![[r#"
ROOT@0..5
LITERAL_IDENT@0..5
IDENT_LIT@0..5 "'foo'"
"#]]);
}
#[test]
fn parse_expr_dynamic_call_basic() {
check_expr("Adder@(target)::sum(x, y)", expect![[r#"
ROOT@0..25
DYNAMIC_CALL_EXPR@0..25
TYPE_PATH@0..5
IDENT@0..5 "Adder"
AT@5..6 "@"
L_PAREN@6..7 "("
PATH_EXPR@7..13
IDENT@7..13 "target"
R_PAREN@13..14 ")"
COLON_COLON@14..16 "::"
IDENT@16..19 "sum"
L_PAREN@19..20 "("
PATH_EXPR@20..21
IDENT@20..21 "x"
COMMA@21..22 ","
WHITESPACE@22..23 " "
PATH_EXPR@23..24
IDENT@23..24 "y"
R_PAREN@24..25 ")"
"#]]);
}
#[test]
fn parse_expr_dynamic_call_identifier_target() {
check_expr("Adder@('foo')::sum(x, y)", expect![[r#"
ROOT@0..24
DYNAMIC_CALL_EXPR@0..24
TYPE_PATH@0..5
IDENT@0..5 "Adder"
AT@5..6 "@"
L_PAREN@6..7 "("
LITERAL_IDENT@7..12
IDENT_LIT@7..12 "'foo'"
R_PAREN@12..13 ")"
COLON_COLON@13..15 "::"
IDENT@15..18 "sum"
L_PAREN@18..19 "("
PATH_EXPR@19..20
IDENT@19..20 "x"
COMMA@20..21 ","
WHITESPACE@21..22 " "
PATH_EXPR@22..23
IDENT@22..23 "y"
R_PAREN@23..24 ")"
"#]]);
}
#[test]
fn parse_expr_dynamic_call_with_network() {
check_expr("Adder@('foo', 'aleo')::sum(x, y)", expect![[r#"
ROOT@0..32
DYNAMIC_CALL_EXPR@0..32
TYPE_PATH@0..5
IDENT@0..5 "Adder"
AT@5..6 "@"
L_PAREN@6..7 "("
LITERAL_IDENT@7..12
IDENT_LIT@7..12 "'foo'"
COMMA@12..13 ","
WHITESPACE@13..14 " "
LITERAL_IDENT@14..20
IDENT_LIT@14..20 "'aleo'"
R_PAREN@20..21 ")"
COLON_COLON@21..23 "::"
IDENT@23..26 "sum"
L_PAREN@26..27 "("
PATH_EXPR@27..28
IDENT@27..28 "x"
COMMA@28..29 ","
WHITESPACE@29..30 " "
PATH_EXPR@30..31
IDENT@30..31 "y"
R_PAREN@31..32 ")"
"#]]);
}
#[test]
fn parse_expr_dynamic_call_no_args() {
check_expr("Adder@(target)::sum()", expect![[r#"
ROOT@0..21
DYNAMIC_CALL_EXPR@0..21
TYPE_PATH@0..5
IDENT@0..5 "Adder"
AT@5..6 "@"
L_PAREN@6..7 "("
PATH_EXPR@7..13
IDENT@7..13 "target"
R_PAREN@13..14 ")"
COLON_COLON@14..16 "::"
IDENT@16..19 "sum"
L_PAREN@19..20 "("
R_PAREN@20..21 ")"
"#]]);
}
#[test]
fn parse_expr_ident() {
check_expr("foo", expect![[r#"
ROOT@0..3
PATH_EXPR@0..3
IDENT@0..3 "foo"
"#]]);
}
#[test]
fn parse_expr_path() {
check_expr("Foo::bar", expect![[r#"
ROOT@0..8
PATH_EXPR@0..8
IDENT@0..3 "Foo"
COLON_COLON@3..5 "::"
IDENT@5..8 "bar"
"#]]);
}
#[test]
fn parse_expr_self() {
check_expr("self", expect![[r#"
ROOT@0..4
SELF_EXPR@0..4
KW_SELF@0..4 "self"
"#]]);
}
#[test]
fn parse_expr_self_colon_colon_is_error() {
let (tokens, _) = lex("self::y");
let mut parser = Parser::new("self::y", &tokens);
let root = parser.start();
parser.parse_expr();
parser.skip_trivia();
root.complete(&mut parser, ROOT);
let parse: Parse = parser.finish(vec![]);
assert!(!parse.errors().is_empty(), "expected error for self::");
assert!(
parse.errors().iter().any(|e| e.message.contains("expected '.'")),
"expected error message to mention expected '.', got: {:?}",
parse.errors()
);
}
#[test]
fn parse_expr_add() {
check_expr("1 + 2", expect![[r#"
ROOT@0..5
BINARY_EXPR@0..5
LITERAL_INT@0..1
INTEGER@0..1 "1"
WHITESPACE@1..2 " "
PLUS@2..3 "+"
WHITESPACE@3..4 " "
LITERAL_INT@4..5
INTEGER@4..5 "2"
"#]]);
}
#[test]
fn parse_expr_mul() {
check_expr("a * b", expect![[r#"
ROOT@0..5
BINARY_EXPR@0..5
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
STAR@2..3 "*"
WHITESPACE@3..4 " "
PATH_EXPR@4..5
IDENT@4..5 "b"
"#]]);
}
#[test]
fn parse_expr_precedence() {
check_expr("1 + 2 * 3", expect![[r#"
ROOT@0..9
BINARY_EXPR@0..9
LITERAL_INT@0..1
INTEGER@0..1 "1"
WHITESPACE@1..2 " "
PLUS@2..3 "+"
WHITESPACE@3..4 " "
BINARY_EXPR@4..9
LITERAL_INT@4..5
INTEGER@4..5 "2"
WHITESPACE@5..6 " "
STAR@6..7 "*"
WHITESPACE@7..8 " "
LITERAL_INT@8..9
INTEGER@8..9 "3"
"#]]);
}
#[test]
fn parse_expr_power_right_assoc() {
check_expr("a ** b ** c", expect![[r#"
ROOT@0..11
BINARY_EXPR@0..11
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
STAR2@2..4 "**"
WHITESPACE@4..5 " "
BINARY_EXPR@5..11
PATH_EXPR@5..7
IDENT@5..6 "b"
WHITESPACE@6..7 " "
STAR2@7..9 "**"
WHITESPACE@9..10 " "
PATH_EXPR@10..11
IDENT@10..11 "c"
"#]]);
}
#[test]
fn parse_expr_unary_neg() {
check_expr("-x", expect![[r#"
ROOT@0..2
UNARY_EXPR@0..2
MINUS@0..1 "-"
PATH_EXPR@1..2
IDENT@1..2 "x"
"#]]);
}
#[test]
fn parse_expr_unary_not() {
check_expr("!flag", expect![[r#"
ROOT@0..5
UNARY_EXPR@0..5
BANG@0..1 "!"
PATH_EXPR@1..5
IDENT@1..5 "flag"
"#]]);
}
#[test]
fn parse_expr_comparison() {
check_expr("a < b", expect![[r#"
ROOT@0..5
BINARY_EXPR@0..5
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
LT@2..3 "<"
WHITESPACE@3..4 " "
PATH_EXPR@4..5
IDENT@4..5 "b"
"#]]);
}
#[test]
fn parse_expr_logical_and() {
check_expr("a && b", expect![[r#"
ROOT@0..6
BINARY_EXPR@0..6
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
AMP2@2..4 "&&"
WHITESPACE@4..5 " "
PATH_EXPR@5..6
IDENT@5..6 "b"
"#]]);
}
#[test]
fn parse_expr_logical_or() {
check_expr("a || b", expect![[r#"
ROOT@0..6
BINARY_EXPR@0..6
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
PIPE2@2..4 "||"
WHITESPACE@4..5 " "
PATH_EXPR@5..6
IDENT@5..6 "b"
"#]]);
}
#[test]
fn parse_expr_ternary() {
check_expr("a ? b : c", expect![[r#"
ROOT@0..9
TERNARY_EXPR@0..9
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
QUESTION@2..3 "?"
WHITESPACE@3..4 " "
PATH_EXPR@4..6
IDENT@4..5 "b"
WHITESPACE@5..6 " "
COLON@6..7 ":"
WHITESPACE@7..8 " "
PATH_EXPR@8..9
IDENT@8..9 "c"
"#]]);
}
#[test]
fn parse_expr_member_access() {
check_expr("foo.bar", expect![[r#"
ROOT@0..7
FIELD_EXPR@0..7
PATH_EXPR@0..3
IDENT@0..3 "foo"
DOT@3..4 "."
IDENT@4..7 "bar"
"#]]);
}
#[test]
fn parse_expr_tuple_access() {
check_expr("tuple.0", expect![[r#"
ROOT@0..7
TUPLE_ACCESS_EXPR@0..7
PATH_EXPR@0..5
IDENT@0..5 "tuple"
DOT@5..6 "."
INTEGER@6..7 "0"
"#]]);
}
#[test]
fn parse_expr_index() {
check_expr("arr[0]", expect![[r#"
ROOT@0..6
INDEX_EXPR@0..6
PATH_EXPR@0..3
IDENT@0..3 "arr"
L_BRACKET@3..4 "["
LITERAL_INT@4..5
INTEGER@4..5 "0"
R_BRACKET@5..6 "]"
"#]]);
}
#[test]
fn parse_expr_call() {
check_expr("foo(a, b)", expect![[r#"
ROOT@0..9
CALL_EXPR@0..9
PATH_EXPR@0..3
IDENT@0..3 "foo"
L_PAREN@3..4 "("
PATH_EXPR@4..5
IDENT@4..5 "a"
COMMA@5..6 ","
WHITESPACE@6..7 " "
PATH_EXPR@7..8
IDENT@7..8 "b"
R_PAREN@8..9 ")"
"#]]);
}
#[test]
fn parse_expr_method_call() {
check_expr("x.foo()", expect![[r#"
ROOT@0..7
METHOD_CALL_EXPR@0..7
PATH_EXPR@0..1
IDENT@0..1 "x"
DOT@1..2 "."
IDENT@2..5 "foo"
L_PAREN@5..6 "("
R_PAREN@6..7 ")"
"#]]);
}
#[test]
fn parse_expr_cast() {
check_expr("x as u64", expect![[r#"
ROOT@0..8
CAST_EXPR@0..8
PATH_EXPR@0..2
IDENT@0..1 "x"
WHITESPACE@1..2 " "
KW_AS@2..4 "as"
WHITESPACE@4..5 " "
TYPE_PRIMITIVE@5..8
KW_U64@5..8 "u64"
"#]]);
}
#[test]
fn parse_expr_paren() {
check_expr("(a + b)", expect![[r#"
ROOT@0..7
PAREN_EXPR@0..7
L_PAREN@0..1 "("
BINARY_EXPR@1..6
PATH_EXPR@1..3
IDENT@1..2 "a"
WHITESPACE@2..3 " "
PLUS@3..4 "+"
WHITESPACE@4..5 " "
PATH_EXPR@5..6
IDENT@5..6 "b"
R_PAREN@6..7 ")"
"#]]);
}
#[test]
fn parse_expr_tuple() {
check_expr("(a, b)", expect![[r#"
ROOT@0..6
TUPLE_EXPR@0..6
L_PAREN@0..1 "("
PATH_EXPR@1..2
IDENT@1..2 "a"
COMMA@2..3 ","
WHITESPACE@3..4 " "
PATH_EXPR@4..5
IDENT@4..5 "b"
R_PAREN@5..6 ")"
"#]]);
}
#[test]
fn parse_expr_unit() {
check_expr("()", expect![[r#"
ROOT@0..2
TUPLE_EXPR@0..2
L_PAREN@0..1 "("
R_PAREN@1..2 ")"
"#]]);
}
#[test]
fn parse_expr_array() {
check_expr("[1, 2, 3]", expect![[r#"
ROOT@0..9
ARRAY_EXPR@0..9
L_BRACKET@0..1 "["
LITERAL_INT@1..2
INTEGER@1..2 "1"
COMMA@2..3 ","
WHITESPACE@3..4 " "
LITERAL_INT@4..5
INTEGER@4..5 "2"
COMMA@5..6 ","
WHITESPACE@6..7 " "
LITERAL_INT@7..8
INTEGER@7..8 "3"
R_BRACKET@8..9 "]"
"#]]);
}
#[test]
fn parse_expr_array_repeat() {
check_expr("[0; 10]", expect![[r#"
ROOT@0..7
REPEAT_EXPR@0..7
L_BRACKET@0..1 "["
LITERAL_INT@1..2
INTEGER@1..2 "0"
SEMICOLON@2..3 ";"
WHITESPACE@3..4 " "
LITERAL_INT@4..6
INTEGER@4..6 "10"
R_BRACKET@6..7 "]"
"#]]);
}
#[test]
fn parse_expr_struct_init() {
check_expr("Point { x: 1, y: 2 }", expect![[r#"
ROOT@0..20
STRUCT_EXPR@0..20
IDENT@0..5 "Point"
WHITESPACE@5..6 " "
L_BRACE@6..7 "{"
STRUCT_FIELD_INIT@7..12
WHITESPACE@7..8 " "
IDENT@8..9 "x"
COLON@9..10 ":"
WHITESPACE@10..11 " "
LITERAL_INT@11..12
INTEGER@11..12 "1"
COMMA@12..13 ","
STRUCT_FIELD_INIT@13..18
WHITESPACE@13..14 " "
IDENT@14..15 "y"
COLON@15..16 ":"
WHITESPACE@16..17 " "
LITERAL_INT@17..18
INTEGER@17..18 "2"
WHITESPACE@18..19 " "
R_BRACE@19..20 "}"
"#]]);
}
#[test]
fn parse_expr_struct_shorthand() {
check_expr("Point { x, y }", expect![[r#"
ROOT@0..14
STRUCT_EXPR@0..14
IDENT@0..5 "Point"
WHITESPACE@5..6 " "
L_BRACE@6..7 "{"
STRUCT_FIELD_SHORTHAND@7..9
WHITESPACE@7..8 " "
IDENT@8..9 "x"
COMMA@9..10 ","
STRUCT_FIELD_SHORTHAND@10..13
WHITESPACE@10..11 " "
IDENT@11..12 "y"
WHITESPACE@12..13 " "
R_BRACE@13..14 "}"
"#]]);
}
fn check_expr_no_errors(input: &str) {
let (tokens, _) = lex(input);
let mut parser = Parser::new(input, &tokens);
let root = parser.start();
parser.parse_expr();
parser.skip_trivia();
root.complete(&mut parser, ROOT);
let parse: Parse = parser.finish(vec![]);
if !parse.errors().is_empty() {
for err in parse.errors() {
eprintln!("error at {:?}: {}", err.range, err.message);
}
eprintln!("tree:\n{:#?}", parse.syntax());
panic!("expression parse had {} error(s)", parse.errors().len());
}
}
#[test]
fn parse_expr_call_const_generic_simple() {
check_expr("foo::[5]()", expect![[r#"
ROOT@0..10
CALL_EXPR@0..10
PATH_EXPR@0..8
IDENT@0..3 "foo"
COLON_COLON@3..5 "::"
CONST_ARG_LIST@5..8
L_BRACKET@5..6 "["
LITERAL_INT@6..7
INTEGER@6..7 "5"
R_BRACKET@7..8 "]"
L_PAREN@8..9 "("
R_PAREN@9..10 ")"
"#]]);
}
#[test]
fn parse_expr_call_const_generic_expr() {
check_expr_no_errors("foo::[N + 1]()");
}
#[test]
fn parse_expr_call_const_generic_multi() {
check_expr_no_errors("bar::[M, K, N]()");
}
#[test]
fn parse_expr_struct_lit_const_generic() {
check_expr("Foo::[8u32] { arr: x }", expect![[r#"
ROOT@0..22
STRUCT_EXPR@0..22
IDENT@0..3 "Foo"
COLON_COLON@3..5 "::"
CONST_ARG_LIST@5..11
L_BRACKET@5..6 "["
LITERAL_INT@6..10
INTEGER@6..10 "8u32"
R_BRACKET@10..11 "]"
WHITESPACE@11..12 " "
L_BRACE@12..13 "{"
STRUCT_FIELD_INIT@13..21
WHITESPACE@13..14 " "
IDENT@14..17 "arr"
COLON@17..18 ":"
WHITESPACE@18..19 " "
PATH_EXPR@19..21
IDENT@19..20 "x"
WHITESPACE@20..21 " "
R_BRACE@21..22 "}"
"#]]);
}
#[test]
fn parse_expr_locator_call_const_generic() {
check_expr_no_errors("child.aleo::foo::[3]()");
}
#[test]
fn parse_expr_assoc_fn_const_generic() {
check_expr_no_errors("Foo::bar::[N]()");
}
#[test]
fn parse_expr_complex() {
check_expr("a.b[c](d) + e", expect![[r#"
ROOT@0..13
BINARY_EXPR@0..13
CALL_EXPR@0..9
INDEX_EXPR@0..6
FIELD_EXPR@0..3
PATH_EXPR@0..1
IDENT@0..1 "a"
DOT@1..2 "."
IDENT@2..3 "b"
L_BRACKET@3..4 "["
PATH_EXPR@4..5
IDENT@4..5 "c"
R_BRACKET@5..6 "]"
L_PAREN@6..7 "("
PATH_EXPR@7..8
IDENT@7..8 "d"
R_PAREN@8..9 ")"
WHITESPACE@9..10 " "
PLUS@10..11 "+"
WHITESPACE@11..12 " "
PATH_EXPR@12..13
IDENT@12..13 "e"
"#]]);
}
fn parse_expr_for_test(input: &str) -> Parse {
let (tokens, _) = lex(input);
let mut parser = Parser::new(input, &tokens);
let root = parser.start();
parser.parse_expr();
parser.skip_trivia();
root.complete(&mut parser, ROOT);
parser.finish(vec![])
}
#[test]
fn parse_expr_chained_eq_is_error() {
let parse = parse_expr_for_test("1 == 2 == 3");
assert!(!parse.errors().is_empty(), "expected error for chained ==, got none");
assert!(
parse.errors().iter().any(|e| e.message.contains("'&&'") || e.message.contains("expected")),
"expected error message about valid operators, got: {:?}",
parse.errors()
);
}
#[test]
fn parse_expr_chained_neq_is_error() {
let parse = parse_expr_for_test("1 != 2 != 3");
assert!(!parse.errors().is_empty(), "expected error for chained !=, got none");
}
#[test]
fn parse_expr_chained_lt_is_error() {
let parse = parse_expr_for_test("1 < 2 < 3");
assert!(!parse.errors().is_empty(), "expected error for chained <, got none");
}
#[test]
fn parse_expr_chained_gt_is_error() {
let parse = parse_expr_for_test("1 > 2 > 3");
assert!(!parse.errors().is_empty(), "expected error for chained >, got none");
}
#[test]
fn parse_expr_comparison_with_logical_is_ok() {
check_expr_no_errors("1 == 2 && 3 == 4");
check_expr_no_errors("1 < 2 || 3 > 4");
}
#[test]
fn parse_expr_group_associated_fn() {
check_expr("group::to_x_coordinate(a)", expect![[r#"
ROOT@0..25
CALL_EXPR@0..25
PATH_EXPR@0..22
IDENT@0..22 "group::to_x_coordinate"
L_PAREN@22..23 "("
PATH_EXPR@23..24
IDENT@23..24 "a"
R_PAREN@24..25 ")"
"#]]);
}
#[test]
fn parse_expr_signature_associated_fn() {
check_expr("signature::verify(s, a, v)", expect![[r#"
ROOT@0..26
CALL_EXPR@0..26
PATH_EXPR@0..17
IDENT@0..17 "signature::verify"
L_PAREN@17..18 "("
PATH_EXPR@18..19
IDENT@18..19 "s"
COMMA@19..20 ","
WHITESPACE@20..21 " "
PATH_EXPR@21..22
IDENT@21..22 "a"
COMMA@22..23 ","
WHITESPACE@23..24 " "
PATH_EXPR@24..25
IDENT@24..25 "v"
R_PAREN@25..26 ")"
"#]]);
}
#[test]
fn parse_expr_chained_le_is_error() {
let parse = parse_expr_for_test("1 <= 2 <= 3");
assert!(!parse.errors().is_empty(), "expected error for chained <=, got none");
}
#[test]
fn parse_expr_chained_ge_is_error() {
let parse = parse_expr_for_test("1 >= 2 >= 3");
assert!(!parse.errors().is_empty(), "expected error for chained >=, got none");
}
#[test]
fn parse_expr_chained_mixed_cmp_is_error() {
let parse = parse_expr_for_test("1 < 2 > 3");
assert!(!parse.errors().is_empty(), "expected error for mixed chained comparisons, got none");
}
#[test]
fn parse_expr_ternary_nested() {
check_expr("a ? b ? c : d : e", expect![[r#"
ROOT@0..17
TERNARY_EXPR@0..17
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
QUESTION@2..3 "?"
WHITESPACE@3..4 " "
TERNARY_EXPR@4..14
PATH_EXPR@4..6
IDENT@4..5 "b"
WHITESPACE@5..6 " "
QUESTION@6..7 "?"
WHITESPACE@7..8 " "
PATH_EXPR@8..10
IDENT@8..9 "c"
WHITESPACE@9..10 " "
COLON@10..11 ":"
WHITESPACE@11..12 " "
PATH_EXPR@12..14
IDENT@12..13 "d"
WHITESPACE@13..14 " "
COLON@14..15 ":"
WHITESPACE@15..16 " "
PATH_EXPR@16..17
IDENT@16..17 "e"
"#]]);
}
#[test]
fn parse_expr_cast_chained() {
check_expr("x as u32 as u64", expect![[r#"
ROOT@0..15
CAST_EXPR@0..15
CAST_EXPR@0..8
PATH_EXPR@0..2
IDENT@0..1 "x"
WHITESPACE@1..2 " "
KW_AS@2..4 "as"
WHITESPACE@4..5 " "
TYPE_PRIMITIVE@5..8
KW_U32@5..8 "u32"
WHITESPACE@8..9 " "
KW_AS@9..11 "as"
WHITESPACE@11..12 " "
TYPE_PRIMITIVE@12..15
KW_U64@12..15 "u64"
"#]]);
}
#[test]
fn parse_expr_array_trailing_comma() {
check_expr("[1, 2, 3,]", expect![[r#"
ROOT@0..10
ARRAY_EXPR@0..10
L_BRACKET@0..1 "["
LITERAL_INT@1..2
INTEGER@1..2 "1"
COMMA@2..3 ","
WHITESPACE@3..4 " "
LITERAL_INT@4..5
INTEGER@4..5 "2"
COMMA@5..6 ","
WHITESPACE@6..7 " "
LITERAL_INT@7..8
INTEGER@7..8 "3"
COMMA@8..9 ","
R_BRACKET@9..10 "]"
"#]]);
}
#[test]
fn parse_expr_array_empty() {
check_expr("[]", expect![[r#"
ROOT@0..2
ARRAY_EXPR@0..2
L_BRACKET@0..1 "["
R_BRACKET@1..2 "]"
"#]]);
}
#[test]
fn parse_expr_tuple_single() {
check_expr("(a,)", expect![[r#"
ROOT@0..4
TUPLE_EXPR@0..4
L_PAREN@0..1 "("
PATH_EXPR@1..2
IDENT@1..2 "a"
COMMA@2..3 ","
R_PAREN@3..4 ")"
"#]]);
}
#[test]
fn parse_expr_tuple_trailing_comma() {
check_expr("(1, 2,)", expect![[r#"
ROOT@0..7
TUPLE_EXPR@0..7
L_PAREN@0..1 "("
LITERAL_INT@1..2
INTEGER@1..2 "1"
COMMA@2..3 ","
WHITESPACE@3..4 " "
LITERAL_INT@4..5
INTEGER@4..5 "2"
COMMA@5..6 ","
R_PAREN@6..7 ")"
"#]]);
}
#[test]
fn parse_expr_struct_empty() {
check_expr("Point { }", expect![[r#"
ROOT@0..9
STRUCT_EXPR@0..9
IDENT@0..5 "Point"
WHITESPACE@5..6 " "
L_BRACE@6..7 "{"
WHITESPACE@7..8 " "
R_BRACE@8..9 "}"
"#]]);
}
#[test]
fn parse_expr_struct_trailing_comma() {
check_expr("Point { x: 1, }", expect![[r#"
ROOT@0..15
STRUCT_EXPR@0..15
IDENT@0..5 "Point"
WHITESPACE@5..6 " "
L_BRACE@6..7 "{"
STRUCT_FIELD_INIT@7..12
WHITESPACE@7..8 " "
IDENT@8..9 "x"
COLON@9..10 ":"
WHITESPACE@10..11 " "
LITERAL_INT@11..12
INTEGER@11..12 "1"
COMMA@12..13 ","
WHITESPACE@13..14 " "
R_BRACE@14..15 "}"
"#]]);
}
#[test]
fn parse_expr_struct_mixed_fields() {
check_expr("Point { x, y: 2 }", expect![[r#"
ROOT@0..17
STRUCT_EXPR@0..17
IDENT@0..5 "Point"
WHITESPACE@5..6 " "
L_BRACE@6..7 "{"
STRUCT_FIELD_SHORTHAND@7..9
WHITESPACE@7..8 " "
IDENT@8..9 "x"
COMMA@9..10 ","
STRUCT_FIELD_INIT@10..15
WHITESPACE@10..11 " "
IDENT@11..12 "y"
COLON@12..13 ":"
WHITESPACE@13..14 " "
LITERAL_INT@14..15
INTEGER@14..15 "2"
WHITESPACE@15..16 " "
R_BRACE@16..17 "}"
"#]]);
}
#[test]
fn parse_expr_string() {
check_expr("\"hello\"", expect![[r#"
ROOT@0..7
LITERAL_STRING@0..7
STRING@0..7 "\"hello\""
"#]]);
}
#[test]
fn parse_expr_address() {
check_expr("aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9", expect![[r#"
ROOT@0..63
LITERAL_ADDRESS@0..63
ADDRESS_LIT@0..63 "aleo1qnr4dkkvkgfqph0v ..."
"#]]);
}
#[test]
fn parse_expr_deep_postfix() {
check_expr("a[0].b.c(x)[1]", expect![[r#"
ROOT@0..14
INDEX_EXPR@0..14
METHOD_CALL_EXPR@0..11
FIELD_EXPR@0..6
INDEX_EXPR@0..4
PATH_EXPR@0..1
IDENT@0..1 "a"
L_BRACKET@1..2 "["
LITERAL_INT@2..3
INTEGER@2..3 "0"
R_BRACKET@3..4 "]"
DOT@4..5 "."
IDENT@5..6 "b"
DOT@6..7 "."
IDENT@7..8 "c"
L_PAREN@8..9 "("
PATH_EXPR@9..10
IDENT@9..10 "x"
R_PAREN@10..11 ")"
L_BRACKET@11..12 "["
LITERAL_INT@12..13
INTEGER@12..13 "1"
R_BRACKET@13..14 "]"
"#]]);
}
#[test]
fn parse_expr_final() {
check_expr("final { foo() }", expect![[r#"
ROOT@0..15
FINAL_EXPR@0..15
KW_FINAL@0..5 "final"
WHITESPACE@5..6 " "
BLOCK@6..15
L_BRACE@6..7 "{"
WHITESPACE@7..8 " "
EXPR_STMT@8..14
CALL_EXPR@8..13
PATH_EXPR@8..11
IDENT@8..11 "foo"
L_PAREN@11..12 "("
R_PAREN@12..13 ")"
WHITESPACE@13..14 " "
ERROR@14..14
R_BRACE@14..15 "}"
"#]]);
}
#[test]
fn parse_expr_mixed_arithmetic() {
check_expr("a + b * c / d - e", expect![[r#"
ROOT@0..17
BINARY_EXPR@0..17
BINARY_EXPR@0..14
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
PLUS@2..3 "+"
WHITESPACE@3..4 " "
BINARY_EXPR@4..14
BINARY_EXPR@4..10
PATH_EXPR@4..6
IDENT@4..5 "b"
WHITESPACE@5..6 " "
STAR@6..7 "*"
WHITESPACE@7..8 " "
PATH_EXPR@8..10
IDENT@8..9 "c"
WHITESPACE@9..10 " "
SLASH@10..11 "/"
WHITESPACE@11..12 " "
PATH_EXPR@12..14
IDENT@12..13 "d"
WHITESPACE@13..14 " "
MINUS@14..15 "-"
WHITESPACE@15..16 " "
PATH_EXPR@16..17
IDENT@16..17 "e"
"#]]);
}
#[test]
fn parse_expr_bitwise_precedence() {
check_expr("a | b & c ^ d", expect![[r#"
ROOT@0..13
BINARY_EXPR@0..13
PATH_EXPR@0..2
IDENT@0..1 "a"
WHITESPACE@1..2 " "
PIPE@2..3 "|"
WHITESPACE@3..4 " "
BINARY_EXPR@4..13
BINARY_EXPR@4..10
PATH_EXPR@4..6
IDENT@4..5 "b"
WHITESPACE@5..6 " "
AMP@6..7 "&"
WHITESPACE@7..8 " "
PATH_EXPR@8..10
IDENT@8..9 "c"
WHITESPACE@9..10 " "
CARET@10..11 "^"
WHITESPACE@11..12 " "
PATH_EXPR@12..13
IDENT@12..13 "d"
"#]]);
}
#[test]
fn parse_expr_shift_chain() {
check_expr("x << 1 >> 2", expect![[r#"
ROOT@0..11
BINARY_EXPR@0..11
BINARY_EXPR@0..6
PATH_EXPR@0..2
IDENT@0..1 "x"
WHITESPACE@1..2 " "
SHL@2..4 "<<"
WHITESPACE@4..5 " "
LITERAL_INT@5..6
INTEGER@5..6 "1"
WHITESPACE@6..7 " "
SHR@7..9 ">>"
WHITESPACE@9..10 " "
LITERAL_INT@10..11
INTEGER@10..11 "2"
"#]]);
}
}