add recent mml syntax

This commit is contained in:
Arpad Ryszka 2018-08-19 19:36:23 +02:00
parent f7ccebbf93
commit 73d78d8030
5 changed files with 3820 additions and 1165 deletions

View File

@ -5,6 +5,8 @@ import "testing"
func checkNodes(t *testing.T, ignorePosition bool, left, right []*Node) { func checkNodes(t *testing.T, ignorePosition bool, left, right []*Node) {
if len(left) != len(right) { if len(left) != len(right) {
t.Error("length doesn't match", len(left), len(right)) t.Error("length doesn't match", len(left), len(right))
t.Log(left)
t.Log(right)
return return
} }

562
examples/mml-exp.treerack Normal file
View File

@ -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 | ";")*;

View File

@ -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"; ws:ws = " " | "\b" | "\f" | "\r" | "\t" | "\v";
wsc:ws = comment; wsc:ws = comment;
nl:alias = "\n"; nl:alias = "\n";
// comments can be line or block comments
// indentation can hold meaning
line-comment-content:nows = [^\n]*; line-comment-content:nows = [^\n]*;
line-comment:alias:nows = "//" line-comment-content; 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 "*/"; block-comment:alias:nows = "/*" block-comment-content "*/";
comment-part:alias = line-comment | block-comment; comment-part:alias = line-comment | block-comment;
comment:alias = comment-part ("\n"? comment-part)*; comment:alias = comment-part ("\n"? comment-part)*;
@ -24,408 +13,120 @@ decimal-digit:alias = [0-9];
octal-digit:alias = [0-7]; octal-digit:alias = [0-7];
hexa-digit:alias = [0-9a-fA-F]; hexa-digit:alias = [0-9a-fA-F];
// interger examples: 42, 0666, 0xfff
decimal:alias:nows = [1-9] decimal-digit*; decimal:alias:nows = [1-9] decimal-digit*;
octal:alias:nows = "0" octal-digit*; octal:alias:nows = "0" octal-digit*;
hexa:alias:nows = "0" [xX] hexa-digit+; hexa:alias:nows = "0" [xX] hexa-digit+;
int = decimal | octal | hexa; int = decimal | octal | hexa;
// float examples: .0, 0., 3.14, 1E-12
exponent:alias:nows = [eE] [+\-]? decimal-digit+; exponent:alias:nows = [eE] [+\-]? decimal-digit+;
float:nows = decimal-digit+ "." decimal-digit* exponent? float:nows = decimal-digit+ "." decimal-digit* exponent?
| "." decimal-digit+ exponent? | "." 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 = "\"" ([^\\"] | "\\" .)* "\""; string:nows = "\"" ([^\\"] | "\\" .)* "\"";
true = "true"; true = "true";
false = "false"; false = "false";
bool:alias = true | 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]*; 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 "..."; spread-expression = primary-expression "...";
list-sep:alias = ("," | "\n") (nl | ",")*; list-sep:alias = (nl | ",")+;
list-item:alias = expression | spread-expression; list-item:alias = expression | spread-expression;
expression-list:alias = list-item (list-sep list-item)*; expression-list:alias = list-item (list-sep list-item)*;
// list example: [1, 2, 3] list-fact:alias = "[" list-sep? expression-list? list-sep? "]";
// lists can be constructed with other lists: [l1..., l2...]
list-fact:alias = "[" (nl | ",")* expression-list? (nl | ",")* "]";
list = list-fact; list = list-fact;
mutable-list = "~" nl* list-fact; mutable-list = "~" nl* list-fact;
indexer-symbol = "[" nl* expression nl* "]"; expression-key = "[" nl* expression nl* "]";
entry = (symbol-expression | indexer-symbol) nl* ":" nl* expression; entry = (symbol | string | expression-key) nl* ":" nl* expression;
entry-list:alias = (entry | spread-expression) (list-sep (entry | spread-expression))*; entry-list:alias = (entry | spread-expression) (list-sep (entry | spread-expression))*;
struct-fact:alias = "{" (nl | ",")* entry-list? (nl | ",")* "}"; struct-fact:alias = "{" list-sep? entry-list? list-sep? "}";
struct = struct-fact; struct = struct-fact;
mutable-struct = "~" nl* struct-fact; mutable-struct = "~" nl* struct-fact;
channel = "<>" | "<" nl* int nl* ">"; channel = "<>" | "<" nl* expression nl* ">";
// and-expression:doc = "and" "(" (nl | ",")* expression-list? (nl | ",")* ")"; parameter-list:alias = symbol (list-sep symbol)*;
// or-expression:doc = "or" "(" (nl | ",")* expression-list? (nl | ",")* ")"; collect-parameter = "..." nl* symbol;
return = "return" (nl* expression)?;
argument-list:alias = static-symbol (list-sep static-symbol)*; block = "{" sep? statement-list? sep? "}";
collect-symbol = "..." nl* static-symbol; function-fact:alias = "(" list-sep?
function-fact:alias = "(" (nl | ",")* parameter-list?
argument-list? (list-sep collect-parameter)?
(nl | ",")* list-sep? ")" nl*
collect-symbol? (simple-statement | block);
(nl | ",")* ")" nl* function = "fn" nl* function-fact;
expression;
function = "fn" nl* function-fact; // can it ever cause a conflict with call and grouping?
effect = "fn" nl* "~" nl* function-fact; 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-from = expression;
range-to = expression; range-to = expression;
range-expression:alias = range-from? nl* ":" nl* range-to?; range: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 | ",")* ")"; 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;
if = "if" nl* expression nl* block function-application = primary-expression "(" list-sep? expression-list? list-sep? ")";
(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* ")"; expression-group:alias = "(" nl* expression nl* ")";
primary-expression:alias = int primary-expression:alias = int
| float | float
| string | string
| bool | bool
| symbol | symbol
| dynamic-symbol
| list | list
| mutable-list | mutable-list
| struct | struct
| mutable-struct | mutable-struct
| channel | channel
// | and-expression // only documentation
// | or-expression // only documentation
| function | function
| effect | effect
| indexer | expression-indexer
| function-application // pseudo-expression | symbol-indexer
| conditional // pseudo-expression | function-application
| receive-call | receive
| select // pseudo-expression
| recover
| block // pseudo-expression
| expression-group; | expression-group;
binary-not = "^";
binary-and = "&";
binary-or = "|";
xor = "^";
and-not = "&^";
lshift = "<<";
rshift = ">>";
plus = "+"; plus = "+";
minus = "-"; minus = "-";
logical-not = "!";
binary-not = "^";
unary-operator:alias = plus | minus | logical-not | binary-not;
unary-expression = unary-operator primary-expression | receive-op;
mul = "*"; mul = "*";
div = "/"; div = "/";
mod = "%"; mod = "%";
lshift = "<<";
rshift = ">>";
binary-and = "&";
and-not = "&^";
add = "+"; add = "+";
sub = "-"; sub = "-";
binary-or = "|";
xor = "^";
logical-not = "!";
eq = "=="; eq = "==";
not-eq = "!="; not-eq = "!=";
less = "<"; less = "<";
less-or-eq = "<="; less-or-eq = "<=";
greater = ">"; greater = ">";
greater-or-eq = ">="; greater-or-eq = ">=";
logical-and = "&&"; logical-and = "&&";
logical-or = "||"; logical-or = "||";
chain = "->"; chain:alias = "->";
binary-op0:alias = mul | div | mod | lshift | rshift | binary-and | and-not; unary-operator:alias = plus | minus | binary-not | logical-not;
binary-op1:alias = add | sub | binary-or | xor; unary-expression = unary-operator primary-expression;
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-op2:alias = eq | not-eq | less | less-or-eq | greater | greater-or-eq;
binary-op3:alias = logical-and; binary-op3:alias = logical-and;
binary-op4:alias = logical-or; binary-op4:alias = logical-or;
binary-op5:alias = chain;
operand0:alias = primary-expression | unary-expression; operand0:alias = primary-expression | unary-expression;
operand1:alias = operand0 | binary0; operand1:alias = operand0 | binary0;
@ -439,9 +140,14 @@ binary1 = operand1 (binary-op1 operand1)+;
binary2 = operand2 (binary-op2 operand2)+; binary2 = operand2 (binary-op2 operand2)+;
binary3 = operand3 (binary-op3 operand3)+; binary3 = operand3 (binary-op3 operand3)+;
binary4 = operand4 (binary-op4 operand4)+; binary4 = operand4 (binary-op4 operand4)+;
binary5 = operand5 (binary-op5 operand5)+; 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; ternary-expression = expression nl* "?" nl* expression nl* ":" nl* expression;
@ -450,84 +156,72 @@ expression:alias = primary-expression
| binary-expression | binary-expression
| ternary-expression; | ternary-expression;
// TODO: code() if = "if" nl* expression nl* block
// TODO: observability (nl* "else" nl* "if" nl* expression nl* block)*
(nl* "else" nl* block)?;
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? "}";
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;
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? "}";
go = "go" nl* function-application;
defer = "defer" nl* function-application;
break = "break"; break = "break";
continue = "continue"; continue = "continue";
loop-control:alias = break | 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);
in-expression = static-symbol nl* "in" nl* (expression | range-expression); assignable:alias = symbol-indexer | simple-indexer | symbol;
loop-expression = expression | in-expression; assign-capture = assignable (nl* "=")? nl* expression;
loop = "for" nl* (block | loop-expression nl* block); assign-capture-list:alias = assign-capture (list-sep assign-capture)*;
/*
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-set:alias = "set" nl* assign-capture;
assign-equal = assignable nl* "=" nl* expression; assign-equal:alias = assignable nl* "=" nl* expression;
assign-captures:alias = assign-capture (list-sep assign-capture)*; assign-group:alias = "set" nl* "(" list-sep? assign-capture-list? list-sep? ")";
assign-group:alias = "set" nl* "(" (nl | ",")* assign-captures? (nl | ",")* ")";
assignment = assign-set | assign-equal | assign-group; assignment = assign-set | assign-equal | assign-group;
/* value-capture-fact:alias = symbol (nl* "=")? nl* expression;
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; value-capture = value-capture-fact;
mutable-capture = "~" nl* value-capture-fact; mutable-capture = "~" nl* value-capture-fact;
value-definition = "let" nl* (value-capture | mutable-capture); value-definition = "let" nl* (value-capture | mutable-capture);
value-captures:alias = value-capture (list-sep value-capture)*; mixed-capture-list:alias = (value-capture | mutable-capture) (list-sep (value-capture | mutable-capture))*;
mixed-captures: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* "(" (nl | ",")* mixed-captures? (nl | ",")* ")"; value-definition-group = "let" nl* "(" list-sep? mixed-capture-list? list-sep? ")";
mutable-definition-group = "let" nl* "~" nl* "(" (nl | ",")* value-captures? (nl | ",")* ")"; mutable-definition-group = "let" nl* "~" nl* "(" list-sep? value-capture-list? list-sep? ")";
/* function-definition-fact:alias = symbol nl* function-fact;
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; function-capture = function-definition-fact;
effect-capture = "~" nl* function-definition-fact; effect-capture = "~" nl* function-definition-fact;
function-definition = "fn" nl* (function-capture | effect-capture); function-definition = "fn" nl* (function-capture | effect-capture);
function-captures:alias = function-capture (list-sep function-capture)*; function-capture-list:alias = function-capture (list-sep function-capture)*;
mixed-function-captures:alias = (function-capture | effect-capture) mixed-function-capture-list:alias = (function-capture | effect-capture)
(list-sep (function-capture | effect-capture))*; (list-sep (function-capture | effect-capture))*;
function-definition-group = "fn" nl* "(" (nl | ",")* function-definition-group = "fn" nl* "(" list-sep?
mixed-function-captures? mixed-function-capture-list?
(nl | ",")* ")"; list-sep? ")";
effect-definition-group = "fn" nl* "~" nl* "(" (nl | ",")* effect-definition-group = "fn" nl* "~" nl* "(" list-sep?
function-captures? function-capture-list?
(nl | ",")* ")"; list-sep? ")";
definition:alias = value-definition definition:alias = value-definition
| value-definition-group | value-definition-group
@ -536,27 +230,42 @@ definition:alias = value-definition
| function-definition-group | function-definition-group
| effect-definition-group; | effect-definition-group;
type-alias = "type" nl* "alias" nl* static-symbol nl* type-set; require-inline = ".";
type-constraint = "type" nl* static-symbol nl* type-set; 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;
export = "export" nl* definition;
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* ")"; statement-group:alias = "(" nl* statement nl* ")";
statement:alias = send sep:alias = (";" | nl)+;
| close statement-list:alias = statement (sep statement)*;
| panic
| require
| loop-control
| go
| loop
| assignment
| definition
| expression
| type-alias
| type-constraint
| statement-group;
shebang-command = [^\n]*; shebang-command = [^\n]*;
shebang = "#!" shebang-command "\n"; shebang = "#!" shebang-command "\n";
sep:alias = (";" | "\n") (nl | ";")*; mml:root = shebang? sep? statement-list? sep?;
statements:alias = statement (sep statement)*;
mml:root = shebang? (nl | ";")* statements? (nl | ";")*;

View File

@ -1,12 +1,6 @@
package treerack package treerack
import ( import "testing"
"bytes"
"io"
"os"
"testing"
"time"
)
func TestMML(t *testing.T) { func TestMML(t *testing.T) {
s, err := openSyntaxFile("examples/mml.treerack") s, err := openSyntaxFile("examples/mml.treerack")
@ -256,18 +250,6 @@ func TestMML(t *testing.T) {
Name: "symbol", Name: "symbol",
To: 3, 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", title: "struct",
text: "{foo: 1, \"bar\": 2, symbol(baz): 3, [qux]: 4}", text: "{foo: 1, \"bar\": 2}",
nodes: []*Node{{ nodes: []*Node{{
Name: "struct", Name: "struct",
To: 44, To: 18,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "entry", Name: "entry",
From: 1, From: 1,
@ -447,42 +429,6 @@ func TestMML(t *testing.T) {
From: 16, From: 16,
To: 17, 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}", text: "{[a]: b}",
nodes: []*Node{{ nodes: []*Node{{
Name: "struct", Name: "struct",
@ -563,7 +509,7 @@ func TestMML(t *testing.T) {
From: 1, From: 1,
To: 7, To: 7,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer-symbol", Name: "expression-key",
From: 1, From: 1,
To: 4, To: 4,
Nodes: []*Node{{ Nodes: []*Node{{
@ -793,7 +739,7 @@ func TestMML(t *testing.T) {
From: 7, From: 7,
To: 8, To: 8,
}, { }, {
Name: "collect-symbol", Name: "collect-parameter",
From: 10, From: 10,
To: 14, To: 14,
Nodes: []*Node{{ Nodes: []*Node{{
@ -844,7 +790,7 @@ func TestMML(t *testing.T) {
title: "indexer", title: "indexer",
text: "a[42]", text: "a[42]",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 5, To: 5,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -859,7 +805,7 @@ func TestMML(t *testing.T) {
title: "range indexer", title: "range indexer",
text: "a[3:9]", text: "a[3:9]",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 6, To: 6,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -888,7 +834,7 @@ func TestMML(t *testing.T) {
title: "range indexer, lower unbound", title: "range indexer, lower unbound",
text: "a[:9]", text: "a[:9]",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 5, To: 5,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -908,7 +854,7 @@ func TestMML(t *testing.T) {
title: "range indexer, upper unbound", title: "range indexer, upper unbound",
text: "a[3:]", text: "a[3:]",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 5, To: 5,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -928,13 +874,13 @@ func TestMML(t *testing.T) {
title: "indexer, chained", title: "indexer, chained",
text: "a[b][c][d]", text: "a[b][c][d]",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 10, To: 10,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 7, To: 7,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer", Name: "expression-indexer",
To: 4, To: 4,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -963,7 +909,7 @@ func TestMML(t *testing.T) {
title: "symbol indexer", title: "symbol indexer",
text: "a.b", text: "a.b",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 3, To: 3,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -974,52 +920,17 @@ func TestMML(t *testing.T) {
To: 3, 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", title: "chained symbol indexer",
text: "a.b.c.d", text: "a.b.c.d",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 7, To: 7,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 5, To: 5,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 3, To: 3,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -1044,10 +955,10 @@ func TestMML(t *testing.T) {
title: "chained symbol indexer on new line", title: "chained symbol indexer on new line",
text: "a\n.b\n.c", text: "a\n.b\n.c",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 7, To: 7,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 4, To: 4,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
@ -1067,10 +978,10 @@ func TestMML(t *testing.T) {
title: "chained symbol indexer on new line after dot", title: "chained symbol indexer on new line after dot",
text: "a.\nb.\nc", text: "a.\nb.\nc",
nodes: []*Node{{ nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 7, To: 7,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "indexer", Name: "symbol-indexer",
To: 4, To: 4,
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", 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) { t.Run("send/receive", func(t *testing.T) {
runTestsSyntax(t, s, []testItem{{ runTestsSyntax(t, s, []testItem{{
title: "receive op", title: "receive op",
text: "<-chan", text: "<<>chan",
nodes: []*Node{{ nodes: []*Node{{
Name: "unary-expression", Name: "receive",
Nodes: []*Node{{
Name: "receive-op",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}}, }},
}}, }},
}},
ignorePosition: true, ignorePosition: true,
}, { }, {
title: "send op", title: "send op",
text: "chan <- a", text: "chan <<> a",
nodes: []*Node{{ nodes: []*Node{{
Name: "send", Name: "send",
Nodes: []*Node{{ Nodes: []*Node{{
@ -1909,8 +1496,8 @@ func TestMML(t *testing.T) {
}, { }, {
title: "select", title: "select",
text: `select { text: `select {
case let a <-r: s <- a case let a <<>r: s <<> a
case s <- f(): g() case s <<> f(): g()
default: h() default: h()
}`, }`,
nodes: []*Node{{ nodes: []*Node{{
@ -1922,7 +1509,7 @@ func TestMML(t *testing.T) {
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}, { }, {
Name: "receive-op", Name: "receive",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}}, }},
@ -1963,78 +1550,6 @@ func TestMML(t *testing.T) {
}}, }},
}}, }},
ignorePosition: true, 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, 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", title: "binary 3, 4, 5",
text: "a * b + c * d == e * f && g || h -> f()", text: "a * b + c * d == e * f && g || h -> f()",
nodes: []*Node{{ nodes: []*Node{{
Name: "binary5", Name: "chaining",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "binary4", Name: "binary4",
Nodes: []*Node{{ Nodes: []*Node{{
@ -2382,8 +1875,6 @@ func TestMML(t *testing.T) {
}, { }, {
Name: "symbol", Name: "symbol",
}}, }},
}, {
Name: "chain",
}, { }, {
Name: "function-application", Name: "function-application",
Nodes: []*Node{{ Nodes: []*Node{{
@ -2498,11 +1989,8 @@ func TestMML(t *testing.T) {
text: "for foo {}", text: "for foo {}",
nodes: []*Node{{ nodes: []*Node{{
Name: "loop", Name: "loop",
Nodes: []*Node{{
Name: "loop-expression",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}},
}, { }, {
Name: "block", Name: "block",
}}, }},
@ -2514,9 +2002,7 @@ func TestMML(t *testing.T) {
nodes: []*Node{{ nodes: []*Node{{
Name: "loop", Name: "loop",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "loop-expression", Name: "range-over-expression",
Nodes: []*Node{{
Name: "in-expression",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}, { }, {
@ -2529,7 +2015,6 @@ func TestMML(t *testing.T) {
Name: "int", Name: "int",
}}, }},
}}, }},
}},
}, { }, {
Name: "block", Name: "block",
}}, }},
@ -2541,9 +2026,7 @@ func TestMML(t *testing.T) {
nodes: []*Node{{ nodes: []*Node{{
Name: "loop", Name: "loop",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "loop-expression", Name: "range-over-expression",
Nodes: []*Node{{
Name: "in-expression",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}, { }, {
@ -2562,7 +2045,6 @@ func TestMML(t *testing.T) {
Name: "int", Name: "int",
}}, }},
}}, }},
}},
}, { }, {
Name: "block", Name: "block",
}}, }},
@ -2578,15 +2060,12 @@ func TestMML(t *testing.T) {
nodes: []*Node{{ nodes: []*Node{{
Name: "loop", Name: "loop",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "loop-expression", Name: "range-over-expression",
Nodes: []*Node{{
Name: "in-expression",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}, { }, {
Name: "symbol", Name: "symbol",
}}, }},
}},
}, { }, {
Name: "block", Name: "block",
Nodes: []*Node{{ Nodes: []*Node{{
@ -2610,7 +2089,7 @@ func TestMML(t *testing.T) {
}, { }, {
Name: "block", Name: "block",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "break", Name: "symbol",
}}, }},
}}, }},
}}, }},
@ -2626,15 +2105,12 @@ func TestMML(t *testing.T) {
text: "a = b", text: "a = b",
nodes: []*Node{{ nodes: []*Node{{
Name: "assignment", Name: "assignment",
Nodes: []*Node{{
Name: "assign-equal",
Nodes: []*Node{{ Nodes: []*Node{{
Name: "symbol", Name: "symbol",
}, { }, {
Name: "symbol", Name: "symbol",
}}, }},
}}, }},
}},
ignorePosition: true, ignorePosition: true,
}, { }, {
title: "assign, set, eq", title: "assign, set, eq",
@ -2903,129 +2379,4 @@ func TestMML(t *testing.T) {
ignorePosition: true, 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)
} }

3031
mmlexp_test.go Normal file

File diff suppressed because it is too large Load Diff