Browse Source

Various additions

master
Sven Slootweg 4 years ago
parent
commit
ac2d6bfa8d
  1. 9
      gulpfile.js
  2. 8
      package.json
  3. 6
      pegjs-options.js
  4. 146
      src/expression.pegjs
  5. 29
      src/index.js
  6. 108
      src/stringify.js
  7. 20
      test.js

9
gulpfile.js

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

8
package.json

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

6
pegjs-options.js

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

146
src/expression.pegjs

@ -8,7 +8,7 @@
operators: ["numericalNegate"]
}, {
associativity: "none",
operators: ["hasAttribute"]
operators: ["?"]
}, {
associativity: "right",
operators: ["++"]
@ -57,10 +57,29 @@
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
= _ expression:reorderedExpression _ { return expression; }
= _ expressions:(reorderedExpression _)* { return expressions.map(expression => expression[0]); }
// Character classes
whitespace
@ -76,9 +95,6 @@ stringLiteralCharacter
= !('"' / "\\") char:. { return char; }
/ "\\" escapableCharacter
numberLiteralCharacter
= [0-9.]
escapableCharacter
= '"'
/ "n"
@ -89,16 +105,45 @@ commentCharacter
= [^\n\r]
// Literals
// FIXME: String interpolation
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
= 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
_
= (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); }
@ -106,26 +151,37 @@ reorderedExpression
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
space
__
right:expression { return {type: "operatorExpression", operator: "call", left: left, right: right} }
hasAttributeOperatorExpression
@ -135,9 +191,14 @@ hasAttributeOperatorExpression
binaryOperatorExpression
= left:nonOperatorExpression
_ operator:("++" / "+" / "-" / "*" / "/" / "//" / "<" / "<=" / ">" / ">=" / "&&" / "||" / "==" / "!=" / "->")
_ 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} }
@ -147,14 +208,22 @@ booleanNegatedOperatorExpression
_ right:expression { return {type: "operatorExpression", operator: "booleanNegate", right: right} }
identifier
= literal:stringLiteral { return {type: "identifier", identifier: literal.value} }
/ chars:[a-z.]i+ { return {type: "identifier", identifier: text()} }
= 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
_ ":"
@ -165,16 +234,16 @@ functionDefinitionArgument
/ setPattern
assignment
= _ identifier:identifier
_ "="
_ expression:reorderedExpression
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} }
= identifier:identifier
_ "="
_ expression:reorderedExpression
_ ";" { return {type: "assignment", identifier: identifier, expression: expression} }
assignmentList
= items:assignment* { return items; }
= _ items:((assignment / inheritStatement) _)* { return items.map(item => item[0]); }
bindingList
= items:assignment* { return {type: "bindings", assignments: items} }
= items:(assignment _)* { return {type: "bindings", assignments: items.map(item => item[0])} }
set
= "{"
@ -189,19 +258,56 @@ letBlock
= "let"
_ bindingList:bindingList
_ "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
= "{"
_ args:setPatternVariableList
_ "}" { return {type: "setPattern", variables: args} }
_ "}" rest:storedRestParameter? { return {type: "setPattern", variables: args, rest}; }
setPatternVariableList
= firstItem:setPatternVariable otherItems:(_ "," _ setPatternVariable)* { return concatRepeat(firstItem, otherItems, 3); }
setPatternVariable
= restParameter
/ identifier
/ setPatternIdentifier
setPatternIdentifier
= identifier:identifier defaultValue:(_ "?" _ expression)? { return {type: "setPatternIdentifier", identifier, defaultValue: maybe(defaultValue, 3)}; }
restParameter
= "..." { return {type: "restParameter"} }
storedRestParameter
= "@" identifier:identifier { return identifier; }

29
src/index.js

@ -1,5 +1,19 @@
'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 = {}) {
let expressionParser, parserOptions;
@ -10,13 +24,16 @@ module.exports = function(body, options = {}) {
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,
format: "commonjs",
dependencies: {
"reorderOperatorExpressions": path.join(__dirname, "./reorder-operator-expressions")
}
});
format: "commonjs"
}, pegOptions));
parserOptions = {
tracer: options.tracer

108
src/stringify.js

@ -1,21 +1,59 @@
'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) {
case "operatorExpression":
return stringifyOperator(node);
return stringifyOperator(node, indentation);
case "numberLiteral":
return stringifyNumberLiteral(node);
return stringifyNumberLiteral(node, indentation);
case "stringLiteral":
return stringifyStringLiteral(node);
return stringifyStringLiteral(node, indentation);
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:
throw new Error(`Unexpected node type '${node.type}'`);
}
}
function stringifyOperator(node) {
function stringifyOperator(node, indentation) {
let result;
switch (node.operator) {
@ -33,34 +71,76 @@ function stringifyOperator(node) {
case "<=":
case ">":
case ">=":
result = `${stringifyNode(node.left)} ${node.operator} ${stringifyNode(node.right)}`;
result = `${stringifyNode(node.left, indentation)} ${node.operator} ${stringifyNode(node.right, indentation)}`;
break;
case "call":
result = `${stringifyNode(node.left)} ${stringifyNode(node.right)}`;
result = `${stringifyNode(node.left, indentation)} ${stringifyNode(node.right, indentation)}`;
break;
case "numericalNegate":
result = `-${stringifyNode(node.right)}`;
result = `-${stringifyNode(node.right, indentation)}`;
break;
case "booleanNegate":
result = `!${stringifyNode(node.right)}`;
result = `!${stringifyNode(node.right, indentation)}`;
break;
}
return `(${result})`;
}
function stringifyStringLiteral(node) {
function stringifyStringLiteral(node, indentation) {
return `"${node.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
}
function stringifyNumberLiteral(node) {
function stringifyNumberLiteral(node, indentation) {
return `${node.value}`;
}
function stringifyIdentifier(node) {
function stringifyIdentifier(node, indentation) {
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) {
return stringifyNode(tree);
return assureArray(tree).map((subTree) => {
return stringifyNode(subTree, 0);
}).join("\n");
};

20
test.js

@ -3,6 +3,8 @@
const util = require("util");
const fs = require("fs");
const createBetterPegTracer = require("better-peg-tracer");
const pegjsBacktrace = require("pegjs-backtrace");
const pegjsPermutationTracer = require("pegjs-permutation-tracer");
const parse = require("./");
const stringify = require("./lib/stringify");
@ -11,10 +13,23 @@ function fullInspect(obj) {
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 {
let contents = fs.readFileSync(process.argv[2]).toString();
let tree = parse(contents, {
tracer: createBetterPegTracer(contents)
tracer: trace ? createBetterPegTracer(contents) : undefined
// tracer: trace ? tracer : undefined
});
console.log(fullInspect(tree));
@ -23,4 +38,5 @@ try {
console.log(stringify(tree));
} catch (err) {
console.log(fullInspect(err))
tracer.printPermutations(194);
}
Loading…
Cancel
Save