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.

99 lines
3.3 KiB
JavaScript

"use strict";
// This is an abstraction for dealing with precedence in generic backtracking parsers; the way this works is that by recursively preferring higher-precedence operators over lower-precedence ones, it will parse those first and then treat them as expression inputs to lower-precedence parsers. This is also why lower-precedence operator rules "fall through" to higher-precedence ones; it's more or less equivalent to attempting them in the specified order, but with support for nesting.
// TODO: Improve this explanation with a visual reference of some sort
// TODO: Maybe linear parsing with post-facto rearranging/grouping of operators would be more performant and simpler to debug? It might provide less opportunity to insert other parsing rules though, and therefore be less composable. Need to investigate this option. Should also consider a utility function that generates an entire operator parsing tree from a list of instructions, leaving space for non-operator (or non-standard operator) rules to be inserted within that tree.
const matchValue = require("match-value");
const { either, oneOrMore, zeroOrMore, optional, wholeMatch } = require("./operations");
const { parse } = require("./index");
function makeBinaryExpressionParser(categoryName, nextStep, operatorMap) {
let validOperators = Object.keys(operatorMap);
let expressionParser = function* () {
let left = yield nextStep;
let rights = yield oneOrMore(function* () {
yield MaybeWhitespace;
let operation = matchValue(yield either(validOperators), operatorMap);
yield MaybeWhitespace;
let right = yield nextStep;
return { operation, right };
});
// NOTE: This implements *left* associativity only!
return rights.reduce((last, { right, operation }) => {
return { type: operation, left: last, right: right };
}, left);
// return { type: operation, left, right };
};
Object.defineProperty(expressionParser, "name", { value: `${categoryName}Expression` });
let stepWrapper = function* () {
return yield either([ expressionParser, nextStep ]);
};
Object.defineProperty(stepWrapper, "name", { value: `${categoryName}Step` });
return [ stepWrapper, expressionParser ];
}
function* ParenStep() {
return yield either([ NumericExpression, ParenExpression ]);
}
var [ MultiplicativeStep, MultiplicativeExpression ] = makeBinaryExpressionParser("Multiplicative", ParenStep, {
"*": "multiply",
"/": "divide"
});
// NOTE: The usage of `var` here is intentional; it is necessary to make recursion work
var [ AdditiveStep, AdditiveExpression ] = makeBinaryExpressionParser("Additive", MultiplicativeStep, {
"+": "add",
"-": "subtract"
});
function* ParenExpression() {
yield "(";
yield MaybeWhitespace;
let expression = yield Expression;
yield MaybeWhitespace;
yield ")";
return { type: "group", expression };
}
function* Expression() {
// return yield AdditiveExpression;
return yield AdditiveStep;
}
function* MaybeWhitespace() {
yield zeroOrMore(either([ " ", "\t", "\n" ]));
}
function* NumericExpression() {
function* parserRule() {
yield oneOrMore(Digit);
yield optional(Fraction);
};
function* Fraction() {
yield ".";
yield oneOrMore(Digit);
}
return parseFloat(yield wholeMatch(parserRule));
}
function* Digit() {
yield /[0-9]/;
}
module.exports = Expression;
console.dir(parse("1 + 4 / 3 * 5 - (2 - 0)", Expression), { depth: null });