257 lines
6.1 KiB
Plaintext
257 lines
6.1 KiB
Plaintext
@external propSource highlighting from "./highlight"
|
|
|
|
@context trackScope from "./scopeTracker"
|
|
|
|
@skip { space | comment }
|
|
|
|
@top Program { item* }
|
|
|
|
@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq }
|
|
|
|
@tokens {
|
|
@precedence { Number Regex }
|
|
|
|
StringFragment { !['\\$]+ }
|
|
NamedArgPrefix { $[a-z]+ "=" }
|
|
Number { ("-" | "+")? $[0-9]+ ('.' $[0-9]+)? }
|
|
Boolean { "true" | "false" }
|
|
newlineOrSemicolon { "\n" | ";" }
|
|
eof { @eof }
|
|
space { " " | "\t" }
|
|
comment { "#" ![\n]* }
|
|
leftParen { "(" }
|
|
rightParen { ")" }
|
|
colon[closedBy="end", @name="colon"] { ":" }
|
|
Underscore { "_" }
|
|
Regex { "//" (![/\\\n[] | "\\" ![\n] | "[" (![\n\\\]] | "\\" ![\n])* "]")+ ("//" $[gimsuy]*)? } // Stolen from the lezer JavaScript grammar
|
|
"|"[@name=operator]
|
|
|
|
}
|
|
|
|
@external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot }
|
|
@external specialize {Identifier} specializeKeyword from "./tokenizer" { Do }
|
|
|
|
@precedence {
|
|
pipe @left,
|
|
or @left,
|
|
and @left,
|
|
comparison @left,
|
|
multiplicative @left,
|
|
additive @left,
|
|
call
|
|
}
|
|
|
|
item {
|
|
consumeToTerminator newlineOrSemicolon |
|
|
consumeToTerminator eof |
|
|
newlineOrSemicolon // allow blank lines
|
|
}
|
|
|
|
|
|
consumeToTerminator {
|
|
PipeExpr |
|
|
ambiguousFunctionCall |
|
|
TryExpr |
|
|
Throw |
|
|
IfExpr |
|
|
FunctionDef |
|
|
CompoundAssign |
|
|
Assign |
|
|
BinOp |
|
|
ConditionalOp |
|
|
expressionWithoutIdentifier
|
|
}
|
|
|
|
PipeExpr {
|
|
pipeOperand (!pipe "|" pipeOperand)+
|
|
}
|
|
|
|
pipeOperand {
|
|
FunctionCall | FunctionCallOrIdentifier
|
|
}
|
|
|
|
FunctionCallOrIdentifier {
|
|
DotGet | Identifier
|
|
}
|
|
|
|
ambiguousFunctionCall {
|
|
FunctionCall | FunctionCallOrIdentifier
|
|
}
|
|
|
|
FunctionCall {
|
|
(DotGet | Identifier | ParenExpr) arg+
|
|
}
|
|
|
|
arg {
|
|
PositionalArg | NamedArg
|
|
}
|
|
|
|
|
|
PositionalArg {
|
|
expression | FunctionDef | Underscore
|
|
}
|
|
|
|
NamedArg {
|
|
NamedArgPrefix (expression | FunctionDef | Underscore)
|
|
}
|
|
|
|
FunctionDef {
|
|
singleLineFunctionDef | multilineFunctionDef
|
|
}
|
|
|
|
singleLineFunctionDef {
|
|
Do Params colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
|
}
|
|
|
|
multilineFunctionDef {
|
|
Do Params colon newlineOrSemicolon block CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
|
}
|
|
|
|
IfExpr {
|
|
singleLineIf | multilineIf
|
|
}
|
|
|
|
singleLineIf {
|
|
@specialize[@name=keyword]<Identifier, "if"> (ConditionalOp | expression) colon SingleLineThenBlock @specialize[@name=keyword]<Identifier, "end">
|
|
}
|
|
|
|
multilineIf {
|
|
@specialize[@name=keyword]<Identifier, "if"> (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock ElseIfExpr* ElseExpr? @specialize[@name=keyword]<Identifier, "end">
|
|
}
|
|
|
|
ElseIfExpr {
|
|
@specialize[@name=keyword]<Identifier, "elseif"> (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock
|
|
}
|
|
|
|
ElseExpr {
|
|
@specialize[@name=keyword]<Identifier, "else"> colon newlineOrSemicolon ThenBlock
|
|
}
|
|
|
|
ThenBlock {
|
|
block
|
|
}
|
|
|
|
SingleLineThenBlock {
|
|
consumeToTerminator
|
|
}
|
|
|
|
TryExpr {
|
|
singleLineTry | multilineTry
|
|
}
|
|
|
|
singleLineTry {
|
|
@specialize[@name=keyword]<Identifier, "try"> colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
|
}
|
|
|
|
multilineTry {
|
|
@specialize[@name=keyword]<Identifier, "try"> colon newlineOrSemicolon TryBlock CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
|
}
|
|
|
|
CatchExpr {
|
|
@specialize[@name=keyword]<Identifier, "catch"> Identifier colon (newlineOrSemicolon TryBlock | consumeToTerminator)
|
|
}
|
|
|
|
FinallyExpr {
|
|
@specialize[@name=keyword]<Identifier, "finally"> colon (newlineOrSemicolon TryBlock | consumeToTerminator)
|
|
}
|
|
|
|
TryBlock {
|
|
block
|
|
}
|
|
|
|
Throw {
|
|
@specialize[@name=keyword]<Identifier, "throw"> (BinOp | ConditionalOp | expression)
|
|
}
|
|
|
|
ConditionalOp {
|
|
expression !comparison EqEq expression |
|
|
expression !comparison Neq expression |
|
|
expression !comparison Lt expression |
|
|
expression !comparison Lte expression |
|
|
expression !comparison Gt expression |
|
|
expression !comparison Gte expression |
|
|
(expression | ConditionalOp) !and And (expression | ConditionalOp) |
|
|
(expression | ConditionalOp) !or Or (expression | ConditionalOp)
|
|
}
|
|
|
|
Params {
|
|
Identifier*
|
|
}
|
|
|
|
Assign {
|
|
AssignableIdentifier Eq consumeToTerminator
|
|
}
|
|
|
|
CompoundAssign {
|
|
AssignableIdentifier (PlusEq | MinusEq | StarEq | SlashEq | ModuloEq) consumeToTerminator
|
|
}
|
|
|
|
BinOp {
|
|
expression !multiplicative Modulo expression |
|
|
(expression | BinOp) !multiplicative Star (expression | BinOp) |
|
|
(expression | BinOp) !multiplicative Slash (expression | BinOp) |
|
|
(expression | BinOp) !additive Plus (expression | BinOp) |
|
|
(expression | BinOp) !additive Minus (expression | BinOp)
|
|
}
|
|
|
|
ParenExpr {
|
|
leftParen (ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen
|
|
}
|
|
|
|
expression {
|
|
expressionWithoutIdentifier | DotGet | Identifier
|
|
}
|
|
|
|
|
|
@local tokens {
|
|
dot { "." }
|
|
}
|
|
|
|
@skip {} {
|
|
DotGet {
|
|
IdentifierBeforeDot dot (Number | Identifier | ParenExpr)
|
|
}
|
|
|
|
String { "'" stringContent* "'" }
|
|
|
|
}
|
|
|
|
stringContent {
|
|
StringFragment |
|
|
Interpolation |
|
|
EscapeSeq
|
|
}
|
|
|
|
Interpolation {
|
|
"$" Identifier |
|
|
"$" ParenExpr
|
|
}
|
|
|
|
EscapeSeq {
|
|
"\\" ("$" | "n" | "t" | "r" | "\\" | "'")
|
|
}
|
|
|
|
Dict {
|
|
"[=]" |
|
|
"[" newlineOrSemicolon* NamedArg (newlineOrSemicolon | NamedArg)* "]"
|
|
}
|
|
|
|
Array {
|
|
"[" newlineOrSemicolon* (expression (newlineOrSemicolon | expression)*)? "]"
|
|
}
|
|
|
|
// We need expressionWithoutIdentifier to avoid conflicts in consumeToTerminator.
|
|
// Without this, when parsing "my-var" at statement level, the parser can't decide:
|
|
// - ambiguousFunctionCall → FunctionCallOrIdentifier → Identifier
|
|
// - expression → Identifier
|
|
// Both want the same Identifier token! So we use expressionWithoutIdentifier
|
|
// to remove Identifier from the second path, forcing standalone identifiers
|
|
// to go through ambiguousFunctionCall (which is what we want semantically).
|
|
// Yes, it is annoying and I gave up trying to use GLR to fix it.
|
|
expressionWithoutIdentifier {
|
|
ParenExpr | Word | String | Number | Boolean | Regex | Dict | Array | @specialize[@name=Null]<Identifier, "null">
|
|
}
|
|
|
|
block {
|
|
(consumeToTerminator? newlineOrSemicolon)*
|
|
} |