diff --git a/check_test.go b/check_test.go index b09ad79..3d33fbb 100644 --- a/check_test.go +++ b/check_test.go @@ -5,6 +5,8 @@ import "testing" func checkNodes(t *testing.T, ignorePosition bool, left, right []*Node) { if len(left) != len(right) { t.Error("length doesn't match", len(left), len(right)) + t.Log(left) + t.Log(right) return } diff --git a/examples/mml-exp.treerack b/examples/mml-exp.treerack new file mode 100644 index 0000000..6016a94 --- /dev/null +++ b/examples/mml-exp.treerack @@ -0,0 +1,562 @@ +// whitespace is ignored except for \n which is only ignored +// most of the time, but can serve as separator in: +// - list +// - struct +// - function args +// - statements +// - list, struct and function type constraints +// +// comments are not ignored because they are needed during formatting +ws:ws = " " | "\b" | "\f" | "\r" | "\t" | "\v"; +wsc:ws = comment; +nl:alias = "\n"; + +// comments can be line or block comments +// indentation can hold meaning +line-comment-content:nows = [^\n]*; +line-comment:alias:nows = "//" line-comment-content; +block-comment-content:nows = ([^*] | "*" [^/])*; // TODO: why is the :nows required here if it is there for block-comment? +block-comment:alias:nows = "/*" block-comment-content "*/"; +comment-part:alias = line-comment | block-comment; +comment:alias = comment-part ("\n"? comment-part)*; + +decimal-digit:alias = [0-9]; +octal-digit:alias = [0-7]; +hexa-digit:alias = [0-9a-fA-F]; + +// interger examples: 42, 0666, 0xfff +decimal:alias:nows = [1-9] decimal-digit*; +octal:alias:nows = "0" octal-digit*; +hexa:alias:nows = "0" [xX] hexa-digit+; +int = decimal | octal | hexa; + +// float examples: .0, 0., 3.14, 1E-12 +exponent:alias:nows = [eE] [+\-]? decimal-digit+; +float:nows = decimal-digit+ "." decimal-digit* exponent? + | "." decimal-digit+ exponent? + | decimal-digit+ exponent; + +// string example: "Hello, world!" +// only \ and " need to be escaped, e.g. allows new lines +// common escaped chars get unescaped, the rest gets unescaped to themselves +string:nows = "\"" ([^\\"] | "\\" .)* "\""; + +true = "true"; +false = "false"; +bool:alias = true | false; + +// symbols normally can have only \w chars: fooBar_baz +// basic symbols cannot start with a digit +// some positions allow strings to be used as symbols, e.g: let "123" 123 +// when this is not possible, dynamic symbols need to be used, but they are +// not allowed in every case, e.g: {symbol(foo()): "bar"} +// TODO: needs decision log for dynamic symbol +// TODO: exclude keywords +// +// dynamic symbol decision log: +// - every value is equatable +// - structs can act as hashtables (optimization is transparent) +// - in structs, must differentiate between symbol and value of a symbol when used as a key +// - js style [a] would be enough for the structs +// - the variables in a scope are like fields in a struct +// - [a] would be ambigous with the list as an expression +// - a logical loophole is closed with symbol(a) +// - dynamic-symbols need to be handled differently in match expressions and type expressions +symbol:nows = [a-zA-Z_][a-zA-Z_0-9]*; +static-symbol:alias = symbol | string; +dynamic-symbol = "symbol" "(" nl* expression nl* ")"; +symbol-expression:alias = static-symbol | dynamic-symbol; + +// TODO: what happens when a dynamic symbol gets exported? + +// list items are separated by comma or new line (or both) +/* + [] + [a, b, c] + [ + a + b + c + ] + [1, 2, a..., [b, c], [d, [e]]...] +*/ +spread-expression = primary-expression "..."; +list-sep:alias = ("," | "\n") (nl | ",")*; +list-item:alias = expression | spread-expression; +expression-list:alias = list-item (list-sep list-item)*; + +// list example: [1, 2, 3] +// lists can be constructed with other lists: [l1..., l2...] +list-fact:alias = "[" (nl | ",")* expression-list? (nl | ",")* "]"; +list = list-fact; +mutable-list = "~" nl* list-fact; + +indexer-symbol = "[" nl* expression nl* "]"; +entry = (symbol-expression | indexer-symbol) nl* ":" nl* expression; +entry-list:alias = (entry | spread-expression) (list-sep (entry | spread-expression))*; +struct-fact:alias = "{" (nl | ",")* entry-list? (nl | ",")* "}"; +struct = struct-fact; +mutable-struct = "~" nl* struct-fact; + +channel = "<>" | "<" nl* int nl* ">"; + +// and-expression:doc = "and" "(" (nl | ",")* expression-list? (nl | ",")* ")"; +// or-expression:doc = "or" "(" (nl | ",")* expression-list? (nl | ",")* ")"; + +argument-list:alias = static-symbol (list-sep static-symbol)*; +collect-symbol = "..." nl* static-symbol; +function-fact:alias = "(" (nl | ",")* + argument-list? + (nl | ",")* + collect-symbol? + (nl | ",")* ")" nl* + expression; +function = "fn" nl* function-fact; // can it ever cause a conflict with call and grouping? +effect = "fn" nl* "~" nl* function-fact; + +/* +a[42] +a[3:9] +a[:9] +a[3:] +a[b][c][d] +a.foo +a."foo" +a.symbol(foo) +*/ +range-from = expression; +range-to = expression; +range-expression:alias = range-from? nl* ":" nl* range-to?; +indexer-expression:alias = expression | range-expression; +expression-indexer:alias = primary-expression "[" nl* indexer-expression nl* "]"; +symbol-indexer:alias = primary-expression nl* "." nl* symbol-expression; +indexer = expression-indexer | symbol-indexer; + +function-application = primary-expression "(" (nl | ",")* expression-list? (nl | ",")* ")"; + +if = "if" nl* expression nl* block + (nl* "else" nl* "if" nl* expression nl* block)* + (nl* "else" nl* block)?; + +default = "default" nl* ":"; +default-line:alias = default (nl | ";")* statement?; +case = "case" nl* expression nl* ":"; +case-line:alias = case ";"* statement?; +switch = "switch" nl* expression? nl* "{" (nl | ";")* + ((case-line | default-line) (sep (case-line | default-line | statement))*)? + (nl | ";")* "}"; + +int-type = "int"; +float-type = "float"; +string-type = "string"; +bool-type = "bool"; +error-type = "error"; + +/* +support: + + go { + foo() + bar() + } + + go { for { f() } } + go func() { for { f() } }() + fn f() { go f() }; go f() + +and not: + + go for {foo()} + +or: + + go for foo() + +because we don't know what the arguments are +*/ + +/* + + fn foo() { + bar() + baz() + } + let qux foo() + +equivalent to: + + let qux { + bar() + baz() + } +*/ + +primitive-type:alias = int-type + | float-type + | string-type + | bool-type + | error-type; + +type-alias-name:alias = static-symbol; + +static-range-from = int; +static-range-to = int; +static-range-expression:alias = static-range-from? nl* ":" nl* static-range-to?; +items-quantifier = int | static-range-expression; +// TODO: maybe this can be confusing with matching constants. Shall we support matching constants, values? + +items-type = items-quantifier + | type-set (nl* ":" nl* items-quantifier)? + | static-symbol nl* type-set (nl* ":" nl* items-quantifier)?; + +destructure-item = type-set | static-symbol nl* type-set; + +collect-destructure-item = "..." nl* destructure-item? + (nl* ":" items-quantifier)?; +list-destructure-type = destructure-item + (list-sep destructure-item)* + (list-sep collect-destructure-item)? + | collect-destructure-item; +list-type-fact:alias = "[" (nl | ",")* + (items-type | list-destructure-type)? + (nl | ",")* "]"; +list-type = list-type-fact; +mutable-list-type = "~" nl* list-type-fact; + +destructure-match-item = match-set + | static-symbol nl* match-set + | static-symbol nl* static-symbol nl* match-set; + +collect-destructure-match-item = "..." nl* destructure-match-item? + (nl* ":" items-quantifier)?; +list-destructure-match = destructure-match-item + (list-sep destructure-match-item)* + (list-sep collect-destructure-match-item)? + | collect-destructure-match-item; +list-match-fact:alias = "[" (nl | ",")* + (list-destructure-match | items-type)? + (nl | ",")* "]"; +list-match = list-match-fact; +mutable-list-match = "~" nl* list-match; + +entry-type = static-symbol (nl* ":" nl* destructure-item)?; +entry-types:alias = entry-type (list-sep entry-type)*; +struct-type-fact:alias = "{" (nl | ",")* entry-types? (nl | ",")* "}"; +struct-type = struct-type-fact; +mutable-struct-type = "~" nl* struct-type-fact; + +entry-match = static-symbol (nl* ":" nl* destructure-match-item)?; +entry-matches:alias = entry-match (list-sep entry-match)*; +struct-match-fact:alias = "{" (nl | ",")* entry-matches? (nl | ",")* "}"; +struct-match = struct-match-fact; +mutable-struct-match = "~" nl* struct-match-fact; + +arg-type = type-set | static-symbol nl* type-set; +args-type:alias = arg-type (list-sep arg-type)*; +function-type-fact:alias = "(" nl* args-type? nl* ")" + (type-set | static-symbol type-set)?; +function-type = "fn" nl* function-type-fact; +effect-type = "fn" nl* "~" nl* function-type-fact; + +// TODO: heavy naming crime + +receive-direction = "receive"; +send-direction = "send"; +channel-type = "<" nl* + (receive-direction | send-direction)? nl* + destructure-item? + nl* ">"; + +type-fact-group:alias = "(" nl* type-fact nl* ")"; +type-fact:alias = primitive-type + | type-alias-name + | list-type + | mutable-list-type + | struct-type + | mutable-struct-type + | function-type + | effect-type + | channel-type + | type-fact-group; + +type-set:alias = type-fact (nl* "|" nl* type-fact)*; +type-expression:alias = type-set | static-symbol type-set; + +match-fact:alias = list-match + | mutable-list-match + | struct-match + | mutable-struct-match; + +match-set:alias = type-set | match-fact; +match-expression:alias = match-set | static-symbol match-set; + +match-case = "case" nl* match-expression nl* ":"; +match-case-line:alias = match-case ";"* statement?; +match = "match" nl* expression nl* "{" (nl | ";")* + ((match-case-line | default-line) + (sep (match-case-line | default-line | statement))*)? + (nl | ";")* "}"; + +conditional:alias = if + | switch + | match; + +receive-call = "receive" "(" (nl | ",")* expression (nl | ",")* ")"; +receive-op = "<-" primary-expression; +receive-expression-group:alias = "(" nl* receive-expression nl* ")"; +receive-expression:alias = receive-call | receive-op | receive-expression-group; + +receive-assign-capture:alias = assignable nl* ("=" nl*)? receive-expression; +receive-assignment = "set" nl* receive-assign-capture; +receive-assignment-equal = assignable nl* "=" nl* receive-expression; +receive-capture:alias = symbol-expression nl* ("=" nl*)? receive-expression; +receive-definition = "let" nl* receive-capture; +receive-mutable-definition = "let" nl* "~" nl* receive-capture; +receive-statement:alias = receive-assignment | receive-definition; + +send-call:alias = "send" "(" (nl | ",")* expression list-sep expression (nl | ",")* ")"; +send-op:alias = primary-expression "<-" expression; +send-call-group:alias = "(" nl* send nl* ")"; +send = send-call | send-op | send-call-group; + +close = "close" "(" (nl | ",")* expression (nl | ",")* ")"; + +communication-group:alias = "(" nl* communication nl* ")"; +communication:alias = receive-expression | receive-statement | send | communication-group; + +select-case = "case" nl* communication nl* ":"; +select-case-line:alias = select-case ";"* statement?; +select = "select" nl* "{" (nl | ";")* + ((select-case-line | default-line) + (sep (select-case-line | default-line | statement))*)? + (nl | ";")* "}"; + +go = "go" nl* (function-application | block); + +/* +require . = "mml/foo" +require bar = "mml/foo" +require . "mml/foo" +require bar "mml/foo" +require "mml/foo" +require ( + . = "mml/foo" + bar = "mml/foo" + . "mml/foo" + bar "mml/foo" + "mml/foo" +) +require () +*/ +require-inline = "."; +require-fact = string + | (static-symbol | require-inline) (nl* "=")? nl* string; +require-facts:alias = require-fact (list-sep require-fact)*; +require-statement:alias = "require" nl* require-fact; +require-statement-group:alias = "require" "(" (nl | ",")* + require-facts? + (nl | ",")* ")"; +require = require-statement | require-statement-group; + +panic = "panic" "(" (nl | ",")* expression (nl | ",")* ")"; +recover = "recover" "(" (nl | ",")* ")"; + +block = "{" (nl | ";")* statements? (nl | ";")* "}"; +expression-group:alias = "(" nl* expression nl* ")"; + +primary-expression:alias = int + | float + | string + | bool + | symbol + | dynamic-symbol + | list + | mutable-list + | struct + | mutable-struct + | channel + // | and-expression // only documentation + // | or-expression // only documentation + | function + | effect + | indexer + | function-application // pseudo-expression + | conditional // pseudo-expression + | receive-call + | select // pseudo-expression + | recover + | block // pseudo-expression + | expression-group; + +plus = "+"; +minus = "-"; +logical-not = "!"; +binary-not = "^"; +unary-operator:alias = plus | minus | logical-not | binary-not; +unary-expression = unary-operator primary-expression | receive-op; + +mul = "*"; +div = "/"; +mod = "%"; +lshift = "<<"; +rshift = ">>"; +binary-and = "&"; +and-not = "&^"; + +add = "+"; +sub = "-"; +binary-or = "|"; +xor = "^"; + +eq = "=="; +not-eq = "!="; +less = "<"; +less-or-eq = "<="; +greater = ">"; +greater-or-eq = ">="; + +logical-and = "&&"; +logical-or = "||"; + +chain = "->"; + +binary-op0:alias = mul | div | mod | lshift | rshift | binary-and | and-not; +binary-op1:alias = add | sub | binary-or | xor; +binary-op2:alias = eq | not-eq | less | less-or-eq | greater | greater-or-eq; +binary-op3:alias = logical-and; +binary-op4:alias = logical-or; +binary-op5:alias = chain; + +operand0:alias = primary-expression | unary-expression; +operand1:alias = operand0 | binary0; +operand2:alias = operand1 | binary1; +operand3:alias = operand2 | binary2; +operand4:alias = operand3 | binary3; +operand5:alias = operand4 | binary4; + +binary0 = operand0 (binary-op0 operand0)+; +binary1 = operand1 (binary-op1 operand1)+; +binary2 = operand2 (binary-op2 operand2)+; +binary3 = operand3 (binary-op3 operand3)+; +binary4 = operand4 (binary-op4 operand4)+; +binary5 = operand5 (binary-op5 operand5)+; + +binary-expression:alias = binary0 | binary1 | binary2 | binary3 | binary4 | binary5; + +ternary-expression = expression nl* "?" nl* expression nl* ":" nl* expression; + +expression:alias = primary-expression + | unary-expression + | binary-expression + | ternary-expression; + +// TODO: code() +// TODO: observability + +break = "break"; +continue = "continue"; +loop-control:alias = break | continue; + +in-expression = static-symbol nl* "in" nl* (expression | range-expression); +loop-expression = expression | in-expression; +loop = "for" nl* (block | loop-expression nl* block); + +/* +a = b +set c = d +set e f +set ( + g = h + i j +) +*/ +assignable:alias = symbol-expression | indexer; +assign-capture = assignable nl* ("=" nl*)? expression; +assign-set:alias = "set" nl* assign-capture; +assign-equal = assignable nl* "=" nl* expression; +assign-captures:alias = assign-capture (list-sep assign-capture)*; +assign-group:alias = "set" nl* "(" (nl | ",")* assign-captures? (nl | ",")* ")"; +assignment = assign-set | assign-equal | assign-group; + +/* +let a = b +let c d +let ~ e = f +let ~ g h +let ( + i = j + k l + ~ m = n + ~ o p +) +let ~ ( + q = r + s t +) +*/ +value-capture-fact:alias = symbol-expression nl* ("=" nl*)? expression; +value-capture = value-capture-fact; +mutable-capture = "~" nl* value-capture-fact; +value-definition = "let" nl* (value-capture | mutable-capture); +value-captures:alias = value-capture (list-sep value-capture)*; +mixed-captures:alias = (value-capture | mutable-capture) (list-sep (value-capture | mutable-capture))*; +value-definition-group = "let" nl* "(" (nl | ",")* mixed-captures? (nl | ",")* ")"; +mutable-definition-group = "let" nl* "~" nl* "(" (nl | ",")* value-captures? (nl | ",")* ")"; + +/* +fn a() b +fn ~ c() d +fn ( + e() f + ~ g() h +) +fn ~ ( + i() + j() +) +*/ +function-definition-fact:alias = static-symbol nl* function-fact; +function-capture = function-definition-fact; +effect-capture = "~" nl* function-definition-fact; +function-definition = "fn" nl* (function-capture | effect-capture); +function-captures:alias = function-capture (list-sep function-capture)*; +mixed-function-captures:alias = (function-capture | effect-capture) + (list-sep (function-capture | effect-capture))*; +function-definition-group = "fn" nl* "(" (nl | ",")* + mixed-function-captures? + (nl | ",")* ")"; +effect-definition-group = "fn" nl* "~" nl* "(" (nl | ",")* + function-captures? + (nl | ",")* ")"; + +definition:alias = value-definition + | value-definition-group + | mutable-definition-group + | function-definition + | function-definition-group + | effect-definition-group; + +type-alias = "type" nl* "alias" nl* static-symbol nl* type-set; +type-constraint = "type" nl* static-symbol nl* type-set; + +statement-group:alias = "(" nl* statement nl* ")"; + +statement:alias = send + | close + | panic + | require + | loop-control + | go + | loop + | assignment + | definition + | expression + | type-alias + | type-constraint + | statement-group; + +shebang-command = [^\n]*; +shebang = "#!" shebang-command "\n"; +sep:alias = (";" | "\n") (nl | ";")*; +statements:alias = statement (sep statement)*; +mml:root = shebang? (nl | ";")* statements? (nl | ";")*; diff --git a/examples/mml.treerack b/examples/mml.treerack index 6016a94..628c8f8 100644 --- a/examples/mml.treerack +++ b/examples/mml.treerack @@ -1,21 +1,10 @@ -// whitespace is ignored except for \n which is only ignored -// most of the time, but can serve as separator in: -// - list -// - struct -// - function args -// - statements -// - list, struct and function type constraints -// -// comments are not ignored because they are needed during formatting ws:ws = " " | "\b" | "\f" | "\r" | "\t" | "\v"; wsc:ws = comment; nl:alias = "\n"; -// comments can be line or block comments -// indentation can hold meaning line-comment-content:nows = [^\n]*; line-comment:alias:nows = "//" line-comment-content; -block-comment-content:nows = ([^*] | "*" [^/])*; // TODO: why is the :nows required here if it is there for block-comment? +block-comment-content:nows = ([^*] | "*" [^/])*; block-comment:alias:nows = "/*" block-comment-content "*/"; comment-part:alias = line-comment | block-comment; comment:alias = comment-part ("\n"? comment-part)*; @@ -24,408 +13,120 @@ decimal-digit:alias = [0-9]; octal-digit:alias = [0-7]; hexa-digit:alias = [0-9a-fA-F]; -// interger examples: 42, 0666, 0xfff decimal:alias:nows = [1-9] decimal-digit*; octal:alias:nows = "0" octal-digit*; hexa:alias:nows = "0" [xX] hexa-digit+; int = decimal | octal | hexa; -// float examples: .0, 0., 3.14, 1E-12 exponent:alias:nows = [eE] [+\-]? decimal-digit+; float:nows = decimal-digit+ "." decimal-digit* exponent? | "." decimal-digit+ exponent? | decimal-digit+ exponent; -// string example: "Hello, world!" -// only \ and " need to be escaped, e.g. allows new lines -// common escaped chars get unescaped, the rest gets unescaped to themselves string:nows = "\"" ([^\\"] | "\\" .)* "\""; true = "true"; false = "false"; bool:alias = true | false; -// symbols normally can have only \w chars: fooBar_baz -// basic symbols cannot start with a digit -// some positions allow strings to be used as symbols, e.g: let "123" 123 -// when this is not possible, dynamic symbols need to be used, but they are -// not allowed in every case, e.g: {symbol(foo()): "bar"} -// TODO: needs decision log for dynamic symbol -// TODO: exclude keywords -// -// dynamic symbol decision log: -// - every value is equatable -// - structs can act as hashtables (optimization is transparent) -// - in structs, must differentiate between symbol and value of a symbol when used as a key -// - js style [a] would be enough for the structs -// - the variables in a scope are like fields in a struct -// - [a] would be ambigous with the list as an expression -// - a logical loophole is closed with symbol(a) -// - dynamic-symbols need to be handled differently in match expressions and type expressions -symbol:nows = [a-zA-Z_][a-zA-Z_0-9]*; -static-symbol:alias = symbol | string; -dynamic-symbol = "symbol" "(" nl* expression nl* ")"; -symbol-expression:alias = static-symbol | dynamic-symbol; +symbol:nows = [a-zA-Z_][a-zA-Z_0-9]*; -// TODO: what happens when a dynamic symbol gets exported? - -// list items are separated by comma or new line (or both) -/* - [] - [a, b, c] - [ - a - b - c - ] - [1, 2, a..., [b, c], [d, [e]]...] -*/ spread-expression = primary-expression "..."; -list-sep:alias = ("," | "\n") (nl | ",")*; +list-sep:alias = (nl | ",")+; list-item:alias = expression | spread-expression; expression-list:alias = list-item (list-sep list-item)*; -// list example: [1, 2, 3] -// lists can be constructed with other lists: [l1..., l2...] -list-fact:alias = "[" (nl | ",")* expression-list? (nl | ",")* "]"; +list-fact:alias = "[" list-sep? expression-list? list-sep? "]"; list = list-fact; mutable-list = "~" nl* list-fact; -indexer-symbol = "[" nl* expression nl* "]"; -entry = (symbol-expression | indexer-symbol) nl* ":" nl* expression; -entry-list:alias = (entry | spread-expression) (list-sep (entry | spread-expression))*; -struct-fact:alias = "{" (nl | ",")* entry-list? (nl | ",")* "}"; -struct = struct-fact; -mutable-struct = "~" nl* struct-fact; +expression-key = "[" nl* expression nl* "]"; +entry = (symbol | string | expression-key) nl* ":" nl* expression; +entry-list:alias = (entry | spread-expression) (list-sep (entry | spread-expression))*; +struct-fact:alias = "{" list-sep? entry-list? list-sep? "}"; +struct = struct-fact; +mutable-struct = "~" nl* struct-fact; -channel = "<>" | "<" nl* int nl* ">"; +channel = "<>" | "<" nl* expression nl* ">"; -// and-expression:doc = "and" "(" (nl | ",")* expression-list? (nl | ",")* ")"; -// or-expression:doc = "or" "(" (nl | ",")* expression-list? (nl | ",")* ")"; +parameter-list:alias = symbol (list-sep symbol)*; +collect-parameter = "..." nl* symbol; +return = "return" (nl* expression)?; +block = "{" sep? statement-list? sep? "}"; +function-fact:alias = "(" list-sep? + parameter-list? + (list-sep collect-parameter)? + list-sep? ")" nl* + (simple-statement | block); +function = "fn" nl* function-fact; +effect = "fn" nl* "~" nl* function-fact; -argument-list:alias = static-symbol (list-sep static-symbol)*; -collect-symbol = "..." nl* static-symbol; -function-fact:alias = "(" (nl | ",")* - argument-list? - (nl | ",")* - collect-symbol? - (nl | ",")* ")" nl* - expression; -function = "fn" nl* function-fact; // can it ever cause a conflict with call and grouping? -effect = "fn" nl* "~" nl* function-fact; +range-from = expression; +range-to = expression; +range:alias = range-from? nl* ":" nl* range-to?; -/* -a[42] -a[3:9] -a[:9] -a[3:] -a[b][c][d] -a.foo -a."foo" -a.symbol(foo) -*/ -range-from = expression; -range-to = expression; -range-expression:alias = range-from? nl* ":" nl* range-to?; -indexer-expression:alias = expression | range-expression; -expression-indexer:alias = primary-expression "[" nl* indexer-expression nl* "]"; -symbol-indexer:alias = primary-expression nl* "." nl* symbol-expression; -indexer = expression-indexer | symbol-indexer; +simple-indexer:alias = primary-expression "[" nl* expression nl* "]"; +range-indexer:alias = primary-expression "[" nl* range nl* "]"; +expression-indexer = simple-indexer | range-indexer; +symbol-indexer = primary-expression nl* "." nl* symbol; -function-application = primary-expression "(" (nl | ",")* expression-list? (nl | ",")* ")"; - -if = "if" nl* expression nl* block - (nl* "else" nl* "if" nl* expression nl* block)* - (nl* "else" nl* block)?; - -default = "default" nl* ":"; -default-line:alias = default (nl | ";")* statement?; -case = "case" nl* expression nl* ":"; -case-line:alias = case ";"* statement?; -switch = "switch" nl* expression? nl* "{" (nl | ";")* - ((case-line | default-line) (sep (case-line | default-line | statement))*)? - (nl | ";")* "}"; - -int-type = "int"; -float-type = "float"; -string-type = "string"; -bool-type = "bool"; -error-type = "error"; - -/* -support: - - go { - foo() - bar() - } - - go { for { f() } } - go func() { for { f() } }() - fn f() { go f() }; go f() - -and not: - - go for {foo()} - -or: - - go for foo() - -because we don't know what the arguments are -*/ - -/* - - fn foo() { - bar() - baz() - } - let qux foo() - -equivalent to: - - let qux { - bar() - baz() - } -*/ - -primitive-type:alias = int-type - | float-type - | string-type - | bool-type - | error-type; - -type-alias-name:alias = static-symbol; - -static-range-from = int; -static-range-to = int; -static-range-expression:alias = static-range-from? nl* ":" nl* static-range-to?; -items-quantifier = int | static-range-expression; -// TODO: maybe this can be confusing with matching constants. Shall we support matching constants, values? - -items-type = items-quantifier - | type-set (nl* ":" nl* items-quantifier)? - | static-symbol nl* type-set (nl* ":" nl* items-quantifier)?; - -destructure-item = type-set | static-symbol nl* type-set; - -collect-destructure-item = "..." nl* destructure-item? - (nl* ":" items-quantifier)?; -list-destructure-type = destructure-item - (list-sep destructure-item)* - (list-sep collect-destructure-item)? - | collect-destructure-item; -list-type-fact:alias = "[" (nl | ",")* - (items-type | list-destructure-type)? - (nl | ",")* "]"; -list-type = list-type-fact; -mutable-list-type = "~" nl* list-type-fact; - -destructure-match-item = match-set - | static-symbol nl* match-set - | static-symbol nl* static-symbol nl* match-set; - -collect-destructure-match-item = "..." nl* destructure-match-item? - (nl* ":" items-quantifier)?; -list-destructure-match = destructure-match-item - (list-sep destructure-match-item)* - (list-sep collect-destructure-match-item)? - | collect-destructure-match-item; -list-match-fact:alias = "[" (nl | ",")* - (list-destructure-match | items-type)? - (nl | ",")* "]"; -list-match = list-match-fact; -mutable-list-match = "~" nl* list-match; - -entry-type = static-symbol (nl* ":" nl* destructure-item)?; -entry-types:alias = entry-type (list-sep entry-type)*; -struct-type-fact:alias = "{" (nl | ",")* entry-types? (nl | ",")* "}"; -struct-type = struct-type-fact; -mutable-struct-type = "~" nl* struct-type-fact; - -entry-match = static-symbol (nl* ":" nl* destructure-match-item)?; -entry-matches:alias = entry-match (list-sep entry-match)*; -struct-match-fact:alias = "{" (nl | ",")* entry-matches? (nl | ",")* "}"; -struct-match = struct-match-fact; -mutable-struct-match = "~" nl* struct-match-fact; - -arg-type = type-set | static-symbol nl* type-set; -args-type:alias = arg-type (list-sep arg-type)*; -function-type-fact:alias = "(" nl* args-type? nl* ")" - (type-set | static-symbol type-set)?; -function-type = "fn" nl* function-type-fact; -effect-type = "fn" nl* "~" nl* function-type-fact; - -// TODO: heavy naming crime - -receive-direction = "receive"; -send-direction = "send"; -channel-type = "<" nl* - (receive-direction | send-direction)? nl* - destructure-item? - nl* ">"; - -type-fact-group:alias = "(" nl* type-fact nl* ")"; -type-fact:alias = primitive-type - | type-alias-name - | list-type - | mutable-list-type - | struct-type - | mutable-struct-type - | function-type - | effect-type - | channel-type - | type-fact-group; - -type-set:alias = type-fact (nl* "|" nl* type-fact)*; -type-expression:alias = type-set | static-symbol type-set; - -match-fact:alias = list-match - | mutable-list-match - | struct-match - | mutable-struct-match; - -match-set:alias = type-set | match-fact; -match-expression:alias = match-set | static-symbol match-set; - -match-case = "case" nl* match-expression nl* ":"; -match-case-line:alias = match-case ";"* statement?; -match = "match" nl* expression nl* "{" (nl | ";")* - ((match-case-line | default-line) - (sep (match-case-line | default-line | statement))*)? - (nl | ";")* "}"; - -conditional:alias = if - | switch - | match; - -receive-call = "receive" "(" (nl | ",")* expression (nl | ",")* ")"; -receive-op = "<-" primary-expression; -receive-expression-group:alias = "(" nl* receive-expression nl* ")"; -receive-expression:alias = receive-call | receive-op | receive-expression-group; - -receive-assign-capture:alias = assignable nl* ("=" nl*)? receive-expression; -receive-assignment = "set" nl* receive-assign-capture; -receive-assignment-equal = assignable nl* "=" nl* receive-expression; -receive-capture:alias = symbol-expression nl* ("=" nl*)? receive-expression; -receive-definition = "let" nl* receive-capture; -receive-mutable-definition = "let" nl* "~" nl* receive-capture; -receive-statement:alias = receive-assignment | receive-definition; - -send-call:alias = "send" "(" (nl | ",")* expression list-sep expression (nl | ",")* ")"; -send-op:alias = primary-expression "<-" expression; -send-call-group:alias = "(" nl* send nl* ")"; -send = send-call | send-op | send-call-group; - -close = "close" "(" (nl | ",")* expression (nl | ",")* ")"; - -communication-group:alias = "(" nl* communication nl* ")"; -communication:alias = receive-expression | receive-statement | send | communication-group; - -select-case = "case" nl* communication nl* ":"; -select-case-line:alias = select-case ";"* statement?; -select = "select" nl* "{" (nl | ";")* - ((select-case-line | default-line) - (sep (select-case-line | default-line | statement))*)? - (nl | ";")* "}"; - -go = "go" nl* (function-application | block); - -/* -require . = "mml/foo" -require bar = "mml/foo" -require . "mml/foo" -require bar "mml/foo" -require "mml/foo" -require ( - . = "mml/foo" - bar = "mml/foo" - . "mml/foo" - bar "mml/foo" - "mml/foo" -) -require () -*/ -require-inline = "."; -require-fact = string - | (static-symbol | require-inline) (nl* "=")? nl* string; -require-facts:alias = require-fact (list-sep require-fact)*; -require-statement:alias = "require" nl* require-fact; -require-statement-group:alias = "require" "(" (nl | ",")* - require-facts? - (nl | ",")* ")"; -require = require-statement | require-statement-group; - -panic = "panic" "(" (nl | ",")* expression (nl | ",")* ")"; -recover = "recover" "(" (nl | ",")* ")"; - -block = "{" (nl | ";")* statements? (nl | ";")* "}"; -expression-group:alias = "(" nl* expression nl* ")"; +function-application = primary-expression "(" list-sep? expression-list? list-sep? ")"; +expression-group:alias = "(" nl* expression nl* ")"; primary-expression:alias = int | float | string | bool | symbol - | dynamic-symbol | list | mutable-list | struct | mutable-struct | channel - // | and-expression // only documentation - // | or-expression // only documentation | function | effect - | indexer - | function-application // pseudo-expression - | conditional // pseudo-expression - | receive-call - | select // pseudo-expression - | recover - | block // pseudo-expression + | expression-indexer + | symbol-indexer + | function-application + | receive | expression-group; -plus = "+"; -minus = "-"; -logical-not = "!"; -binary-not = "^"; -unary-operator:alias = plus | minus | logical-not | binary-not; -unary-expression = unary-operator primary-expression | receive-op; - -mul = "*"; -div = "/"; -mod = "%"; +binary-not = "^"; +binary-and = "&"; +binary-or = "|"; +xor = "^"; +and-not = "&^"; lshift = "<<"; rshift = ">>"; -binary-and = "&"; -and-not = "&^"; -add = "+"; -sub = "-"; -binary-or = "|"; -xor = "^"; +plus = "+"; +minus = "-"; +mul = "*"; +div = "/"; +mod = "%"; +add = "+"; +sub = "-"; +logical-not = "!"; eq = "=="; not-eq = "!="; less = "<"; less-or-eq = "<="; greater = ">"; greater-or-eq = ">="; +logical-and = "&&"; +logical-or = "||"; -logical-and = "&&"; -logical-or = "||"; +chain:alias = "->"; -chain = "->"; +unary-operator:alias = plus | minus | binary-not | logical-not; +unary-expression = unary-operator primary-expression; -binary-op0:alias = mul | div | mod | lshift | rshift | binary-and | and-not; -binary-op1:alias = add | sub | binary-or | xor; +binary-op0:alias = binary-and | and-not | lshift | rshift | mul | div | mod; +binary-op1:alias = binary-or | xor | add | sub; binary-op2:alias = eq | not-eq | less | less-or-eq | greater | greater-or-eq; binary-op3:alias = logical-and; binary-op4:alias = logical-or; -binary-op5:alias = chain; operand0:alias = primary-expression | unary-expression; operand1:alias = operand0 | binary0; @@ -434,14 +135,19 @@ operand3:alias = operand2 | binary2; operand4:alias = operand3 | binary3; operand5:alias = operand4 | binary4; -binary0 = operand0 (binary-op0 operand0)+; -binary1 = operand1 (binary-op1 operand1)+; -binary2 = operand2 (binary-op2 operand2)+; -binary3 = operand3 (binary-op3 operand3)+; -binary4 = operand4 (binary-op4 operand4)+; -binary5 = operand5 (binary-op5 operand5)+; +binary0 = operand0 (binary-op0 operand0)+; +binary1 = operand1 (binary-op1 operand1)+; +binary2 = operand2 (binary-op2 operand2)+; +binary3 = operand3 (binary-op3 operand3)+; +binary4 = operand4 (binary-op4 operand4)+; +chaining = operand5 (nl* chain nl* operand5)+; -binary-expression:alias = binary0 | binary1 | binary2 | binary3 | binary4 | binary5; +binary-expression:alias = binary0 + | binary1 + | binary2 + | binary3 + | binary4 + | chaining; ternary-expression = expression nl* "?" nl* expression nl* ":" nl* expression; @@ -450,84 +156,72 @@ expression:alias = primary-expression | binary-expression | ternary-expression; -// TODO: code() -// TODO: observability +if = "if" nl* expression nl* block + (nl* "else" nl* "if" nl* expression nl* block)* + (nl* "else" nl* block)?; -break = "break"; -continue = "continue"; -loop-control:alias = break | continue; +default = "default" nl* ":"; +default-line:alias = default ";"* statement?; +case = "case" nl* expression nl* ":"; +case-line:alias = case ";"* statement?; +switch = "switch" nl* expression? nl* "{" sep? + ((case-line | default-line) (sep (case-line | default-line | statement))*)? + sep? "}"; -in-expression = static-symbol nl* "in" nl* (expression | range-expression); -loop-expression = expression | in-expression; -loop = "for" nl* (block | loop-expression nl* block); +receive = "<<>" primary-expression; +receive-definition = "let" nl* symbol nl* receive; +receive-assignment = "set" nl* symbol nl* receive; +receive-statement:alias = receive-definition + | receive-assignment; +send = primary-expression "<<>" primary-expression; +communication:alias = receive | receive-statement | send; -/* -a = b -set c = d -set e f -set ( - g = h - i j -) -*/ -assignable:alias = symbol-expression | indexer; -assign-capture = assignable nl* ("=" nl*)? expression; -assign-set:alias = "set" nl* assign-capture; -assign-equal = assignable nl* "=" nl* expression; -assign-captures:alias = assign-capture (list-sep assign-capture)*; -assign-group:alias = "set" nl* "(" (nl | ",")* assign-captures? (nl | ",")* ")"; -assignment = assign-set | assign-equal | assign-group; +select-case = "case" nl* communication nl* ":"; +select-case-line:alias = select-case ";"* statement?; +select = "select" nl* "{" sep? + ((select-case-line | default-line) + (sep (select-case-line | default-line | statement))*)? + sep? "}"; -/* -let a = b -let c d -let ~ e = f -let ~ g h -let ( - i = j - k l - ~ m = n - ~ o p -) -let ~ ( - q = r - s t -) -*/ -value-capture-fact:alias = symbol-expression nl* ("=" nl*)? expression; +go = "go" nl* function-application; +defer = "defer" nl* function-application; + +break = "break"; +continue = "continue"; +range-over-expression = symbol nl* "in" nl* (expression | range) | range; +loop-expression:alias = expression | range-over-expression; +loop = "for" ((nl* loop-expression)? nl* block | nl* block); + +assignable:alias = symbol-indexer | simple-indexer | symbol; +assign-capture = assignable (nl* "=")? nl* expression; +assign-capture-list:alias = assign-capture (list-sep assign-capture)*; +assign-set:alias = "set" nl* assign-capture; +assign-equal:alias = assignable nl* "=" nl* expression; +assign-group:alias = "set" nl* "(" list-sep? assign-capture-list? list-sep? ")"; +assignment = assign-set | assign-equal | assign-group; + +value-capture-fact:alias = symbol (nl* "=")? nl* expression; value-capture = value-capture-fact; mutable-capture = "~" nl* value-capture-fact; value-definition = "let" nl* (value-capture | mutable-capture); -value-captures:alias = value-capture (list-sep value-capture)*; -mixed-captures:alias = (value-capture | mutable-capture) (list-sep (value-capture | mutable-capture))*; -value-definition-group = "let" nl* "(" (nl | ",")* mixed-captures? (nl | ",")* ")"; -mutable-definition-group = "let" nl* "~" nl* "(" (nl | ",")* value-captures? (nl | ",")* ")"; +mixed-capture-list:alias = (value-capture | mutable-capture) (list-sep (value-capture | mutable-capture))*; +value-capture-list:alias = value-capture (list-sep value-capture)*; +value-definition-group = "let" nl* "(" list-sep? mixed-capture-list? list-sep? ")"; +mutable-definition-group = "let" nl* "~" nl* "(" list-sep? value-capture-list? list-sep? ")"; -/* -fn a() b -fn ~ c() d -fn ( - e() f - ~ g() h -) -fn ~ ( - i() - j() -) -*/ -function-definition-fact:alias = static-symbol nl* function-fact; -function-capture = function-definition-fact; -effect-capture = "~" nl* function-definition-fact; -function-definition = "fn" nl* (function-capture | effect-capture); -function-captures:alias = function-capture (list-sep function-capture)*; -mixed-function-captures:alias = (function-capture | effect-capture) - (list-sep (function-capture | effect-capture))*; -function-definition-group = "fn" nl* "(" (nl | ",")* - mixed-function-captures? - (nl | ",")* ")"; -effect-definition-group = "fn" nl* "~" nl* "(" (nl | ",")* - function-captures? - (nl | ",")* ")"; +function-definition-fact:alias = symbol nl* function-fact; +function-capture = function-definition-fact; +effect-capture = "~" nl* function-definition-fact; +function-definition = "fn" nl* (function-capture | effect-capture); +function-capture-list:alias = function-capture (list-sep function-capture)*; +mixed-function-capture-list:alias = (function-capture | effect-capture) + (list-sep (function-capture | effect-capture))*; +function-definition-group = "fn" nl* "(" list-sep? + mixed-function-capture-list? + list-sep? ")"; +effect-definition-group = "fn" nl* "~" nl* "(" list-sep? + function-capture-list? + list-sep? ")"; definition:alias = value-definition | value-definition-group @@ -536,27 +230,42 @@ definition:alias = value-definition | function-definition-group | effect-definition-group; -type-alias = "type" nl* "alias" nl* static-symbol nl* type-set; -type-constraint = "type" nl* static-symbol nl* type-set; +require-inline = "."; +require-fact = string + | (symbol | require-inline) (nl* "=")? nl* string; +require-fact-list:alias = require-fact (list-sep require-fact)*; +require-statement:alias = "require" nl* require-fact; +require-statement-group:alias = "require" nl* "(" list-sep? + require-fact-list? + list-sep? ")"; +require = require-statement | require-statement-group; -statement-group:alias = "(" nl* statement nl* ")"; +export = "export" nl* definition; -statement:alias = send - | close - | panic - | require - | loop-control - | go - | loop - | assignment - | definition - | expression - | type-alias - | type-constraint - | statement-group; +simple-statement:alias = expression + | send + | go + | defer + | assignment + | simple-statement-group; +simple-statement-group:alias = "(" nl* simple-statement nl* ")"; +statement:alias = simple-statement + | return + | if + | switch + | select + | break + | continue + | loop + | definition + | require + | export + | statement-group; +statement-group:alias = "(" nl* statement nl* ")"; -shebang-command = [^\n]*; -shebang = "#!" shebang-command "\n"; -sep:alias = (";" | "\n") (nl | ";")*; -statements:alias = statement (sep statement)*; -mml:root = shebang? (nl | ";")* statements? (nl | ";")*; +sep:alias = (";" | nl)+; +statement-list:alias = statement (sep statement)*; + +shebang-command = [^\n]*; +shebang = "#!" shebang-command "\n"; +mml:root = shebang? sep? statement-list? sep?; diff --git a/mml_test.go b/mml_test.go index 60b7476..10c9f72 100644 --- a/mml_test.go +++ b/mml_test.go @@ -1,12 +1,6 @@ package treerack -import ( - "bytes" - "io" - "os" - "testing" - "time" -) +import "testing" func TestMML(t *testing.T) { s, err := openSyntaxFile("examples/mml.treerack") @@ -256,18 +250,6 @@ func TestMML(t *testing.T) { Name: "symbol", To: 3, }}, - }, { - title: "dynamic-symbol", - text: "symbol(a)", - nodes: []*Node{{ - Name: "dynamic-symbol", - To: 9, - Nodes: []*Node{{ - Name: "symbol", - From: 7, - To: 8, - }}, - }}, }}) }) @@ -417,10 +399,10 @@ func TestMML(t *testing.T) { }}, }, { title: "struct", - text: "{foo: 1, \"bar\": 2, symbol(baz): 3, [qux]: 4}", + text: "{foo: 1, \"bar\": 2}", nodes: []*Node{{ Name: "struct", - To: 44, + To: 18, Nodes: []*Node{{ Name: "entry", From: 1, @@ -447,42 +429,6 @@ func TestMML(t *testing.T) { From: 16, To: 17, }}, - }, { - Name: "entry", - From: 19, - To: 33, - Nodes: []*Node{{ - Name: "dynamic-symbol", - From: 19, - To: 30, - Nodes: []*Node{{ - Name: "symbol", - From: 26, - To: 29, - }}, - }, { - Name: "int", - From: 32, - To: 33, - }}, - }, { - Name: "entry", - From: 35, - To: 43, - Nodes: []*Node{{ - Name: "indexer-symbol", - From: 35, - To: 40, - Nodes: []*Node{{ - Name: "symbol", - From: 36, - To: 39, - }}, - }, { - Name: "int", - From: 42, - To: 43, - }}, }}, }}, }, { @@ -553,7 +499,7 @@ func TestMML(t *testing.T) { }}, }}, }, { - title: "struct with indexer key", + title: "with indexer key", text: "{[a]: b}", nodes: []*Node{{ Name: "struct", @@ -563,7 +509,7 @@ func TestMML(t *testing.T) { From: 1, To: 7, Nodes: []*Node{{ - Name: "indexer-symbol", + Name: "expression-key", From: 1, To: 4, Nodes: []*Node{{ @@ -793,7 +739,7 @@ func TestMML(t *testing.T) { From: 7, To: 8, }, { - Name: "collect-symbol", + Name: "collect-parameter", From: 10, To: 14, Nodes: []*Node{{ @@ -844,7 +790,7 @@ func TestMML(t *testing.T) { title: "indexer", text: "a[42]", nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 5, Nodes: []*Node{{ Name: "symbol", @@ -859,7 +805,7 @@ func TestMML(t *testing.T) { title: "range indexer", text: "a[3:9]", nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 6, Nodes: []*Node{{ Name: "symbol", @@ -888,7 +834,7 @@ func TestMML(t *testing.T) { title: "range indexer, lower unbound", text: "a[:9]", nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 5, Nodes: []*Node{{ Name: "symbol", @@ -908,7 +854,7 @@ func TestMML(t *testing.T) { title: "range indexer, upper unbound", text: "a[3:]", nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 5, Nodes: []*Node{{ Name: "symbol", @@ -928,13 +874,13 @@ func TestMML(t *testing.T) { title: "indexer, chained", text: "a[b][c][d]", nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 10, Nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 7, Nodes: []*Node{{ - Name: "indexer", + Name: "expression-indexer", To: 4, Nodes: []*Node{{ Name: "symbol", @@ -963,7 +909,7 @@ func TestMML(t *testing.T) { title: "symbol indexer", text: "a.b", nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 3, Nodes: []*Node{{ Name: "symbol", @@ -974,52 +920,17 @@ func TestMML(t *testing.T) { To: 3, }}, }}, - }, { - title: "symbol indexer, with string", - text: "a.\"b\"", - nodes: []*Node{{ - Name: "indexer", - To: 5, - Nodes: []*Node{{ - Name: "symbol", - To: 1, - }, { - Name: "string", - From: 2, - To: 5, - }}, - }}, - }, { - title: "symbol indexer, with dynamic symbol", - text: "a.symbol(b)", - nodes: []*Node{{ - Name: "indexer", - To: 11, - Nodes: []*Node{{ - Name: "symbol", - To: 1, - }, { - Name: "dynamic-symbol", - From: 2, - To: 11, - Nodes: []*Node{{ - Name: "symbol", - From: 9, - To: 10, - }}, - }}, - }}, }, { title: "chained symbol indexer", text: "a.b.c.d", nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 7, Nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 5, Nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 3, Nodes: []*Node{{ Name: "symbol", @@ -1044,10 +955,10 @@ func TestMML(t *testing.T) { title: "chained symbol indexer on new line", text: "a\n.b\n.c", nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 7, Nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 4, Nodes: []*Node{{ Name: "symbol", @@ -1067,10 +978,10 @@ func TestMML(t *testing.T) { title: "chained symbol indexer on new line after dot", text: "a.\nb.\nc", nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 7, Nodes: []*Node{{ - Name: "indexer", + Name: "symbol-indexer", To: 4, Nodes: []*Node{{ Name: "symbol", @@ -1548,344 +1459,20 @@ func TestMML(t *testing.T) { }}) }) - t.Run("match", func(t *testing.T) { - runTestsSyntax(t, s, []testItem{{ - title: "match expression, empty", - text: "match a {}", - nodes: []*Node{{ - Name: "match", - To: 10, - Nodes: []*Node{{ - Name: "symbol", - From: 6, - To: 7, - }}, - }}, - }, { - title: "match expression", - text: "match a {\ncase [first, ...rest]: first\n}", - nodes: []*Node{{ - Name: "match", - To: 40, - Nodes: []*Node{{ - Name: "symbol", - From: 6, - To: 7, - }, { - Name: "match-case", - From: 10, - To: 32, - Nodes: []*Node{{ - Name: "list-type", - From: 15, - To: 31, - Nodes: []*Node{{ - Name: "list-destructure-type", - From: 16, - To: 30, - Nodes: []*Node{{ - Name: "destructure-item", - From: 16, - To: 21, - Nodes: []*Node{{ - Name: "symbol", - From: 16, - To: 21, - }}, - }, { - Name: "collect-destructure-item", - From: 23, - To: 30, - Nodes: []*Node{{ - Name: "destructure-item", - From: 26, - To: 30, - Nodes: []*Node{{ - Name: "symbol", - From: 26, - To: 30, - }}, - }}, - }}, - }}, - }}, - }, { - Name: "symbol", - From: 33, - To: 38, - }}, - }}, - }, { - title: "match expression, multiple cases", - text: `match a { - case [0]: [] - case [2:]: a[2:] - default: error("invalid length") - }`, - nodes: []*Node{{ - Name: "match", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "match-case", - Nodes: []*Node{{ - Name: "list-type", - Nodes: []*Node{{ - Name: "items-type", - Nodes: []*Node{{ - Name: "items-quantifier", - Nodes: []*Node{{ - Name: "int", - }}, - }}, - }}, - }}, - }, { - Name: "list", - }, { - Name: "match-case", - Nodes: []*Node{{ - Name: "list-type", - Nodes: []*Node{{ - Name: "items-type", - Nodes: []*Node{{ - Name: "items-quantifier", - Nodes: []*Node{{ - Name: "static-range-from", - Nodes: []*Node{{ - Name: "int", - }}, - }}, - }}, - }}, - }}, - }, { - Name: "indexer", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "range-from", - Nodes: []*Node{{ - Name: "int", - }}, - }}, - }, { - Name: "default", - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "string", - }}, - }}, - }}, - ignorePosition: true, - }, { - title: "match function", - text: `match a { - case fn () int: a() - default: 42 - }`, - nodes: []*Node{{ - Name: "match", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "match-case", - Nodes: []*Node{{ - Name: "function-type", - Nodes: []*Node{{ - Name: "int-type", - }}, - }}, - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }, { - Name: "default", - }, { - Name: "int", - }}, - }}, - ignorePosition: true, - }, { - title: "match expression, combined", - text: `match a { - case [fn (int)]: a[0]() - default: 42 - }`, - nodes: []*Node{{ - Name: "match", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "match-case", - Nodes: []*Node{{ - Name: "list-type", - Nodes: []*Node{{ - Name: "items-type", - Nodes: []*Node{{ - Name: "function-type", - Nodes: []*Node{{ - Name: "arg-type", - Nodes: []*Node{{ - Name: "int-type", - }}, - }}, - }}, - }}, - }}, - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "indexer", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "int", - }}, - }}, - }, { - Name: "default", - }, { - Name: "int", - }}, - }}, - ignorePosition: true, - }, { - title: "match expression, complex", - text: `match a { - case [first T int|string, op fn ([T, int, ...T]) int, ...rest T]: - op([first, now(), rest...]) - default: - error("invalid list") - }`, - nodes: []*Node{{ - Name: "match", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "match-case", - Nodes: []*Node{{ - Name: "list-match", - Nodes: []*Node{{ - Name: "list-destructure-match", - Nodes: []*Node{{ - Name: "destructure-match-item", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "symbol", - }, { - Name: "int-type", - }, { - Name: "string-type", - }}, - }, { - Name: "destructure-match-item", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "function-type", - Nodes: []*Node{{ - Name: "arg-type", - Nodes: []*Node{{ - Name: "list-type", - Nodes: []*Node{{ - Name: "list-destructure-type", - Nodes: []*Node{{ - Name: "destructure-item", - Nodes: []*Node{{ - Name: "symbol", - }}, - }, { - Name: "destructure-item", - Nodes: []*Node{{ - Name: "int-type", - }}, - }, { - Name: "collect-destructure-item", - Nodes: []*Node{{ - Name: "destructure-item", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, - }}, - }}, - }, { - Name: "int-type", - }}, - }}, - }, { - Name: "collect-destructure-match-item", - Nodes: []*Node{{ - Name: "destructure-match-item", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "symbol", - }}, - }}, - }}, - }}, - }}, - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "list", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }, { - Name: "spread-expression", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, - }, { - Name: "default", - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "string", - }}, - }}, - }}, - ignorePosition: true, - }}) - }) - t.Run("send/receive", func(t *testing.T) { runTestsSyntax(t, s, []testItem{{ title: "receive op", - text: "<-chan", + text: "<<>chan", nodes: []*Node{{ - Name: "unary-expression", + Name: "receive", Nodes: []*Node{{ - Name: "receive-op", - Nodes: []*Node{{ - Name: "symbol", - }}, + Name: "symbol", }}, }}, ignorePosition: true, }, { title: "send op", - text: "chan <- a", + text: "chan <<> a", nodes: []*Node{{ Name: "send", Nodes: []*Node{{ @@ -1909,10 +1496,10 @@ func TestMML(t *testing.T) { }, { title: "select", text: `select { - case let a <-r: s <- a - case s <- f(): g() - default: h() - }`, + case let a <<>r: s <<> a + case s <<> f(): g() + default: h() + }`, nodes: []*Node{{ Name: "select", Nodes: []*Node{{ @@ -1922,7 +1509,7 @@ func TestMML(t *testing.T) { Nodes: []*Node{{ Name: "symbol", }, { - Name: "receive-op", + Name: "receive", Nodes: []*Node{{ Name: "symbol", }}, @@ -1963,78 +1550,6 @@ func TestMML(t *testing.T) { }}, }}, ignorePosition: true, - }, { - title: "select, call", - text: `select { - case let a receive(r): f() - case send(s, g()): h() - default: i() - }`, - nodes: []*Node{{ - Name: "select", - Nodes: []*Node{{ - Name: "select-case", - Nodes: []*Node{{ - Name: "receive-definition", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "receive-call", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }, { - Name: "select-case", - Nodes: []*Node{{ - Name: "send", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }, { - Name: "default", - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, - ignorePosition: true, - }}) - }) - - t.Run("block", func(t *testing.T) { - runTestsSyntax(t, s, []testItem{{ - title: "block", - ignorePosition: true, - text: "{ f() }", - nodes: []*Node{{ - Name: "block", - Nodes: []*Node{{ - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, }}) }) @@ -2052,28 +1567,6 @@ func TestMML(t *testing.T) { }}, }}, ignorePosition: true, - }, { - title: "go, block", - text: "go { for { f() } }", - nodes: []*Node{{ - Name: "go", - Nodes: []*Node{{ - Name: "block", - Nodes: []*Node{{ - Name: "loop", - Nodes: []*Node{{ - Name: "block", - Nodes: []*Node{{ - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }}, - }}, - }}, - }}, - }}, - }}, - ignorePosition: true, }}) }) @@ -2330,7 +1823,7 @@ func TestMML(t *testing.T) { title: "binary 3, 4, 5", text: "a * b + c * d == e * f && g || h -> f()", nodes: []*Node{{ - Name: "binary5", + Name: "chaining", Nodes: []*Node{{ Name: "binary4", Nodes: []*Node{{ @@ -2382,8 +1875,6 @@ func TestMML(t *testing.T) { }, { Name: "symbol", }}, - }, { - Name: "chain", }, { Name: "function-application", Nodes: []*Node{{ @@ -2499,10 +1990,7 @@ func TestMML(t *testing.T) { nodes: []*Node{{ Name: "loop", Nodes: []*Node{{ - Name: "loop-expression", - Nodes: []*Node{{ - Name: "symbol", - }}, + Name: "symbol", }, { Name: "block", }}, @@ -2514,20 +2002,17 @@ func TestMML(t *testing.T) { nodes: []*Node{{ Name: "loop", Nodes: []*Node{{ - Name: "loop-expression", + Name: "range-over-expression", Nodes: []*Node{{ - Name: "in-expression", + Name: "symbol", + }, { + Name: "list", Nodes: []*Node{{ - Name: "symbol", + Name: "int", }, { - Name: "list", - Nodes: []*Node{{ - Name: "int", - }, { - Name: "int", - }, { - Name: "int", - }}, + Name: "int", + }, { + Name: "int", }}, }}, }, { @@ -2541,27 +2026,24 @@ func TestMML(t *testing.T) { nodes: []*Node{{ Name: "loop", Nodes: []*Node{{ - Name: "loop-expression", + Name: "range-over-expression", Nodes: []*Node{{ - Name: "in-expression", + Name: "symbol", + }, { + Name: "range-from", Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "range-from", - Nodes: []*Node{{ - Name: "unary-expression", - Nodes: []*Node{{ - Name: "minus", - }, { - Name: "int", - }}, - }}, - }, { - Name: "range-to", + Name: "unary-expression", Nodes: []*Node{{ + Name: "minus", + }, { Name: "int", }}, }}, + }, { + Name: "range-to", + Nodes: []*Node{{ + Name: "int", + }}, }}, }, { Name: "block", @@ -2571,21 +2053,18 @@ func TestMML(t *testing.T) { }, { title: "loop control", text: `for i in l { - if i % 2 == 0 { - break - } - }`, + if i % 2 == 0 { + break + } + }`, nodes: []*Node{{ Name: "loop", Nodes: []*Node{{ - Name: "loop-expression", + Name: "range-over-expression", Nodes: []*Node{{ - Name: "in-expression", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "symbol", - }}, + Name: "symbol", + }, { + Name: "symbol", }}, }, { Name: "block", @@ -2610,7 +2089,7 @@ func TestMML(t *testing.T) { }, { Name: "block", Nodes: []*Node{{ - Name: "break", + Name: "symbol", }}, }}, }}, @@ -2627,12 +2106,9 @@ func TestMML(t *testing.T) { nodes: []*Node{{ Name: "assignment", Nodes: []*Node{{ - Name: "assign-equal", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "symbol", - }}, + Name: "symbol", + }, { + Name: "symbol", }}, }}, ignorePosition: true, @@ -2903,129 +2379,4 @@ func TestMML(t *testing.T) { ignorePosition: true, }}) }) - - t.Run("type", func(t *testing.T) { - runTestsSyntax(t, s, []testItem{{ - title: "type constraint", - text: ` - type a fn ([]) int - fn a(l) len(l) - `, - nodes: []*Node{{ - Name: "type-constraint", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "function-type", - Nodes: []*Node{{ - Name: "arg-type", - Nodes: []*Node{{ - Name: "list-type", - }}, - }, { - Name: "int-type", - }}, - }}, - }, { - Name: "function-definition", - Nodes: []*Node{{ - Name: "function-capture", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "symbol", - }, { - Name: "function-application", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "symbol", - }}, - }}, - }}, - }}, - ignorePosition: true, - }, { - title: "type alias", - text: "type alias a int|(fn () int|string)|string", - nodes: []*Node{{ - Name: "type-alias", - Nodes: []*Node{{ - Name: "symbol", - }, { - Name: "int-type", - }, { - Name: "function-type", - Nodes: []*Node{{ - Name: "int-type", - }, { - Name: "string-type", - }}, - }, { - Name: "string-type", - }}, - }}, - ignorePosition: true, - }, { - title: "statement group", - text: "(for {})", - nodes: []*Node{{ - Name: "loop", - Nodes: []*Node{{ - Name: "block", - }}, - }}, - ignorePosition: true, - }}) - }) -} - -func TestMMLFile(t *testing.T) { - if testing.Short() { - t.Skip() - } - - const n = 180 - - s, err := openSyntaxFile("examples/mml.treerack") - if err != nil { - t.Error(err) - return - } - - s.Init() - - f, err := os.Open("examples/test.mml") - if err != nil { - t.Error(err) - return - } - - defer f.Close() - - var d time.Duration - for i := 0; i < n && !t.Failed(); i++ { - func() { - if _, err := f.Seek(0, 0); err != nil { - t.Error(err) - return - } - - b := bytes.NewBuffer(nil) - if _, err := io.Copy(b, f); err != nil { - t.Error(err) - return - } - - start := time.Now() - _, err = s.Parse(b) - d += time.Now().Sub(start) - - if err != nil { - t.Error(err) - } - }() - } - - t.Log("average duration:", d/n) } diff --git a/mmlexp_test.go b/mmlexp_test.go new file mode 100644 index 0000000..43d38ea --- /dev/null +++ b/mmlexp_test.go @@ -0,0 +1,3031 @@ +package treerack + +import ( + "bytes" + "io" + "os" + "testing" + "time" +) + +func TestMMLExp(t *testing.T) { + s, err := openSyntaxFile("examples/mml-exp.treerack") + if err != nil { + t.Error(err) + return + } + + t.Run("comment", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "empty", + node: &Node{Name: "mml"}, + }, { + title: "single line comment", + text: "// foo bar baz", + nodes: []*Node{{ + Name: "line-comment-content", + From: 2, + To: 14, + }}, + }, { + title: "multiple line comments", + text: "// foo bar\n// baz qux", + nodes: []*Node{{ + Name: "line-comment-content", + From: 2, + To: 10, + }, { + Name: "line-comment-content", + From: 13, + To: 21, + }}, + }, { + title: "block comment", + text: "/* foo bar baz */", + nodes: []*Node{{ + Name: "block-comment-content", + From: 2, + To: 15, + }}, + }, { + title: "block comments", + text: "/* foo bar */\n/* baz qux */", + nodes: []*Node{{ + Name: "block-comment-content", + From: 2, + To: 11, + }, { + Name: "block-comment-content", + From: 16, + To: 25, + }}, + }, { + title: "mixed comments", + text: "// foo\n/* bar */\n// baz", + nodes: []*Node{{ + Name: "line-comment-content", + From: 2, + To: 6, + }, { + Name: "block-comment-content", + From: 9, + To: 14, + }, { + Name: "line-comment-content", + From: 19, + To: 23, + }}, + }}) + }) + + t.Run("int", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "int", + text: "42", + nodes: []*Node{{ + Name: "int", + To: 2, + }}, + }, { + title: "ints", + text: "1; 2; 3", + nodes: []*Node{{ + Name: "int", + To: 1, + }, { + Name: "int", + From: 3, + To: 4, + }, { + Name: "int", + From: 6, + To: 7, + }}, + }, { + title: "int, octal", + text: "052", + nodes: []*Node{{ + Name: "int", + To: 3, + }}, + }, { + title: "int, hexa", + text: "0x2a", + nodes: []*Node{{ + Name: "int", + To: 4, + }}, + }}) + }) + + t.Run("float", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "float, 0.", + text: "0.", + nodes: []*Node{{ + Name: "float", + To: 2, + }}, + }, { + title: "float, 72.40", + text: "72.40", + nodes: []*Node{{ + Name: "float", + To: 5, + }}, + }, { + title: "float, 072.40", + text: "072.40", + nodes: []*Node{{ + Name: "float", + To: 6, + }}, + }, { + title: "float, 2.71828", + text: "2.71828", + nodes: []*Node{{ + Name: "float", + To: 7, + }}, + }, { + title: "float, 6.67428e-11", + text: "6.67428e-11", + nodes: []*Node{{ + Name: "float", + To: 11, + }}, + }, { + title: "float, 1E6", + text: "1E6", + nodes: []*Node{{ + Name: "float", + To: 3, + }}, + }, { + title: "float, .25", + text: ".25", + nodes: []*Node{{ + Name: "float", + To: 3, + }}, + }, { + title: "float, .12345E+5", + text: ".12345E+5", + nodes: []*Node{{ + Name: "float", + To: 9, + }}, + }, { + + title: "float on a new line", + text: "f()\n.9", + nodes: []*Node{{ + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "float", + }}, + ignorePosition: true, + }}) + }) + + t.Run("string", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "string, empty", + text: "\"\"", + nodes: []*Node{{ + Name: "string", + To: 2, + }}, + }, { + title: "string", + text: "\"foo\"", + nodes: []*Node{{ + Name: "string", + To: 5, + }}, + }, { + title: "string, with new line", + text: "\"foo\nbar\"", + nodes: []*Node{{ + Name: "string", + To: 9, + }}, + }, { + title: "string, with escaped new line", + text: "\"foo\\nbar\"", + nodes: []*Node{{ + Name: "string", + To: 10, + }}, + }, { + title: "string, with quotes", + text: "\"foo \\\"bar\\\" baz\"", + nodes: []*Node{{ + Name: "string", + To: 17, + }}, + }}) + }) + + t.Run("bool", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "bool, true", + text: "true", + nodes: []*Node{{ + Name: "true", + To: 4, + }}, + }, { + title: "bool, false", + text: "false", + nodes: []*Node{{ + Name: "false", + To: 5, + }}, + }}) + }) + + t.Run("symbol", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "symbol", + text: "foo", + nodes: []*Node{{ + Name: "symbol", + To: 3, + }}, + }, { + title: "dynamic-symbol", + text: "symbol(a)", + nodes: []*Node{{ + Name: "dynamic-symbol", + To: 9, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }}, + }}, + }}) + }) + + t.Run("list", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "empty list", + text: "[]", + nodes: []*Node{{ + Name: "list", + To: 2, + }}, + }, { + title: "list", + text: "[a, b, c]", + nodes: []*Node{{ + Name: "list", + To: 9, + Nodes: []*Node{{ + Name: "symbol", + From: 1, + To: 2, + }, { + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 7, + To: 8, + }}, + }}, + }, { + title: "list, new lines", + text: "[ \n a \n b \n c \n ]", + nodes: []*Node{{ + Name: "list", + To: 17, + Nodes: []*Node{{ + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 8, + To: 9, + }, { + Name: "symbol", + From: 12, + To: 13, + }}, + }}, + }, { + title: "list, complex", + text: "[a, b, c..., [d, e], [f, [g]]...]", + nodes: []*Node{{ + Name: "list", + To: 33, + Nodes: []*Node{{ + Name: "symbol", + From: 1, + To: 2, + }, { + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "spread-expression", + From: 7, + To: 11, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }}, + }, { + Name: "list", + From: 13, + To: 19, + Nodes: []*Node{{ + Name: "symbol", + From: 14, + To: 15, + }, { + Name: "symbol", + From: 17, + To: 18, + }}, + }, { + Name: "spread-expression", + From: 21, + To: 32, + Nodes: []*Node{{ + Name: "list", + From: 21, + To: 29, + Nodes: []*Node{{ + Name: "symbol", + From: 22, + To: 23, + }, { + Name: "list", + From: 25, + To: 28, + Nodes: []*Node{{ + Name: "symbol", + From: 26, + To: 27, + }}, + }}, + }}, + }}, + }}, + }}) + }) + + t.Run("mutable list", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "mutable list", + text: "~[a, b, c]", + nodes: []*Node{{ + Name: "mutable-list", + To: 10, + Nodes: []*Node{{ + Name: "symbol", + From: 2, + To: 3, + }, { + Name: "symbol", + From: 5, + To: 6, + }, { + Name: "symbol", + From: 8, + To: 9, + }}, + }}, + }}) + }) + + t.Run("struct", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "empty struct", + text: "{}", + nodes: []*Node{{ + Name: "struct", + To: 2, + }}, + }, { + title: "struct", + text: "{foo: 1, \"bar\": 2, symbol(baz): 3, [qux]: 4}", + nodes: []*Node{{ + Name: "struct", + To: 44, + Nodes: []*Node{{ + Name: "entry", + From: 1, + To: 7, + Nodes: []*Node{{ + Name: "symbol", + From: 1, + To: 4, + }, { + Name: "int", + From: 6, + To: 7, + }}, + }, { + Name: "entry", + From: 9, + To: 17, + Nodes: []*Node{{ + Name: "string", + From: 9, + To: 14, + }, { + Name: "int", + From: 16, + To: 17, + }}, + }, { + Name: "entry", + From: 19, + To: 33, + Nodes: []*Node{{ + Name: "dynamic-symbol", + From: 19, + To: 30, + Nodes: []*Node{{ + Name: "symbol", + From: 26, + To: 29, + }}, + }, { + Name: "int", + From: 32, + To: 33, + }}, + }, { + Name: "entry", + From: 35, + To: 43, + Nodes: []*Node{{ + Name: "indexer-symbol", + From: 35, + To: 40, + Nodes: []*Node{{ + Name: "symbol", + From: 36, + To: 39, + }}, + }, { + Name: "int", + From: 42, + To: 43, + }}, + }}, + }}, + }, { + title: "struct, complex", + text: "{foo: 1, {bar: 2}..., {baz: {}}...}", + nodes: []*Node{{ + Name: "struct", + To: 35, + Nodes: []*Node{{ + Name: "entry", + From: 1, + To: 7, + Nodes: []*Node{{ + Name: "symbol", + From: 1, + To: 4, + }, { + Name: "int", + From: 6, + To: 7, + }}, + }, { + Name: "spread-expression", + From: 9, + To: 20, + Nodes: []*Node{{ + Name: "struct", + From: 9, + To: 17, + Nodes: []*Node{{ + Name: "entry", + From: 10, + To: 16, + Nodes: []*Node{{ + Name: "symbol", + From: 10, + To: 13, + }, { + Name: "int", + From: 15, + To: 16, + }}, + }}, + }}, + }, { + Name: "spread-expression", + From: 22, + To: 34, + Nodes: []*Node{{ + Name: "struct", + From: 22, + To: 31, + Nodes: []*Node{{ + Name: "entry", + From: 23, + To: 30, + Nodes: []*Node{{ + Name: "symbol", + From: 23, + To: 26, + }, { + Name: "struct", + From: 28, + To: 30, + }}, + }}, + }}, + }}, + }}, + }, { + title: "struct with indexer key", + text: "{[a]: b}", + nodes: []*Node{{ + Name: "struct", + To: 8, + Nodes: []*Node{{ + Name: "entry", + From: 1, + To: 7, + Nodes: []*Node{{ + Name: "indexer-symbol", + From: 1, + To: 4, + Nodes: []*Node{{ + Name: "symbol", + From: 2, + To: 3, + }}, + }, { + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }}, + }}) + }) + + t.Run("mutable struct", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "mutable struct", + text: "~{foo: 1}", + nodes: []*Node{{ + Name: "mutable-struct", + To: 9, + Nodes: []*Node{{ + Name: "entry", + From: 2, + To: 8, + Nodes: []*Node{{ + Name: "symbol", + From: 2, + To: 5, + }, { + Name: "int", + From: 7, + To: 8, + }}, + }}, + }}, + }}) + }) + + t.Run("channel", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "channel", + text: "<>", + nodes: []*Node{{ + Name: "channel", + To: 2, + }}, + }, { + title: "buffered channel", + text: "<42>", + nodes: []*Node{{ + Name: "channel", + To: 4, + Nodes: []*Node{{ + Name: "int", + From: 1, + To: 3, + }}, + }}, + }}) + }) + + t.Run("boolean expressions", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "and expression", + text: "and(a, b, c)", + nodes: []*Node{{ + Name: "function-application", + To: 12, + Nodes: []*Node{{ + Name: "symbol", + To: 3, + }, { + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 7, + To: 8, + }, { + Name: "symbol", + From: 10, + To: 11, + }}, + }}, + }, { + title: "or expression", + text: "or(a, b, c)", + nodes: []*Node{{ + Name: "function-application", + To: 11, + Nodes: []*Node{{ + Name: "symbol", + To: 2, + }, { + Name: "symbol", + From: 3, + To: 4, + }, { + Name: "symbol", + From: 6, + To: 7, + }, { + Name: "symbol", + From: 9, + To: 10, + }}, + }}, + }}) + }) + + t.Run("function", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "function", + text: "fn () 42", + nodes: []*Node{{ + Name: "function", + To: 8, + Nodes: []*Node{{ + Name: "int", + From: 6, + To: 8, + }}, + }}, + }, { + title: "function, noop", + text: "fn () {;}", + nodes: []*Node{{ + Name: "function", + To: 9, + Nodes: []*Node{{ + Name: "block", + From: 6, + To: 9, + }}, + }}, + }, { + title: "function with args", + text: "fn (a, b, c) [a, b, c]", + nodes: []*Node{{ + Name: "function", + To: 22, + Nodes: []*Node{{ + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 7, + To: 8, + }, { + Name: "symbol", + From: 10, + To: 11, + }, { + Name: "list", + From: 13, + To: 22, + Nodes: []*Node{{ + Name: "symbol", + From: 14, + To: 15, + }, { + Name: "symbol", + From: 17, + To: 18, + }, { + Name: "symbol", + From: 20, + To: 21, + }}, + }}, + }}, + }, { + title: "function with args in new lines", + text: "fn ( \n a \n b \n c ) [a, b, c]", + nodes: []*Node{{ + Name: "function", + To: 28, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }, { + Name: "symbol", + From: 11, + To: 12, + }, { + Name: "symbol", + From: 15, + To: 16, + }, { + Name: "list", + From: 19, + To: 28, + Nodes: []*Node{{ + Name: "symbol", + From: 20, + To: 21, + }, { + Name: "symbol", + From: 23, + To: 24, + }, { + Name: "symbol", + From: 26, + To: 27, + }}, + }}, + }}, + }, { + title: "function with spread arg", + text: "fn (a, b, ...c) [a, b, c]", + nodes: []*Node{{ + Name: "function", + To: 25, + Nodes: []*Node{{ + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 7, + To: 8, + }, { + Name: "collect-symbol", + From: 10, + To: 14, + Nodes: []*Node{{ + Name: "symbol", + From: 13, + To: 14, + }}, + }, { + Name: "list", + From: 16, + To: 25, + Nodes: []*Node{{ + Name: "symbol", + From: 17, + To: 18, + }, { + Name: "symbol", + From: 20, + To: 21, + }, { + Name: "symbol", + From: 23, + To: 24, + }}, + }}, + }}, + }}) + }) + + t.Run("effect", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "effect", + text: "fn ~ () 42", + nodes: []*Node{{ + Name: "effect", + To: 10, + Nodes: []*Node{{ + Name: "int", + From: 8, + To: 10, + }}, + }}, + }}) + }) + + t.Run("indexer", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "indexer", + text: "a[42]", + nodes: []*Node{{ + Name: "indexer", + To: 5, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "int", + From: 2, + To: 4, + }}, + }}, + }, { + title: "range indexer", + text: "a[3:9]", + nodes: []*Node{{ + Name: "indexer", + To: 6, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "range-from", + From: 2, + To: 3, + Nodes: []*Node{{ + Name: "int", + From: 2, + To: 3, + }}, + }, { + Name: "range-to", + From: 4, + To: 5, + Nodes: []*Node{{ + Name: "int", + From: 4, + To: 5, + }}, + }}, + }}, + }, { + title: "range indexer, lower unbound", + text: "a[:9]", + nodes: []*Node{{ + Name: "indexer", + To: 5, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "range-to", + From: 3, + To: 4, + Nodes: []*Node{{ + Name: "int", + From: 3, + To: 4, + }}, + }}, + }}, + }, { + title: "range indexer, upper unbound", + text: "a[3:]", + nodes: []*Node{{ + Name: "indexer", + To: 5, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "range-from", + From: 2, + To: 3, + Nodes: []*Node{{ + Name: "int", + From: 2, + To: 3, + }}, + }}, + }}, + }, { + title: "indexer, chained", + text: "a[b][c][d]", + nodes: []*Node{{ + Name: "indexer", + To: 10, + Nodes: []*Node{{ + Name: "indexer", + To: 7, + Nodes: []*Node{{ + Name: "indexer", + To: 4, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }}, + }, { + Name: "symbol", + From: 5, + To: 6, + }}, + }, { + Name: "symbol", + From: 8, + To: 9, + }}, + }}, + }}) + }) + + t.Run("symbol indexer", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "symbol indexer", + text: "a.b", + nodes: []*Node{{ + Name: "indexer", + To: 3, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }}, + }}, + }, { + title: "symbol indexer, with string", + text: "a.\"b\"", + nodes: []*Node{{ + Name: "indexer", + To: 5, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "string", + From: 2, + To: 5, + }}, + }}, + }, { + title: "symbol indexer, with dynamic symbol", + text: "a.symbol(b)", + nodes: []*Node{{ + Name: "indexer", + To: 11, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "dynamic-symbol", + From: 2, + To: 11, + Nodes: []*Node{{ + Name: "symbol", + From: 9, + To: 10, + }}, + }}, + }}, + }, { + title: "chained symbol indexer", + text: "a.b.c.d", + nodes: []*Node{{ + Name: "indexer", + To: 7, + Nodes: []*Node{{ + Name: "indexer", + To: 5, + Nodes: []*Node{{ + Name: "indexer", + To: 3, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }}, + }, { + Name: "symbol", + From: 4, + To: 5, + }}, + }, { + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }, { + title: "chained symbol indexer on new line", + text: "a\n.b\n.c", + nodes: []*Node{{ + Name: "indexer", + To: 7, + Nodes: []*Node{{ + Name: "indexer", + To: 4, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 3, + To: 4, + }}, + }, { + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }, { + title: "chained symbol indexer on new line after dot", + text: "a.\nb.\nc", + nodes: []*Node{{ + Name: "indexer", + To: 7, + Nodes: []*Node{{ + Name: "indexer", + To: 4, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 3, + To: 4, + }}, + }, { + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }}) + }) + + t.Run("function application", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "function application", + text: "f()", + nodes: []*Node{{ + Name: "function-application", + To: 3, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }}, + }}, + }, { + title: "function application, single arg", + text: "f(a)", + nodes: []*Node{{ + Name: "function-application", + To: 4, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }}, + }}, + }, { + title: "function application, multiple args", + text: "f(a, b, c)", + nodes: []*Node{{ + Name: "function-application", + To: 10, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }, { + Name: "symbol", + From: 5, + To: 6, + }, { + Name: "symbol", + From: 8, + To: 9, + }}, + }}, + }, { + title: "function application, multiple args, new line", + text: "f(a\nb\nc\n)", + nodes: []*Node{{ + Name: "function-application", + To: 9, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }, { + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }, { + title: "function application, spread", + text: "f(a, b..., c, d...)", + nodes: []*Node{{ + Name: "function-application", + To: 19, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }, { + Name: "spread-expression", + From: 5, + To: 9, + Nodes: []*Node{{ + Name: "symbol", + From: 5, + To: 6, + }}, + }, { + Name: "symbol", + From: 11, + To: 12, + }, { + Name: "spread-expression", + From: 14, + To: 18, + Nodes: []*Node{{ + Name: "symbol", + From: 14, + To: 15, + }}, + }}, + }}, + }, { + title: "chained function application", + text: "f(a)(b)(c)", + nodes: []*Node{{ + Name: "function-application", + To: 10, + Nodes: []*Node{{ + Name: "function-application", + To: 7, + Nodes: []*Node{{ + Name: "function-application", + To: 4, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 2, + To: 3, + }}, + }, { + Name: "symbol", + From: 5, + To: 6, + }}, + }, { + Name: "symbol", + From: 8, + To: 9, + }}, + }}, + }, { + title: "embedded function application", + text: "f(g(h(a)))", + nodes: []*Node{{ + Name: "function-application", + To: 10, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "function-application", + From: 2, + To: 9, + Nodes: []*Node{{ + Name: "symbol", + From: 2, + To: 3, + }, { + Name: "function-application", + From: 4, + To: 8, + Nodes: []*Node{{ + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }}, + }}, + }}) + }) + + t.Run("if", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "if", + text: "if a { b() }", + nodes: []*Node{{ + Name: "if", + To: 12, + Nodes: []*Node{{ + Name: "symbol", + From: 3, + To: 4, + }, { + Name: "block", + From: 5, + To: 12, + Nodes: []*Node{{ + Name: "function-application", + From: 7, + To: 10, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }}, + }}, + }}, + }}, + }, { + title: "if, else", + text: "if a { b } else { c }", + nodes: []*Node{{ + Name: "if", + To: 21, + Nodes: []*Node{{ + Name: "symbol", + From: 3, + To: 4, + }, { + Name: "block", + From: 5, + To: 10, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }}, + }, { + Name: "block", + From: 16, + To: 21, + Nodes: []*Node{{ + Name: "symbol", + From: 18, + To: 19, + }}, + }}, + }}, + }, { + title: "if, else if, else if, else", + text: "if a { b }\nelse if c { d }\nelse if e { f }\nelse { g }", + nodes: []*Node{{ + Name: "if", + From: 0, + To: 53, + Nodes: []*Node{{ + Name: "symbol", + From: 3, + To: 4, + }, { + Name: "block", + From: 5, + To: 10, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }}, + }, { + Name: "symbol", + From: 19, + To: 20, + }, { + Name: "block", + From: 21, + To: 26, + Nodes: []*Node{{ + Name: "symbol", + From: 23, + To: 24, + }}, + }, { + Name: "symbol", + From: 35, + To: 36, + }, { + Name: "block", + From: 37, + To: 42, + Nodes: []*Node{{ + Name: "symbol", + From: 39, + To: 40, + }}, + }, { + Name: "block", + From: 48, + To: 53, + Nodes: []*Node{{ + Name: "symbol", + From: 50, + To: 51, + }}, + }}, + }}, + }}) + }) + + t.Run("switch", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "switch, empty", + text: "switch {default:}", + nodes: []*Node{{ + Name: "switch", + To: 17, + Nodes: []*Node{{ + Name: "default", + From: 8, + To: 16, + }}, + }}, + }, { + title: "switch, empty cases", + text: ` + switch { + case a: + case b: + default: + f() + } + `, + nodes: []*Node{{ + Name: "switch", + Nodes: []*Node{{ + Name: "case", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "case", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "default", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "switch, single case", + text: "switch a {case b: c}", + nodes: []*Node{{ + Name: "switch", + To: 20, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }, { + Name: "case", + From: 10, + To: 17, + Nodes: []*Node{{ + Name: "symbol", + From: 15, + To: 16, + }}, + }, { + Name: "symbol", + From: 18, + To: 19, + }}, + }}, + }, { + title: "switch", + text: "switch a {case b: c; case d: e; default: f}", + nodes: []*Node{{ + Name: "switch", + To: 43, + Nodes: []*Node{{ + Name: "symbol", + From: 7, + To: 8, + }, { + Name: "case", + From: 10, + To: 17, + Nodes: []*Node{{ + Name: "symbol", + From: 15, + To: 16, + }}, + }, { + Name: "symbol", + From: 18, + To: 19, + }, { + Name: "case", + From: 21, + To: 28, + Nodes: []*Node{{ + Name: "symbol", + From: 26, + To: 27, + }}, + }, { + Name: "symbol", + From: 29, + To: 30, + }, { + Name: "default", + From: 32, + To: 40, + }, { + Name: "symbol", + From: 41, + To: 42, + }}, + }}, + }, { + title: "switch, all new lines", + text: "switch \n a \n { \n case \n b \n : \n c \n case \n d \n : \n e \n default \n : \n f \n }", + nodes: []*Node{{ + Name: "switch", + To: 74, + Nodes: []*Node{{ + Name: "symbol", + From: 9, + To: 10, + }, { + Name: "case", + From: 17, + To: 29, + Nodes: []*Node{{ + Name: "symbol", + From: 24, + To: 25, + }}, + }, { + Name: "symbol", + From: 32, + To: 33, + }, { + Name: "case", + From: 36, + To: 48, + Nodes: []*Node{{ + Name: "symbol", + From: 43, + To: 44, + }}, + }, { + Name: "symbol", + From: 51, + To: 52, + }, { + Name: "default", + From: 55, + To: 66, + }, { + Name: "symbol", + From: 69, + To: 70, + }}, + }}, + }}) + }) + + t.Run("match", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "match expression, empty", + text: "match a {}", + nodes: []*Node{{ + Name: "match", + To: 10, + Nodes: []*Node{{ + Name: "symbol", + From: 6, + To: 7, + }}, + }}, + }, { + title: "match expression", + text: "match a {\ncase [first, ...rest]: first\n}", + nodes: []*Node{{ + Name: "match", + To: 40, + Nodes: []*Node{{ + Name: "symbol", + From: 6, + To: 7, + }, { + Name: "match-case", + From: 10, + To: 32, + Nodes: []*Node{{ + Name: "list-type", + From: 15, + To: 31, + Nodes: []*Node{{ + Name: "list-destructure-type", + From: 16, + To: 30, + Nodes: []*Node{{ + Name: "destructure-item", + From: 16, + To: 21, + Nodes: []*Node{{ + Name: "symbol", + From: 16, + To: 21, + }}, + }, { + Name: "collect-destructure-item", + From: 23, + To: 30, + Nodes: []*Node{{ + Name: "destructure-item", + From: 26, + To: 30, + Nodes: []*Node{{ + Name: "symbol", + From: 26, + To: 30, + }}, + }}, + }}, + }}, + }}, + }, { + Name: "symbol", + From: 33, + To: 38, + }}, + }}, + }, { + title: "match expression, multiple cases", + text: `match a { + case [0]: [] + case [2:]: a[2:] + default: error("invalid length") + }`, + nodes: []*Node{{ + Name: "match", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "match-case", + Nodes: []*Node{{ + Name: "list-type", + Nodes: []*Node{{ + Name: "items-type", + Nodes: []*Node{{ + Name: "items-quantifier", + Nodes: []*Node{{ + Name: "int", + }}, + }}, + }}, + }}, + }, { + Name: "list", + }, { + Name: "match-case", + Nodes: []*Node{{ + Name: "list-type", + Nodes: []*Node{{ + Name: "items-type", + Nodes: []*Node{{ + Name: "items-quantifier", + Nodes: []*Node{{ + Name: "static-range-from", + Nodes: []*Node{{ + Name: "int", + }}, + }}, + }}, + }}, + }}, + }, { + Name: "indexer", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "range-from", + Nodes: []*Node{{ + Name: "int", + }}, + }}, + }, { + Name: "default", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "match function", + text: `match a { + case fn () int: a() + default: 42 + }`, + nodes: []*Node{{ + Name: "match", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "match-case", + Nodes: []*Node{{ + Name: "function-type", + Nodes: []*Node{{ + Name: "int-type", + }}, + }}, + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "default", + }, { + Name: "int", + }}, + }}, + ignorePosition: true, + }, { + title: "match expression, combined", + text: `match a { + case [fn (int)]: a[0]() + default: 42 + }`, + nodes: []*Node{{ + Name: "match", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "match-case", + Nodes: []*Node{{ + Name: "list-type", + Nodes: []*Node{{ + Name: "items-type", + Nodes: []*Node{{ + Name: "function-type", + Nodes: []*Node{{ + Name: "arg-type", + Nodes: []*Node{{ + Name: "int-type", + }}, + }}, + }}, + }}, + }}, + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "indexer", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "int", + }}, + }}, + }, { + Name: "default", + }, { + Name: "int", + }}, + }}, + ignorePosition: true, + }, { + title: "match expression, complex", + text: `match a { + case [first T int|string, op fn ([T, int, ...T]) int, ...rest T]: + op([first, now(), rest...]) + default: + error("invalid list") + }`, + nodes: []*Node{{ + Name: "match", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "match-case", + Nodes: []*Node{{ + Name: "list-match", + Nodes: []*Node{{ + Name: "list-destructure-match", + Nodes: []*Node{{ + Name: "destructure-match-item", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }, { + Name: "int-type", + }, { + Name: "string-type", + }}, + }, { + Name: "destructure-match-item", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "function-type", + Nodes: []*Node{{ + Name: "arg-type", + Nodes: []*Node{{ + Name: "list-type", + Nodes: []*Node{{ + Name: "list-destructure-type", + Nodes: []*Node{{ + Name: "destructure-item", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "destructure-item", + Nodes: []*Node{{ + Name: "int-type", + }}, + }, { + Name: "collect-destructure-item", + Nodes: []*Node{{ + Name: "destructure-item", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }}, + }}, + }, { + Name: "int-type", + }}, + }}, + }, { + Name: "collect-destructure-match-item", + Nodes: []*Node{{ + Name: "destructure-match-item", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + }}, + }}, + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "list", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "spread-expression", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }, { + Name: "default", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("send/receive", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "receive op", + text: "<-chan", + nodes: []*Node{{ + Name: "unary-expression", + Nodes: []*Node{{ + Name: "receive-op", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "send op", + text: "chan <- a", + nodes: []*Node{{ + Name: "send", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("select", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "select, empty", + text: "select {\n}", + nodes: []*Node{{ + Name: "select", + To: 10, + }}, + }, { + title: "select", + text: `select { + case let a <-r: s <- a + case s <- f(): g() + default: h() + }`, + nodes: []*Node{{ + Name: "select", + Nodes: []*Node{{ + Name: "select-case", + Nodes: []*Node{{ + Name: "receive-definition", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "receive-op", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }, { + Name: "send", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "select-case", + Nodes: []*Node{{ + Name: "send", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "default", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "select, call", + text: `select { + case let a receive(r): f() + case send(s, g()): h() + default: i() + }`, + nodes: []*Node{{ + Name: "select", + Nodes: []*Node{{ + Name: "select-case", + Nodes: []*Node{{ + Name: "receive-definition", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "receive-call", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "select-case", + Nodes: []*Node{{ + Name: "send", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "default", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("block", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "block", + ignorePosition: true, + text: "{ f() }", + nodes: []*Node{{ + Name: "block", + Nodes: []*Node{{ + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }}) + }) + + t.Run("go", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "go", + text: "go f()", + nodes: []*Node{{ + Name: "go", + Nodes: []*Node{{ + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "go, block", + text: "go { for { f() } }", + nodes: []*Node{{ + Name: "go", + Nodes: []*Node{{ + Name: "block", + Nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "block", + Nodes: []*Node{{ + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("require", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "require, dot, equal", + text: "require . = \"mml/foo\"", + nodes: []*Node{{ + Name: "require", + Nodes: []*Node{{ + Name: "require-fact", + Nodes: []*Node{{ + Name: "require-inline", + }, { + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "require, symbol, equal", + text: "require bar = \"mml/foo\"", + nodes: []*Node{{ + Name: "require", + Nodes: []*Node{{ + Name: "require-fact", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "require, symbol", + text: "require bar \"mml/foo\"", + nodes: []*Node{{ + Name: "require", + Nodes: []*Node{{ + Name: "require-fact", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "require", + text: "require \"mml/foo\"", + nodes: []*Node{{ + Name: "require", + Nodes: []*Node{{ + Name: "require-fact", + Nodes: []*Node{{ + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "require, group", + text: `require ( + . = "mml/foo" + bar = "mml/foo" + . "mml/foo" + bar "mml/foo" + "mml/foo" + )`, + nodes: []*Node{{ + Name: "require", + Nodes: []*Node{{ + Name: "require-fact", + Nodes: []*Node{{ + Name: "require-inline", + }, { + Name: "string", + }}, + }, { + Name: "require-fact", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "string", + }}, + }, { + Name: "require-fact", + Nodes: []*Node{{ + Name: "require-inline", + }, { + Name: "string", + }}, + }, { + Name: "require-fact", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "string", + }}, + }, { + Name: "require-fact", + Nodes: []*Node{{ + Name: "string", + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("expression group", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "expression group", + text: "(fn (a) a)(a)", + nodes: []*Node{{ + Name: "function-application", + Nodes: []*Node{{ + Name: "function", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "symbol", + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("unary", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "unary operator", + text: "!foo", + nodes: []*Node{{ + Name: "unary-expression", + Nodes: []*Node{{ + Name: "logical-not", + }, { + Name: "symbol", + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("binary", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "binary 0", + text: "a * b", + nodes: []*Node{{ + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }}, + ignorePosition: true, + }, { + title: "binary 1", + text: "a * b + c * d", + nodes: []*Node{{ + Name: "binary1", + Nodes: []*Node{{ + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }, { + Name: "add", + }, { + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "binary 2", + text: "a * b + c * d == e * f", + nodes: []*Node{{ + Name: "binary2", + Nodes: []*Node{{ + Name: "binary1", + Nodes: []*Node{{ + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }, { + Name: "add", + }, { + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }}, + }, { + Name: "eq", + }, { + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "binary 1, 1, 1", + text: "a + b + c", + nodes: []*Node{{ + Name: "binary1", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "add", + }, { + Name: "symbol", + }, { + Name: "add", + }, { + Name: "symbol", + }}, + }}, + ignorePosition: true, + }, { + title: "binary 3, 4, 5", + text: "a * b + c * d == e * f && g || h -> f()", + nodes: []*Node{{ + Name: "binary5", + Nodes: []*Node{{ + Name: "binary4", + Nodes: []*Node{{ + Name: "binary3", + Nodes: []*Node{{ + Name: "binary2", + Nodes: []*Node{{ + Name: "binary1", + Nodes: []*Node{{ + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }, { + Name: "add", + }, { + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }}, + }, { + Name: "eq", + }, { + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mul", + }, { + Name: "symbol", + }}, + }}, + }, { + Name: "logical-and", + }, { + Name: "symbol", + }}, + }, { + Name: "logical-or", + }, { + Name: "symbol", + }}, + }, { + Name: "chain", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("ternary", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "ternary expression", + text: "a ? b : c", + nodes: []*Node{{ + Name: "ternary-expression", + To: 9, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 8, + To: 9, + }}, + }}, + }, { + title: "multiple ternary expressions, consequence", + text: "a ? b ? c : d : e", + nodes: []*Node{{ + Name: "ternary-expression", + To: 17, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "ternary-expression", + From: 4, + To: 13, + Nodes: []*Node{{ + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "symbol", + From: 8, + To: 9, + }, { + Name: "symbol", + From: 12, + To: 13, + }}, + }, { + Name: "symbol", + From: 16, + To: 17, + }}, + }}, + }, { + title: "multiple ternary expressions, alternative", + text: "a ? b : c ? d : e", + nodes: []*Node{{ + Name: "ternary-expression", + To: 17, + Nodes: []*Node{{ + Name: "symbol", + To: 1, + }, { + Name: "symbol", + From: 4, + To: 5, + }, { + Name: "ternary-expression", + From: 8, + To: 17, + Nodes: []*Node{{ + Name: "symbol", + From: 8, + To: 9, + }, { + Name: "symbol", + From: 12, + To: 13, + }, { + Name: "symbol", + From: 16, + To: 17, + }}, + }}, + }}, + }}) + }) + + t.Run("loop", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "infinite loop", + text: "for {}", + nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "block", + }}, + }}, + ignorePosition: true, + }, { + title: "conditional loop", + text: "for foo {}", + nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "loop-expression", + Nodes: []*Node{{ + Name: "symbol", + }}, + }, { + Name: "block", + }}, + }}, + ignorePosition: true, + }, { + title: "in list loop", + text: "for i in [1, 2, 3] {}", + nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "loop-expression", + Nodes: []*Node{{ + Name: "in-expression", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "list", + Nodes: []*Node{{ + Name: "int", + }, { + Name: "int", + }, { + Name: "int", + }}, + }}, + }}, + }, { + Name: "block", + }}, + }}, + ignorePosition: true, + }, { + title: "in range loop", + text: "for i in -3:42 {}", + nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "loop-expression", + Nodes: []*Node{{ + Name: "in-expression", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "range-from", + Nodes: []*Node{{ + Name: "unary-expression", + Nodes: []*Node{{ + Name: "minus", + }, { + Name: "int", + }}, + }}, + }, { + Name: "range-to", + Nodes: []*Node{{ + Name: "int", + }}, + }}, + }}, + }, { + Name: "block", + }}, + }}, + ignorePosition: true, + }, { + title: "loop control", + text: `for i in l { + if i % 2 == 0 { + break + } + }`, + nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "loop-expression", + Nodes: []*Node{{ + Name: "in-expression", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }, { + Name: "block", + Nodes: []*Node{{ + Name: "if", + Nodes: []*Node{{ + Name: "binary2", + Nodes: []*Node{{ + Name: "binary0", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "mod", + }, { + Name: "int", + }}, + }, { + Name: "eq", + }, { + Name: "int", + }}, + }, { + Name: "block", + Nodes: []*Node{{ + Name: "break", + }}, + }}, + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("assign", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "assign, eq", + text: "a = b", + nodes: []*Node{{ + Name: "assignment", + Nodes: []*Node{{ + Name: "assign-equal", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "assign, set, eq", + text: "set a = b", + nodes: []*Node{{ + Name: "assignment", + Nodes: []*Node{{ + Name: "assign-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "assign, set", + text: "set a b", + nodes: []*Node{{ + Name: "assignment", + Nodes: []*Node{{ + Name: "assign-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "assign, group", + text: `set ( + a = b + c d + )`, + nodes: []*Node{{ + Name: "assignment", + Nodes: []*Node{{ + Name: "assign-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "assign-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("define", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "define, eq", + text: "let a = b", + nodes: []*Node{{ + Name: "value-definition", + Nodes: []*Node{{ + Name: "value-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define", + text: "let a b", + nodes: []*Node{{ + Name: "value-definition", + Nodes: []*Node{{ + Name: "value-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define mutable, eq", + text: "let ~ a = b", + nodes: []*Node{{ + Name: "value-definition", + Nodes: []*Node{{ + Name: "mutable-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define mutable", + text: "let ~ a b", + nodes: []*Node{{ + Name: "value-definition", + Nodes: []*Node{{ + Name: "mutable-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "mixed define group", + text: `let ( + a = b + c d + ~ e f + ~ g h + )`, + nodes: []*Node{{ + Name: "value-definition-group", + Nodes: []*Node{{ + Name: "value-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "value-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "mutable-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "mutable-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "mutable define group", + text: `let ~ ( + a = b + c d + )`, + nodes: []*Node{{ + Name: "mutable-definition-group", + Nodes: []*Node{{ + Name: "value-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "value-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define function", + text: "fn a() b", + nodes: []*Node{{ + Name: "function-definition", + Nodes: []*Node{{ + Name: "function-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define effect", + text: "fn ~ a() b", + nodes: []*Node{{ + Name: "function-definition", + Nodes: []*Node{{ + Name: "effect-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define function group", + text: `fn ( + a() b + ~ c() d + )`, + nodes: []*Node{{ + Name: "function-definition-group", + Nodes: []*Node{{ + Name: "function-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "effect-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "define effect group", + text: `fn ~ ( + a() b + c() d + )`, + nodes: []*Node{{ + Name: "effect-definition-group", + Nodes: []*Node{{ + Name: "function-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }, { + Name: "function-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + ignorePosition: true, + }}) + }) + + t.Run("type", func(t *testing.T) { + runTestsSyntax(t, s, []testItem{{ + title: "type constraint", + text: ` + type a fn ([]) int + fn a(l) len(l) + `, + nodes: []*Node{{ + Name: "type-constraint", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "function-type", + Nodes: []*Node{{ + Name: "arg-type", + Nodes: []*Node{{ + Name: "list-type", + }}, + }, { + Name: "int-type", + }}, + }}, + }, { + Name: "function-definition", + Nodes: []*Node{{ + Name: "function-capture", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }, { + Name: "function-application", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "symbol", + }}, + }}, + }}, + }}, + ignorePosition: true, + }, { + title: "type alias", + text: "type alias a int|(fn () int|string)|string", + nodes: []*Node{{ + Name: "type-alias", + Nodes: []*Node{{ + Name: "symbol", + }, { + Name: "int-type", + }, { + Name: "function-type", + Nodes: []*Node{{ + Name: "int-type", + }, { + Name: "string-type", + }}, + }, { + Name: "string-type", + }}, + }}, + ignorePosition: true, + }, { + title: "statement group", + text: "(for {})", + nodes: []*Node{{ + Name: "loop", + Nodes: []*Node{{ + Name: "block", + }}, + }}, + ignorePosition: true, + }}) + }) +} + +func TestMMLFile(t *testing.T) { + if testing.Short() { + t.Skip() + } + + const n = 180 + + s, err := openSyntaxFile("examples/mml.treerack") + if err != nil { + t.Error(err) + return + } + + s.Init() + + f, err := os.Open("examples/test.mml") + if err != nil { + t.Error(err) + return + } + + defer f.Close() + + var d time.Duration + for i := 0; i < n && !t.Failed(); i++ { + func() { + if _, err := f.Seek(0, 0); err != nil { + t.Error(err) + return + } + + b := bytes.NewBuffer(nil) + if _, err := io.Copy(b, f); err != nil { + t.Error(err) + return + } + + start := time.Now() + _, err = s.Parse(b) + d += time.Now().Sub(start) + + if err != nil { + t.Error(err) + } + }() + } + + t.Log("average duration:", d/n) +}