diff --git a/ngcompiler/lib/v1/src/compiler/expression_parser/analyzer_parser.dart b/ngcompiler/lib/v1/src/compiler/expression_parser/analyzer_parser.dart index 1f11db1443..926fc066b7 100644 --- a/ngcompiler/lib/v1/src/compiler/expression_parser/analyzer_parser.dart +++ b/ngcompiler/lib/v1/src/compiler/expression_parser/analyzer_parser.dart @@ -11,8 +11,8 @@ import 'ast.dart' as ast; import 'parser.dart'; /// Implements [ExpressionParser] using `package:analyzer`'s AST parser. -class AnalyzerExpressionParser extends ExpressionParser { - AnalyzerExpressionParser() : super.forInheritence(); +class AnalyzerExpressionParser extends ExpressionParserImpl { + AnalyzerExpressionParser(); @override ast.AST parseActionImpl( diff --git a/ngcompiler/lib/v1/src/compiler/expression_parser/ast.dart b/ngcompiler/lib/v1/src/compiler/expression_parser/ast.dart index 5e3f68af00..5b67f53350 100644 --- a/ngcompiler/lib/v1/src/compiler/expression_parser/ast.dart +++ b/ngcompiler/lib/v1/src/compiler/expression_parser/ast.dart @@ -1,7 +1,7 @@ import 'package:ngcompiler/v1/src/compiler/compile_metadata.dart'; /// An abstraction representing a component of a parsed Dart expression. -abstract class AST { +sealed class AST { /// Given a [visitor] and optionally a [context], produce a return value [R]. R visit(AstVisitor visitor, [CO? context]); } @@ -14,7 +14,7 @@ abstract class AST { /// ``` /// NamedExpr('foo', LiteralPrimitive('bar')) // foo: 'bar' /// ``` -class NamedExpr extends AST { +final class NamedExpr extends AST { /// Name (identifier) being assigned [expression]. final String name; @@ -23,23 +23,19 @@ class NamedExpr extends AST { NamedExpr(this.name, this.expression); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitNamedExpr(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitNamedExpr(this, context); + } } /// A placeholder expression used when otherwise no expression is parsed/found. /// /// For example, this might result from parsing `[foo]=""`. -class EmptyExpr extends AST { +final class EmptyExpr extends AST { @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitEmptyExpr(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitEmptyExpr(this, context); + } } /// A reference to a "static" variable or identifier represented by [id]. @@ -56,17 +52,15 @@ class EmptyExpr extends AST { /// ``` /// StaticRead(AppViewIdentifiers.someField); // appViewUtils.someField /// ``` -class StaticRead extends AST { +final class StaticRead extends AST { final CompileIdentifierMetadata id; StaticRead(this.id); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitStaticRead(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitStaticRead(this, context); + } } /// A reference to a local variable [name]. @@ -82,28 +76,24 @@ class StaticRead extends AST { /// ``` /// VariableRead('foo') // foo /// ``` -class VariableRead extends AST { +final class VariableRead extends AST { /// Name of a local variable. final String name; VariableRead(this.name); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitVariableRead(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitVariableRead(this, context); + } } /// The "root" expression (the context in which the expression is evaluated). -class ImplicitReceiver extends AST { +final class ImplicitReceiver extends AST { @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitImplicitReceiver(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitImplicitReceiver(this, context); + } } /// A ternary or where [condition] will either pick [trueExpr] or [falseExpr]. @@ -116,23 +106,19 @@ class ImplicitReceiver extends AST { /// VariableRead('c'), /// ) /// ``` -class Conditional extends AST { +final class Conditional extends AST { final AST condition; + final AST trueExp; + final AST falseExp; - Conditional( - this.condition, - this.trueExp, - this.falseExp, - ); + Conditional(this.condition, this.trueExp, this.falseExp); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitConditional(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitConditional(this, context); + } } /// Represents the "if null" (`??`) operator. @@ -140,24 +126,19 @@ class Conditional extends AST { /// ``` /// IfNull(VariableRead('a'), VariableRead('b')) // a ?? b /// ``` -class IfNull extends AST { +final class IfNull extends AST { /// Condition for the null check and result if it is not null. final AST condition; /// Result if the [condition] operand is null. final AST nullExp; - IfNull( - this.condition, - this.nullExp, - ); + IfNull(this.condition, this.nullExp); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitIfNull(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitIfNull(this, context); + } } /// Reads a property (getter or field) [name] from [receiver]. @@ -165,7 +146,7 @@ class IfNull extends AST { /// ``` /// PropertyRead(VariableRead('a'), 'b') // a.b /// ``` -class PropertyRead extends AST { +final class PropertyRead extends AST { /// Context to read [name] from. final AST receiver; @@ -175,11 +156,9 @@ class PropertyRead extends AST { PropertyRead(this.receiver, this.name); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitPropertyRead(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitPropertyRead(this, context); + } } /// Similar to [PropertyRead], but avoids NPEs by using `?.` instead of `.`. @@ -187,7 +166,7 @@ class PropertyRead extends AST { /// ``` /// SafePropertyRead(VariableRead('a'), VariableRead('b')) // a?.b /// ``` -class SafePropertyRead extends AST { +final class SafePropertyRead extends AST { /// Context to read [name] from. final AST receiver; @@ -197,11 +176,9 @@ class SafePropertyRead extends AST { SafePropertyRead(this.receiver, this.name); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitSafePropertyRead(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitSafePropertyRead(this, context); + } } /// Similar to [PropertyRead], but uses bracket operator `[]` to refer to [key]. @@ -209,7 +186,7 @@ class SafePropertyRead extends AST { /// ``` /// KeyedRead(VariableRead('a'), LiteralPrimitive('b')) // a['b'] /// ``` -class KeyedRead extends AST { +final class KeyedRead extends AST { /// Context to read [key] from. final AST receiver; @@ -219,8 +196,9 @@ class KeyedRead extends AST { KeyedRead(this.receiver, this.key); @override - R visit(AstVisitor visitor, [CO? context]) => - visitor.visitKeyedRead(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitKeyedRead(this, context); + } } /// Writes a property (setter or field) [name] to [receiver]. @@ -228,7 +206,7 @@ class KeyedRead extends AST { /// ``` /// PropertyWrite(VariableRead('a'), 'b', LiteralPrimitive('c')) // a.b = 'c' /// ``` -class PropertyWrite extends AST { +final class PropertyWrite extends AST { /// Context to write [name] to. final AST receiver; @@ -241,11 +219,9 @@ class PropertyWrite extends AST { PropertyWrite(this.receiver, this.name, this.value); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitPropertyWrite(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitPropertyWrite(this, context); + } } /// Similar to [PropertyWrite] using bracket operator `[]=` to refer to [key]. @@ -258,7 +234,7 @@ class PropertyWrite extends AST { /// LiteralPrimitive('c'), /// ) /// ``` -class KeyedWrite extends AST { +final class KeyedWrite extends AST { /// Context to write [key] to. final AST receiver; @@ -271,11 +247,9 @@ class KeyedWrite extends AST { KeyedWrite(this.receiver, this.key, this.value); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitKeyedWrite(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitKeyedWrite(this, context); + } } /// A method call that has been interpreted as a specialized "pipe" invocation. @@ -294,7 +268,7 @@ class KeyedWrite extends AST { /// [LiteralPrimitive('baz')], /// ) /// ``` -class BindingPipe extends AST { +final class BindingPipe extends AST { /// Name of the pipe. final String name; @@ -307,11 +281,9 @@ class BindingPipe extends AST { BindingPipe(this.exp, this.name, this.args); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitPipe(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitPipe(this, context); + } } /// Represents a primitive value (either [number], [String], [bool], or `null`). @@ -326,7 +298,7 @@ class BindingPipe extends AST { /// // 5 /// LiteralPrimitive(5) /// ``` -class LiteralPrimitive extends AST { +final class LiteralPrimitive extends AST { /// Value being parsed. /// /// This is either [number], [String], [bool], or `null`. @@ -335,11 +307,9 @@ class LiteralPrimitive extends AST { LiteralPrimitive(this.value); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitLiteralPrimitive(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitLiteralPrimitive(this, context); + } } /// Represents converting a result or multiple results explicitly to a [String]. @@ -354,7 +324,7 @@ class LiteralPrimitive extends AST { /// [VariableRead('place'), EmptyExpr()], /// ) /// ``` -class Interpolation extends AST { +final class Interpolation extends AST { /// For a given expression `i`, the preceding string (if any). /// /// In practice, this is an empty string (`''`) if there is no preceding @@ -367,11 +337,9 @@ class Interpolation extends AST { Interpolation(this.strings, this.expressions); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitInterpolation(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitInterpolation(this, context); + } } /// Represents a binary expression, i.e. `left operator right`. @@ -380,7 +348,7 @@ class Interpolation extends AST { /// // 2 + 3 /// Binary('+', LiteralPrimitive(2), LiteralPrimitive(3)) /// ``` -class Binary extends AST { +final class Binary extends AST { /// A literal result of parsing a binary operator. final String operator; @@ -393,11 +361,9 @@ class Binary extends AST { Binary(this.operator, this.left, this.right); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitBinary(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitBinary(this, context); + } } /// A unary prefixed "not" expression, i.e. `!expr`. @@ -406,18 +372,16 @@ class Binary extends AST { /// // !true /// PrefixNot(LiteralPrimitive(true)) /// ``` -class PrefixNot extends AST { +final class PrefixNot extends AST { /// Expression to negate. final AST expression; PrefixNot(this.expression); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitPrefixNot(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitPrefixNot(this, context); + } } /// Coerces `T?` to `T`, throwing if null, i.e. `var!`. @@ -426,17 +390,15 @@ class PrefixNot extends AST { /// // a! /// PostfixNotNull(VariableRead('a')) /// ``` -class PostfixNotNull extends AST { +final class PostfixNotNull extends AST { final AST expression; PostfixNotNull(this.expression); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitPostfixNotNull(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitPostfixNotNull(this, context); + } } /// A call to a method. @@ -450,7 +412,7 @@ class PostfixNotNull extends AST { /// [NamedExpr('baz', LiteralPrimitive(123))], /// ) /// ``` -class MethodCall extends AST { +final class MethodCall extends AST { final AST receiver; final String name; final List args; @@ -464,15 +426,13 @@ class MethodCall extends AST { ]); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitMethodCall(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitMethodCall(this, context); + } } /// Similar to [MethodCall], but only if the [receiver] is non-null. -class SafeMethodCall extends AST { +final class SafeMethodCall extends AST { final AST receiver; final String name; final List args; @@ -486,11 +446,9 @@ class SafeMethodCall extends AST { ]); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitSafeMethodCall(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitSafeMethodCall(this, context); + } } /// Similar to [MethodCall], but [target] is callable. @@ -503,7 +461,7 @@ class SafeMethodCall extends AST { /// [NamedExpr('baz', LiteralPrimitive(123))], /// ) /// ``` -class FunctionCall extends AST { +final class FunctionCall extends AST { final AST target; final List args; final List namedArgs; @@ -515,15 +473,13 @@ class FunctionCall extends AST { ]); @override - R visit( - AstVisitor visitor, [ - CO? context, - ]) => - visitor.visitFunctionCall(this, context); + R visit(AstVisitor visitor, [CO? context]) { + return visitor.visitFunctionCall(this, context); + } } /// Wraps an [AST] with [source] and [location] information. -class ASTWithSource { +final class ASTWithSource { final AST ast; final String? source; final String? location; @@ -552,40 +508,62 @@ class ASTWithSource { abstract class AstVisitor { R visitBinary(Binary ast, C context); + R visitConditional(Conditional ast, C context); + R visitEmptyExpr(EmptyExpr ast, C context); + R visitFunctionCall(FunctionCall ast, C context); + R visitIfNull(IfNull ast, C context); + R visitImplicitReceiver(ImplicitReceiver ast, C context); + R visitInterpolation(Interpolation ast, C context); + R visitKeyedRead(KeyedRead ast, C context); + R visitKeyedWrite(KeyedWrite ast, C context); + R visitLiteralPrimitive(LiteralPrimitive ast, C context); + R visitMethodCall(MethodCall ast, C context); + R visitNamedExpr(NamedExpr ast, C context); + R visitPipe(BindingPipe ast, C context); + R visitPostfixNotNull(PostfixNotNull ast, C context); + R visitPrefixNot(PrefixNot ast, C context); + R visitPropertyRead(PropertyRead ast, C context); + R visitPropertyWrite(PropertyWrite ast, C context); + R visitSafeMethodCall(SafeMethodCall ast, C context); + R visitSafePropertyRead(SafePropertyRead ast, C context); + R visitStaticRead(StaticRead ast, C context); + R visitVariableRead(VariableRead ast, C context); } class RecursiveAstVisitor implements AstVisitor { @override void visitBinary(Binary ast, C context) { - ast.left.visit(this, context); - ast.right.visit(this, context); + ast + ..left.visit(this, context) + ..right.visit(this, context); } @override void visitConditional(Conditional ast, C context) { - ast.condition.visit(this, context); - ast.trueExp.visit(this, context); - ast.falseExp.visit(this, context); + ast + ..condition.visit(this, context) + ..trueExp.visit(this, context) + ..falseExp.visit(this, context); } @override @@ -611,8 +589,9 @@ class RecursiveAstVisitor implements AstVisitor { @override void visitIfNull(IfNull ast, C context) { - ast.condition.visit(this, context); - ast.nullExp.visit(this, context); + ast + ..condition.visit(this, context) + ..nullExp.visit(this, context); } @override @@ -625,15 +604,17 @@ class RecursiveAstVisitor implements AstVisitor { @override void visitKeyedRead(KeyedRead ast, C context) { - ast.receiver.visit(this, context); - ast.key.visit(this, context); + ast + ..receiver.visit(this, context) + ..key.visit(this, context); } @override void visitKeyedWrite(KeyedWrite ast, C context) { - ast.receiver.visit(this, context); - ast.key.visit(this, context); - ast.value.visit(this, context); + ast + ..receiver.visit(this, context) + ..key.visit(this, context) + ..value.visit(this, context); } @override @@ -663,8 +644,9 @@ class RecursiveAstVisitor implements AstVisitor { @override void visitPropertyWrite(PropertyWrite ast, C context) { - ast.receiver.visit(this, context); - ast.value.visit(this, context); + ast + ..receiver.visit(this, context) + ..value.visit(this, context); } @override @@ -694,95 +676,135 @@ class RecursiveAstVisitor implements AstVisitor { class AstTransformer implements AstVisitor { @override - AST visitImplicitReceiver(ImplicitReceiver ast, _) => ast; + AST visitImplicitReceiver(ImplicitReceiver ast, void context) { + return ast; + } @override - AST visitStaticRead(StaticRead ast, _) => ast; + AST visitStaticRead(StaticRead ast, void context) { + return ast; + } @override - AST visitVariableRead(VariableRead ast, _) => ast; + AST visitVariableRead(VariableRead ast, void context) { + return ast; + } @override - AST visitInterpolation(Interpolation ast, _) => - Interpolation(ast.strings, _visitAll(ast.expressions)); + AST visitInterpolation(Interpolation ast, void context) { + return Interpolation(ast.strings, _visitAll(ast.expressions)); + } @override - AST visitLiteralPrimitive(LiteralPrimitive ast, _) => - LiteralPrimitive(ast.value); + AST visitLiteralPrimitive(LiteralPrimitive ast, void context) { + return LiteralPrimitive(ast.value); + } @override - AST visitPropertyRead(PropertyRead ast, _) => - PropertyRead(ast.receiver.visit(this), ast.name); + AST visitPropertyRead(PropertyRead ast, void context) { + return PropertyRead(ast.receiver.visit(this), ast.name); + } @override - AST visitPropertyWrite(PropertyWrite ast, _) => - PropertyWrite(ast.receiver.visit(this), ast.name, ast.value); + AST visitPropertyWrite(PropertyWrite ast, void context) { + return PropertyWrite(ast.receiver.visit(this), ast.name, ast.value); + } @override - AST visitSafePropertyRead(SafePropertyRead ast, _) => - SafePropertyRead(ast.receiver.visit(this), ast.name); + AST visitSafePropertyRead(SafePropertyRead ast, void context) { + return SafePropertyRead(ast.receiver.visit(this), ast.name); + } @override - AST visitMethodCall(MethodCall ast, _) => MethodCall(ast.receiver.visit(this), - ast.name, _visitAll(ast.args), _visitAll(ast.namedArgs)); + AST visitMethodCall(MethodCall ast, void context) { + return MethodCall( + ast.receiver.visit(this), + ast.name, + _visitAll(ast.args), + _visitAll(ast.namedArgs), + ); + } @override - AST visitSafeMethodCall(SafeMethodCall ast, _) => SafeMethodCall( + AST visitSafeMethodCall(SafeMethodCall ast, void context) { + return SafeMethodCall( ast.receiver.visit(this), ast.name, _visitAll(ast.args), - _visitAll(ast.namedArgs)); + _visitAll(ast.namedArgs), + ); + } @override - AST visitFunctionCall(FunctionCall ast, _) => FunctionCall( - ast.target.visit(this), _visitAll(ast.args), _visitAll(ast.namedArgs)); + AST visitFunctionCall(FunctionCall ast, void context) { + return FunctionCall( + ast.target.visit(this), + _visitAll(ast.args), + _visitAll(ast.namedArgs), + ); + } @override - AST visitNamedExpr(NamedExpr ast, _) => ast; + AST visitNamedExpr(NamedExpr ast, void context) { + return ast; + } @override - AST visitBinary(Binary ast, _) => - Binary(ast.operator, ast.left.visit(this), ast.right.visit(this)); + AST visitBinary(Binary ast, void context) { + return Binary(ast.operator, ast.left.visit(this), ast.right.visit(this)); + } @override - AST visitPostfixNotNull(PostfixNotNull ast, _) => - PostfixNotNull(ast.expression.visit(this)); + AST visitPostfixNotNull(PostfixNotNull ast, void context) { + return PostfixNotNull(ast.expression.visit(this)); + } @override - AST visitPrefixNot(PrefixNot ast, _) => PrefixNot(ast.expression.visit(this)); + AST visitPrefixNot(PrefixNot ast, void context) { + return PrefixNot(ast.expression.visit(this)); + } @override - AST visitConditional(Conditional ast, _) => Conditional( + AST visitConditional(Conditional ast, void context) { + return Conditional( ast.condition.visit(this), ast.trueExp.visit(this), - ast.falseExp.visit(this)); + ast.falseExp.visit(this), + ); + } @override - AST visitIfNull(IfNull ast, _) => - IfNull(ast.condition.visit(this), ast.nullExp.visit(this)); + AST visitIfNull(IfNull ast, void context) { + return IfNull(ast.condition.visit(this), ast.nullExp.visit(this)); + } @override - AST visitPipe(BindingPipe ast, _) => - BindingPipe(ast.exp.visit(this), ast.name, _visitAll(ast.args)); + AST visitPipe(BindingPipe ast, void context) { + return BindingPipe(ast.exp.visit(this), ast.name, _visitAll(ast.args)); + } @override - AST visitKeyedRead(KeyedRead ast, _) => - KeyedRead(ast.receiver.visit(this), ast.key.visit(this)); + AST visitKeyedRead(KeyedRead ast, void context) { + return KeyedRead(ast.receiver.visit(this), ast.key.visit(this)); + } @override - AST visitKeyedWrite(KeyedWrite ast, _) => KeyedWrite( - ast.receiver.visit(this), ast.key.visit(this), ast.value.visit(this)); + AST visitKeyedWrite(KeyedWrite ast, void context) { + return KeyedWrite( + ast.receiver.visit(this), + ast.key.visit(this), + ast.value.visit(this), + ); + } @override - AST visitEmptyExpr(EmptyExpr ast, _) => EmptyExpr(); + AST visitEmptyExpr(EmptyExpr ast, void context) { + return EmptyExpr(); + } List _visitAll(List asts) { - var res = []; - for (var i = 0; i < asts.length; ++i) { - final ast = asts[i]; - final result = ast.visit(this); - res.add(result as R); - } - return res; + return [ + for (var i = 0; i < asts.length; ++i) asts[i].visit(this) as R, + ]; } } diff --git a/ngcompiler/lib/v1/src/compiler/expression_parser/lexer.dart b/ngcompiler/lib/v1/src/compiler/expression_parser/lexer.dart new file mode 100644 index 0000000000..47c685e375 --- /dev/null +++ b/ngcompiler/lib/v1/src/compiler/expression_parser/lexer.dart @@ -0,0 +1,547 @@ +import 'package:ngcompiler/v2/src/context/build_error.dart'; + +enum TokenType { + character, + identifier, + keyword, + string, + operator, + number, +} + +final class Lexer { + List tokenize(String text) { + var scanner = _Scanner(text); + var tokens = []; + var token = scanner.scanToken(); + + while (token != null) { + tokens.add(token); + token = scanner.scanToken(); + } + + return tokens; + } +} + +final class Token { + static final Token eof = Token(-1, TokenType.character, 0, ''); + + final int index; + + final TokenType type; + + final num numberValue; + + final String stringValue; + + Token(this.index, this.type, this.numberValue, this.stringValue); + + bool isCharacter(num code) { + return type == TokenType.character && numberValue == code; + } + + bool get isNumber => type == TokenType.number; + + bool get isString => type == TokenType.string; + + bool isOperator(String operator) { + return type == TokenType.operator && stringValue == operator; + } + + bool get isIdentifier => type == TokenType.identifier; + + bool get isKeyword => type == TokenType.keyword; + + bool get isKeywordDeprecatedVar => + type == TokenType.keyword && stringValue == 'var'; + + bool get isKeywordLet => type == TokenType.keyword && stringValue == 'let'; + + bool get isKeywordNull => type == TokenType.keyword && stringValue == 'null'; + + bool get isKeywordUndefined => + type == TokenType.keyword && stringValue == 'undefined'; + + bool get isKeywordTrue => type == TokenType.keyword && stringValue == 'true'; + + bool get isKeywordFalse => + type == TokenType.keyword && stringValue == 'false'; + + /// Returns numeric value or -1 if not a number token type. -1 is parsed as + /// unaryminus(1) so it is ok to use here. + num toNumber() { + return type == TokenType.number ? numberValue : -1; + } + + @override + String toString() { + return switch (type) { + TokenType.character || + TokenType.identifier || + TokenType.keyword || + TokenType.operator || + TokenType.string => + stringValue, + TokenType.number => '$numberValue', + }; + } +} + +Token newCharacterToken(int index, int code) { + return Token(index, TokenType.character, code, String.fromCharCode(code)); +} + +Token newIdentifierToken(int index, String text) { + return Token(index, TokenType.identifier, 0, text); +} + +Token newKeywordToken(int index, String text) { + return Token(index, TokenType.keyword, 0, text); +} + +Token newOperatorToken(int index, String text) { + return Token(index, TokenType.operator, 0, text); +} + +Token newStringToken(int index, String text) { + return Token(index, TokenType.string, 0, text); +} + +Token newNumberToken(int index, num n) { + return Token(index, TokenType.number, n, ''); +} + +const int $EOF = 0; +const int $TAB = 9; +const int $LF = 10; +const int $VTAB = 11; +const int $FF = 12; +const int $CR = 13; +const int $SPACE = 32; +const int $BANG = 33; +const int $DQ = 34; +const int $HASH = 35; +const int $$ = 36; +const int $PERCENT = 37; +const int $AMPERSAND = 38; +const int $SQ = 39; +const int $LPAREN = 40; +const int $RPAREN = 41; +const int $STAR = 42; +const int $PLUS = 43; +const int $COMMA = 44; +const int $MINUS = 45; +const int $PERIOD = 46; +const int $SLASH = 47; +const int $COLON = 58; +const int $SEMICOLON = 59; +const int $LT = 60; +const int $EQ = 61; +const int $GT = 62; +const int $QUESTION = 63; +const int $0 = 48; +const int $9 = 57; +const int $A = 65, $E = 69, $Z = 90; +const int $LBRACKET = 91; +const int $BACKSLASH = 92; +const int $RBRACKET = 93; +const int $CARET = 94; +const int $_ = 95; +const int $BT = 96; +const int $a = 97, + $e = 101, + $f = 102, + $n = 110, + $r = 114, + $t = 116, + $u = 117, + $v = 118, + $z = 122; +const int $LBRACE = 123; +const int $BAR = 124; +const int $RBRACE = 125; +const int $NBSP = 160; + +class ScannerError extends Error { + final String message; + + ScannerError(this.message); + + @override + String toString() => message; +} + +class _Scanner { + String input; + + int length; + + int peek = 0; + + int index = -1; + + _Scanner(this.input) : length = input.length { + advance(); + } + + void advance() { + peek = ++index >= length ? $EOF : input.codeUnitAt(index); + } + + Token? scanToken() { + String input = this.input; + int length = this.length; + int charAfterWhitespace = peek; + int indexAfterWhitespace = index; + + // Skip whitespace. + while (charAfterWhitespace <= $SPACE) { + if (++indexAfterWhitespace >= length) { + charAfterWhitespace = $EOF; + break; + } + + charAfterWhitespace = input.codeUnitAt(indexAfterWhitespace); + } + + peek = charAfterWhitespace; + index = indexAfterWhitespace; + + if (index >= length) { + return null; + } + + // Handle identifiers and numbers. + if (isIdentifierStart(peek)) { + return scanIdentifier(); + } + + if (isDigit(peek)) { + return scanNumber(index); + } + + int start = index; + + switch (peek) { + case $PERIOD: + advance(); + return isDigit(peek) + ? scanNumber(start) + : newCharacterToken(start, $PERIOD); + case $LPAREN: + case $RPAREN: + case $LBRACE: + case $RBRACE: + case $LBRACKET: + case $RBRACKET: + case $COMMA: + case $COLON: + case $SEMICOLON: + return scanCharacter(start, peek); + case $SQ: + case $DQ: + return scanString(); + case $HASH: + case $PLUS: + case $MINUS: + case $STAR: + case $SLASH: + case $PERCENT: + case $CARET: + return scanOperator(start, String.fromCharCode(peek)); + case $QUESTION: + return scanComplexOperator(start, '?', $PERIOD, '.', $QUESTION, '?'); + case $LT: + case $GT: + return scanComplexOperator(start, String.fromCharCode(peek), $EQ, '='); + case $BANG: + case $EQ: + return scanComplexOperator( + start, String.fromCharCode(peek), $EQ, '=', $EQ, '='); + case $AMPERSAND: + return scanComplexOperator(start, '&', $AMPERSAND, '&'); + case $BAR: + return scanComplexOperator(start, '|', $BAR, '|'); + case $NBSP: + while (isWhitespace(peek)) { + advance(); + } + + return scanToken(); + } + + error('Unexpected character [${String.fromCharCode(peek)}]', 0); + } + + Token scanCharacter(int start, int code) { + advance(); + return newCharacterToken(start, code); + } + + Token scanOperator(int start, String str) { + advance(); + return newOperatorToken(start, str); + } + + /// Tokenize a 2/3 char long operator + Token scanComplexOperator( + int start, + String one, + int twoCode, + String two, [ + int? threeCode, + String? three, + ]) { + advance(); + + String str = one; + + if (peek == twoCode) { + advance(); + str += two; + } + + if (threeCode != null && peek == threeCode) { + advance(); + str += three!; + } + + return newOperatorToken(start, str); + } + + Token scanIdentifier() { + int startIndex = index; + advance(); + + while (isIdentifierPart(peek)) { + advance(); + } + + String string = input.substring(startIndex, index); + + if (keywords.contains(string)) { + return newKeywordToken(startIndex, string); + } + + return newIdentifierToken(startIndex, string); + } + + Token scanNumber(int start) { + bool simple = index == start; + advance(); + + while (true) { + if (isDigit(peek)) { + // Intentionally left blank. + } else if (peek == $PERIOD) { + simple = false; + } else if (isExponentStart(peek)) { + advance(); + + if (isExponentSign(peek)) { + advance(); + } + + if (!isDigit(peek)) { + error('Invalid exponent', -1); + } + + simple = false; + } else { + break; + } + + advance(); + } + + String string = input.substring(start, index); + num value = simple ? int.parse(string) : double.parse(string); + return newNumberToken(start, value); + } + + Token scanString() { + int quote = peek; + int start = index; + advance(); // Consume opening quote. + + StringBuffer? buffer; + int marker = index; + + while (true) { + if (peek == quote) { + String value = input.substring(marker, index); + + if (buffer != null) { + // Only use buffer if it was created for an escaped code point. + buffer.write(value); + value = buffer.toString(); + } + + advance(); // Consume closing quote. + return newStringToken(start, value); + } + + if (peek == $BACKSLASH) { + buffer ??= StringBuffer(); + buffer.write(input.substring(marker, index)); + buffer.writeCharCode(_consumeEscape()); + marker = index; + } else if (peek == $EOF) { + error('Unterminated quote', 0); + } else { + advance(); + } + } + } + + Never error(String message, int offset) { + int position = index + offset; + throw BuildError.withoutContext( + 'Lexer Error: $message at column $position in expression [$input]'); + } + + int _consumeEscape() { + advance(); // Consume '\'. + + int escapeStart = index; + + // Check if we're consuming a Unicode code point. + if (peek == $u) { + advance(); // Consume 'u'. + + String hex; + + if (peek == $LBRACE) { + advance(); // Consume '{'. + + int start = index; + + // Consume 1-6 hexadecimal digits. + for (int i = 0; i < 6; ++i) { + if (peek == $EOF) { + error('Incomplete escape sequence', escapeStart - index); + } else if (i != 0 && peek == $RBRACE) { + break; + } + + advance(); + } + + hex = input.substring(start, index); + + if (peek != $RBRACE) { + error("Expected '}'", 0); + } + + advance(); // Consume '}'. + } else { + // Consume exactly 4 hexadecimal digits. + if (index + 4 >= input.length) { + error('Expected four hexadecimal digits', 0); + } + + int start = index; + + for (int i = 4; i > 0; --i) { + advance(); + } + + hex = input.substring(start, index); + } + + int? unescaped = int.tryParse(hex, radix: 16); + + if (unescaped == null || unescaped > 0x10FFFF) { + error('Invalid unicode escape [\\u$hex]', escapeStart - index); + } + + return unescaped; + } + + int unescaped = unescape(peek); + advance(); + return unescaped; + } +} + +bool isWhitespace(num code) { + return code >= $TAB && code <= $SPACE || code == $NBSP; +} + +bool isIdentifierStart(num code) { + return $a <= code && code <= $z || + $A <= code && code <= $Z || + code == $_ || + code == $$; +} + +bool isIdentifier(String input) { + if (input.isEmpty) { + return false; + } + + _Scanner scanner = _Scanner(input); + + if (!isIdentifierStart(scanner.peek)) { + return false; + } + + scanner.advance(); + + while (!identical(scanner.peek, $EOF)) { + if (!isIdentifierPart(scanner.peek)) { + return false; + } + + scanner.advance(); + } + + return true; +} + +bool isIdentifierPart(int code) { + return $a <= code && code <= $z || + $A <= code && code <= $Z || + $0 <= code && code <= $9 || + code == $_ || + code == $$; +} + +bool isDigit(int code) { + return $0 <= code && code <= $9; +} + +bool isExponentStart(int code) { + return code == $e || code == $E; +} + +bool isExponentSign(num code) { + return code == $MINUS || code == $PLUS; +} + +bool isQuote(int code) { + return code == $SQ || code == $DQ || code == $BT; +} + +int unescape(int code) { + return switch (code) { + $n => $LF, + $f => $FF, + $r => $CR, + $t => $TAB, + $v => $VTAB, + _ => code, + }; +} + +const Set keywords = { + 'var', + 'let', + 'null', + 'undefined', + 'true', + 'false', + 'if', + 'else', +}; diff --git a/ngcompiler/lib/v1/src/compiler/expression_parser/parser.dart b/ngcompiler/lib/v1/src/compiler/expression_parser/parser.dart index 25e3887c42..af1a358666 100644 --- a/ngcompiler/lib/v1/src/compiler/expression_parser/parser.dart +++ b/ngcompiler/lib/v1/src/compiler/expression_parser/parser.dart @@ -1,11 +1,12 @@ import 'package:meta/meta.dart'; +import 'package:ngcompiler/v1/src/compiler/compile_metadata.dart'; +import 'package:ngcompiler/v1/src/compiler/expression_parser/analyzer_parser.dart'; +import 'package:ngcompiler/v1/src/compiler/expression_parser/ast.dart'; +import 'package:ngcompiler/v1/src/compiler/expression_parser/lexer.dart'; +import 'package:ngcompiler/v1/src/compiler/js_split_facade.dart'; import 'package:ngcompiler/v2/context.dart'; -import '../compile_metadata.dart'; -import '../js_split_facade.dart'; -import 'analyzer_parser.dart'; -import 'ast.dart' show AST, ASTWithSource; - +final _implicitReceiver = ImplicitReceiver(); final _findInterpolation = RegExp(r'{{([\s\S]*?)}}'); class ParseException extends BuildError { @@ -20,14 +21,102 @@ class ParseException extends BuildError { 'Parser Error: $message $errLocation [$input] in $ctxLocation'; @override - String toString() => _message; + String toString() { + return _message; + } } -abstract class ExpressionParser { - factory ExpressionParser() = AnalyzerExpressionParser; +abstract class ExpressionParserImpl { + AST parseActionImpl( + String input, + String location, + List exports, + ); + + AST parseBindingImpl( + String input, + String location, + List exports, + ); + + AST? parseInterpolationImpl( + String input, + String location, + List exports, + ); + + /// Helper method for implementing [parseInterpolation]. + /// + /// Splits a longer multi-expression interpolation into [SplitInterpolation]. + @protected + SplitInterpolation? splitInterpolation(String input, String location) { + List parts = jsSplit(input, _findInterpolation); + + if (parts.length <= 1) { + return null; + } + + List strings = []; + List expressions = []; + + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + + if (i.isEven) { + // fixed string + strings.add(part); + } else if (part.trim().isNotEmpty) { + expressions.add(part); + } else { + throw ParseException( + 'Blank expressions are not allowed in interpolated strings', + input, + 'at column ${findInterpolationErrorColumn(parts, i)} in', + location, + ); + } + } + + return SplitInterpolation._(strings, expressions); + } + + @protected + void checkNoInterpolation(String input, String location) { + List parts = jsSplit(input, _findInterpolation); + + if (parts.length > 1) { + throw ParseException( + 'Got interpolation ({{}}) where expression was expected', + input, + 'at column ${findInterpolationErrorColumn(parts, 1)} in', + location, + ); + } + } @protected - const ExpressionParser.forInheritence(); + static int findInterpolationErrorColumn( + List parts, + int partInErrIdx, + ) { + String errLocation = ''; + + for (int i = 0; i < partInErrIdx; i++) { + errLocation += i.isEven ? parts[i] : '{{${parts[i]}}}'; + } + + return errLocation.length; + } +} + +class ExpressionParser extends ExpressionParserImpl { + final AnalyzerExpressionParser analyzerParser; + + final Lexer lexer; + + ExpressionParser() + : analyzerParser = AnalyzerExpressionParser(), + lexer = Lexer(); /// Parses an event binding (historically called an "action"). /// @@ -47,7 +136,8 @@ abstract class ExpressionParser { location, ); } - _checkNoInterpolation(input, location); + + checkNoInterpolation(input, location); return ASTWithSource( parseActionImpl(input, location, exports), input, @@ -55,6 +145,23 @@ abstract class ExpressionParser { ); } + /// Override to implement [parseAction]. + /// + /// Basic validation is already performed that [input] is seemingly valid. + @override + AST parseActionImpl( + String input, + String location, + List exports, + ) { + if (input.contains(r'$pipe')) { + return analyzerParser.parseActionImpl(input, location, exports); + } + + List tokens = lexer.tokenize(_stripComments(input)); + return _ParseAST(input, location, tokens, true, exports).parsePipe(); + } + /// Parses an input, property, or attribute binding. /// /// ``` @@ -66,7 +173,7 @@ abstract class ExpressionParser { String location, List exports, ) { - _checkNoInterpolation(input, location); + checkNoInterpolation(input, location); return ASTWithSource( parseBindingImpl(input, location, exports), input, @@ -74,6 +181,23 @@ abstract class ExpressionParser { ); } + /// Override to implement [parseBinding]. + /// + /// Basic validation is already performed that [input] is seemingly valid. + @override + AST parseBindingImpl( + String input, + String location, + List exports, + ) { + if (input.contains(r'$pipe')) { + return analyzerParser.parseBindingImpl(input, location, exports); + } + + List tokens = lexer.tokenize(_stripComments(input)); + return _ParseAST(input, location, tokens, false, exports).parsePipe(); + } + /// Parses a text interpolation. /// /// ``` @@ -87,104 +211,562 @@ abstract class ExpressionParser { String location, List exports, ) { - final result = parseInterpolationImpl(input, location, exports); + AST? result = parseInterpolationImpl(input, location, exports); + if (result == null) { return null; } - return ASTWithSource( - result, - input, - location, - ); - } - - /// Override to implement [parseAction]. - /// - /// Basic validation is already performed that [input] is seemingly valid. - @visibleForOverriding - AST parseActionImpl( - String input, - String location, - List exports, - ); - /// Override to implement [parseBinding]. - /// - /// Basic validation is already performed that [input] is seemingly valid. - @visibleForOverriding - AST parseBindingImpl( - String input, - String location, - List exports, - ); + return ASTWithSource(result, input, location); + } /// Override to implement [parseInterpolation]. /// /// Basic validation is already performed that [input] is seemingly valid. - @visibleForOverriding + @override AST? parseInterpolationImpl( String input, String location, List exports, - ); + ) { + if (input.contains(r'$pipe')) { + return analyzerParser.parseInterpolationImpl(input, location, exports); + } - /// Helper method for implementing [parseInterpolation]. - /// - /// Splits a longer multi-expression interpolation into [SplitInterpolation]. - @protected - SplitInterpolation? splitInterpolation(String input, String location) { - var parts = jsSplit(input, _findInterpolation); - if (parts.length <= 1) { + SplitInterpolation? split = splitInterpolation(input, location); + + if (split == null) { return null; } - var strings = []; - var expressions = []; - for (var i = 0; i < parts.length; i++) { - var part = parts[i]; - if (i.isEven) { - // fixed string - strings.add(part); - } else if (part.trim().isNotEmpty) { - expressions.add(part); - } else { - throw ParseException( - 'Blank expressions are not allowed in interpolated strings', - input, - 'at column ${_findInterpolationErrorColumn(parts, i)} in', - location, - ); - } + + List expressions = []; + + for (int i = 0; i < split.expressions.length; ++i) { + List tokens = lexer.tokenize(_stripComments(split.expressions[i])); + AST ast = _ParseAST(input, location, tokens, false, exports).parsePipe(); + expressions.add(ast); } - return SplitInterpolation._(strings, expressions); + + return Interpolation(split.strings, expressions); } - void _checkNoInterpolation(String input, String location) { - var parts = jsSplit(input, _findInterpolation); - if (parts.length > 1) { - throw ParseException( - 'Got interpolation ({{}}) where expression was expected', - input, - 'at column ${_findInterpolationErrorColumn(parts, 1)} in', - location); - } + static String _stripComments(String input) { + var i = _commentStart(input); + return i != null ? input.substring(0, i).trim() : input; } - static int _findInterpolationErrorColumn( - List parts, - int partInErrIdx, - ) { - var errLocation = ''; - for (var j = 0; j < partInErrIdx; j++) { - errLocation += j.isEven ? parts[j] : '{{${parts[j]}}}'; + static int? _commentStart(String input) { + int? outerQuote; + + for (var i = 0; i < input.length - 1; i++) { + var char = input.codeUnitAt(i); + var nextChar = input.codeUnitAt(i + 1); + + if (identical(char, $SLASH) && nextChar == $SLASH && outerQuote == null) { + return i; + } + + if (identical(outerQuote, char)) { + outerQuote = null; + } else if (outerQuote == null && isQuote(char)) { + outerQuote = char; + } } - return errLocation.length; + + return null; } } /// Splits a longer interpolation expression into [strings] and [expressions]. -class SplitInterpolation { +final class SplitInterpolation { final List strings; + final List expressions; SplitInterpolation._(this.strings, this.expressions); } + +final class _ParseAST { + final String input; + + final String location; + + final List tokens; + + final bool parseAction; + + Map exports; + + Map> prefixes; + + int index = 0; + + bool _parseCall = false; + + _ParseAST( + this.input, + this.location, + this.tokens, + this.parseAction, + List exports, + ) : exports = {}, + prefixes = >{} { + for (var export in exports) { + if (export.prefix == null) { + this.exports[export.name] = export; + } else { + (prefixes[export.prefix!] ??= {})[export.name] = export; + } + } + } + + Token peek(int offset) { + var i = index + offset; + return i < tokens.length ? tokens[i] : Token.eof; + } + + Token get next => peek(0); + + int get inputIndex => index < tokens.length ? next.index : input.length; + + void advance() { + index++; + } + + bool optionalCharacter(int code) { + if (next.isCharacter(code)) { + advance(); + return true; + } + + return false; + } + + bool peekKeywordLet() { + return next.isKeywordLet; + } + + bool peekDeprecatedKeywordVar() { + return next.isKeywordDeprecatedVar; + } + + bool peekDeprecatedOperatorHash() { + return next.isOperator('#'); + } + + void expectCharacter(int code) { + if (optionalCharacter(code)) { + return; + } + + error('Missing expected ${String.fromCharCode(code)}'); + } + + bool optionalOperator(String op) { + if (next.isOperator(op)) { + advance(); + return true; + } + + return false; + } + + void expectOperator(String operator) { + if (optionalOperator(operator)) { + return; + } + + error('Missing expected operator $operator'); + } + + String expectIdentifierOrKeyword() { + var n = next; + + if (!n.isIdentifier && !n.isKeyword) { + error('Unexpected token $n, expected identifier or keyword'); + } + + advance(); + return n.toString(); + } + + String expectIdentifierOrKeywordOrString() { + var n = next; + + if (!n.isIdentifier && !n.isKeyword && !n.isString) { + error('Unexpected token $n, expected identifier, keyword, or string'); + } + + advance(); + return n.toString(); + } + + AST parseArgument() { + return parseExpression(); + } + + AST parsePipe() { + AST result = parseExpression(); + + if (optionalOperator('|')) { + if (parseAction) { + error('Cannot have a pipe in an action expression'); + } + + do { + String name = expectIdentifierOrKeyword(); + List args = []; + bool prevParseCall = _parseCall; + _parseCall = false; + + while (optionalCharacter($COLON)) { + args.add(parseExpression()); + } + + _parseCall = prevParseCall; + result = BindingPipe(result, name, args); + } while (optionalOperator('|')); + } + + return result; + } + + AST parseExpression() { + return parseConditional(); + } + + AST parseConditional() { + int start = inputIndex; + AST result = parseLogicalOr(); + + if (optionalOperator('??')) { + AST nullExp = parsePipe(); + return IfNull(result, nullExp); + } + + if (optionalOperator('?')) { + bool prevParseCall = _parseCall; + _parseCall = false; + + AST yes = parsePipe(); + + if (!optionalCharacter($COLON)) { + int end = inputIndex; + String expression = input.substring(start, end); + error('Conditional expression $expression requires all 3 expressions'); + } + + AST no = parsePipe(); + _parseCall = prevParseCall; + return Conditional(result, yes, no); + } + + return result; + } + + AST parseLogicalOr() { + // '||' + AST result = parseLogicalAnd(); + + while (optionalOperator('||')) { + result = Binary('||', result, parseLogicalAnd()); + } + + return result; + } + + AST parseLogicalAnd() { + // '&&' + AST result = parseEquality(); + + while (optionalOperator('&&')) { + result = Binary('&&', result, parseEquality()); + } + + return result; + } + + AST parseEquality() { + // '==','!=','===','!==' + AST result = parseRelational(); + + while (true) { + if (optionalOperator('==')) { + result = Binary('==', result, parseRelational()); + } else if (optionalOperator('===')) { + result = Binary('===', result, parseRelational()); + } else if (optionalOperator('!=')) { + result = Binary('!=', result, parseRelational()); + } else if (optionalOperator('!==')) { + result = Binary('!==', result, parseRelational()); + } else { + return result; + } + } + } + + AST parseRelational() { + // '<', '>', '<=', '>=' + AST result = parseAdditive(); + + while (true) { + if (optionalOperator('<')) { + result = Binary('<', result, parseAdditive()); + } else if (optionalOperator('>')) { + result = Binary('>', result, parseAdditive()); + } else if (optionalOperator('<=')) { + result = Binary('<=', result, parseAdditive()); + } else if (optionalOperator('>=')) { + result = Binary('>=', result, parseAdditive()); + } else { + return result; + } + } + } + + AST parseAdditive() { + // '+', '-' + AST result = parseMultiplicative(); + + while (true) { + if (optionalOperator('+')) { + result = Binary('+', result, parseMultiplicative()); + } else if (optionalOperator('-')) { + result = Binary('-', result, parseMultiplicative()); + } else { + return result; + } + } + } + + AST parseMultiplicative() { + // '*', '%', '/' + AST result = parsePrefix(); + + while (true) { + if (optionalOperator('*')) { + result = Binary('*', result, parsePrefix()); + } else if (optionalOperator('%')) { + result = Binary('%', result, parsePrefix()); + } else if (optionalOperator('/')) { + result = Binary('/', result, parsePrefix()); + } else { + return result; + } + } + } + + AST parsePrefix() { + if (optionalOperator('+')) { + return parsePrefix(); + } + + if (optionalOperator('-')) { + return Binary('-', LiteralPrimitive(0), parsePrefix()); + } + + if (optionalOperator('!')) { + return PrefixNot(parsePrefix()); + } + + return parseCallChain(); + } + + AST parseCallChain() { + AST result = parsePrimary(); + + while (true) { + if (optionalCharacter($PERIOD)) { + result = parseAccessMemberOrMethodCall(result, false); + } else if (optionalOperator('?.')) { + result = parseAccessMemberOrMethodCall(result, true); + } else if (optionalCharacter($LBRACKET)) { + AST key = parsePipe(); + expectCharacter($RBRACKET); + + if (optionalOperator('=')) { + AST value = parseConditional(); + result = KeyedWrite(result, key, value); + } else { + result = KeyedRead(result, key); + } + } else if (_parseCall && optionalCharacter($COLON)) { + _parseCall = false; + + AST expression = parseExpression(); + _parseCall = true; + + if (result is PropertyRead) { + result = NamedExpr(result.name, expression); + } else if (result is StaticRead && result.id.prefix == null) { + result = NamedExpr(result.id.name, expression); + } else { + error('Expected previous token to be an identifier'); + } + } else if (optionalCharacter($LPAREN)) { + _CallArguments arguments = parseCallArguments(); + expectCharacter($RPAREN); + result = FunctionCall(result, arguments.positional, arguments.named); + } else { + return result; + } + } + } + + AST parsePrimary() { + if (optionalCharacter($LPAREN)) { + AST result = parsePipe(); + expectCharacter($RPAREN); + return result; + } + + if (next.isKeywordNull || next.isKeywordUndefined) { + advance(); + return LiteralPrimitive(null); + } + + if (next.isKeywordTrue) { + advance(); + return LiteralPrimitive(true); + } + + if (next.isKeywordFalse) { + advance(); + return LiteralPrimitive(false); + } + + if (next.isIdentifier) { + AST receiver = _implicitReceiver; + String identifier = next.stringValue; + + if (exports.containsKey(identifier)) { + advance(); + return StaticRead(exports[identifier]!); + } + + if (prefixes.containsKey(identifier)) { + if (peek(1).isCharacter($PERIOD)) { + Token nextId = peek(2); + + if (nextId.isIdentifier && + prefixes[identifier]!.containsKey(nextId.stringValue)) { + // consume the prefix, the '.', and the next identifier + advance(); + advance(); + advance(); + return StaticRead(prefixes[identifier]![nextId.stringValue]!); + } + } + } + + return parseAccessMemberOrMethodCall(receiver, false); + } + + if (next.isNumber) { + num value = next.toNumber(); + advance(); + return LiteralPrimitive(value); + } + + if (next.isString) { + String literalValue = next.toString(); + advance(); + return LiteralPrimitive(literalValue); + } + + if (index >= tokens.length) { + error('Unexpected end of expression: $input'); + } else { + error('Unexpected token $next'); + } + } + + AST parseAccessMemberOrMethodCall(AST receiver, [bool isSafe = false]) { + String id = expectIdentifierOrKeyword(); + + if (optionalCharacter($LPAREN)) { + _CallArguments args = parseCallArguments(); + expectCharacter($RPAREN); + return isSafe + ? SafeMethodCall(receiver, id, args.positional, args.named) + : MethodCall(receiver, id, args.positional, args.named); + } + + if (isSafe) { + if (optionalOperator('=')) { + error("The '?.' operator cannot be used in the assignment"); + } + + return SafePropertyRead(receiver, id); + } + + if (optionalOperator('=')) { + if (!parseAction) { + error('Bindings cannot contain assignments'); + } + + AST value = parseConditional(); + return PropertyWrite(receiver, id, value); + } + + return PropertyRead(receiver, id); + } + + _CallArguments parseCallArguments() { + List positional = []; + List named = []; + + if (next.isCharacter($RPAREN)) { + return _CallArguments(positional, named); + } + + do { + _parseCall = true; + + AST ast = parsePipe(); + + if (ast is NamedExpr) { + named.add(ast); + } else { + positional.add(ast); + } + } while (optionalCharacter($COMMA)); + + _parseCall = false; + return _CallArguments(positional, named); + } + + /// An identifier, a keyword, a string with an optional `-` inbetween. + String expectTemplateBindingKey() { + String result = ''; + bool operatorFound = false; + + do { + result += expectIdentifierOrKeywordOrString(); + operatorFound = optionalOperator('-'); + + if (operatorFound) { + result += '-'; + } + } while (operatorFound); + + return result.toString(); + } + + Never error(String message, [int? index]) { + index ??= this.index; + + String location = (index < tokens.length) + ? 'at column ${tokens[index].index + 1} in' + : 'at the end of the expression'; + throw ParseException(message, input, location, this.location); + } +} + +final class _CallArguments { + final List positional; + + final List named; + + _CallArguments(this.positional, this.named); +} diff --git a/ngcompiler/lib/v2/src/context/build_error.dart b/ngcompiler/lib/v2/src/context/build_error.dart index acc4f388bc..95e3c906c9 100644 --- a/ngcompiler/lib/v2/src/context/build_error.dart +++ b/ngcompiler/lib/v2/src/context/build_error.dart @@ -35,8 +35,9 @@ abstract class BuildError extends Error { factory BuildError.fromMultiple( Iterable errors, [ String header = 'Multiple errors occurred', - ]) => - _MultipleBuildError(errors.toList(), header); + ]) { + return _MultipleBuildError(errors.toList(), header); + } /// Create a build error using the provided source span as [context]. factory BuildError.forSourceSpan( @@ -86,8 +87,8 @@ abstract class BuildError extends Error { /// /// Where possible, it is greatly preferable to use another [BuildError] /// constructor or sub-type that provides context on why the error occurred; - /// using [withoutContext] can be suitable when either the context is - /// explicitly provided as part of the [message]. + /// using [BuildError.withoutContext] can be suitable when either the context + /// is explicitly provided as part of the [message]. factory BuildError.withoutContext(String message) = _SimpleBuildError; } @@ -97,7 +98,9 @@ class _SimpleBuildError extends BuildError { _SimpleBuildError(this._message); @override - String toString() => _message; + String toString() { + return _message; + } } class _MultipleBuildError extends BuildError { @@ -111,7 +114,9 @@ class _MultipleBuildError extends BuildError { assert(_header != null && !_header.endsWith(':')); @override - String toString() => '$_header:\n${_errors.join('\n')}'; + String toString() { + return '$_header:\n${_errors.join('\n')}'; + } } class _SourceSpanBuildError extends BuildError { @@ -124,5 +129,7 @@ class _SourceSpanBuildError extends BuildError { ); @override - String toString() => _sourceSpan.message(_message); + String toString() { + return _sourceSpan.message(_message); + } }