|
|
|
@ -1,4 +1,6 @@
|
|
|
|
|
{
|
|
|
|
|
/* FIXME: `or` operator */
|
|
|
|
|
|
|
|
|
|
let operators = [{
|
|
|
|
|
associativity: "left",
|
|
|
|
|
operators: ["call"]
|
|
|
|
@ -8,7 +10,7 @@
|
|
|
|
|
operators: ["numericalNegate"]
|
|
|
|
|
}, {
|
|
|
|
|
associativity: "none",
|
|
|
|
|
operators: ["hasAttribute"]
|
|
|
|
|
operators: ["?"]
|
|
|
|
|
}, {
|
|
|
|
|
associativity: "right",
|
|
|
|
|
operators: ["++"]
|
|
|
|
@ -57,10 +59,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,28 +97,60 @@ stringLiteralCharacter
|
|
|
|
|
= !('"' / "\\") char:. { return char; }
|
|
|
|
|
/ "\\" escapableCharacter
|
|
|
|
|
|
|
|
|
|
numberLiteralCharacter
|
|
|
|
|
= [0-9.]
|
|
|
|
|
|
|
|
|
|
escapableCharacter
|
|
|
|
|
= '"'
|
|
|
|
|
/ "n"
|
|
|
|
|
/ "t"
|
|
|
|
|
/ "r"
|
|
|
|
|
|
|
|
|
|
commentCharacter
|
|
|
|
|
inlineCommentCharacter
|
|
|
|
|
= [^\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}; }
|
|
|
|
|
|
|
|
|
|
uri
|
|
|
|
|
= [a-zA-Z][a-zA-Z0-9+\-.]* ":" [a-zA-Z0-9%/?:@&=+$,\-_.!~*']+
|
|
|
|
|
|
|
|
|
|
// Utilities
|
|
|
|
|
_
|
|
|
|
|
= (whitespace / comment)* {}
|
|
|
|
|
= (whitespace / inlineComment)* {}
|
|
|
|
|
|
|
|
|
|
__
|
|
|
|
|
= whitespace+ {}
|
|
|
|
|
|
|
|
|
|
reservedWord
|
|
|
|
|
= name:reservedWordName (& [^a-zA-Z0-9._-]) { return name; } // FIXME: Unify identifier character set definition
|
|
|
|
|
|
|
|
|
|
reservedWordName // FIXME: Missing reserved words?
|
|
|
|
|
= "if"
|
|
|
|
|
/ "then"
|
|
|
|
|
/ "else"
|
|
|
|
|
/ "inherit"
|
|
|
|
|
/ "with"
|
|
|
|
|
/ "assert" // Is this really a reserved word?
|
|
|
|
|
|
|
|
|
|
// Language constructs
|
|
|
|
|
reorderedExpression
|
|
|
|
@ -106,55 +159,108 @@ reorderedExpression
|
|
|
|
|
expression
|
|
|
|
|
= numericallyNegatedOperatorExpression
|
|
|
|
|
/ booleanNegatedOperatorExpression
|
|
|
|
|
/ hasAttributeOperatorExpression
|
|
|
|
|
/ binaryOperatorExpression
|
|
|
|
|
/ functionCallOperatorExpression
|
|
|
|
|
/ divisionOperatorExpression
|
|
|
|
|
/ nonOperatorExpression
|
|
|
|
|
|
|
|
|
|
nonOperatorExpression
|
|
|
|
|
= group
|
|
|
|
|
/ letBlock
|
|
|
|
|
/ stringLiteral
|
|
|
|
|
/ multilineString
|
|
|
|
|
/ numberLiteral
|
|
|
|
|
/ functionDefinition
|
|
|
|
|
/ recursiveSet
|
|
|
|
|
/ set
|
|
|
|
|
/ identifier
|
|
|
|
|
/ list
|
|
|
|
|
/ blockComment
|
|
|
|
|
/ withStatement
|
|
|
|
|
/ conditional
|
|
|
|
|
/ path
|
|
|
|
|
/ uri
|
|
|
|
|
/ attributePathAlternate
|
|
|
|
|
|
|
|
|
|
inlineComment
|
|
|
|
|
= "#" chars:inlineCommentCharacter* _ { return {type: "inlineComment", text: chars.join("")} }
|
|
|
|
|
|
|
|
|
|
comment
|
|
|
|
|
= "#" chars:commentCharacter* _ { return {type: "comment", text: chars.join("")} }
|
|
|
|
|
/* FIXME: Where else to allow blockComments? Needs to be allowed everywhere, not just in expressions. */
|
|
|
|
|
blockComment
|
|
|
|
|
= "/*" segments:(! "*/" . [^*]*)* "*/" { return {type: "blockComment", text: joinBlockText(segments)} }
|
|
|
|
|
|
|
|
|
|
functionCallOperatorExpression
|
|
|
|
|
= left:nonOperatorExpression
|
|
|
|
|
space
|
|
|
|
|
__
|
|
|
|
|
right:expression { return {type: "operatorExpression", operator: "call", left: left, right: right} }
|
|
|
|
|
|
|
|
|
|
hasAttributeOperatorExpression
|
|
|
|
|
= left:nonOperatorExpression
|
|
|
|
|
_ "?"
|
|
|
|
|
_ right:identifier { return {type: "operatorExpression", operator: "?", left: left, right: right} } // FIXME: attribute path
|
|
|
|
|
_ right:identifier { return {type: "operatorExpression", operator: "?", left: left, right: right} } /* FIXME: Is an attribute path allowed here? */
|
|
|
|
|
|
|
|
|
|
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} }
|
|
|
|
|
_ right:expression {
|
|
|
|
|
return {type: "operatorExpression", operator: "numericalNegate", right: right};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
booleanNegatedOperatorExpression
|
|
|
|
|
= "!"
|
|
|
|
|
_ right:expression { return {type: "operatorExpression", operator: "booleanNegate", right: right} }
|
|
|
|
|
_ right:expression {
|
|
|
|
|
return {type: "operatorExpression", operator: "booleanNegate", right: right};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attributePathAlternate
|
|
|
|
|
= attributePath:attributePath alternate:(_ "or" _ attributePathAlternate)? {
|
|
|
|
|
if (alternate != null) {
|
|
|
|
|
return {type: "attributePathAlternate", path: attributePath, alternate: alternate};
|
|
|
|
|
} else {
|
|
|
|
|
return attributePath;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attributePath
|
|
|
|
|
= firstIdentifier:attributeItem nextIdentifiers:("." attributeItem)* {
|
|
|
|
|
return {type: "attributePath", items: [firstIdentifier].concat(nextIdentifiers.map(item => item[1]))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attributeItem
|
|
|
|
|
= "\"" identifier:identifier "\"" { return identifier; } /* FIXME: What characters should be allowed here? */
|
|
|
|
|
/ identifier:identifier { return identifier; }
|
|
|
|
|
|
|
|
|
|
identifier
|
|
|
|
|
= literal:stringLiteral { return {type: "identifier", identifier: literal.value} }
|
|
|
|
|
/ chars:[a-z.]i+ { return {type: "identifier", identifier: text()} }
|
|
|
|
|
= identifier:(! reservedWord identifierValue) {
|
|
|
|
|
return {type: "identifier", identifier: identifier[1]};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* FIXME: Quoted attributes? */
|
|
|
|
|
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
|
|
|
|
|
_ ":"
|
|
|
|
@ -164,17 +270,18 @@ functionDefinitionArgument
|
|
|
|
|
= identifier
|
|
|
|
|
/ setPattern
|
|
|
|
|
|
|
|
|
|
/* FIXME: Is `"foo" = "bar"` valid in an assignment list? */
|
|
|
|
|
assignment
|
|
|
|
|
= _ identifier:identifier
|
|
|
|
|
_ "="
|
|
|
|
|
_ expression:reorderedExpression
|
|
|
|
|
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} }
|
|
|
|
|
= attributePath:attributePath
|
|
|
|
|
_ "="
|
|
|
|
|
_ expression:reorderedExpression
|
|
|
|
|
_ ";" { return {type: "assignment", attributePath: attributePath, expression: expression} }
|
|
|
|
|
|
|
|
|
|
assignmentList
|
|
|
|
|
= items:assignment* { return items; }
|
|
|
|
|
= _ items:((assignment / inheritStatement / blockComment) _)* { return items.map(item => item[0]); }
|
|
|
|
|
|
|
|
|
|
bindingList
|
|
|
|
|
= items:assignment* { return {type: "bindings", assignments: items} }
|
|
|
|
|
bindingList // FIXME: Allow inherit - point at assignmentList instead?
|
|
|
|
|
= items:((assignment / blockComment) _)* { return {type: "bindings", assignments: items.map(item => item[0])} }
|
|
|
|
|
|
|
|
|
|
set
|
|
|
|
|
= "{"
|
|
|
|
@ -189,19 +296,58 @@ 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; }
|
|
|
|
|
|
|
|
|
|
/* FIXME: What to return here? */
|
|
|
|
|
withStatement
|
|
|
|
|
= "with"
|
|
|
|
|
_ withExpression:expression
|
|
|
|
|
_ ";"
|
|
|
|
|
_ expression
|
|
|
|
|
|
|
|
|
|
inheritStatement /* FIXME: Distinguish between identifiers and attribute paths? */
|
|
|
|
|
= "inherit"
|
|
|
|
|
_ namespace:("(" reorderedExpression ")")?
|
|
|
|
|
_ attributePaths:(attributePathAlternate _)+ /* FIXME: Is attributePathAlternate allowed here, or just attributePath? */
|
|
|
|
|
_ ";" { return {type: "inheritStatement", attributePaths: attributePaths.map(item => item[0]), namespace: maybe(namespace, 1)} }
|
|
|
|
|
|
|
|
|
|
/* FIXME: Are attribute paths allowed in set patterns? */
|
|
|
|
|
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; }
|
|
|
|
|