diff --git a/.changeset/lemon-knives-fry.md b/.changeset/lemon-knives-fry.md new file mode 100644 index 0000000000..4c45168eae --- /dev/null +++ b/.changeset/lemon-knives-fry.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/slang": patch +--- + +fix selection order of prefix/postfix AST fields diff --git a/crates/codegen/parser/generator/src/ast_model.rs b/crates/codegen/parser/generator/src/ast_model.rs index 12de1afeaf..29db4f8c4a 100644 --- a/crates/codegen/parser/generator/src/ast_model.rs +++ b/crates/codegen/parser/generator/src/ast_model.rs @@ -221,10 +221,10 @@ impl AstModel { match operator.model { model::OperatorModel::Prefix => { - fields.insert(0, operand("operand")); + fields.push(operand("operand")); } model::OperatorModel::Postfix => { - fields.push(operand("operand")); + fields.insert(0, operand("operand")); } model::OperatorModel::BinaryLeftAssociative | model::OperatorModel::BinaryRightAssociative => { diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/ast_selectors.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/ast_selectors.rs index 8f34599abc..3e3c5fce44 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/ast_selectors.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/napi_interface/ast_selectors.rs @@ -234,8 +234,8 @@ impl Selector { impl Selector { fn version_pragma_prefix_expression(&mut self) -> Result>> { Ok(vec![ - Some(self.select(|node| node.is_rule_with_kind(RuleKind::VersionPragmaExpression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::Caret))?), + Some(self.select(|node| node.is_rule_with_kind(RuleKind::VersionPragmaExpression))?), ]) } } @@ -672,10 +672,10 @@ impl Selector { impl Selector { fn array_type_name(&mut self) -> Result>> { Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::TypeName))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::OpenBracket))?), self.try_select(|node| node.is_rule_with_kind(RuleKind::Expression))?, Some(self.select(|node| node.is_token_with_kind(TokenKind::CloseBracket))?), - Some(self.select(|node| node.is_rule_with_kind(RuleKind::TypeName))?), ]) } } @@ -1019,11 +1019,11 @@ impl Selector { impl Selector { fn conditional_expression(&mut self) -> Result>> { Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::QuestionMark))?), Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::Colon))?), Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), - Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), ]) } } @@ -1141,8 +1141,8 @@ impl Selector { impl Selector { fn postfix_expression(&mut self) -> Result>> { Ok(vec![ - Some(self.select(|node| node.is_token_with_kind(TokenKind::PlusPlus))?), Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), + Some(self.select(|node| node.is_token_with_kind(TokenKind::PlusPlus))?), ]) } } @@ -1150,8 +1150,8 @@ impl Selector { impl Selector { fn prefix_expression(&mut self) -> Result>> { Ok(vec![ - Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::PlusPlus))?), + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), ]) } } @@ -1159,9 +1159,9 @@ impl Selector { impl Selector { fn function_call_expression(&mut self) -> Result>> { Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), self.try_select(|node| node.is_rule_with_kind(RuleKind::FunctionCallOptions))?, Some(self.select(|node| node.is_rule_with_kind(RuleKind::ArgumentsDeclaration))?), - Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), ]) } } @@ -1169,9 +1169,9 @@ impl Selector { impl Selector { fn member_access_expression(&mut self) -> Result>> { Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::Period))?), Some(self.select(|node| node.is_rule_with_kind(RuleKind::MemberAccess))?), - Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), ]) } } @@ -1179,11 +1179,11 @@ impl Selector { impl Selector { fn index_access_expression(&mut self) -> Result>> { Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::OpenBracket))?), self.try_select(|node| node.is_rule_with_kind(RuleKind::Expression))?, self.try_select(|node| node.is_rule_with_kind(RuleKind::IndexAccessEnd))?, Some(self.select(|node| node.is_token_with_kind(TokenKind::CloseBracket))?), - Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), ]) } } @@ -1460,10 +1460,10 @@ impl Selector { impl Selector { fn yul_function_call_expression(&mut self) -> Result>> { Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::YulExpression))?), Some(self.select(|node| node.is_token_with_kind(TokenKind::OpenParen))?), self.try_select(|node| node.is_rule_with_kind(RuleKind::YulArguments))?, Some(self.select(|node| node.is_token_with_kind(TokenKind::CloseParen))?), - Some(self.select(|node| node.is_rule_with_kind(RuleKind::YulExpression))?), ]) } } diff --git a/crates/solidity/outputs/npm/package/src/ast/generated/ast_types.ts b/crates/solidity/outputs/npm/package/src/ast/generated/ast_types.ts index e4797d362b..41a9f6ad21 100644 --- a/crates/solidity/outputs/npm/package/src/ast/generated/ast_types.ts +++ b/crates/solidity/outputs/npm/package/src/ast/generated/ast_types.ts @@ -182,11 +182,11 @@ export class VersionPragmaRangeExpression { export class VersionPragmaPrefixExpression { private readonly fetch = once(() => { - const [$operand, $operator] = ast_internal.selectSequence(this.cst); + const [$operator, $operand] = ast_internal.selectSequence(this.cst); return { - operand: new VersionPragmaExpression($operand as RuleNode), operator: $operator as TokenNode, + operand: new VersionPragmaExpression($operand as RuleNode), }; }); @@ -194,13 +194,13 @@ export class VersionPragmaPrefixExpression { assertKind(this.cst.kind, RuleKind.VersionPragmaPrefixExpression); } - public get operand(): VersionPragmaExpression { - return this.fetch().operand; - } - public get operator(): TokenNode { return this.fetch().operator; } + + public get operand(): VersionPragmaExpression { + return this.fetch().operand; + } } export class ImportDirective { @@ -1465,13 +1465,13 @@ export class ErrorParameter { export class ArrayTypeName { private readonly fetch = once(() => { - const [$openBracket, $index, $closeBracket, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $openBracket, $index, $closeBracket] = ast_internal.selectSequence(this.cst); return { + operand: new TypeName($operand as RuleNode), openBracket: $openBracket as TokenNode, index: $index === null ? undefined : new Expression($index as RuleNode), closeBracket: $closeBracket as TokenNode, - operand: new TypeName($operand as RuleNode), }; }); @@ -1479,6 +1479,10 @@ export class ArrayTypeName { assertKind(this.cst.kind, RuleKind.ArrayTypeName); } + public get operand(): TypeName { + return this.fetch().operand; + } + public get openBracket(): TokenNode { return this.fetch().openBracket; } @@ -1490,10 +1494,6 @@ export class ArrayTypeName { public get closeBracket(): TokenNode { return this.fetch().closeBracket; } - - public get operand(): TypeName { - return this.fetch().operand; - } } export class FunctionType { @@ -2468,14 +2468,14 @@ export class AssignmentExpression { export class ConditionalExpression { private readonly fetch = once(() => { - const [$questionMark, $trueExpression, $colon, $falseExpression, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $questionMark, $trueExpression, $colon, $falseExpression] = ast_internal.selectSequence(this.cst); return { + operand: new Expression($operand as RuleNode), questionMark: $questionMark as TokenNode, trueExpression: new Expression($trueExpression as RuleNode), colon: $colon as TokenNode, falseExpression: new Expression($falseExpression as RuleNode), - operand: new Expression($operand as RuleNode), }; }); @@ -2483,6 +2483,10 @@ export class ConditionalExpression { assertKind(this.cst.kind, RuleKind.ConditionalExpression); } + public get operand(): Expression { + return this.fetch().operand; + } + public get questionMark(): TokenNode { return this.fetch().questionMark; } @@ -2498,10 +2502,6 @@ export class ConditionalExpression { public get falseExpression(): Expression { return this.fetch().falseExpression; } - - public get operand(): Expression { - return this.fetch().operand; - } } export class OrExpression { @@ -2814,11 +2814,11 @@ export class ExponentiationExpression { export class PostfixExpression { private readonly fetch = once(() => { - const [$operator, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $operator] = ast_internal.selectSequence(this.cst); return { - operator: $operator as TokenNode, operand: new Expression($operand as RuleNode), + operator: $operator as TokenNode, }; }); @@ -2826,22 +2826,22 @@ export class PostfixExpression { assertKind(this.cst.kind, RuleKind.PostfixExpression); } - public get operator(): TokenNode { - return this.fetch().operator; - } - public get operand(): Expression { return this.fetch().operand; } + + public get operator(): TokenNode { + return this.fetch().operator; + } } export class PrefixExpression { private readonly fetch = once(() => { - const [$operand, $operator] = ast_internal.selectSequence(this.cst); + const [$operator, $operand] = ast_internal.selectSequence(this.cst); return { - operand: new Expression($operand as RuleNode), operator: $operator as TokenNode, + operand: new Expression($operand as RuleNode), }; }); @@ -2849,23 +2849,23 @@ export class PrefixExpression { assertKind(this.cst.kind, RuleKind.PrefixExpression); } - public get operand(): Expression { - return this.fetch().operand; - } - public get operator(): TokenNode { return this.fetch().operator; } + + public get operand(): Expression { + return this.fetch().operand; + } } export class FunctionCallExpression { private readonly fetch = once(() => { - const [$options, $arguments, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $options, $arguments] = ast_internal.selectSequence(this.cst); return { + operand: new Expression($operand as RuleNode), options: $options === null ? undefined : new FunctionCallOptions($options as RuleNode), arguments: new ArgumentsDeclaration($arguments as RuleNode), - operand: new Expression($operand as RuleNode), }; }); @@ -2873,6 +2873,10 @@ export class FunctionCallExpression { assertKind(this.cst.kind, RuleKind.FunctionCallExpression); } + public get operand(): Expression { + return this.fetch().operand; + } + public get options(): FunctionCallOptions | undefined { return this.fetch().options; } @@ -2880,20 +2884,16 @@ export class FunctionCallExpression { public get arguments(): ArgumentsDeclaration { return this.fetch().arguments; } - - public get operand(): Expression { - return this.fetch().operand; - } } export class MemberAccessExpression { private readonly fetch = once(() => { - const [$period, $member, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $period, $member] = ast_internal.selectSequence(this.cst); return { + operand: new Expression($operand as RuleNode), period: $period as TokenNode, member: new MemberAccess($member as RuleNode), - operand: new Expression($operand as RuleNode), }; }); @@ -2901,6 +2901,10 @@ export class MemberAccessExpression { assertKind(this.cst.kind, RuleKind.MemberAccessExpression); } + public get operand(): Expression { + return this.fetch().operand; + } + public get period(): TokenNode { return this.fetch().period; } @@ -2908,22 +2912,18 @@ export class MemberAccessExpression { public get member(): MemberAccess { return this.fetch().member; } - - public get operand(): Expression { - return this.fetch().operand; - } } export class IndexAccessExpression { private readonly fetch = once(() => { - const [$openBracket, $start, $end, $closeBracket, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $openBracket, $start, $end, $closeBracket] = ast_internal.selectSequence(this.cst); return { + operand: new Expression($operand as RuleNode), openBracket: $openBracket as TokenNode, start: $start === null ? undefined : new Expression($start as RuleNode), end: $end === null ? undefined : new IndexAccessEnd($end as RuleNode), closeBracket: $closeBracket as TokenNode, - operand: new Expression($operand as RuleNode), }; }); @@ -2931,6 +2931,10 @@ export class IndexAccessExpression { assertKind(this.cst.kind, RuleKind.IndexAccessExpression); } + public get operand(): Expression { + return this.fetch().operand; + } + public get openBracket(): TokenNode { return this.fetch().openBracket; } @@ -2946,10 +2950,6 @@ export class IndexAccessExpression { public get closeBracket(): TokenNode { return this.fetch().closeBracket; } - - public get operand(): Expression { - return this.fetch().operand; - } } export class IndexAccessEnd { @@ -3683,13 +3683,13 @@ export class YulLabel { export class YulFunctionCallExpression { private readonly fetch = once(() => { - const [$openParen, $arguments, $closeParen, $operand] = ast_internal.selectSequence(this.cst); + const [$operand, $openParen, $arguments, $closeParen] = ast_internal.selectSequence(this.cst); return { + operand: new YulExpression($operand as RuleNode), openParen: $openParen as TokenNode, arguments: $arguments === null ? undefined : new YulArguments($arguments as RuleNode), closeParen: $closeParen as TokenNode, - operand: new YulExpression($operand as RuleNode), }; }); @@ -3697,6 +3697,10 @@ export class YulFunctionCallExpression { assertKind(this.cst.kind, RuleKind.YulFunctionCallExpression); } + public get operand(): YulExpression { + return this.fetch().operand; + } + public get openParen(): TokenNode { return this.fetch().openParen; } @@ -3708,10 +3712,6 @@ export class YulFunctionCallExpression { public get closeParen(): TokenNode { return this.fetch().closeParen; } - - public get operand(): YulExpression { - return this.fetch().operand; - } } /* diff --git a/crates/testlang/inputs/language/src/definition.rs b/crates/testlang/inputs/language/src/definition.rs index 6904b44d61..b43826dafb 100644 --- a/crates/testlang/inputs/language/src/definition.rs +++ b/crates/testlang/inputs/language/src/definition.rs @@ -31,6 +31,7 @@ codegen_language_macros::compile!(Language( name = SourceUnitMember, variants = [ EnumVariant(reference = Tree), + EnumVariant(reference = Expression), EnumVariant(reference = SeparatedIdentifiers), EnumVariant(reference = Literal) ] @@ -80,11 +81,45 @@ codegen_language_macros::compile!(Language( ), Topic( title = "DummyToAvoidWarnings", - items = [Separated( - name = SeparatedIdentifiers, - reference = Identifier, - separator = Period - )] + items = [ + Precedence( + name = Expression, + precedence_expressions = [ + PrecedenceExpression( + name = AdditionExpression, + operators = [PrecedenceOperator( + model = BinaryLeftAssociative, + fields = (operator = Required(Plus)) + )] + ), + PrecedenceExpression( + name = NegationExpression, + operators = [PrecedenceOperator( + model = Prefix, + fields = (operator = Required(Bang)) + )] + ), + PrecedenceExpression( + name = MemberAccessExpression, + operators = [PrecedenceOperator( + model = Postfix, + fields = + (period = Required(Period), member = Required(Identifier)) + )] + ) + ], + primary_expressions = [ + PrimaryExpression(reference = StringLiteral), + PrimaryExpression(reference = Identifier) + ] + ), + Separated( + name = SeparatedIdentifiers, + reference = Identifier, + separator = Period, + enabled = From("1.0.0") + ) + ] ), Topic( title = "Literals", @@ -211,6 +246,10 @@ codegen_language_macros::compile!(Language( Topic( title = "Punctuation", items = [ + Token( + name = Bang, + definitions = [TokenDefinition(scanner = Atom("!"))] + ), Token( name = OpenBracket, definitions = [TokenDefinition(scanner = Atom("["))] @@ -223,6 +262,10 @@ codegen_language_macros::compile!(Language( name = Period, definitions = [TokenDefinition(scanner = Atom("."))] ), + Token( + name = Plus, + definitions = [TokenDefinition(scanner = Atom("+"))] + ), Token( name = Semicolon, definitions = [TokenDefinition(scanner = Atom(";"))] diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/kinds.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/kinds.rs index 3c10aea217..3c96a7a45a 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/kinds.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/kinds.rs @@ -18,8 +18,12 @@ use napi_derive::napi; #[cfg_attr(feature = "slang_napi_interfaces", /* derives `Clone` and `Copy` */ napi(string_enum, namespace = "kinds"))] #[cfg_attr(not(feature = "slang_napi_interfaces"), derive(Clone, Copy))] pub enum RuleKind { + AdditionExpression, + Expression, LeadingTrivia, Literal, + MemberAccessExpression, + NegationExpression, SeparatedIdentifiers, SourceUnit, SourceUnitMember, @@ -68,10 +72,13 @@ pub enum FieldName { // Generated CloseBracket, Keyword, + Member, Members, Name, Node, OpenBracket, + Operator, + Period, Semicolon, } @@ -91,6 +98,7 @@ pub enum FieldName { #[cfg_attr(not(feature = "slang_napi_interfaces"), derive(Clone, Copy))] pub enum TokenKind { SKIPPED, + Bang, CloseBracket, DelimitedIdentifier, EndOfLine, @@ -98,6 +106,7 @@ pub enum TokenKind { MultiLineComment, OpenBracket, Period, + Plus, Semicolon, SingleLineComment, StringLiteral, diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/language.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/language.rs index 841e174945..b6df6b5ace 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/language.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/language.rs @@ -31,6 +31,7 @@ use crate::parser_support::{ #[cfg_attr(feature = "slang_napi_interfaces", napi(namespace = "language"))] pub struct Language { pub(crate) version: Version, + pub(crate) version_is_at_least_1_0_0: bool, } #[derive(thiserror::Error, Debug)] @@ -60,7 +61,10 @@ impl Language { pub fn new(version: Version) -> std::result::Result { if Self::SUPPORTED_VERSIONS.binary_search(&version).is_ok() { - Ok(Self { version }) + Ok(Self { + version_is_at_least_1_0_0: Version::new(1, 0, 0) <= version, + version, + }) } else { Err(Error::UnsupportedLanguageVersion(version)) } @@ -74,6 +78,136 @@ impl Language { * Parser Functions ********************************************/ + #[allow(unused_assignments, unused_parens)] + fn addition_expression(&self, input: &mut ParserContext<'_>) -> ParserResult { + let result = self.expression(input); + let ParserResult::Match(r#match) = &result else { + return result; + }; + match &r#match.nodes[..] { + [cst::NamedNode { + name: _, + node: cst::Node::Rule(node), + }] if node.kind == RuleKind::Expression => match &node.children[..] { + [inner @ cst::NamedNode { + name: _, + node: cst::Node::Rule(rule), + }] if rule.kind == RuleKind::AdditionExpression => { + ParserResult::r#match(vec![inner.clone()], r#match.expected_tokens.clone()) + } + _ => ParserResult::no_match(vec![]), + }, + _ => ParserResult::no_match(vec![]), + } + } + + #[allow(unused_assignments, unused_parens)] + fn expression(&self, input: &mut ParserContext<'_>) -> ParserResult { + let parse_left_addition_expression = |input: &mut ParserContext<'_>| { + PrecedenceHelper::to_binary_operator( + RuleKind::AdditionExpression, + 1u8, + 1u8 + 1, + self.parse_token_with_trivia::(input, TokenKind::Plus) + .with_name(FieldName::Operator), + ) + }; + let parse_prefix_negation_expression = |input: &mut ParserContext<'_>| { + PrecedenceHelper::to_prefix_operator( + RuleKind::NegationExpression, + 3u8, + self.parse_token_with_trivia::(input, TokenKind::Bang) + .with_name(FieldName::Operator), + ) + }; + let parse_postfix_member_access_expression = |input: &mut ParserContext<'_>| { + PrecedenceHelper::to_postfix_operator( + RuleKind::MemberAccessExpression, + 5u8, + SequenceHelper::run(|mut seq| { + seq.elem_named( + FieldName::Period, + self.parse_token_with_trivia::( + input, + TokenKind::Period, + ), + )?; + seq.elem_named( + FieldName::Member, + self.parse_token_with_trivia::( + input, + TokenKind::Identifier, + ), + )?; + seq.finish() + }), + ) + }; + let prefix_operator_parser = |input: &mut ParserContext<'_>| { + ChoiceHelper::run(input, |mut choice, input| { + let result = parse_prefix_negation_expression(input); + choice.consider(input, result)?; + choice.finish(input) + }) + }; + let primary_expression_parser = |input: &mut ParserContext<'_>| { + ChoiceHelper::run(input, |mut choice, input| { + let result = self.parse_token_with_trivia::( + input, + TokenKind::StringLiteral, + ); + choice.consider(input, result)?; + let result = self.parse_token_with_trivia::( + input, + TokenKind::Identifier, + ); + choice.consider(input, result)?; + choice.finish(input) + }) + .with_name(FieldName::Variant) + }; + let postfix_operator_parser = |input: &mut ParserContext<'_>| { + ChoiceHelper::run(input, |mut choice, input| { + let result = parse_postfix_member_access_expression(input); + choice.consider(input, result)?; + choice.finish(input) + }) + }; + let binary_operand_parser = |input: &mut ParserContext<'_>| { + SequenceHelper::run(|mut seq| { + seq.elem(ZeroOrMoreHelper::run(input, prefix_operator_parser))?; + seq.elem(primary_expression_parser(input))?; + seq.elem(ZeroOrMoreHelper::run(input, postfix_operator_parser))?; + seq.finish() + }) + }; + let binary_operator_parser = |input: &mut ParserContext<'_>| { + ChoiceHelper::run(input, |mut choice, input| { + let result = parse_left_addition_expression(input); + choice.consider(input, result)?; + choice.finish(input) + }) + }; + let linear_expression_parser = |input: &mut ParserContext<'_>| { + SequenceHelper::run(|mut seq| { + seq.elem(binary_operand_parser(input))?; + seq.elem(ZeroOrMoreHelper::run(input, |input| { + SequenceHelper::run(|mut seq| { + seq.elem(binary_operator_parser(input))?; + seq.elem(binary_operand_parser(input))?; + seq.finish() + }) + }))?; + seq.finish() + }) + }; + PrecedenceHelper::reduce_precedence_result( + RuleKind::Expression, + linear_expression_parser(input), + ) + .with_kind(RuleKind::Expression) + } + #[allow(unused_assignments, unused_parens)] fn leading_trivia(&self, input: &mut ParserContext<'_>) -> ParserResult { OneOrMoreHelper::run(input, |input| { @@ -113,20 +247,70 @@ impl Language { } #[allow(unused_assignments, unused_parens)] - fn separated_identifiers(&self, input: &mut ParserContext<'_>) -> ParserResult { - SeparatedHelper::run::<_, LexicalContextType::Default>( - input, - self, - |input| { - self.parse_token_with_trivia::( - input, - TokenKind::Identifier, - ) - .with_name(FieldName::Item) + fn member_access_expression(&self, input: &mut ParserContext<'_>) -> ParserResult { + let result = self.expression(input); + let ParserResult::Match(r#match) = &result else { + return result; + }; + match &r#match.nodes[..] { + [cst::NamedNode { + name: _, + node: cst::Node::Rule(node), + }] if node.kind == RuleKind::Expression => match &node.children[..] { + [inner @ cst::NamedNode { + name: _, + node: cst::Node::Rule(rule), + }] if rule.kind == RuleKind::MemberAccessExpression => { + ParserResult::r#match(vec![inner.clone()], r#match.expected_tokens.clone()) + } + _ => ParserResult::no_match(vec![]), }, - TokenKind::Period, - FieldName::Separator, - ) + _ => ParserResult::no_match(vec![]), + } + } + + #[allow(unused_assignments, unused_parens)] + fn negation_expression(&self, input: &mut ParserContext<'_>) -> ParserResult { + let result = self.expression(input); + let ParserResult::Match(r#match) = &result else { + return result; + }; + match &r#match.nodes[..] { + [cst::NamedNode { + name: _, + node: cst::Node::Rule(node), + }] if node.kind == RuleKind::Expression => match &node.children[..] { + [inner @ cst::NamedNode { + name: _, + node: cst::Node::Rule(rule), + }] if rule.kind == RuleKind::NegationExpression => { + ParserResult::r#match(vec![inner.clone()], r#match.expected_tokens.clone()) + } + _ => ParserResult::no_match(vec![]), + }, + _ => ParserResult::no_match(vec![]), + } + } + + #[allow(unused_assignments, unused_parens)] + fn separated_identifiers(&self, input: &mut ParserContext<'_>) -> ParserResult { + if self.version_is_at_least_1_0_0 { + SeparatedHelper::run::<_, LexicalContextType::Default>( + input, + self, + |input| { + self.parse_token_with_trivia::( + input, + TokenKind::Identifier, + ) + .with_name(FieldName::Item) + }, + TokenKind::Period, + FieldName::Separator, + ) + } else { + ParserResult::disabled() + } .with_kind(RuleKind::SeparatedIdentifiers) } @@ -142,6 +326,8 @@ impl Language { ChoiceHelper::run(input, |mut choice, input| { let result = self.tree(input); choice.consider(input, result)?; + let result = self.expression(input); + choice.consider(input, result)?; let result = self.separated_identifiers(input); choice.consider(input, result)?; let result = self.literal(input); @@ -457,8 +643,14 @@ impl Language { pub fn parse(&self, kind: RuleKind, input: &str) -> ParseOutput { match kind { + RuleKind::AdditionExpression => Self::addition_expression.parse(self, input, true), + RuleKind::Expression => Self::expression.parse(self, input, true), RuleKind::LeadingTrivia => Self::leading_trivia.parse(self, input, false), RuleKind::Literal => Self::literal.parse(self, input, true), + RuleKind::MemberAccessExpression => { + Self::member_access_expression.parse(self, input, true) + } + RuleKind::NegationExpression => Self::negation_expression.parse(self, input, true), RuleKind::SeparatedIdentifiers => Self::separated_identifiers.parse(self, input, true), RuleKind::SourceUnit => Self::source_unit.parse(self, input, true), RuleKind::SourceUnitMember => Self::source_unit_member.parse(self, input, true), @@ -511,10 +703,15 @@ impl Lexer for Language { match LexCtx::value() { LexicalContext::Default => { - if let Some(kind) = if scan_chars!(input, '.') { - Some(TokenKind::Period) - } else { - None + if let Some(kind) = match input.next() { + Some('!') => Some(TokenKind::Bang), + Some('+') => Some(TokenKind::Plus), + Some('.') => Some(TokenKind::Period), + Some(_) => { + input.undo(); + None + } + None => None, } { furthest_position = input.position(); longest_token = Some(kind); diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/ast_selectors.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/ast_selectors.rs index 1751307f54..7124263e2d 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/ast_selectors.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/napi_interface/ast_selectors.rs @@ -29,6 +29,9 @@ pub fn select_sequence( RuleKind::SourceUnit => selector.source_unit()?, RuleKind::Tree => selector.tree()?, RuleKind::TreeNode => selector.tree_node()?, + RuleKind::AdditionExpression => selector.addition_expression()?, + RuleKind::NegationExpression => selector.negation_expression()?, + RuleKind::MemberAccessExpression => selector.member_access_expression()?, _ => { return Error::UnexpectedParent(node.kind()).into(); } @@ -67,6 +70,35 @@ impl Selector { } } +impl Selector { + fn addition_expression(&mut self) -> Result>> { + Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), + Some(self.select(|node| node.is_token_with_kind(TokenKind::Plus))?), + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), + ]) + } +} + +impl Selector { + fn negation_expression(&mut self) -> Result>> { + Ok(vec![ + Some(self.select(|node| node.is_token_with_kind(TokenKind::Bang))?), + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), + ]) + } +} + +impl Selector { + fn member_access_expression(&mut self) -> Result>> { + Ok(vec![ + Some(self.select(|node| node.is_rule_with_kind(RuleKind::Expression))?), + Some(self.select(|node| node.is_token_with_kind(TokenKind::Period))?), + Some(self.select(|node| node.is_token_with_kind(TokenKind::Identifier))?), + ]) + } +} + // // Choices: // @@ -81,6 +113,7 @@ pub fn select_choice( let result = match node.kind() { RuleKind::SourceUnitMember => selector.source_unit_member()?, RuleKind::TreeNodeChild => selector.tree_node_child()?, + RuleKind::Expression => selector.expression()?, RuleKind::Literal => selector.literal()?, _ => { return Error::UnexpectedParent(node.kind()).into(); @@ -96,6 +129,7 @@ impl Selector { self.select(|node| { node.is_rule_with_kinds(&[ RuleKind::Tree, + RuleKind::Expression, RuleKind::SeparatedIdentifiers, RuleKind::Literal, ]) @@ -112,6 +146,18 @@ impl Selector { } } +impl Selector { + fn expression(&mut self) -> Result { + self.select(|node| { + node.is_rule_with_kinds(&[ + RuleKind::AdditionExpression, + RuleKind::NegationExpression, + RuleKind::MemberAccessExpression, + ]) || node.is_token_with_kinds(&[TokenKind::StringLiteral, TokenKind::Identifier]) + }) + } +} + impl Selector { fn literal(&mut self) -> Result { self.select(|node| node.is_token_with_kind(TokenKind::StringLiteral)) diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/lib.rs b/crates/testlang/outputs/cargo/slang_testlang/src/lib.rs index be211a4e1f..14ed671113 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/lib.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/lib.rs @@ -1,4 +1,2 @@ -#![allow(dead_code)] - mod generated; pub use generated::*; diff --git a/crates/testlang/outputs/npm/package/src/ast/generated/ast_types.ts b/crates/testlang/outputs/npm/package/src/ast/generated/ast_types.ts index a07b8a918b..4bb5404838 100644 --- a/crates/testlang/outputs/npm/package/src/ast/generated/ast_types.ts +++ b/crates/testlang/outputs/npm/package/src/ast/generated/ast_types.ts @@ -88,17 +88,98 @@ export class TreeNode { } } +export class AdditionExpression { + private readonly fetch = once(() => { + const [$leftOperand, $operator, $rightOperand] = ast_internal.selectSequence(this.cst); + + return { + leftOperand: new Expression($leftOperand as RuleNode), + operator: $operator as TokenNode, + rightOperand: new Expression($rightOperand as RuleNode), + }; + }); + + public constructor(public readonly cst: RuleNode) { + assertKind(this.cst.kind, RuleKind.AdditionExpression); + } + + public get leftOperand(): Expression { + return this.fetch().leftOperand; + } + + public get operator(): TokenNode { + return this.fetch().operator; + } + + public get rightOperand(): Expression { + return this.fetch().rightOperand; + } +} + +export class NegationExpression { + private readonly fetch = once(() => { + const [$operator, $operand] = ast_internal.selectSequence(this.cst); + + return { + operator: $operator as TokenNode, + operand: new Expression($operand as RuleNode), + }; + }); + + public constructor(public readonly cst: RuleNode) { + assertKind(this.cst.kind, RuleKind.NegationExpression); + } + + public get operator(): TokenNode { + return this.fetch().operator; + } + + public get operand(): Expression { + return this.fetch().operand; + } +} + +export class MemberAccessExpression { + private readonly fetch = once(() => { + const [$operand, $period, $member] = ast_internal.selectSequence(this.cst); + + return { + operand: new Expression($operand as RuleNode), + period: $period as TokenNode, + member: $member as TokenNode, + }; + }); + + public constructor(public readonly cst: RuleNode) { + assertKind(this.cst.kind, RuleKind.MemberAccessExpression); + } + + public get operand(): Expression { + return this.fetch().operand; + } + + public get period(): TokenNode { + return this.fetch().period; + } + + public get member(): TokenNode { + return this.fetch().member; + } +} + /* * Choices: */ export class SourceUnitMember { - private readonly fetch: () => Tree | SeparatedIdentifiers | Literal = once(() => { + private readonly fetch: () => Tree | Expression | SeparatedIdentifiers | Literal = once(() => { const variant = ast_internal.selectChoice(this.cst); switch (variant.kind) { case RuleKind.Tree: return new Tree(variant as RuleNode); + case RuleKind.Expression: + return new Expression(variant as RuleNode); case RuleKind.SeparatedIdentifiers: return new SeparatedIdentifiers(variant as RuleNode); case RuleKind.Literal: @@ -113,7 +194,7 @@ export class SourceUnitMember { assertKind(this.cst.kind, RuleKind.SourceUnitMember); } - public get variant(): Tree | SeparatedIdentifiers | Literal { + public get variant(): Tree | Expression | SeparatedIdentifiers | Literal { return this.fetch(); } } @@ -143,6 +224,38 @@ export class TreeNodeChild { } } +export class Expression { + private readonly fetch: () => AdditionExpression | NegationExpression | MemberAccessExpression | TokenNode = once( + () => { + const variant = ast_internal.selectChoice(this.cst); + + switch (variant.kind) { + case RuleKind.AdditionExpression: + return new AdditionExpression(variant as RuleNode); + case RuleKind.NegationExpression: + return new NegationExpression(variant as RuleNode); + case RuleKind.MemberAccessExpression: + return new MemberAccessExpression(variant as RuleNode); + + case TokenKind.StringLiteral: + case TokenKind.Identifier: + return variant as TokenNode; + + default: + assert.fail(`Unexpected variant: ${variant.kind}`); + } + }, + ); + + public constructor(public readonly cst: RuleNode) { + assertKind(this.cst.kind, RuleKind.Expression); + } + + public get variant(): AdditionExpression | NegationExpression | MemberAccessExpression | TokenNode { + return this.fetch(); + } +} + export class Literal { private readonly fetch: () => TokenNode = once(() => { const variant = ast_internal.selectChoice(this.cst); diff --git a/crates/testlang/outputs/npm/package/src/generated/index.d.ts b/crates/testlang/outputs/npm/package/src/generated/index.d.ts index 8c382e05bc..116a554891 100644 --- a/crates/testlang/outputs/npm/package/src/generated/index.d.ts +++ b/crates/testlang/outputs/npm/package/src/generated/index.d.ts @@ -10,8 +10,12 @@ export namespace kinds { export enum RuleKind { + AdditionExpression = "AdditionExpression", + Expression = "Expression", LeadingTrivia = "LeadingTrivia", Literal = "Literal", + MemberAccessExpression = "MemberAccessExpression", + NegationExpression = "NegationExpression", SeparatedIdentifiers = "SeparatedIdentifiers", SourceUnit = "SourceUnit", SourceUnitMember = "SourceUnitMember", @@ -31,14 +35,18 @@ export namespace kinds { RightOperand = "RightOperand", CloseBracket = "CloseBracket", Keyword = "Keyword", + Member = "Member", Members = "Members", Name = "Name", Node = "Node", OpenBracket = "OpenBracket", + Operator = "Operator", + Period = "Period", Semicolon = "Semicolon", } export enum TokenKind { SKIPPED = "SKIPPED", + Bang = "Bang", CloseBracket = "CloseBracket", DelimitedIdentifier = "DelimitedIdentifier", EndOfLine = "EndOfLine", @@ -46,6 +54,7 @@ export namespace kinds { MultiLineComment = "MultiLineComment", OpenBracket = "OpenBracket", Period = "Period", + Plus = "Plus", Semicolon = "Semicolon", SingleLineComment = "SingleLineComment", StringLiteral = "StringLiteral", diff --git a/crates/testlang/outputs/npm/tests/src/tests/ast.ts b/crates/testlang/outputs/npm/tests/src/tests/ast.ts index c3e0a7569c..8a23cf19dc 100644 --- a/crates/testlang/outputs/npm/tests/src/tests/ast.ts +++ b/crates/testlang/outputs/npm/tests/src/tests/ast.ts @@ -1,6 +1,17 @@ +import assert from "node:assert"; import { Language } from "@slang-private/slang-testlang/language"; import { RuleKind, TokenKind } from "@slang-private/slang-testlang/kinds"; -import { SeparatedIdentifiers, SourceUnit, Tree, TreeNode, TreeNodeChild } from "@slang-private/slang-testlang/ast"; +import { + AdditionExpression, + Expression, + MemberAccessExpression, + NegationExpression, + SeparatedIdentifiers, + SourceUnit, + Tree, + TreeNode, + TreeNodeChild, +} from "@slang-private/slang-testlang/ast"; import { expectRule, expectToken } from "../utils/cst-helpers"; import { TokenNode } from "@slang-private/slang-testlang/cst"; @@ -127,3 +138,62 @@ test("throws an exception on on using an incorrect/incomplete CST node", () => { "Unexpected SKIPPED token at index '1'. Creating AST types from incorrect/incomplete CST nodes is not supported yet.", ); }); + +test("create and use prefix expressions", () => { + const source = `!foo`; + + const language = new Language("1.0.0"); + + const parseOutput = language.parse(RuleKind.Expression, source); + expect(parseOutput.errors()).toHaveLength(0); + + const cst = parseOutput.tree(); + expectRule(cst, RuleKind.Expression); + + const expression = new Expression(cst); + assert(expression.variant instanceof NegationExpression); + + const { operator, operand } = expression.variant; + expectToken(operator, TokenKind.Bang, "!"); + expectToken(operand.variant, TokenKind.Identifier, "foo"); +}); + +test("create and use postfix expressions", () => { + const source = `foo.bar`; + + const language = new Language("1.0.0"); + + const parseOutput = language.parse(RuleKind.Expression, source); + expect(parseOutput.errors()).toHaveLength(0); + + const cst = parseOutput.tree(); + expectRule(cst, RuleKind.Expression); + + const expression = new Expression(cst); + assert(expression.variant instanceof MemberAccessExpression); + + const { operand, period, member } = expression.variant; + expectToken(operand.variant, TokenKind.Identifier, "foo"); + expectToken(period, TokenKind.Period, "."); + expectToken(member, TokenKind.Identifier, "bar"); +}); + +test("create and use binary expressions", () => { + const source = `foo + bar`; + + const language = new Language("1.0.0"); + + const parseOutput = language.parse(RuleKind.Expression, source); + expect(parseOutput.errors()).toHaveLength(0); + + const cst = parseOutput.tree(); + expectRule(cst, RuleKind.Expression); + + const expression = new Expression(cst); + assert(expression.variant instanceof AdditionExpression); + + const { leftOperand, operator, rightOperand } = expression.variant; + expectToken(leftOperand.variant, TokenKind.Identifier, "foo"); + expectToken(operator, TokenKind.Plus, "+"); + expectToken(rightOperand.variant, TokenKind.Identifier, "bar"); +});