// Note: indent takes an argument in order to circumvent memoization ometa OffsideRuleParser <: Parser { space = char:x ?(x != '\n' && x.charCodeAt(0) <= 32) -> x, spaces = {0}:n (space {n = n + 1})* -> n, nl = char:x ?(x == '\n') -> x, eol = spaces nl, indented :r = spaces nl {self.level}:oldLevel &(spaces:newLevel) ?(newLevel > oldLevel) {self.level = newLevel} ( apply(r):ans &(spaces:newLevel) (?(newLevel <= oldLevel) | (space | nl)* end) {self.level = oldLevel} -> ans | {self.level = oldLevel} ?(false) ), indent :_ = spaces:n ?(n == self.level) } OffsideRuleParser.level = 0 /* ometa OffsideRuleParserTest <: OffsideRuleParser { name = "x" | "foo" | "bar" | "baz", stmt = indent(0) name:n nl -> n | indent(0) ``if'' name:c indented('stmts'):t indent(0) ``else'' indented('stmts'):e -> ['if', c, t, e] | indent(0) ``if'' name:c indented('stmts'):t -> ['if', c, t], stmts = ((spaces nl)* stmt)*, start = stmts:ans (space | nl)* end -> ans } OffsideRuleParserTest.matchAll(' foo if x foo bar if x baz baz foo if bar baz else baz ', 'start') */ ometa NileParser <: OffsideRuleParser { ns_ident = letter:x letterOrDigit*:xs exactly("'")*:ps -> [x].concat(xs).concat(ps).join(''), ident = spaces ns_ident, scalar = spaces digit+:xs -> parseInt(xs.join('')), type = exactly("TODO!"), kType = ident:t1 ">>" (ident | "⏚"):t2 -> ['sType', t1, t2], ki = ident:k "(" listOf(#expr, ','):as ")" -> ['kApp', k, as] | ident -> ['k', k], kis = listOf(#ki, ">>"):kis -> ['kis', kis], kernel = ident:n kernArgs:as ":" kType:t indented(#body):b -> ['kernel', n, as, t, b], kernArgs = "(" listOf(#kasPart, ','):as ")" -> ['args', as.reduce(function(xs, x) { return xs.concat(x) }, [])] | empty -> ['args'], kasPart = listOf(#ident, ','):ns ":" ident:t -> ns.map(function(a) { return [a, t] }), body = prologue:p meat:m epilogue:e -> ['body', p, m, e], prologue = stmt*, meat = indent(0) "∀" pat:p block:b -> ['forall', p, b], epilogue = stmt*, pat = ident:x -> ['val', x] | "_" -> ['wildcard'] | "[" listOf(#pat, ','):ps "]" -> ['vector', ps], exprs = listOf(#expr, ','), primExpr = ident:x -> ['variable', x] | scalar:s -> ['scalar', s] | "[" exprs:xs "]" -> ['vector', xs] | "(" expr:x ")" -> x | "|" expr:x "|" -> ['abs', xs] | "⌊" expr:x "⌋" -> ['floor', xs] | "⌈" expr:x "⌉" -> ['ceil', xs] | "@" ns_ident:x -> ['fieldName', x], expr1 = primExpr:e ":" type:t -> ['typedExpr', e, t] | expr1:e "." ident:f -> ['fieldAccess', e, f] | primExpr, expr2 = expr2:x "∙" expr1:y -> ['binaryOp', x, '∙', y] | expr1, expr3 = expr3:x "+" expr2:y -> ['binaryOp', x, '+', y] | expr3:x "-" expr2:y -> ['binaryOp', x, '-', y] | expr2, expr4 = expr3:x "<" expr3:y -> ['binaryOp', x, '<', y] | expr3:x "≤" expr3:y -> ['binaryOp', x, '≤', y] | expr3:x ">" expr3:y -> ['binaryOp', x, '>', y] | expr3, expr5 = expr4:x "=" expr4:y -> ['binaryOp', x, '=', y] | expr4:x "≠" expr4:y -> ['binaryOp', x, '≠', y] | expr4, expr6 = expr6:x "⋀" expr5:y -> ['binaryOp', x, '⋀', y] | expr5, expr7 = expr7:x "⋁" expr6:y -> ['binaryOp', x, '⋁', y] | expr6, expr = expr7, stmt = indent(0) ">>" expr:e eol -> ['output', e] | indent(0) "<<" expr:e eol -> ['input', e] | indent(0) ident:x "←" expr:e eol -> ['assign', x, e] | cond, stmts = ((spaces nl)* stmt)*, block = indented(#stmts):xs -> ['compound', xs], cond = indent(0) "if" expr:c block:t {[[c, t]]}:cts ("else" "if" expr:c block:t {cts.push([c, t])})* ("else" block:e {cts.push([['true'], e])} | empty) -> cts, topLevel = kernel, start = ((space | nl)* topLevel)*:ans (space | nl)* end -> ans } NileParser.matchAll(" TransformBezier (M : Matrix) : Bezier >> Bezier ∀ [A, B, C] >> [M ∙ A, M ∙ B, M ∙ C] ", "start")