You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
7.7 KiB
JavaScript

{
let operators = [{
associativity: "left",
operators: ["call"]
}, {
associativity: "none",
unary: true,
operators: ["numericalNegate"]
}, {
associativity: "none",
operators: ["?"]
}, {
associativity: "right",
operators: ["++"]
}, {
associativity: "left",
operators: ["*", "/"]
}, {
associativity: "left",
operators: ["+", "-"]
}, {
associativity: "none",
unary: true,
operators: ["booleanNegate"]
}, {
associativity: "right",
operators: ["//"]
}, {
associativity: "left",
operators: ["<", "<=", ">", ">="]
}, {
// NOTE: Special case, can only occur once in sequence!
associativity: "none",
operators: ["==", "!="]
}, {
associativity: "left",
operators: ["&&"]
}, {
associativity: "left",
operators: ["||"]
}, {
// NOTE: Special case, can only occur once in sequence!
associativity: "none",
operators: ["->"]
}];
function concatRepeat(first, rest, restIndex) {
return [first].concat(rest.map(function(item) {
return item[restIndex];
}));
}
function maybeReorderOperatorExpressions(node) {
if (node.type === "operatorExpression") {
return reorderOperatorExpressions(node, operators);
} else {
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
= _ expressions:(reorderedExpression _)* { return expressions.map(expression => expression[0]); }
// Character classes
whitespace
= "\t"
/ "\n"
/ "\r"
/ space
space
= " "
stringLiteralCharacter
= !('"' / "\\") char:. { return char; }
/ "\\" escapableCharacter
escapableCharacter
= '"'
/ "n"
/ "t"
/ "r"
commentCharacter
= [^\n\r]
// Literals
// FIXME: String interpolation
stringLiteral
= '"' chars:stringLiteralCharacter+ '"' { return {type: "stringLiteral", value: chars.join(""), multiline: false} }
multilineString
= "''" segments:(! "''" . [^']*)* "''" { return {type: "stringLiteral", value: joinBlockText(segments), multiline: true} }
numberLiteral
= [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); }
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
__
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
binaryOperatorExpression
= left:nonOperatorExpression
_ 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} }
booleanNegatedOperatorExpression
= "!"
_ right:expression { return {type: "operatorExpression", operator: "booleanNegate", right: right} }
identifier
= 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
_ ":"
_ body:reorderedExpression { return {type: "functionDefinition", argument: argument, body: body} }
functionDefinitionArgument
= identifier
/ setPattern
assignment
= identifier:identifier
_ "="
_ expression:reorderedExpression
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} }
assignmentList
= _ items:((assignment / inheritStatement) _)* { return items.map(item => item[0]); }
bindingList
= items:(assignment _)* { return {type: "bindings", assignments: items.map(item => item[0])} }
set
= "{"
_ items:assignmentList
_ "}" { return {type: "set", assignments: items} }
recursiveSet
= "rec"
_ setData:set { return {type: "recursiveSet", assignments: setData.assignments} }
letBlock
= "let"
_ bindingList:bindingList
_ "in"
_ 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
_ "}" rest:storedRestParameter? { return {type: "setPattern", variables: args, rest}; }
setPatternVariableList
= firstItem:setPatternVariable otherItems:(_ "," _ setPatternVariable)* { return concatRepeat(firstItem, otherItems, 3); }
setPatternVariable
= restParameter
/ setPatternIdentifier
setPatternIdentifier
= identifier:identifier defaultValue:(_ "?" _ expression)? { return {type: "setPatternIdentifier", identifier, defaultValue: maybe(defaultValue, 3)}; }
restParameter
= "..." { return {type: "restParameter"} }
storedRestParameter
= "@" identifier:identifier { return identifier; }