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