|
|
@ -8,7 +8,7 @@ |
|
|
|
operators: ["numericalNegate"] |
|
|
|
}, { |
|
|
|
associativity: "none", |
|
|
|
operators: ["hasAttribute"] |
|
|
|
operators: ["?"] |
|
|
|
}, { |
|
|
|
associativity: "right", |
|
|
|
operators: ["++"] |
|
|
@ -57,10 +57,29 @@ |
|
|
|
return node; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function maybe(array, index) { |
|
|
|
if (array != null) { |
|
|
|
return array[index]; |
|
|
|
} else { |
|
|
|
return undefined; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function joinBlockText(segments) { |
|
|
|
return segments.map((segment) => { |
|
|
|
return segment[1] + segment[2].join(""); |
|
|
|
}).join(""); |
|
|
|
} |
|
|
|
|
|
|
|
function log(value) { |
|
|
|
console.log(value); |
|
|
|
return value; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
start |
|
|
|
= _ expression:reorderedExpression _ { return expression; } |
|
|
|
= _ expressions:(reorderedExpression _)* { return expressions.map(expression => expression[0]); } |
|
|
|
|
|
|
|
// Character classes |
|
|
|
whitespace |
|
|
@ -76,9 +95,6 @@ stringLiteralCharacter |
|
|
|
= !('"' / "\\") char:. { return char; } |
|
|
|
/ "\\" escapableCharacter |
|
|
|
|
|
|
|
numberLiteralCharacter |
|
|
|
= [0-9.] |
|
|
|
|
|
|
|
escapableCharacter |
|
|
|
= '"' |
|
|
|
/ "n" |
|
|
@ -89,16 +105,45 @@ commentCharacter |
|
|
|
= [^\n\r] |
|
|
|
|
|
|
|
// Literals |
|
|
|
// FIXME: String interpolation |
|
|
|
stringLiteral |
|
|
|
= '"' chars:stringLiteralCharacter+ '"' { return {type: "stringLiteral", value: chars.join("")} } |
|
|
|
= '"' chars:stringLiteralCharacter+ '"' { return {type: "stringLiteral", value: chars.join(""), multiline: false} } |
|
|
|
|
|
|
|
multilineString |
|
|
|
= "''" segments:(! "''" . [^']*)* "''" { return {type: "stringLiteral", value: joinBlockText(segments), multiline: true} } |
|
|
|
|
|
|
|
numberLiteral |
|
|
|
= chars:numberLiteralCharacter+ { return {type: "numberLiteral", value: chars.join("")} } |
|
|
|
= [0-9]+ { return {type: "numberLiteral", value: text()} } |
|
|
|
|
|
|
|
path |
|
|
|
= regularPath |
|
|
|
/ homePath |
|
|
|
/ storePath |
|
|
|
|
|
|
|
regularPath |
|
|
|
= [a-zA-Z0-9\.\_\-\+]* ("/" [a-zA-Z0-9\.\_\-\+]+)+ "/"? { return {type: "path", pathType: "regular", path: text()}; } |
|
|
|
|
|
|
|
homePath |
|
|
|
= "~" ("/" [a-zA-Z0-9\.\_\-\+]+)+ "/"? { return {type: "path", pathType: "home", path: text()}; } |
|
|
|
|
|
|
|
storePath |
|
|
|
= "<" path:([a-zA-Z0-9\.\_\-\+]+ ("/" [a-zA-Z0-9\.\_\-\+]+)*) ">" { return {type: "path", pathType: "store", storePath: path}; } |
|
|
|
|
|
|
|
// Utilities |
|
|
|
_ |
|
|
|
= (whitespace / comment)* {} |
|
|
|
|
|
|
|
__ |
|
|
|
= whitespace+ {} |
|
|
|
|
|
|
|
reservedWord // FIXME: Missing reserved words? |
|
|
|
= "if" |
|
|
|
/ "then" |
|
|
|
/ "else" |
|
|
|
/ "inherit" |
|
|
|
/ "with" |
|
|
|
/ "assert" // Is this really a reserved word? |
|
|
|
|
|
|
|
// Language constructs |
|
|
|
reorderedExpression |
|
|
|
= expression:expression { return maybeReorderOperatorExpressions(expression); } |
|
|
@ -106,26 +151,37 @@ reorderedExpression |
|
|
|
expression |
|
|
|
= numericallyNegatedOperatorExpression |
|
|
|
/ booleanNegatedOperatorExpression |
|
|
|
/ hasAttributeOperatorExpression |
|
|
|
/ binaryOperatorExpression |
|
|
|
/ functionCallOperatorExpression |
|
|
|
/ divisionOperatorExpression |
|
|
|
/ nonOperatorExpression |
|
|
|
|
|
|
|
nonOperatorExpression |
|
|
|
= group |
|
|
|
/ letBlock |
|
|
|
/ stringLiteral |
|
|
|
/ multilineString |
|
|
|
/ numberLiteral |
|
|
|
/ functionDefinition |
|
|
|
/ recursiveSet |
|
|
|
/ set |
|
|
|
/ list |
|
|
|
/ blockComment |
|
|
|
/ withStatement |
|
|
|
/ conditional |
|
|
|
/ path |
|
|
|
/ identifier |
|
|
|
|
|
|
|
comment |
|
|
|
= "#" chars:commentCharacter* _ { return {type: "comment", text: chars.join("")} } |
|
|
|
|
|
|
|
blockComment |
|
|
|
= "/*" segments:(! "*/" . [^*]*)* "*/" { return {type: "blockComment", text: joinBlockText(segments)} } |
|
|
|
|
|
|
|
functionCallOperatorExpression |
|
|
|
= left:nonOperatorExpression |
|
|
|
space |
|
|
|
__ |
|
|
|
right:expression { return {type: "operatorExpression", operator: "call", left: left, right: right} } |
|
|
|
|
|
|
|
hasAttributeOperatorExpression |
|
|
@ -135,9 +191,14 @@ hasAttributeOperatorExpression |
|
|
|
|
|
|
|
binaryOperatorExpression |
|
|
|
= left:nonOperatorExpression |
|
|
|
_ operator:("++" / "+" / "-" / "*" / "/" / "//" / "<" / "<=" / ">" / ">=" / "&&" / "||" / "==" / "!=" / "->") |
|
|
|
_ operator:("++" / "//" / "+" / "-" / "*" / "<" / "<=" / ">" / ">=" / "&&" / "||" / "==" / "!=" / "->") |
|
|
|
_ right:expression { return {type: "operatorExpression", operator: operator, left: left, right: right} } |
|
|
|
|
|
|
|
divisionOperatorExpression |
|
|
|
= left:nonOperatorExpression |
|
|
|
_ "/" |
|
|
|
_ right:expression { return {type: "operatorExpression", operator: "/", left: left, right: right} } |
|
|
|
|
|
|
|
numericallyNegatedOperatorExpression |
|
|
|
= "-" |
|
|
|
_ right:expression { return {type: "operatorExpression", operator: "numericalNegate", right: right} } |
|
|
@ -147,14 +208,22 @@ booleanNegatedOperatorExpression |
|
|
|
_ right:expression { return {type: "operatorExpression", operator: "booleanNegate", right: right} } |
|
|
|
|
|
|
|
identifier |
|
|
|
= literal:stringLiteral { return {type: "identifier", identifier: literal.value} } |
|
|
|
/ chars:[a-z.]i+ { return {type: "identifier", identifier: text()} } |
|
|
|
= identifier:(! (reservedWord [^a-z0-9._-]) identifierValue) { return {type: "identifier", identifier: identifier[1]} } |
|
|
|
|
|
|
|
identifierValue |
|
|
|
= literal:stringLiteral { return literal.value; } |
|
|
|
/ chars:[a-z_]i [a-z0-9._-]i+ { return text(); } |
|
|
|
|
|
|
|
group |
|
|
|
= "(" |
|
|
|
_ expression:reorderedExpression |
|
|
|
_ ")" { return {type: "group", expression: expression} } |
|
|
|
|
|
|
|
list |
|
|
|
= "[" |
|
|
|
_ firstExpression:nonOperatorExpression? nextExpressions:(__ nonOperatorExpression)* |
|
|
|
_ "]" { return {type: "list", items: [firstExpression].concat(nextExpressions.map(expr => expr[1]))} } |
|
|
|
|
|
|
|
functionDefinition |
|
|
|
= argument:functionDefinitionArgument |
|
|
|
_ ":" |
|
|
@ -165,16 +234,16 @@ functionDefinitionArgument |
|
|
|
/ setPattern |
|
|
|
|
|
|
|
assignment |
|
|
|
= _ identifier:identifier |
|
|
|
_ "=" |
|
|
|
_ expression:reorderedExpression |
|
|
|
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} } |
|
|
|
= identifier:identifier |
|
|
|
_ "=" |
|
|
|
_ expression:reorderedExpression |
|
|
|
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} } |
|
|
|
|
|
|
|
assignmentList |
|
|
|
= items:assignment* { return items; } |
|
|
|
= _ items:((assignment / inheritStatement) _)* { return items.map(item => item[0]); } |
|
|
|
|
|
|
|
bindingList |
|
|
|
= items:assignment* { return {type: "bindings", assignments: items} } |
|
|
|
= items:(assignment _)* { return {type: "bindings", assignments: items.map(item => item[0])} } |
|
|
|
|
|
|
|
set |
|
|
|
= "{" |
|
|
@ -189,19 +258,56 @@ letBlock |
|
|
|
= "let" |
|
|
|
_ bindingList:bindingList |
|
|
|
_ "in" |
|
|
|
_ expression:reorderedExpression { return {type: "letBlock", bindings: bindingList.assignments, expression: expression} } |
|
|
|
_ expression:reorderedExpression { return {type: "letBlock", bindings: bindingList.assignments, expression: expression}; } |
|
|
|
|
|
|
|
conditional |
|
|
|
= conditionalWithAlternative |
|
|
|
/ conditionalWithoutAlternative |
|
|
|
|
|
|
|
// The following rule exists to force the alternative to parse, if it exists. |
|
|
|
conditionalWithAlternative |
|
|
|
= original:conditionalWithoutAlternative |
|
|
|
_ alternative:elseClause { return Object.assign({alternative: alternative}, original); } |
|
|
|
|
|
|
|
conditionalWithoutAlternative |
|
|
|
= "if" |
|
|
|
_ condition:expression |
|
|
|
_ "then" |
|
|
|
_ body:expression { return {type: "conditional", condition: condition, body: body}; } |
|
|
|
|
|
|
|
elseClause |
|
|
|
= "else" |
|
|
|
_ alternative:expression { return alternative; } |
|
|
|
|
|
|
|
withStatement |
|
|
|
= "with" |
|
|
|
_ identifier:identifier |
|
|
|
_ ";" |
|
|
|
_ expression |
|
|
|
|
|
|
|
inheritStatement // FIXME: Distinguish between identifiers and attribute paths? |
|
|
|
= "inherit" |
|
|
|
_ namespace:("(" identifier ")")? |
|
|
|
_ identifiers:(identifier _)+ |
|
|
|
_ ";" { return {type: "inheritStatement", identifiers: identifiers.map(item => item[0]), namespace: maybe(namespace, 1)} } |
|
|
|
|
|
|
|
setPattern |
|
|
|
= "{" |
|
|
|
_ args:setPatternVariableList |
|
|
|
_ "}" { return {type: "setPattern", variables: args} } |
|
|
|
_ "}" rest:storedRestParameter? { return {type: "setPattern", variables: args, rest}; } |
|
|
|
|
|
|
|
setPatternVariableList |
|
|
|
= firstItem:setPatternVariable otherItems:(_ "," _ setPatternVariable)* { return concatRepeat(firstItem, otherItems, 3); } |
|
|
|
|
|
|
|
setPatternVariable |
|
|
|
= restParameter |
|
|
|
/ identifier |
|
|
|
/ setPatternIdentifier |
|
|
|
|
|
|
|
setPatternIdentifier |
|
|
|
= identifier:identifier defaultValue:(_ "?" _ expression)? { return {type: "setPatternIdentifier", identifier, defaultValue: maybe(defaultValue, 3)}; } |
|
|
|
|
|
|
|
restParameter |
|
|
|
= "..." { return {type: "restParameter"} } |
|
|
|
|
|
|
|
storedRestParameter |
|
|
|
= "@" identifier:identifier { return identifier; } |
|
|
|