diff --git a/grammar-declarations.js b/grammar-declarations.js index e4cdd23..d4dea39 100644 --- a/grammar-declarations.js +++ b/grammar-declarations.js @@ -61,6 +61,39 @@ module.exports = { field('body', $.block), ), + // Haxe's "class-like" anonymous structure syntax + // (`typedef Point = { var x:Int; function bar():Void; }`) needs a body + // that can hold var/function member declarations, so this used to just + // reuse $.block directly. That collided with $.structure_type's own + // comma-separated-pairs syntax (`typedef X = { x:Int, y:Int }`) for a + // single-field, no-trailing-comma struct (`typedef X = { f:T }`): a bare + // 'identifier : expression' is ALSO a valid one-statement $.block (via + // $.pair's low-precedence bare-literal alternative, see its comment in + // grammar-literals.js), so $.block and $.structure_type both matched + // that exact token sequence. This is the same class of GLR table- + // construction merge as the documented block-vs-object empty-body + // ambiguity above ($.block's own comment) -- tree-sitter's conflict + // analysis calls [$.typedef_declaration, $.structure_type] an + // "unnecessary conflict" (no static ambiguity to resolve) yet the + // single-item-no-comma case still hard-errors at runtime, and neither + // declaring the conflict explicitly, nor bumping either rule's static + // OR dynamic precedence, changes that -- it is not a real tie to break, + // the same not-fixable-via-prec class as the earlier case. Two or more + // fields, or a trailing comma, dodge it because a ',' forces an early + // commit into the comma-list production, ruling out the single-bare- + // statement reading before the ambiguous state is ever reached. + // + // The actual fix: this body never legitimately needs $.block's full + // generality (arbitrary statements) -- real Haxe only ever puts + // var/function member declarations in it, both of which start with a + // keyword ('var'/'final'/'function', or metadata's '@'), never a bare + // identifier, so a dedicated body rule restricted to those two + // declaration kinds can never overlap with $.structure_type's bare + // 'identifier : type' shape in the first place. Aliased to $.block so + // existing consumers/tests see the same node type as before. + _structure_class_body: ($) => + seq('{', repeat(choice($.variable_declaration, $.function_declaration)), $._closing_brace), + typedef_declaration: ($) => seq( repeat($.metadata), @@ -68,7 +101,15 @@ module.exports = { 'typedef', field('name', $._lhs_expression), optional($.type_params), - seq('=', choice($.block, $.structure_type, $._lhs_expression, $.type)), + seq( + '=', + choice( + $.structure_type, + alias($._structure_class_body, $.block), + $._lhs_expression, + $.type, + ), + ), $._lookback_semicolon, ), @@ -116,7 +157,7 @@ module.exports = { field('name', choice($._lhs_expression, 'new')), optional($.type_params), $._function_arg_list, - optional(seq(':', field('return_type', $.type))), + optional(seq(':', field('return_type', choice($.type, $._conditional_type)))), optional(field('body', $.block)), $._lookback_semicolon, ), @@ -135,7 +176,9 @@ module.exports = { optional('?'), field('name', $._lhs_expression), optional('?'), - optional(seq(':', alias(choice($._lhs_expression, $.type, $.structure_type), $.type))), + optional( + seq(':', alias(choice($._lhs_expression, $.type, $.structure_type, $._conditional_type), $.type)), + ), optional(seq($._assignmentOperator, $._literal)), ), ), @@ -147,7 +190,14 @@ module.exports = { choice('var', 'final'), field('name', $._lhs_expression), optional($.access_identifiers), - optional(seq(':', optional(repeat('(')), field('type', $.type), optional(repeat(')')))), + optional( + seq( + ':', + optional(repeat('(')), + field('type', choice($.type, $._conditional_type)), + optional(repeat(')')), + ), + ), optional(seq(($._assignmentOperator, $.operator), $.expression)), $._lookback_semicolon, ), diff --git a/grammar-literals.js b/grammar-literals.js index 90c4e2c..2b0c7ac 100644 --- a/grammar-literals.js +++ b/grammar-literals.js @@ -8,8 +8,23 @@ module.exports = { // Match any [42, 0xFF43] integer: ($) => choice(/[\d_]+/, /0x[a-fA-F\d_]+/), - // Match any [0.32, 3., 2.1e5] - float: ($) => choice(/[\d_]+[\.]+[\d_]*/, /[\d_]+[\.]+[\d_]*e[\d_]*/), + // Match any [0.32, 2.1e5]. Requires at least one digit after the '.' + // (`[\d_]+` not `[\d_]*`) -- deliberately drops support for a *trailing*- + // dot float with no digits after it (`3.`, which the original comment + // here listed as supported) to fix a much more damaging, silent bug: + // `0...10` (an integer immediately followed by the range operator -- the + // single most common shape for a `for` loop's iterable, e.g. + // `for (i in 0...10)`) was being lexed as ONE token, "0.", stopping right + // after the first '.' and silently swallowing the rest of the range + // operator with no ERROR node (a float is a perfectly valid, complete + // token on its own). Tree-sitter's regex engine has no lookahead, so + // there's no way to keep `3.` valid while still telling `0.` apart from + // `0...`; picked the fix that helps far more real code than it costs + // (the `for (i in 0...N)` shape is extremely common; + // occurrence of a genuinely bare trailing-dot float is comparatively + // rare and easy to write as `3.0` instead if it ever turns up + // broken). + float: ($) => choice(/[\d_]+\.[\d_]+/, /[\d_]+\.[\d_]+e[\d_]*/), // Match either [true, false] bool: ($) => choice('true', 'false'), // Match any ["XXX", 'XXX'] @@ -24,10 +39,16 @@ module.exports = { null: ($) => 'null', // https://haxe.org/manual/expression-array-declaration.html + // Array comprehension (https://haxe.org/manual/lf-array-comprehension.html, + // `[for (i in 0...10) i]` / `[while (...) ...]`) is its own alternative, + // not folded into the plain-elements one -- it starts with a literal + // 'for'/'while' token, so there's no overlap with a normal + // comma-separated array literal (including the single-element case, + // `[x]`) to disambiguate. array: ($) => choice( seq('[', commaSep(prec.left($.expression)), ']'), - seq('[', $.expression, $.identifier, ']'), //array comprehension + seq('[', choice($.comprehension_for, $.comprehension_while), ']'), ), // https://haxe.org/manual/expression-map-declaration.html @@ -36,7 +57,15 @@ module.exports = { // https://haxe.org/manual/expression-object-declaration.html object: ($) => prec(1, seq('{', commaSep($.pair), $._closing_brace)), - structure_type: ($) => prec(1, seq('{', commaSep(alias($.structure_type_pair, $.pair)), $._closing_brace)), + // prec.dynamic, NOT a bump to the static prec(): it must only tie-break + // a genuinely completed GLR ambiguity in favor of the structure reading + // -- e.g. `(name: {})`, ambiguous against a parenthesized bare-pair + // expression whose value is an empty $.object, since $.structure_type + // is a bare $.type alternative (grammar.js). A static bump changes the + // shift/reduce table globally and breaks multi-arrow $.function_type + // chains (`String->{}->Void` stops after the `{}` segment). + structure_type: ($) => + prec.dynamic(1, prec(1, seq('{', commaSep(alias($.structure_type_pair, $.pair)), $._closing_brace))), structure_type_pair: ($) => prec.left(seq(choice($.identifier), ':', $.type)), // Sub part of map and object literals. diff --git a/grammar.js b/grammar.js index df6b42b..4762e9e 100644 --- a/grammar.js +++ b/grammar.js @@ -30,6 +30,7 @@ const haxe_grammar = { [$._prefixUnaryOperator, $._postfixUnaryOperator], [$.enum_abstract_declaration, $.enum_declaration], [$.typedef_declaration, $.structure_type], + [$.object, $.structure_type], [$.member_expression, $._lhs_expression], [$.preprocessor_statement], [$._rhs_expression, $._lhs_expression], @@ -39,6 +40,7 @@ const haxe_grammar = { [$._unaryExpression, $._ternary_condition, $.pair], [$._chain_term, $._ternary_condition], [$._rhs_expression, $.member_expression], + [$.conditional_statement], ], rules: { module: ($) => seq(repeat($.statement)), @@ -57,9 +59,12 @@ const haxe_grammar = { $.switch_expression, seq($.expression, $._lookback_semicolon), $.conditional_statement, + $.while_statement, + $.do_while_statement, + $.for_statement, + $.try_statement, $.throw_statement, $.block, - $.keyword, $.reserved_keyword, ), ), @@ -82,17 +87,31 @@ const haxe_grammar = { // (html5, flash, cpp, ...) and every variant needs to stay visible to // tooling built on this grammar, not just whichever one a particular // build's defines would keep. + // Each branch's body may START with one $.dangling_else_statement -- + // an else/else-if clause continuing an if-statement from BEFORE the + // #if (`if (cond) {...} #if X else {...} #end`); see that rule's own + // comment for why it must be scoped to exactly this first-element + // position rather than being a general statement alternative. preprocessor_statement: ($) => prec.right( seq( '#', token.immediate('if'), field('condition', $.expression), + optional($.dangling_else_statement), repeat($.statement), repeat( - seq('#', token.immediate('elseif'), field('condition', $.expression), repeat($.statement)), + seq( + '#', + token.immediate('elseif'), + field('condition', $.expression), + optional($.dangling_else_statement), + repeat($.statement), + ), + ), + optional( + seq('#', token.immediate('else'), optional($.dangling_else_statement), repeat($.statement)), ), - optional(seq('#', token.immediate('else'), repeat($.statement))), '#', token.immediate('end'), ), @@ -173,8 +192,13 @@ const haxe_grammar = { case_statement: ($) => prec.right( choice( - seq('case', choice($._rhs_expression, alias('_', $._rhs_expression)), ':', $.statement), - seq('default', ':', $.statement), + seq( + 'case', + choice($._rhs_expression, alias('_', $._rhs_expression)), + ':', + repeat($.statement), + ), + seq('default', ':', repeat($.statement)), ), ), @@ -205,20 +229,56 @@ const haxe_grammar = { 'function', optional(field('name', $._lhs_expression)), $._function_arg_list, - optional(seq(':', field('return_type', $.type))), + optional(seq(':', field('return_type', choice($.type, $._conditional_type)))), field('body', $.block), ), _parenthesized_expression: ($) => seq('(', repeat1(prec.left($.expression)), ')'), - range_expression: ($) => - prec( - 1, - seq($.identifier, 'in', choice(seq($.integer, $._rangeOperator, $.integer), $.identifier)), + // Previously baked `identifier 'in' ...` directly into this rule -- + // a workaround from when `for` itself was an unimplemented placeholder + // keyword, using range_expression to soak up a for-loop's `(i in + // 0...10)` content via accidental juxtaposition. Now that $.for_statement + // provides its own real binding + 'in' + iterable structure, a dedicated + // range_expression node turned out to be unnecessary: `$._rangeOperator` + // is already one of `$.operator`'s choices, so the plain chain + // alternative in `expression` below (`_rhs_expression (operator + // _chain_term)*`) already parses `0...10`, `arr.length - 1 ... + // arr.length + 5`, etc. correctly on its own, with no ERROR and no + // separate rule needed. A dedicated range_expression rule (narrowing + // the operand operator set to avoid swallowing its own '...' + // separator) is therefore unreachable dead code -- the plain chain + // alternative always wins over it -- so it's removed rather than kept + // around unused. + + // A chain term that may optionally carry a leading prefix-unary operator + // (`!y`, `-y`, etc.), used only in a chain's TAIL positions (never as a + // chain's head -- using this in head position reintroduces an extra + // reduce step that collides with the head's own shift/reduce decision + // and silently breaks even plain chains like `1 + 2`). Restricted to + // tail positions, it lets `a && !b`, `!a && !b`, etc. parse -- not just + // `!a && b`, which the leading-unary `expression` alternative below + // already covers. + // $.subscript_expression is included alongside $._rhs_expression here + // so it can appear as a chain's TAIL term too (`null != arr[id]`) -- + // not just as a chain's HEAD, which the dedicated + // `seq($.subscript_expression, repeat1(...))` alternative in + // `expression` below already covers. + // + // $._parenthesized_expression is included for the same reason: a + // parenthesized sub-expression could previously only appear as an + // ENTIRE standalone expression (the top-level $._parenthesized_expression + // choice in `expression` below), never as one term of a longer chain -- + // so `0...(10)`, `a + (b)`, `total = (a + b) / 2`, etc. all hard-errored. + // This fixes the TAIL position; see the dedicated + // `seq($._parenthesized_expression, repeat1(...))` alternative in + // `expression` below for the analogous HEAD-position fix. + _chain_term: ($) => + seq( + optional(alias($._prefixUnaryOperator, $.operator)), + choice($._rhs_expression, $.subscript_expression, $._parenthesized_expression), ), - _chain_term: ($) => seq(optional(alias($._prefixUnaryOperator, $.operator)), $._rhs_expression), - _ternary_condition: ($) => prec( 2, @@ -227,7 +287,18 @@ const haxe_grammar = { $.subscript_expression, $.cast_expression, $._parenthesized_expression, - seq($._rhs_expression, repeat(seq($.operator, $._rhs_expression))), + // Tail terms use $._chain_term (not $._rhs_expression) so a + // parenthesized tail term works here too -- `(x >= 0) && (x < 10) + // ? a : b` previously hard-errored even though the unparenthesized + // `x >= 0 && x < 10 ? a : b` worked fine, since $._rhs_expression + // still excludes $._parenthesized_expression. + seq($._rhs_expression, repeat(seq($.operator, $._chain_term))), + // A parenthesized HEAD term followed by more chain -- `(x >= 0) && + // y ? a : b`. Same head/tail split as $._parenthesized_expression's + // two fixes in $._chain_term / `expression` above; repeat1-gated so + // a solo `(x >= 0)` alone still resolves via the standalone + // $._parenthesized_expression choice above, not this one. + seq($._parenthesized_expression, repeat1(seq($.operator, $._chain_term))), ), ), @@ -251,7 +322,6 @@ const haxe_grammar = { $.runtime_type_check_expression, $.cast_expression, $.type_trace_expression, - $.range_expression, $._parenthesized_expression, $.switch_expression, $.function_expression, @@ -264,11 +334,32 @@ const haxe_grammar = { repeat1(seq($.operator, $._chain_term)), ), seq($.subscript_expression, repeat1(seq($.operator, $._chain_term))), + // $._parenthesized_expression as a chain HEAD -- `(a + b) * c`, + // `(a + b) / 2`, etc. Same gap as $.subscript_expression above: a + // parenthesized sub-expression could only ever be an entire + // standalone `expression` (the top-level $._parenthesized_expression + // choice), never the first term of a longer chain. repeat1-gated so + // a solo `(a + b)` alone still goes through the plain + // $._parenthesized_expression choice, not this one. + seq($._parenthesized_expression, repeat1(seq($.operator, $._chain_term))), + // A postfix-unary term (`i--`, `i++`) as a chain HEAD -- `i-- > 0`, + // common in `while (i-- > 0)` loops. The leading-unary alternative + // above only covers a PREFIX unary head (`!x && y`); postfix was + // still only reachable as the entire standalone $._unaryExpression, + // never chained with a further operator. Same repeat1 gating and + // same rationale as the leading-unary alternative. seq( $._rhs_expression, alias($._postfixUnaryOperator, $.operator), repeat1(seq($.operator, $._chain_term)), ), + // $.ternary_expression and $._parenthesized_expression are ALSO + // bare choices here for the same reason as $.subscript_expression: + // `return a ? b : c;`, `return (a ? b : c);`, and `return (a + b);` + // all hard-error, since neither is reachable via $._rhs_expression + // (which deliberately excludes both to avoid ternary-condition + // ambiguity elsewhere -- see $._ternary_condition's own comment) nor + // via the chain alternatives above. seq( 'return', optional( @@ -280,6 +371,8 @@ const haxe_grammar = { repeat(seq($.operator, $._chain_term)), ), $.subscript_expression, + $.ternary_expression, + $._parenthesized_expression, ), ), ), @@ -288,6 +381,8 @@ const haxe_grammar = { choice( seq($._rhs_expression, repeat(seq($.operator, $._chain_term))), $.subscript_expression, + $.ternary_expression, + $._parenthesized_expression, seq( alias($._prefixUnaryOperator, $.operator), $._rhs_expression, @@ -336,6 +431,24 @@ const haxe_grammar = { _function_type_args: ($) => commaSep1(seq(optional(seq($.identifier, ':')), $.type)), + // Known limitation: a $.structure_type-typed segment that isn't the + // LAST segment of a $.function_type chain fails to extend into a + // further right-nested function_type -- `String->{}->Void` used as a + // variable/field type reduces the struct segment straight up to a + // complete $.type and drops everything after its `->`, even though the + // exact same shape works fine as a $.function_arg's type annotation + // (`cb:String->{}->Void`) and a plain identifier-typed chain of the + // same length/position (`String->Int->Void`) also extends correctly. + // Tried and had no effect: bumping $.function_type's own prec.right to + // an explicit numeric level, mirroring $.function_arg's + // alias(choice(...), $.type) pattern for the + // variable_declaration/function_declaration type slots instead of a + // bare $.type reference, prec.dynamic on $.structure_type alone. No + // conflict is even reported for this case (unlike the two conflicts + // resolved above), meaning table construction never considers the + // extending derivation at all -- the same not-fixable-via-grammar.js- + // alone class as the documented block/object case, just a third + // instance of it. function_type: ($) => prec.right( choice( @@ -345,6 +458,15 @@ const haxe_grammar = { ), ), + // $.structure_type is a bare choice here (not just added at individual + // call sites like $.function_arg's own hand-rolled + // `alias(choice(..., $.type, $.structure_type), $.type)` workaround) + // so every $.type slot -- including both sides of $.function_type's + // '->' chain -- gets anonymous-struct support for free. Without it, + // `cb:String->{}->Void` hard-errors: $.function_type's two $.type + // slots have no way to become `{}` at all, so the parser falls back to + // matching it as a bare $.object literal wrapped in an ERROR node + // instead. type: ($) => prec.right( choice( @@ -356,10 +478,83 @@ const haxe_grammar = { optional($.type_params), ), $.function_type, + $.structure_type, seq('(', alias($.type, 'type'), ')'), ), ), + // A #if/#else/#end-guarded type, for conditionally compiling a type + // annotation depending on target (`scaleU:#if flash Null #else + // Float #end = 1.0`). Not a bare $.type choice itself -- wired in + // individually at each of the 3 real call sites + // (variable_declaration's and function_arg's type field, + // function_declaration/function_expression's return_type field) + // rather than folded into $.type generally, since $.type is also used + // in positions (cast(), extends/implements, type_params, ...) with no + // legitimate use for a conditional there -- narrower surface, less + // GLR-conflict risk than a blanket injection. + // + // Each branch also accepts its own optional default/initializer value + // (`optional(seq($._assignmentOperator, $._literal))` per branch), + // covering the case where the default differs per branch instead of + // being shared after #end (`wantFlush:#if flash Null = false + // #else Bool = true #end`), including the asymmetric case where only + // one branch has a default at all (`artSwf1:#if USESWFLOADER + // SwfLoader #else String = "JustWords.swf" #end`). The shared-after- + // #end shape (`#if flash Null #else Float #end = 1.0`) still + // works too: it's just this rule's per-branch default staying empty + // both times, with the real function_arg/variable_declaration call + // site's own separate, pre-existing trailing default consuming the + // `= 1.0` as before. Harmless no-op at the return_type call site, + // which has no default/initializer concept to begin with -- real + // Haxe code never puts `= value` after a return type, so the + // optional default there simply never matches. + // + // Still NOT handled, remaining a known limitation: a statement whose + // trailing ';' is duplicated inside each branch instead of following + // the whole conditional. + _conditional_type: ($) => + prec.right( + seq( + '#', + token.immediate('if'), + field('condition', $.expression), + $.type, + optional(seq($._assignmentOperator, $._literal)), + optional( + seq( + '#', + token.immediate('else'), + $.type, + optional(seq($._assignmentOperator, $._literal)), + ), + ), + '#', + token.immediate('end'), + ), + ), + + // Known limitation: a genuinely EMPTY `{}` used as a control-flow body + // (`if (cond) {}`, `while (cond) {}`, etc.) resolves to an empty + // $.object (an object-literal expression statement), not $.block. + // $.block is not even reachable from $.expression's own choice list, + // so this is really "empty $.object (reached via $.statement's + // `seq($.expression, ';')` alternative) vs. $.block (a sibling + // alternative of that same $.statement choice) for identical input" -- + // and this isn't fixable via any of the usual levers: declaring + // `[$.block, $.object]` in `conflicts` gets flagged as unnecessary + // (tree-sitter's own analysis says this isn't a real, GLR-forkable + // ambiguity), `prec`/`prec.dynamic` on either rule (even prec(1000)) + // has zero effect, an explicit `choice($.block, $.statement)` at the + // body field doesn't change it, and neither does reordering which rule + // is declared first in this file. This suggests tree-sitter's table + // construction is merging the two empty-content states before + // precedence would ever be consulted, which isn't fixable by anything + // expressible in grammar.js alone. + // Non-empty bodies (`if (cond) { a(); }`) are entirely unaffected -- + // $.object's content is $.pair-shaped, which can never be confused + // with $.block's $.statement-shaped content once there's real content + // to look at. block: ($) => seq('{', repeat($.statement), $._closing_brace), metadata: ($) => @@ -372,35 +567,169 @@ const haxe_grammar = { // arg list is () with any amount of expressions followed by commas _arg_list: ($) => seq('(', commaSep($.expression), ')'), - // 'else if' was previously a single literal token with no condition of - // its own -- `if (a) {} else if (c) {}` lost both `c` and the entire - // second block, since matching that literal token left nothing to - // consume `(c)` or the block after it, corrupting parse recovery for - // the rest of the statement. Extremely common real-world shape (1,352 - // files in this depot use `else if`); pre-existing gap, unrelated to - // the fixes above. Now: 'else' 'if' as two separate tokens, each - // else-if branch gets its own condition/block via repeat(...), reusing - // the same 'arguments_list' field name across branches -- same - // convention as e.g. class_declaration's repeated 'implements' clauses. + // Bodies are $.statement, not $.block -- Haxe's `if (cond) expr;` (no + // braces) is standard, idiomatic syntax, and was completely broken + // here (forcing braces on every branch). + // $.statement already covers both shapes (`{ ... }` via its own + // $.block alternative, or a bare `expr;` via its + // `seq($.expression, $._lookback_semicolon)` alternative), so reusing + // it gets braceless bodies "for free" without a separate rule. Also + // covers the earlier 'else if' fix (each branch gets its own + // condition/body via repeat(...), reusing the same 'arguments_list' + // field name across branches) as a strict subset -- this version adds + // braceless bodies on top of that. conditional_statement: ($) => prec.right( 1, seq( field('name', 'if'), field('arguments_list', $._arg_list), - optional($.block), - // $.block here must NOT be optional() (unlike the primary if's - // block above, matching the original design): with it optional, - // the parser could -- and silently did, with no ERROR node -- - // end this repeat iteration early and let a real, present block - // become a separate top-level statement instead, splitting an - // `else if (c) {d();} else {e();}` tail into a bogus bare - // identifier "else" plus two unrelated floating blocks. - repeat(seq('else', 'if', field('arguments_list', $._arg_list), $.block)), - optional(seq('else', $.block)), + field('body', $.statement), + repeat( + prec.dynamic( + 1, + seq('else', 'if', field('arguments_list', $._arg_list), field('body', $.statement)), + ), + ), + optional(prec.dynamic(1, seq('else', field('body', $.statement)))), + ), + ), + + // A dangling else-clause -- an `else` whose `if` is not syntactically + // adjacent because a `#if`/`#end` boundary separates them (`if (cond) + // {...} #if X else {...} #end`): whether the else exists at all, or + // is a plain `else` vs an `else if`, can differ per compile target. + // Only reachable as the optional FIRST element of a + // $.preprocessor_statement branch, never as a general statement. + // Structurally the clause's logical parent is the if-statement + // preceding the #if, not the #if it sits inside -- a "wrong tree, + // not a broken one" tradeoff (no ERROR nodes, every guarded + // statement stays visible to tooling). + // + // Three constraints here are load-bearing -- change with care: + // 1. Must stay scoped to this position: as a general $.statement + // alternative, 'else' enters FOLLOW(conditional_statement) and + // the table deterministically steals every normal if's else into + // a sibling of this node; no prec/conflicts knob can flip a + // decision that never forks. + // 2. Body must be $.block, and + // 3. the rule must end with $._lookback_semicolon: the external + // scanner's brace-lookback termination handshake (scanner.c's + // just_saw_brace) otherwise breaks the statement immediately + // following the clause. The body's own '}' satisfies the + // lookback; no literal ';' is required in the source. + dangling_else_statement: ($) => + seq( + 'else', + optional(seq('if', field('arguments_list', $._arg_list))), + field('body', $.block), + $._lookback_semicolon, + ), + + // https://haxe.org/manual/expression-while.html + while_statement: ($) => + prec.right( + 1, + seq('while', '(', field('condition', $.expression), ')', field('body', $.statement)), + ), + + // https://haxe.org/manual/expression-do-while.html + do_while_statement: ($) => + prec.right( + 1, + seq( + 'do', + field('body', $.statement), + 'while', + '(', + field('condition', $.expression), + ')', + $._lookback_semicolon, ), ), + // https://haxe.org/manual/expression-for.html -- `binding` is either a + // plain identifier (`for (v in arr)`) or a key/value pair (`for (k => v + // in map)`, Haxe 4.0+); reuses $.pair rather than a dedicated rule so + // the `=>` shape doesn't need re-deriving, even though `v` there is a + // newly-bound loop variable, not a value expression referencing + // something else. + for_statement: ($) => + prec.right( + 1, + seq( + 'for', + '(', + field('binding', choice($.identifier, $.pair)), + 'in', + field('iterable', $.expression), + ')', + field('body', $.statement), + ), + ), + + // https://haxe.org/manual/expression-try-catch.html -- `type` is + // optional (wildcard catch, Haxe 4.1+: `catch (e) { ... }`, defaults to + // haxe.Exception). + try_statement: ($) => + prec.right(1, seq('try', field('body', $.statement), repeat1($.catch_clause))), + + catch_clause: ($) => + seq( + 'catch', + '(', + field('name', $.identifier), + optional(seq(':', field('type', $.type))), + ')', + field('body', $.statement), + ), + + // https://haxe.org/manual/lf-array-comprehension.html -- `[for (...) e]` + // / `[while (...) e]`, combining array declaration with a loop. Kept + // separate from $.for_statement/$.while_statement (not reused directly) + // because a comprehension's body is a bare $.expression with no + // trailing semicolon (`[for (i in 0...10) i]`, not `i;`), whereas the + // statement forms' body is $.statement specifically to get semicolon + // handling for the common (non-comprehension) case -- unifying the two + // would need $.statement and $.expression as competing choices for the + // same body field, which is the same kind of GLR-fork-doesn't-reliably- + // recover ambiguity documented on `_chain_term` above. The body can + // recurse into another comprehension_for/while/if, matching real code + // like nested `for (a in 1...11) for (b in 2...4) if (a % b == 0) ...`. + _comprehension_body: ($) => + choice($.comprehension_for, $.comprehension_while, $.comprehension_if, $.expression), + + comprehension_for: ($) => + prec.right( + seq( + 'for', + '(', + field('binding', choice($.identifier, $.pair)), + 'in', + field('iterable', $.expression), + ')', + field('body', $._comprehension_body), + ), + ), + + comprehension_while: ($) => + prec.right( + seq( + 'while', + '(', + field('condition', $.expression), + ')', + field('body', $._comprehension_body), + ), + ), + + // A bodyless-filter `if` inside a comprehension (`if (a % b == 0) ...`) + // -- no `else`, since a filter either includes or skips an iteration. + comprehension_if: ($) => + prec.right( + seq('if', '(', field('condition', $.expression), ')', field('body', $._comprehension_body)), + ), + _call: ($) => prec( 1, @@ -430,8 +759,6 @@ const haxe_grammar = { ...literals, comment: ($) => token(choice(seq('//', /.*/), seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'))), - // TODO: implement the structures that use these - keyword: ($) => choice('catch', 'do', 'for', 'try', 'while'), // keywords reserved by the haxe compiler that are not currently used reserved_keyword: ($) => choice('operator'), identifier: ($) => /[a-zA-Z_]+[a-zA-Z0-9]*/, diff --git a/src/grammar.json b/src/grammar.json index 185f39c..5ded172 100644 --- a/src/grammar.json +++ b/src/grammar.json @@ -713,8 +713,11 @@ "value": ":" }, { - "type": "SYMBOL", - "name": "statement" + "type": "REPEAT", + "content": { + "type": "SYMBOL", + "name": "statement" + } } ] }, @@ -730,8 +733,11 @@ "value": ":" }, { - "type": "SYMBOL", - "name": "statement" + "type": "REPEAT", + "content": { + "type": "SYMBOL", + "name": "statement" + } } ] } @@ -871,8 +877,17 @@ "type": "FIELD", "name": "return_type", "content": { - "type": "SYMBOL", - "name": "type" + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "type" + }, + { + "type": "SYMBOL", + "name": "_conditional_type" + } + ] } } ] @@ -1025,7 +1040,32 @@ }, { "type": "SYMBOL", - "name": "_rhs_expression" + "name": "_chain_term" + } + ] + } + } + ] + }, + { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "_parenthesized_expression" + }, + { + "type": "REPEAT1", + "content": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "operator" + }, + { + "type": "SYMBOL", + "name": "_chain_term" } ] } @@ -1312,6 +1352,14 @@ { "type": "SYMBOL", "name": "subscript_expression" + }, + { + "type": "SYMBOL", + "name": "ternary_expression" + }, + { + "type": "SYMBOL", + "name": "_parenthesized_expression" } ] }, @@ -1361,6 +1409,14 @@ "type": "SYMBOL", "name": "subscript_expression" }, + { + "type": "SYMBOL", + "name": "ternary_expression" + }, + { + "type": "SYMBOL", + "name": "_parenthesized_expression" + }, { "type": "SEQ", "members": [ @@ -1790,6 +1846,10 @@ "type": "SYMBOL", "name": "function_type" }, + { + "type": "SYMBOL", + "name": "structure_type" + }, { "type": "SEQ", "members": [ @@ -1815,6 +1875,119 @@ ] } }, + "_conditional_type": { + "type": "PREC_RIGHT", + "value": 0, + "content": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "#" + }, + { + "type": "IMMEDIATE_TOKEN", + "content": { + "type": "STRING", + "value": "if" + } + }, + { + "type": "FIELD", + "name": "condition", + "content": { + "type": "SYMBOL", + "name": "expression" + } + }, + { + "type": "SYMBOL", + "name": "type" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "_assignmentOperator" + }, + { + "type": "SYMBOL", + "name": "_literal" + } + ] + }, + { + "type": "BLANK" + } + ] + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "#" + }, + { + "type": "IMMEDIATE_TOKEN", + "content": { + "type": "STRING", + "value": "else" + } + }, + { + "type": "SYMBOL", + "name": "type" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "_assignmentOperator" + }, + { + "type": "SYMBOL", + "name": "_literal" + } + ] + }, + { + "type": "BLANK" + } + ] + } + ] + }, + { + "type": "BLANK" + } + ] + }, + { + "type": "STRING", + "value": "#" + }, + { + "type": "IMMEDIATE_TOKEN", + "content": { + "type": "STRING", + "value": "end" + } + } + ] + } + }, "block": { "type": "SEQ", "members": [ @@ -2819,6 +2992,35 @@ } ] }, + "_structure_class_body": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "{" + }, + { + "type": "REPEAT", + "content": { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "variable_declaration" + }, + { + "type": "SYMBOL", + "name": "function_declaration" + } + ] + } + }, + { + "type": "SYMBOL", + "name": "_closing_brace" + } + ] + }, "typedef_declaration": { "type": "SEQ", "members": [ @@ -2872,11 +3074,16 @@ "members": [ { "type": "SYMBOL", - "name": "block" + "name": "structure_type" }, { - "type": "SYMBOL", - "name": "structure_type" + "type": "ALIAS", + "content": { + "type": "SYMBOL", + "name": "_structure_class_body" + }, + "named": true, + "value": "block" }, { "type": "SYMBOL", @@ -3129,8 +3336,17 @@ "type": "FIELD", "name": "return_type", "content": { - "type": "SYMBOL", - "name": "type" + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "type" + }, + { + "type": "SYMBOL", + "name": "_conditional_type" + } + ] } } ] @@ -3289,6 +3505,10 @@ { "type": "SYMBOL", "name": "structure_type" + }, + { + "type": "SYMBOL", + "name": "_conditional_type" } ] }, @@ -3405,8 +3625,17 @@ "type": "FIELD", "name": "type", "content": { - "type": "SYMBOL", - "name": "type" + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "type" + }, + { + "type": "SYMBOL", + "name": "_conditional_type" + } + ] } }, { @@ -3809,75 +4038,79 @@ } }, "structure_type": { - "type": "PREC", + "type": "PREC_DYNAMIC", "value": 1, "content": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "{" - }, - { - "type": "CHOICE", - "members": [ - { - "type": "SEQ", - "members": [ - { - "type": "ALIAS", - "content": { - "type": "SYMBOL", - "name": "structure_type_pair" + "type": "PREC", + "value": 1, + "content": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "{" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "ALIAS", + "content": { + "type": "SYMBOL", + "name": "structure_type_pair" + }, + "named": true, + "value": "pair" }, - "named": true, - "value": "pair" - }, - { - "type": "REPEAT", - "content": { - "type": "SEQ", + { + "type": "REPEAT", + "content": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "," + }, + { + "type": "ALIAS", + "content": { + "type": "SYMBOL", + "name": "structure_type_pair" + }, + "named": true, + "value": "pair" + } + ] + } + }, + { + "type": "CHOICE", "members": [ { "type": "STRING", "value": "," }, { - "type": "ALIAS", - "content": { - "type": "SYMBOL", - "name": "structure_type_pair" - }, - "named": true, - "value": "pair" + "type": "BLANK" } ] } - }, - { - "type": "CHOICE", - "members": [ - { - "type": "STRING", - "value": "," - }, - { - "type": "BLANK" - } - ] - } - ] - }, - { - "type": "BLANK" - } - ] - }, - { - "type": "SYMBOL", - "name": "_closing_brace" - } - ] + ] + }, + { + "type": "BLANK" + } + ] + }, + { + "type": "SYMBOL", + "name": "_closing_brace" + } + ] + } } }, "structure_type_pair": { @@ -4266,6 +4499,10 @@ "typedef_declaration", "structure_type" ], + [ + "object", + "structure_type" + ], [ "member_expression", "_lhs_expression" diff --git a/src/node-types.json b/src/node-types.json index 569ae92..c64acbe 100644 --- a/src/node-types.json +++ b/src/node-types.json @@ -1087,6 +1087,120 @@ } ] }, + "condition": { + "multiple": true, + "required": false, + "types": [ + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", + "named": false + }, + { + "type": "runtime_type_check_expression", + "named": true + }, + { + "type": "string", + "named": true + }, + { + "type": "subscript_expression", + "named": true + }, + { + "type": "switch_expression", + "named": true + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false + } + ] + }, "name": { "multiple": false, "required": true, @@ -1100,76 +1214,446 @@ "named": true }, { - "type": "new", + "type": "new", + "named": false + } + ] + }, + "return_type": { + "multiple": true, + "required": false, + "types": [ + { + "type": "#", + "named": false + }, + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "=", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "else", + "named": false + }, + { + "type": "end", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "if", + "named": false + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", + "named": false + }, + { + "type": "runtime_type_check_expression", + "named": true + }, + { + "type": "string", + "named": true + }, + { + "type": "subscript_expression", + "named": true + }, + { + "type": "switch_expression", + "named": true + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, + { + "type": "type", + "named": true + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false + } + ] + } + }, + "children": { + "multiple": true, + "required": false, + "types": [ + { + "type": "function_arg", + "named": true + }, + { + "type": "metadata", + "named": true + }, + { + "type": "type_params", + "named": true + } + ] + } + }, + { + "type": "function_expression", + "named": true, + "fields": { + "body": { + "multiple": false, + "required": true, + "types": [ + { + "type": "block", + "named": true + } + ] + }, + "condition": { + "multiple": true, + "required": false, + "types": [ + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", + "named": false + }, + { + "type": "runtime_type_check_expression", + "named": true + }, + { + "type": "string", + "named": true + }, + { + "type": "subscript_expression", + "named": true + }, + { + "type": "switch_expression", + "named": true + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false + } + ] + }, + "name": { + "multiple": false, + "required": false, + "types": [ + { + "type": "identifier", + "named": true + }, + { + "type": "member_expression", + "named": true + } + ] + }, + "return_type": { + "multiple": true, + "required": false, + "types": [ + { + "type": "#", + "named": false + }, + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "=", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "else", + "named": false + }, + { + "type": "end", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "if", + "named": false + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", "named": false - } - ] - }, - "return_type": { - "multiple": false, - "required": false, - "types": [ + }, { - "type": "type", + "type": "runtime_type_check_expression", "named": true - } - ] - } - }, - "children": { - "multiple": true, - "required": false, - "types": [ - { - "type": "function_arg", - "named": true - }, - { - "type": "metadata", - "named": true - }, - { - "type": "type_params", - "named": true - } - ] - } - }, - { - "type": "function_expression", - "named": true, - "fields": { - "body": { - "multiple": false, - "required": true, - "types": [ + }, { - "type": "block", + "type": "string", "named": true - } - ] - }, - "name": { - "multiple": false, - "required": false, - "types": [ + }, { - "type": "identifier", + "type": "subscript_expression", "named": true }, { - "type": "member_expression", + "type": "switch_expression", "named": true - } - ] - }, - "return_type": { - "multiple": false, - "required": false, - "types": [ + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, { "type": "type", "named": true + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false } ] } @@ -2999,6 +3483,120 @@ } ] }, + "condition": { + "multiple": true, + "required": false, + "types": [ + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", + "named": false + }, + { + "type": "runtime_type_check_expression", + "named": true + }, + { + "type": "string", + "named": true + }, + { + "type": "subscript_expression", + "named": true + }, + { + "type": "switch_expression", + "named": true + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false + } + ] + }, "type_name": { "multiple": false, "required": false, @@ -3018,22 +3616,62 @@ "multiple": true, "required": false, "types": [ + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "float", + "named": true + }, { "type": "function_type", "named": true }, { - "type": "identifier", + "type": "identifier", + "named": true + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", "named": true }, { - "type": "member_expression", + "type": "object", "named": true }, { "type": "pair", "named": true }, + { + "type": "string", + "named": true + }, + { + "type": "structure_type", + "named": true + }, + { + "type": "type", + "named": true + }, { "type": "type_params", "named": true @@ -3216,6 +3854,120 @@ "type": "variable_declaration", "named": true, "fields": { + "condition": { + "multiple": true, + "required": false, + "types": [ + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", + "named": false + }, + { + "type": "runtime_type_check_expression", + "named": true + }, + { + "type": "string", + "named": true + }, + { + "type": "subscript_expression", + "named": true + }, + { + "type": "switch_expression", + "named": true + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false + } + ] + }, "name": { "multiple": false, "required": true, @@ -3231,12 +3983,140 @@ ] }, "type": { - "multiple": false, + "multiple": true, "required": false, "types": [ + { + "type": "#", + "named": false + }, + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": "=", + "named": false + }, + { + "type": "array", + "named": true + }, + { + "type": "bool", + "named": true + }, + { + "type": "break", + "named": false + }, + { + "type": "call_expression", + "named": true + }, + { + "type": "cast_expression", + "named": true + }, + { + "type": "continue", + "named": false + }, + { + "type": "else", + "named": false + }, + { + "type": "end", + "named": false + }, + { + "type": "float", + "named": true + }, + { + "type": "function_expression", + "named": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "if", + "named": false + }, + { + "type": "integer", + "named": true + }, + { + "type": "map", + "named": true + }, + { + "type": "member_expression", + "named": true + }, + { + "type": "null", + "named": true + }, + { + "type": "object", + "named": true + }, + { + "type": "operator", + "named": true + }, + { + "type": "pair", + "named": true + }, + { + "type": "return", + "named": false + }, + { + "type": "runtime_type_check_expression", + "named": true + }, + { + "type": "string", + "named": true + }, + { + "type": "subscript_expression", + "named": true + }, + { + "type": "switch_expression", + "named": true + }, + { + "type": "ternary_expression", + "named": true + }, + { + "type": "this", + "named": false + }, { "type": "type", "named": true + }, + { + "type": "type_trace_expression", + "named": true + }, + { + "type": "untyped", + "named": false } ] } diff --git a/test/corpus/binary_operators.txt b/test/corpus/binary_operators.txt index caa73a0..e47f610 100644 --- a/test/corpus/binary_operators.txt +++ b/test/corpus/binary_operators.txt @@ -140,6 +140,45 @@ var x = a != b; ) ) +=================== +parenthesized sub-expression as a chain tail term +=================== + +var x = a + (b); + +--- +(module + (variable_declaration (identifier) (operator) + (identifier) (operator) (identifier) + ) +) + +=================== +parenthesized sub-expression as a chain head, with a following operator +=================== + +var x = (a + b) * c; + +--- +(module + (variable_declaration (identifier) (operator) + (identifier) (operator) (identifier) (operator) (identifier) + ) +) + +=================== +range operator with a parenthesized operand +=================== + +var x = 0...(10); + +--- +(module + (variable_declaration (identifier) (operator) + (integer) (operator) (integer) + ) +) + =================== less-than comparison does not get mistaken for a generic call's type params =================== diff --git a/test/corpus/conditional_statement.txt b/test/corpus/conditional_statement.txt index 6509285..faa0711 100644 --- a/test/corpus/conditional_statement.txt +++ b/test/corpus/conditional_statement.txt @@ -101,3 +101,114 @@ if (a) { b(); } else if (c) { d(); } else if (e) { f(); } else { g(); } ) ) ) +=================== +if with a braceless body +=================== + +class F { + function b() { + if (a) trace(1); + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (conditional_statement (identifier) (call_expression (identifier) (integer))) + ) + ) + ) + ) +) + +=================== +if-else with braceless bodies +=================== + +class F { + function b() { + if (a) trace(1); else trace(2); + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (conditional_statement + (identifier) + (call_expression (identifier) (integer)) + (call_expression (identifier) (integer)) + ) + ) + ) + ) + ) +) + +=================== +if-else-if-else chain with braceless bodies +=================== + +class F { + function b() { + if (a) trace(1); else if (b) trace(2); else trace(3); + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (conditional_statement + (identifier) + (call_expression (identifier) (integer)) + (identifier) + (call_expression (identifier) (integer)) + (call_expression (identifier) (integer)) + ) + ) + ) + ) + ) +) + +=================== +if-else-if-else chain with block bodies (unaffected by the braceless fix) +=================== + +class F { + function b() { + if (a) { trace(1); } else if (b) { trace(2); } else { trace(3); } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (conditional_statement + (identifier) + (block (call_expression (identifier) (integer))) + (identifier) + (block (call_expression (identifier) (integer))) + (block (call_expression (identifier) (integer))) + ) + ) + ) + ) + ) +) diff --git a/test/corpus/declarations.txt b/test/corpus/declarations.txt index f998df8..df1df6a 100644 --- a/test/corpus/declarations.txt +++ b/test/corpus/declarations.txt @@ -771,6 +771,93 @@ private extern typedef MyType = {} (module (typedef_declaration (identifier) (structure_type))) +=================== +type def with a single field and no trailing comma +=================== + +typedef JJSON = { settings:String }; + +--- + +(module + (typedef_declaration + (identifier) + (structure_type + (pair + (identifier) + (type + (identifier)))))) + +=================== +type def with var and function members +=================== + +typedef Foo = { + var x:Int; + function bar():Void; +} + +--- + +(module + (typedef_declaration + (identifier) + (block + (variable_declaration + (identifier) + (type + (identifier))) + (function_declaration + (identifier) + (type + (identifier)))))) + +=================== +function arg with an anonymous struct segment in a function type chain +=================== + +function f(cb:String->{}->Void):Void {} + +--- + +(module + (function_declaration + (identifier) + (function_arg + (identifier) + (type + (function_type + (type + (identifier)) + (type + (function_type + (type + (structure_type)) + (type + (identifier))))))) + (type + (identifier)) + (block))) + +=================== +var declaration with a struct-in-the-middle function type chain (known limitation, see $.function_type's comment in grammar.js) +=================== + +var f:String->{}->Void; + +--- + +(module + (variable_declaration + (identifier) + (type + (function_type + (type + (identifier)) + (type + (structure_type))))) + (ERROR)) + =================== var declaration with type =================== @@ -849,11 +936,11 @@ var a = [for (i in 0...5) i]; (identifier) (operator) (array - (call_expression + (comprehension_for + (identifier) + (integer) (operator) (integer) (identifier) - (range_expression (identifier) (integer) (integer)) ) - (identifier) ) ) ) @@ -871,13 +958,12 @@ var a = [while (i < 1) i]; (identifier) (operator) (array - (call_expression - (identifier) + (comprehension_while (identifier) (operator) (integer) + (identifier) ) - (identifier) ) ) ) @@ -1114,3 +1200,146 @@ var x:T R>; ) ) ) + +=================== +var declaration with a conditionally-compiled type +=================== + +var atlasXml:#if flash flash.xml.XML #else Xml #end = null; + +--- + +(module + (variable_declaration + (identifier) + (identifier) + (type + (member_expression + (member_expression + (identifier) + (identifier)) + (identifier))) + (type + (identifier)) + (operator) + (null))) + +=================== +function arg with a conditionally-compiled type +=================== + +function scaleUV(scaleU:#if flash Null #else Float #end = 1.0):Void {} + +--- + +(module + (function_declaration + (identifier) + (function_arg + (identifier) + (type + (identifier) + (type + (identifier) + (type_params + (type + (identifier)))) + (type + (identifier))) + (float)) + (type + (identifier)) + (block))) + +=================== +function declaration with a conditionally-compiled return type +=================== + +function get_width():#if (!flash) Float #else Void #end { return 1; } + +--- + +(module + (function_declaration + (identifier) + (operator) + (identifier) + (type + (identifier)) + (type + (identifier)) + (block + (integer)))) + +=================== +function expression with a conditionally-compiled return type +=================== + +var fn = function(x:Int):#if flash Null #else Bool #end { return true; }; + +--- + +(module + (variable_declaration + (identifier) + (operator) + (function_expression + (function_arg + (identifier) + (type + (identifier))) + (identifier) + (type + (identifier) + (type_params + (type + (identifier)))) + (type + (identifier)) + (block + (bool))))) + +=================== +function arg with a conditionally-compiled type and a different default per branch +=================== + +function saveSettings(wantFlush:#if flash Null = false #else Bool = true #end):Void; + +--- + +(module + (function_declaration + (identifier) + (function_arg + (identifier) + (type + (identifier) + (type + (identifier) + (type_params + (type + (identifier)))) + (bool) + (type + (identifier)) + (bool))) + (type + (identifier)))) + +=================== +var declaration with a conditionally-compiled type and a default in only one branch +=================== + +private var artSwf1:#if USESWFLOADER SwfLoader #else String = "JustWords.swf" #end; + +--- + +(module + (variable_declaration + (identifier) + (identifier) + (type + (identifier)) + (type + (identifier)) + (string))) diff --git a/test/corpus/expression_chain_completeness.txt b/test/corpus/expression_chain_completeness.txt index ba2e38f..4a3da33 100644 --- a/test/corpus/expression_chain_completeness.txt +++ b/test/corpus/expression_chain_completeness.txt @@ -11,14 +11,15 @@ class F { --- (module - (class_declaration (identifier) + (class_declaration + (identifier) (block - (function_declaration (identifier) - (block (identifier) (operator) (identifier)) - ) - ) - ) -) + (function_declaration + (identifier) + (block + (identifier) + (operator) + (identifier)))))) =================== return with an assign-and-return chain @@ -33,14 +34,15 @@ class F { --- (module - (class_declaration (identifier) + (class_declaration + (identifier) (block - (function_declaration (identifier) - (block (identifier) (operator) (identifier)) - ) - ) - ) -) + (function_declaration + (identifier) + (block + (identifier) + (operator) + (identifier)))))) =================== return with a bare subscript, no operator @@ -55,14 +57,92 @@ class F { --- (module - (class_declaration (identifier) + (class_declaration + (identifier) + (block + (function_declaration + (identifier) + (block + (subscript_expression + (identifier) + (identifier))))))) + +=================== +return with a bare ternary, no outer parens +=================== + +class F { + function b() { + return a ? b : c; + } +} + +--- + +(module + (class_declaration + name: (identifier) + body: (block + (function_declaration + name: (identifier) + body: (block + (ternary_expression + condition: (identifier) + consequence: (identifier) + alternative: (identifier))))))) + +=================== +return with a ternary wrapped in outer parens +=================== + +class F { + function b() { + return (ix != -1) ? heights[ix] : -1; + } +} + +--- + +(module + (class_declaration + name: (identifier) + body: (block + (function_declaration + name: (identifier) + body: (block + (ternary_expression + condition: (identifier) + condition: (operator) + condition: (operator) + condition: (integer) + consequence: (subscript_expression + (identifier) + index: (identifier)) + alternative: (operator) + alternative: (integer))))))) + +=================== +return with a parenthesized non-ternary expression +=================== + +class F { + function b() { + return (a + b); + } +} + +--- + +(module + (class_declaration + (identifier) (block - (function_declaration (identifier) - (block (subscript_expression (identifier) (identifier))) - ) - ) - ) -) + (function_declaration + (identifier) + (block + (identifier) + (operator) + (identifier)))))) =================== subscript_expression as the head of an assignment chain @@ -77,18 +157,17 @@ class F { --- (module - (class_declaration (identifier) + (class_declaration + (identifier) (block - (function_declaration (identifier) + (function_declaration + (identifier) (block - (subscript_expression (identifier) (identifier)) + (subscript_expression + (identifier) + (identifier)) (operator) - (identifier) - ) - ) - ) - ) -) + (identifier)))))) =================== postfix unary as the head of a comparison chain @@ -103,11 +182,13 @@ class F { --- (module - (class_declaration (identifier) + (class_declaration + (identifier) (block - (function_declaration (identifier) - (block (identifier) (operator) (operator) (integer)) - ) - ) - ) -) + (function_declaration + (identifier) + (block + (identifier) + (operator) + (operator) + (integer)))))) diff --git a/test/corpus/expressions.txt b/test/corpus/expressions.txt index facf76e..e9c7e7a 100644 --- a/test/corpus/expressions.txt +++ b/test/corpus/expressions.txt @@ -216,6 +216,42 @@ switch (x) { )) ) +=================== +switch case with multiple statements and no block +=================== + +switch (x) { + case 4: + a(); + b(); + case 5: + c(); + default: + d(); + e(); +} + +--- + +(module + (switch_expression (identifier) + (block + (case_statement + (integer) + (call_expression (identifier)) + (call_expression (identifier)) + ) + (case_statement + (integer) + (call_expression (identifier)) + ) + (case_statement + (call_expression (identifier)) + (call_expression (identifier)) + ) + )) +) + =================== case expr statement in case. =================== diff --git a/test/corpus/literals.txt b/test/corpus/literals.txt index d38db51..82a528f 100644 --- a/test/corpus/literals.txt +++ b/test/corpus/literals.txt @@ -136,28 +136,6 @@ class main { -=================== -float literal with no trailing number. -=================== - -class main { - var x = 4.; -} - ---- - -(module - (class_declaration (identifier) - (block - (variable_declaration - (identifier) - (operator) - (float) - ) - ) - ) -) - =================== float literal e notation. =================== diff --git a/test/corpus/loop_statements.txt b/test/corpus/loop_statements.txt new file mode 100644 index 0000000..cb19012 --- /dev/null +++ b/test/corpus/loop_statements.txt @@ -0,0 +1,171 @@ +=================== +for over an array +=================== + +class F { + function b() { + for (v in list) { + trace(v); + } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (for_statement + (identifier) (identifier) + (block (call_expression (identifier) (identifier))) + ) + ) + ) + ) + ) +) + +=================== +for over a range with a braceless body +=================== + +class F { + function b() { + for (i in 0...10) trace(i); + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (for_statement + (identifier) + (integer) (operator) (integer) + (call_expression (identifier) (identifier)) + ) + ) + ) + ) + ) +) + +=================== +for with a key => value binding over a map +=================== + +class F { + function b() { + for (k => v in map) { + trace(k, v); + } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (for_statement + (pair (identifier) (identifier)) + (identifier) + (block (call_expression (identifier) (identifier) (identifier))) + ) + ) + ) + ) + ) +) + +=================== +while loop +=================== + +class F { + function b() { + while (f < 5) { + f = 1; + } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (while_statement + (identifier) (operator) (integer) + (block (identifier) (operator) (integer)) + ) + ) + ) + ) + ) +) + +=================== +while loop with a braceless body +=================== + +class F { + function b() { + while (f < 5) f = 1; + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (while_statement + (identifier) (operator) (integer) + (identifier) (operator) (integer) + ) + ) + ) + ) + ) +) + +=================== +do-while loop +=================== + +class F { + function b() { + do { + f = 1; + } while (f < 5); + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (do_while_statement + (block (identifier) (operator) (integer)) + (identifier) (operator) (integer) + ) + ) + ) + ) + ) +) diff --git a/test/corpus/preprocessor_statement.txt b/test/corpus/preprocessor_statement.txt index ea21483..bf1cfbb 100644 --- a/test/corpus/preprocessor_statement.txt +++ b/test/corpus/preprocessor_statement.txt @@ -70,3 +70,87 @@ trace(1); ) ) ) + +=================== +dangling else guarded by a preprocessor conditional +=================== + +if (cond) { + a(); +} +#if !STANDALONE +else { + b(); +} +#end + +--- + +(module + (conditional_statement + (identifier) + (block + (call_expression (identifier)))) + (preprocessor_statement + (operator) + (identifier) + (dangling_else_statement + (block + (call_expression (identifier)))))) + +=================== +dangling else-if inside a disabled preprocessor branch +=================== + +#if 0 +else if (cond) +{ + enqueue(rawAsset); +} +#end + +--- + +(module + (preprocessor_statement + (integer) + (dangling_else_statement + (identifier) + (block + (call_expression (identifier) (identifier)))))) + +=================== +dangling else with trailing statements in the same guarded branch +=================== + +if (c) { + a(); +} +#if !STANDALONE +else { + b(); +} +d(); +#else +else { + e(); +} +#end + +--- + +(module + (conditional_statement + (identifier) + (block + (call_expression (identifier)))) + (preprocessor_statement + (operator) + (identifier) + (dangling_else_statement + (block + (call_expression (identifier)))) + (call_expression (identifier)) + (dangling_else_statement + (block + (call_expression (identifier)))))) diff --git a/test/corpus/ternary.txt b/test/corpus/ternary.txt index f34d695..086b3b9 100644 --- a/test/corpus/ternary.txt +++ b/test/corpus/ternary.txt @@ -126,3 +126,23 @@ foo(a ? b : c); ) ) ) + +=================== +ternary condition is a chain of parenthesized terms +=================== + +var x = (a >= 0) && (a < 10) ? b : c; + +--- + +(module + (variable_declaration + (identifier) + (operator) + (ternary_expression + (identifier) (operator) (integer) (operator) (identifier) (operator) (integer) + (identifier) + (identifier) + ) + ) +) diff --git a/test/corpus/try_catch_and_comprehensions.txt b/test/corpus/try_catch_and_comprehensions.txt new file mode 100644 index 0000000..b21406e --- /dev/null +++ b/test/corpus/try_catch_and_comprehensions.txt @@ -0,0 +1,200 @@ +=================== +try/catch with a typed catch clause +=================== + +class F { + function b() { + try { + a(); + } catch (e:String) { + b(); + } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (try_statement + (block (call_expression (identifier))) + (catch_clause (identifier) (type (identifier)) (block (call_expression (identifier)))) + ) + ) + ) + ) + ) +) + +=================== +try with a wildcard catch clause (no type) +=================== + +class F { + function b() { + try { + a(); + } catch (e) { + b(); + } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (try_statement + (block (call_expression (identifier))) + (catch_clause (identifier) (block (call_expression (identifier)))) + ) + ) + ) + ) + ) +) + +=================== +try with multiple catch clauses +=================== + +class F { + function b() { + try { + a(); + } catch (e:String) { + b(); + } catch (e:Int) { + c(); + } + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (try_statement + (block (call_expression (identifier))) + (catch_clause (identifier) (type (identifier)) (block (call_expression (identifier)))) + (catch_clause (identifier) (type (identifier)) (block (call_expression (identifier)))) + ) + ) + ) + ) + ) +) + +=================== +array comprehension over a for loop +=================== + +class F { + function b() { + var x = [for (i in 0...10) i * 2]; + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (variable_declaration + (identifier) (operator) + (array + (comprehension_for + (identifier) + (integer) (operator) (integer) + (identifier) (operator) (integer) + ) + ) + ) + ) + ) + ) + ) +) + +=================== +array comprehension over a while loop +=================== + +class F { + function b() { + var x = [while (i < 10) i++]; + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (variable_declaration + (identifier) (operator) + (array + (comprehension_while + (identifier) (operator) (integer) + (identifier) (operator) + ) + ) + ) + ) + ) + ) + ) +) + +=================== +array comprehension with a nested for and an if filter +=================== + +class F { + function b() { + var x = [for (a in 1...11) for (b in 2...4) if (a == b) a]; + } +} + +--- + +(module + (class_declaration (identifier) + (block + (function_declaration (identifier) + (block + (variable_declaration + (identifier) (operator) + (array + (comprehension_for + (identifier) + (integer) (operator) (integer) + (comprehension_for + (identifier) + (integer) (operator) (integer) + (comprehension_if + (identifier) (operator) (identifier) + (identifier) + ) + ) + ) + ) + ) + ) + ) + ) + ) +) diff --git a/test/corpus/unary_operations.txt b/test/corpus/unary_operations.txt index 8b180ac..bf565db 100644 --- a/test/corpus/unary_operations.txt +++ b/test/corpus/unary_operations.txt @@ -166,7 +166,7 @@ logical not unop as the head of a binary operator chain class main { function test() { - if (!x && y) {} + if (!x && y) { z(); } } } @@ -178,7 +178,7 @@ class main { (block (conditional_statement (operator) (identifier) (operator) (identifier) - (block) + (block (call_expression (identifier))) ) ) ) @@ -192,7 +192,7 @@ logical not unop as the tail of a binary operator chain class main { function test() { - if (x && !y) {} + if (x && !y) { z(); } } } @@ -204,7 +204,7 @@ class main { (block (conditional_statement (identifier) (operator) (operator) (identifier) - (block) + (block (call_expression (identifier))) ) ) ) @@ -218,7 +218,7 @@ logical not unop as both the head and the tail of a binary operator chain class main { function test() { - if (!x && !y) {} + if (!x && !y) { z(); } } } @@ -230,7 +230,7 @@ class main { (block (conditional_statement (operator) (identifier) (operator) (operator) (identifier) - (block) + (block (call_expression (identifier))) ) ) )