Various additions

master
Sven Slootweg 7 years ago
parent cb7619fe5e
commit ac2d6bfa8d

@ -19,12 +19,9 @@ gulp.task('pegjs', function() {
return gulp.src(sources.pegjs) return gulp.src(sources.pegjs)
.pipe(presetPegjs({ .pipe(presetPegjs({
basePath: __dirname, basePath: __dirname,
pegjs: { pegjs: Object.assign({
format: "commonjs", format: "commonjs"
dependencies: { }, require("./pegjs-options"))
"reorderOperatorExpressions": "./reorder-operator-expressions"
}
}
})) }))
.pipe(gulp.dest("lib/")); .pipe(gulp.dest("lib/"));
}); });

@ -17,13 +17,17 @@
], ],
"author": "Sven Slootweg", "author": "Sven Slootweg",
"license": "WTFPL", "license": "WTFPL",
"dependencies": {}, "dependencies": {
"assure-array": "^1.0.0",
"left-pad": "^1.1.3"
},
"devDependencies": { "devDependencies": {
"@joepie91/gulp-preset-es2015": "^1.0.1", "@joepie91/gulp-preset-es2015": "^1.0.1",
"@joepie91/gulp-preset-pegjs": "^1.0.0", "@joepie91/gulp-preset-pegjs": "^1.0.0",
"babel-preset-es2015": "^6.6.0", "babel-preset-es2015": "^6.6.0",
"better-peg-tracer": "^1.0.0", "better-peg-tracer": "^1.0.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"pegjs": "^0.10.0" "pegjs": "^0.10.0",
"repeat-string": "^1.6.1"
} }
} }

@ -0,0 +1,6 @@
module.exports = {
dependencies: {
reorderOperatorExpressions: "./reorder-operator-expressions"
},
cache: true
}

@ -8,7 +8,7 @@
operators: ["numericalNegate"] operators: ["numericalNegate"]
}, { }, {
associativity: "none", associativity: "none",
operators: ["hasAttribute"] operators: ["?"]
}, { }, {
associativity: "right", associativity: "right",
operators: ["++"] operators: ["++"]
@ -57,10 +57,29 @@
return node; 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 start
= _ expression:reorderedExpression _ { return expression; } = _ expressions:(reorderedExpression _)* { return expressions.map(expression => expression[0]); }
// Character classes // Character classes
whitespace whitespace
@ -76,9 +95,6 @@ stringLiteralCharacter
= !('"' / "\\") char:. { return char; } = !('"' / "\\") char:. { return char; }
/ "\\" escapableCharacter / "\\" escapableCharacter
numberLiteralCharacter
= [0-9.]
escapableCharacter escapableCharacter
= '"' = '"'
/ "n" / "n"
@ -89,16 +105,45 @@ commentCharacter
= [^\n\r] = [^\n\r]
// Literals // Literals
// FIXME: String interpolation
stringLiteral 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 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}; }
// Utilities // Utilities
_ _
= (whitespace / comment)* {} = (whitespace / comment)* {}
__
= whitespace+ {}
reservedWord // FIXME: Missing reserved words?
= "if"
/ "then"
/ "else"
/ "inherit"
/ "with"
/ "assert" // Is this really a reserved word?
// Language constructs // Language constructs
reorderedExpression reorderedExpression
= expression:expression { return maybeReorderOperatorExpressions(expression); } = expression:expression { return maybeReorderOperatorExpressions(expression); }
@ -106,26 +151,37 @@ reorderedExpression
expression expression
= numericallyNegatedOperatorExpression = numericallyNegatedOperatorExpression
/ booleanNegatedOperatorExpression / booleanNegatedOperatorExpression
/ hasAttributeOperatorExpression
/ binaryOperatorExpression / binaryOperatorExpression
/ functionCallOperatorExpression / functionCallOperatorExpression
/ divisionOperatorExpression
/ nonOperatorExpression / nonOperatorExpression
nonOperatorExpression nonOperatorExpression
= group = group
/ letBlock / letBlock
/ stringLiteral / stringLiteral
/ multilineString
/ numberLiteral / numberLiteral
/ functionDefinition / functionDefinition
/ recursiveSet / recursiveSet
/ set / set
/ list
/ blockComment
/ withStatement
/ conditional
/ path
/ identifier / identifier
comment comment
= "#" chars:commentCharacter* _ { return {type: "comment", text: chars.join("")} } = "#" chars:commentCharacter* _ { return {type: "comment", text: chars.join("")} }
blockComment
= "/*" segments:(! "*/" . [^*]*)* "*/" { return {type: "blockComment", text: joinBlockText(segments)} }
functionCallOperatorExpression functionCallOperatorExpression
= left:nonOperatorExpression = left:nonOperatorExpression
space __
right:expression { return {type: "operatorExpression", operator: "call", left: left, right: right} } right:expression { return {type: "operatorExpression", operator: "call", left: left, right: right} }
hasAttributeOperatorExpression hasAttributeOperatorExpression
@ -135,9 +191,14 @@ hasAttributeOperatorExpression
binaryOperatorExpression binaryOperatorExpression
= left:nonOperatorExpression = left:nonOperatorExpression
_ operator:("++" / "+" / "-" / "*" / "/" / "//" / "<" / "<=" / ">" / ">=" / "&&" / "||" / "==" / "!=" / "->") _ operator:("++" / "//" / "+" / "-" / "*" / "<" / "<=" / ">" / ">=" / "&&" / "||" / "==" / "!=" / "->")
_ right:expression { return {type: "operatorExpression", operator: operator, left: left, right: right} } _ 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 numericallyNegatedOperatorExpression
= "-" = "-"
_ right:expression { return {type: "operatorExpression", operator: "numericalNegate", right: right} } _ right:expression { return {type: "operatorExpression", operator: "numericalNegate", right: right} }
@ -147,14 +208,22 @@ booleanNegatedOperatorExpression
_ right:expression { return {type: "operatorExpression", operator: "booleanNegate", right: right} } _ right:expression { return {type: "operatorExpression", operator: "booleanNegate", right: right} }
identifier identifier
= literal:stringLiteral { return {type: "identifier", identifier: literal.value} } = identifier:(! (reservedWord [^a-z0-9._-]) identifierValue) { return {type: "identifier", identifier: identifier[1]} }
/ chars:[a-z.]i+ { return {type: "identifier", identifier: text()} }
identifierValue
= literal:stringLiteral { return literal.value; }
/ chars:[a-z_]i [a-z0-9._-]i+ { return text(); }
group group
= "(" = "("
_ expression:reorderedExpression _ expression:reorderedExpression
_ ")" { return {type: "group", expression: expression} } _ ")" { return {type: "group", expression: expression} }
list
= "["
_ firstExpression:nonOperatorExpression? nextExpressions:(__ nonOperatorExpression)*
_ "]" { return {type: "list", items: [firstExpression].concat(nextExpressions.map(expr => expr[1]))} }
functionDefinition functionDefinition
= argument:functionDefinitionArgument = argument:functionDefinitionArgument
_ ":" _ ":"
@ -165,16 +234,16 @@ functionDefinitionArgument
/ setPattern / setPattern
assignment assignment
= _ identifier:identifier = identifier:identifier
_ "=" _ "="
_ expression:reorderedExpression _ expression:reorderedExpression
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} } _ ";" { return {type: "assignment", identifier: identifier, expression: expression} }
assignmentList assignmentList
= items:assignment* { return items; } = _ items:((assignment / inheritStatement) _)* { return items.map(item => item[0]); }
bindingList bindingList
= items:assignment* { return {type: "bindings", assignments: items} } = items:(assignment _)* { return {type: "bindings", assignments: items.map(item => item[0])} }
set set
= "{" = "{"
@ -189,19 +258,56 @@ letBlock
= "let" = "let"
_ bindingList:bindingList _ bindingList:bindingList
_ "in" _ "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; }
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 setPattern
= "{" = "{"
_ args:setPatternVariableList _ args:setPatternVariableList
_ "}" { return {type: "setPattern", variables: args} } _ "}" rest:storedRestParameter? { return {type: "setPattern", variables: args, rest}; }
setPatternVariableList setPatternVariableList
= firstItem:setPatternVariable otherItems:(_ "," _ setPatternVariable)* { return concatRepeat(firstItem, otherItems, 3); } = firstItem:setPatternVariable otherItems:(_ "," _ setPatternVariable)* { return concatRepeat(firstItem, otherItems, 3); }
setPatternVariable setPatternVariable
= restParameter = restParameter
/ identifier / setPatternIdentifier
setPatternIdentifier
= identifier:identifier defaultValue:(_ "?" _ expression)? { return {type: "setPatternIdentifier", identifier, defaultValue: maybe(defaultValue, 3)}; }
restParameter restParameter
= "..." { return {type: "restParameter"} } = "..." { return {type: "restParameter"} }
storedRestParameter
= "@" identifier:identifier { return identifier; }

@ -1,5 +1,19 @@
'use strict'; 'use strict';
const path = require("path");
function fixDependencyPaths(dependencies) {
/* When generating a parser on runtime, the parser source will be evaluated
* from within the PEG.js source code, which means that relative dependency
* paths will not work. This function patches all relative paths, by
* resolving them to absolute paths relative from this file. */
return Object.keys(dependencies).reduce((obj, key) => {
obj[key] = path.join(__dirname, dependencies[key]);
return obj;
}, {});
}
module.exports = function(body, options = {}) { module.exports = function(body, options = {}) {
let expressionParser, parserOptions; let expressionParser, parserOptions;
@ -10,13 +24,16 @@ module.exports = function(body, options = {}) {
let grammar = fs.readFileSync(path.join(__dirname, "../src/expression.pegjs"), {encoding: "utf8"}); let grammar = fs.readFileSync(path.join(__dirname, "../src/expression.pegjs"), {encoding: "utf8"});
expressionParser = pegjs.generate(grammar, { let pegOptions = require("../pegjs-options.js");
if (pegOptions.dependencies != null) {
pegOptions.dependencies = fixDependencyPaths(pegOptions.dependencies);
}
expressionParser = pegjs.generate(grammar, Object.assign({
trace: true, trace: true,
format: "commonjs", format: "commonjs"
dependencies: { }, pegOptions));
"reorderOperatorExpressions": path.join(__dirname, "./reorder-operator-expressions")
}
});
parserOptions = { parserOptions = {
tracer: options.tracer tracer: options.tracer

@ -1,21 +1,59 @@
'use strict'; 'use strict';
function stringifyNode(node) { const repeatString = require("repeat-string");
const assureArray = require("assure-array");
function debugPrintNode(node) {
return `${node.type} | ${node.value || node.operator || node.identifier}`;
}
function prettyPrint(items, indentation) {
if (indentation == null) {
throw new Error(`Missing indentation parameter (node: ${debugPrintNode(node)})`);
}
return items.map((item) => {
if (typeof item === "string") {
return item.split("\n").map((line) => {
return repeatString("\t", indentation) + line;
}).join("\n");
} else {
return prettyPrint(item, indentation + 1);
}
}).join("\n");
}
function stringifyNode(node, indentation) {
if (indentation == null) {
throw new Error(`Missing indentation parameter (node: ${debugPrintNode(node)})`);
}
switch (node.type) { switch (node.type) {
case "operatorExpression": case "operatorExpression":
return stringifyOperator(node); return stringifyOperator(node, indentation);
case "numberLiteral": case "numberLiteral":
return stringifyNumberLiteral(node); return stringifyNumberLiteral(node, indentation);
case "stringLiteral": case "stringLiteral":
return stringifyStringLiteral(node); return stringifyStringLiteral(node, indentation);
case "identifier": case "identifier":
return stringifyIdentifier(node); return stringifyIdentifier(node, indentation);
case "set":
return stringifySet(node, indentation);
case "letBlock":
return stringifyLetBlock(node, indentation);
case "list":
return stringifyList(node, indentation);
case "functionDefinition":
return stringifyFunctionDefinition(node, indentation);
case "group":
return stringifyGroup(node, indentation);
default: default:
throw new Error(`Unexpected node type '${node.type}'`); throw new Error(`Unexpected node type '${node.type}'`);
} }
} }
function stringifyOperator(node) { function stringifyOperator(node, indentation) {
let result; let result;
switch (node.operator) { switch (node.operator) {
@ -33,34 +71,76 @@ function stringifyOperator(node) {
case "<=": case "<=":
case ">": case ">":
case ">=": case ">=":
result = `${stringifyNode(node.left)} ${node.operator} ${stringifyNode(node.right)}`; result = `${stringifyNode(node.left, indentation)} ${node.operator} ${stringifyNode(node.right, indentation)}`;
break; break;
case "call": case "call":
result = `${stringifyNode(node.left)} ${stringifyNode(node.right)}`; result = `${stringifyNode(node.left, indentation)} ${stringifyNode(node.right, indentation)}`;
break; break;
case "numericalNegate": case "numericalNegate":
result = `-${stringifyNode(node.right)}`; result = `-${stringifyNode(node.right, indentation)}`;
break; break;
case "booleanNegate": case "booleanNegate":
result = `!${stringifyNode(node.right)}`; result = `!${stringifyNode(node.right, indentation)}`;
break; break;
} }
return `(${result})`; return `(${result})`;
} }
function stringifyStringLiteral(node) { function stringifyStringLiteral(node, indentation) {
return `"${node.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`; return `"${node.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
} }
function stringifyNumberLiteral(node) { function stringifyNumberLiteral(node, indentation) {
return `${node.value}`; return `${node.value}`;
} }
function stringifyIdentifier(node) { function stringifyIdentifier(node, indentation) {
return `${node.identifier}`; return `${node.identifier}`;
} }
function stringifyBinding(node, indentation) {
return `${stringifyNode(node.identifier, indentation)} = ${stringifyNode(node.expression, indentation)};`
}
function stringifyGroup(node, indentation) {
return `(${stringifyNode(node.expression, indentation)})`
}
function stringifySet(node, indentation) {
return prettyPrint([
"{",
node.assignments.map((binding) => {
return stringifyBinding(binding, indentation);
}),
"}"
], indentation);
}
function stringifyLetBlock(node, indentation) {
return prettyPrint([
"let",
node.bindings.map((binding) => {
return stringifyBinding(binding, indentation);
}),
"in",
[stringifyNode(node.expression, indentation)]
], indentation);
}
function stringifyList(node, indentation) {
return `[ ${node.items.map(item => stringifyNode(item, indentation)).join(" ")} ]`;
}
function stringifyFunctionDefinition(node, indentation) {
return prettyPrint([
`${stringifyNode(node.argument, indentation)}:`,
[stringifyNode(node.body, indentation)]
], indentation);
}
module.exports = function stringifyTree(tree) { module.exports = function stringifyTree(tree) {
return stringifyNode(tree); return assureArray(tree).map((subTree) => {
return stringifyNode(subTree, 0);
}).join("\n");
}; };

@ -3,6 +3,8 @@
const util = require("util"); const util = require("util");
const fs = require("fs"); const fs = require("fs");
const createBetterPegTracer = require("better-peg-tracer"); const createBetterPegTracer = require("better-peg-tracer");
const pegjsBacktrace = require("pegjs-backtrace");
const pegjsPermutationTracer = require("pegjs-permutation-tracer");
const parse = require("./"); const parse = require("./");
const stringify = require("./lib/stringify"); const stringify = require("./lib/stringify");
@ -11,10 +13,23 @@ function fullInspect(obj) {
return util.inspect(obj, {colors: true, depth: null, customInspect: false}) return util.inspect(obj, {colors: true, depth: null, customInspect: false})
} }
let trace, file;
if (process.argv[2] === "--trace") {
trace = true;
file = process.argv[3];
} else {
trace = false;
file = process.argv[2];
}
let contents = fs.readFileSync(file).toString();
let tracer = pegjsPermutationTracer(contents);
try { try {
let contents = fs.readFileSync(process.argv[2]).toString();
let tree = parse(contents, { let tree = parse(contents, {
tracer: createBetterPegTracer(contents) tracer: trace ? createBetterPegTracer(contents) : undefined
// tracer: trace ? tracer : undefined
}); });
console.log(fullInspect(tree)); console.log(fullInspect(tree));
@ -23,4 +38,5 @@ try {
console.log(stringify(tree)); console.log(stringify(tree));
} catch (err) { } catch (err) {
console.log(fullInspect(err)) console.log(fullInspect(err))
tracer.printPermutations(194);
} }

Loading…
Cancel
Save