{ /* 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; }