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.

354 lines
9.1 KiB
JavaScript

{
/* FIXME: `or` operator */
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"
inlineCommentCharacter
= [^\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}; }
uri
= [a-zA-Z][a-zA-Z0-9+\-.]* ":" [a-zA-Z0-9%/?:@&=+$,\-_.!~*']+
// Utilities
_
= (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
= 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
/ uri
/ attributePathAlternate
inlineComment
= "#" chars:inlineCommentCharacter* _ { return {type: "inlineComment", 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
__
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: Is an attribute path allowed here? */
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};
}
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
= 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
_ ":"
_ body:reorderedExpression { return {type: "functionDefinition", argument: argument, body: body} }
functionDefinitionArgument
= identifier
/ setPattern
/* FIXME: Is `"foo" = "bar"` valid in an assignment list? */
assignment
= attributePath:attributePath
_ "="
_ expression:reorderedExpression
_ ";" { return {type: "assignment", attributePath: attributePath, expression: expression} }
assignmentList
= _ items:((assignment / inheritStatement / blockComment) _)* { return items.map(item => item[0]); }
bindingList // FIXME: Allow inherit - point at assignmentList instead?
= items:((assignment / blockComment) _)* { 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; }
/* 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
_ "}" 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; }