823 lines
19 KiB
JavaScript
823 lines
19 KiB
JavaScript
"use strict";
|
|
|
|
class ParseError extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "ParseError";
|
|
}
|
|
}
|
|
|
|
const OPEN_TO_CLOSE = {
|
|
"(": ")",
|
|
"[": "]",
|
|
"{": "}",
|
|
"<": ">",
|
|
"\u00ab": "\u00bb",
|
|
};
|
|
const OPEN_BRACKETS = new Set(Object.keys(OPEN_TO_CLOSE));
|
|
const CLOSE_BRACKETS = new Set(Object.values(OPEN_TO_CLOSE));
|
|
const BRACKET_RE = /\u00ab|\u00bb|[()[\]{}<>]|[^\s()[\]{}<>\u00ab\u00bb]+/g;
|
|
|
|
function isConst(e) {
|
|
return e instanceof Const;
|
|
}
|
|
function isZero(e) {
|
|
return isConst(e) && e._value === 0;
|
|
}
|
|
function isOne(e) {
|
|
return isConst(e) && e._value === 1;
|
|
}
|
|
|
|
class Expression {
|
|
diff(v) {
|
|
throw new Error("diff not implemented");
|
|
}
|
|
simplify() {
|
|
return this;
|
|
}
|
|
isConst() {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class Const extends Expression {
|
|
constructor(value) {
|
|
super();
|
|
this._value = value;
|
|
}
|
|
evaluate(_x, _y, _z) {
|
|
return this._value;
|
|
}
|
|
toString() {
|
|
return String(this._value);
|
|
}
|
|
prefix() {
|
|
return String(this._value);
|
|
}
|
|
postfix() {
|
|
return String(this._value);
|
|
}
|
|
diff(_v) {
|
|
return ZERO;
|
|
}
|
|
isConst() {
|
|
return true;
|
|
}
|
|
contains(_v) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const ZERO = new Const(0);
|
|
const ONE = new Const(1);
|
|
const TWO = new Const(2);
|
|
|
|
const VAR_INDEX = { x: 0, y: 1, z: 2 };
|
|
|
|
class Variable extends Expression {
|
|
constructor(name) {
|
|
super();
|
|
this._name = name;
|
|
}
|
|
evaluate(x, y, z) {
|
|
return [x, y, z][VAR_INDEX[this._name]];
|
|
}
|
|
toString() {
|
|
return this._name;
|
|
}
|
|
prefix() {
|
|
return this._name;
|
|
}
|
|
postfix() {
|
|
return this._name;
|
|
}
|
|
diff(v) {
|
|
return v === this._name ? ONE : ZERO;
|
|
}
|
|
contains(v) {
|
|
return v === this._name;
|
|
}
|
|
}
|
|
|
|
class Operation extends Expression {
|
|
constructor(...args) {
|
|
super();
|
|
this._args = args;
|
|
}
|
|
evaluate(x, y, z) {
|
|
return this._calc(...this._args.map((a) => a.evaluate(x, y, z)));
|
|
}
|
|
toString() {
|
|
return (
|
|
this._args.map((a) => a.toString()).join(" ") +
|
|
" " +
|
|
this.constructor.symbol
|
|
);
|
|
}
|
|
postfix() {
|
|
return (
|
|
"(" +
|
|
this._args.map((a) => a.postfix()).join(" ") +
|
|
" " +
|
|
this.constructor.symbol +
|
|
")"
|
|
);
|
|
}
|
|
prefix() {
|
|
return (
|
|
"(" +
|
|
this.constructor.symbol +
|
|
" " +
|
|
this._args.map((a) => a.prefix()).join(" ") +
|
|
")"
|
|
);
|
|
}
|
|
isConst() {
|
|
return this._args.every((a) => a.isConst());
|
|
}
|
|
simplify() {
|
|
const args = this._args.map((a) => a.simplify());
|
|
const ruled = this._simplifyArgs(args);
|
|
if (!(ruled instanceof Operation)) return ruled;
|
|
if (ruled._args.every((a) => a.isConst())) {
|
|
const val = ruled._calc(...ruled._args.map((a) => a._value));
|
|
return new Const(val);
|
|
}
|
|
return ruled;
|
|
}
|
|
contains(v) {
|
|
return this._args.some((a) => a.contains(v));
|
|
}
|
|
diff(v) {
|
|
if (this.isConst()) return ZERO;
|
|
if (!this.contains(v)) return ZERO;
|
|
return this._diff(v);
|
|
}
|
|
_diff(v) {
|
|
throw new Error("_diff not implemented");
|
|
}
|
|
_simplifyArgs(args) {
|
|
return new this.constructor(...args);
|
|
}
|
|
}
|
|
|
|
function makeOperation(symbol, calc, diffFn, simplifyArgsFn) {
|
|
class Op extends Operation {
|
|
_calc(...vals) {
|
|
return calc(...vals);
|
|
}
|
|
_diff(v) {
|
|
return diffFn(v, ...this._args);
|
|
}
|
|
_simplifyArgs(args) {
|
|
if (simplifyArgsFn) {
|
|
const result = simplifyArgsFn(...args);
|
|
if (result !== undefined) return result;
|
|
}
|
|
return new Op(...args);
|
|
}
|
|
}
|
|
Op.symbol = symbol;
|
|
return Op;
|
|
}
|
|
|
|
function exprEqual(a, b) {
|
|
if (a instanceof Const && b instanceof Const) return a._value === b._value;
|
|
if (a instanceof Variable && b instanceof Variable)
|
|
return a._name === b._name;
|
|
if (a.constructor !== b.constructor) return false;
|
|
if (!(a instanceof Operation)) return false;
|
|
if (a._args.length !== b._args.length) return false;
|
|
return a._args.every((arg, i) => exprEqual(arg, b._args[i]));
|
|
}
|
|
|
|
function factors(e) {
|
|
if (e.constructor === Multiply) {
|
|
return [...factors(e._args[0]), ...factors(e._args[1])];
|
|
}
|
|
return [e];
|
|
}
|
|
|
|
function cancelDivide(f, g) {
|
|
const numFactors = factors(f);
|
|
const denFactors = factors(g);
|
|
|
|
for (let i = 0; i < numFactors.length; i++) {
|
|
for (let j = 0; j < denFactors.length; j++) {
|
|
if (exprEqual(numFactors[i], denFactors[j])) {
|
|
const newNum = rebuildProduct(numFactors.filter((_, k) => k !== i));
|
|
const newDen = rebuildProduct(denFactors.filter((_, k) => k !== j));
|
|
if (isOne(newDen)) return newNum;
|
|
const further = cancelDivide(newNum, newDen);
|
|
if (further !== undefined) return further;
|
|
return new Divide(newNum, newDen);
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function rebuildProduct(fs) {
|
|
if (fs.length === 0) return ONE;
|
|
return fs.reduce((acc, f) => new Multiply(acc, f));
|
|
}
|
|
|
|
const Add = makeOperation(
|
|
"+",
|
|
(a, b) => a + b,
|
|
(v, f, g) => new Add(f.diff(v), g.diff(v)),
|
|
(f, g) => (isZero(f) ? g : isZero(g) ? f : undefined),
|
|
);
|
|
|
|
const Subtract = makeOperation(
|
|
"-",
|
|
(a, b) => a - b,
|
|
(v, f, g) => new Subtract(f.diff(v), g.diff(v)),
|
|
(f, g) => (isZero(g) ? f : isZero(f) ? new Negate(g) : undefined),
|
|
);
|
|
|
|
const Multiply = makeOperation(
|
|
"*",
|
|
(a, b) => a * b,
|
|
(v, f, g) => new Add(new Multiply(f.diff(v), g), new Multiply(f, g.diff(v))),
|
|
(f, g) => {
|
|
if (isZero(f) || isZero(g)) return ZERO;
|
|
if (isOne(f)) return g;
|
|
if (isOne(g)) return f;
|
|
if (g.constructor === Divide && isOne(g._args[0]))
|
|
return new Divide(f, g._args[1]);
|
|
if (f.constructor === Divide && isOne(f._args[0]))
|
|
return new Divide(g, f._args[1]);
|
|
return undefined;
|
|
},
|
|
);
|
|
|
|
const Divide = makeOperation(
|
|
"/",
|
|
(a, b) => a / b,
|
|
(v, f, g) =>
|
|
new Divide(
|
|
new Subtract(new Multiply(f.diff(v), g), new Multiply(f, g.diff(v))),
|
|
new Multiply(g, g),
|
|
),
|
|
(f, g) => {
|
|
if (isZero(f)) return ZERO;
|
|
if (isOne(g)) return f;
|
|
if (exprEqual(f, g)) return ONE;
|
|
if (f.constructor === Divide) {
|
|
return new Divide(f._args[0], new Multiply(f._args[1], g));
|
|
}
|
|
if (g.constructor === Divide) {
|
|
return new Divide(new Multiply(f, g._args[1]), g._args[0]);
|
|
}
|
|
const cancelled = cancelDivide(f, g);
|
|
if (cancelled) return cancelled;
|
|
return undefined;
|
|
},
|
|
);
|
|
|
|
const Negate = makeOperation(
|
|
"negate",
|
|
(a) => -a,
|
|
(v, f) => new Negate(f.diff(v)),
|
|
(f) => (isZero(f) ? ZERO : undefined),
|
|
);
|
|
|
|
const Power = makeOperation(
|
|
"pow",
|
|
(a, b) => Math.pow(a, b),
|
|
(v, f, g) =>
|
|
new Add(
|
|
new Multiply(
|
|
new Multiply(g, new Power(f, new Subtract(g, ONE))),
|
|
f.diff(v),
|
|
),
|
|
new Multiply(
|
|
new Multiply(new Power(f, g), new Log(new Const(Math.E), f)),
|
|
g.diff(v),
|
|
),
|
|
),
|
|
);
|
|
Power.symbol = "pow";
|
|
|
|
const Log = makeOperation(
|
|
"log",
|
|
(a, b) => Math.log(Math.abs(b)) / Math.log(Math.abs(a)),
|
|
(v, base, x) =>
|
|
new Divide(
|
|
new Subtract(
|
|
new Divide(x.diff(v), x),
|
|
new Multiply(new Log(base, x), new Divide(base.diff(v), base)),
|
|
),
|
|
new Log(new Const(Math.E), base),
|
|
),
|
|
);
|
|
Log.symbol = "log";
|
|
|
|
function makeSumN(n) {
|
|
class SumNOp extends Operation {
|
|
_calc(...vals) {
|
|
return vals.reduce((s, v) => s + v, 0);
|
|
}
|
|
_diff(v) {
|
|
return this._args
|
|
.map((a) => a.diff(v))
|
|
.reduce((acc, d) => new Add(acc, d));
|
|
}
|
|
}
|
|
SumNOp.symbol = "sum" + n;
|
|
return SumNOp;
|
|
}
|
|
|
|
const Sum1 = makeSumN(1);
|
|
const Sum2 = makeSumN(2);
|
|
const Sum3 = makeSumN(3);
|
|
const Sum4 = makeSumN(4);
|
|
const Sum5 = makeSumN(5);
|
|
|
|
function makeAvgN(n) {
|
|
class AvgNOp extends Operation {
|
|
_calc(...vals) {
|
|
return vals.reduce((s, v) => s + v, 0) / n;
|
|
}
|
|
_diff(v) {
|
|
const sumDiff = this._args
|
|
.map((a) => a.diff(v))
|
|
.reduce((acc, d) => new Add(acc, d));
|
|
return new Divide(sumDiff, new Const(n));
|
|
}
|
|
}
|
|
AvgNOp.symbol = "avg" + n;
|
|
return AvgNOp;
|
|
}
|
|
|
|
const Avg1 = makeAvgN(1);
|
|
const Avg2 = makeAvgN(2);
|
|
const Avg3 = makeAvgN(3);
|
|
const Avg4 = makeAvgN(4);
|
|
const Avg5 = makeAvgN(5);
|
|
|
|
const Gauss = makeOperation(
|
|
"gauss",
|
|
(a, b, c, x) => a * Math.exp(-((x - b) ** 2) / (2 * c * c)),
|
|
(v, a, b, c, x) => {
|
|
if (a.isConst() && isZero(a)) return ZERO;
|
|
const gauss = new Gauss(a, b, c, x);
|
|
const xmb = new Subtract(x, b);
|
|
const c2 = new Multiply(c, c);
|
|
const da = new Divide(a.diff(v), a);
|
|
const dxb = new Divide(
|
|
new Multiply(xmb, new Subtract(x.diff(v), b.diff(v))),
|
|
c2,
|
|
);
|
|
const dc = new Divide(
|
|
new Multiply(new Multiply(xmb, xmb), c.diff(v)),
|
|
new Multiply(c2, c),
|
|
);
|
|
return new Multiply(gauss, new Add(da, new Subtract(dc, dxb)));
|
|
},
|
|
(a, b, c, x) => (isZero(a) ? ZERO : undefined),
|
|
);
|
|
Gauss.symbol = "gauss";
|
|
|
|
function wrapCalc(x, mn, mx) {
|
|
const range = mx - mn;
|
|
if (range === 0) return mn;
|
|
return mn + ((((x - mn) % range) + range) % range);
|
|
}
|
|
|
|
const Floor = makeOperation(
|
|
"__floor__",
|
|
(a) => Math.floor(a),
|
|
() => ZERO,
|
|
);
|
|
Floor.symbol = "__floor__";
|
|
|
|
const Wrap = makeOperation("wrap", wrapCalc, (v, x, mn, mx) => {
|
|
const q = new Floor(new Divide(new Subtract(x, mn), new Subtract(mx, mn)));
|
|
return new Subtract(
|
|
x.diff(v),
|
|
new Multiply(q, new Subtract(mx.diff(v), mn.diff(v))),
|
|
);
|
|
});
|
|
Wrap.symbol = "wrap";
|
|
|
|
const Sin = makeOperation(
|
|
"__sin__",
|
|
(a) => Math.sin(a),
|
|
(v, f) => new Multiply(new Cos(f), f.diff(v)),
|
|
);
|
|
Sin.symbol = "__sin__";
|
|
|
|
const Cos = makeOperation(
|
|
"__cos__",
|
|
(a) => Math.cos(a),
|
|
(v, f) => new Negate(new Multiply(new Sin(f), f.diff(v))),
|
|
);
|
|
Cos.symbol = "__cos__";
|
|
|
|
const Asin = makeOperation(
|
|
"__asin__",
|
|
(a) => Math.asin(a),
|
|
(v, f) =>
|
|
new Divide(
|
|
f.diff(v),
|
|
new Power(new Subtract(ONE, new Multiply(f, f)), new Const(0.5)),
|
|
),
|
|
);
|
|
Asin.symbol = "__asin__";
|
|
|
|
const Tanh = makeOperation(
|
|
"__tanh__",
|
|
(a) => Math.tanh(a),
|
|
(v, f) => {
|
|
const th = new Tanh(f);
|
|
return new Multiply(new Subtract(ONE, new Multiply(th, th)), f.diff(v));
|
|
},
|
|
);
|
|
Tanh.symbol = "__tanh__";
|
|
|
|
function softWrapCalc(x, mn, mx, lam) {
|
|
const range = mx - mn;
|
|
if (range === 0) return mn;
|
|
const a = (Math.PI * (x - mn)) / range;
|
|
return (
|
|
(Math.asin(Math.cos(a) * Math.tanh(lam * -Math.sin(a))) * range) / Math.PI +
|
|
(mx + mn) / 2
|
|
);
|
|
}
|
|
|
|
class SoftWrap extends Operation {
|
|
_calc(...vals) {
|
|
return softWrapCalc(...vals);
|
|
}
|
|
|
|
_tree() {
|
|
const [x, mn, mx, lam] = this._args;
|
|
const PI = new Const(Math.PI);
|
|
const range = new Subtract(mx, mn);
|
|
const a = new Divide(new Multiply(PI, new Subtract(x, mn)), range);
|
|
const th = new Tanh(new Multiply(lam, new Negate(new Sin(a))));
|
|
const inner = new Asin(new Multiply(new Cos(a), th));
|
|
return new Add(
|
|
new Multiply(inner, new Divide(range, PI)),
|
|
new Divide(new Add(mx, mn), TWO),
|
|
);
|
|
}
|
|
|
|
_diff(v) {
|
|
return this._tree().diff(v);
|
|
}
|
|
}
|
|
SoftWrap.symbol = "softWrap";
|
|
|
|
class ArcTan12 extends Operation {
|
|
_calc(...vals) {
|
|
return vals.length === 1
|
|
? Math.atan(vals[0])
|
|
: Math.atan2(vals[0], vals[1]);
|
|
}
|
|
_diff(v) {
|
|
if (this._args.length === 1) {
|
|
const [x] = this._args;
|
|
return new Divide(x.diff(v), new Add(ONE, new Multiply(x, x)));
|
|
}
|
|
const [y, x] = this._args;
|
|
return new Divide(
|
|
new Subtract(new Multiply(x, y.diff(v)), new Multiply(y, x.diff(v))),
|
|
new Add(new Multiply(x, x), new Multiply(y, y)),
|
|
);
|
|
}
|
|
}
|
|
ArcTan12.symbol = "atan12";
|
|
|
|
const Exp = makeOperation(
|
|
"exp",
|
|
(a) => Math.exp(a),
|
|
(v, f) => new Multiply(new Exp(f), f.diff(v)),
|
|
);
|
|
Exp.symbol = "exp";
|
|
|
|
class MeanExp extends Operation {
|
|
_calc(...vals) {
|
|
return vals.reduce((s, v) => s + Math.exp(v), 0) / vals.length;
|
|
}
|
|
_diff(v) {
|
|
const n = this._args.length;
|
|
const sumDiff = this._args
|
|
.map((a) => new Multiply(new Exp(a), a.diff(v)))
|
|
.reduce((acc, d) => new Add(acc, d));
|
|
return new Divide(sumDiff, new Const(n));
|
|
}
|
|
}
|
|
MeanExp.symbol = "meanExp";
|
|
|
|
class SumExp extends Operation {
|
|
_calc(...vals) {
|
|
return vals.reduce((s, v) => s + Math.exp(v), 0);
|
|
}
|
|
_diff(v) {
|
|
return this._args
|
|
.map((a) => new Multiply(new Exp(a), a.diff(v)))
|
|
.reduce((acc, d) => new Add(acc, d));
|
|
}
|
|
}
|
|
SumExp.symbol = "sumExp";
|
|
|
|
class Lse extends Operation {
|
|
_calc(...vals) {
|
|
return Math.log(vals.reduce((s, v) => s + Math.exp(v), 0));
|
|
}
|
|
_diff(v) {
|
|
const sumExpNode = new SumExp(...this._args);
|
|
const sumDiff = this._args
|
|
.map((a) => new Multiply(new Exp(a), a.diff(v)))
|
|
.reduce((acc, d) => new Add(acc, d));
|
|
return new Divide(sumDiff, sumExpNode);
|
|
}
|
|
}
|
|
Lse.symbol = "lse";
|
|
|
|
class Lme extends Operation {
|
|
_calc(...vals) {
|
|
return Math.log(vals.reduce((s, v) => s + Math.exp(v), 0) / vals.length);
|
|
}
|
|
_diff(v) {
|
|
const n = this._args.length;
|
|
const sumExpNode = new SumExp(...this._args);
|
|
const sumDiff = this._args
|
|
.map((a) => new Multiply(new Exp(a), a.diff(v)))
|
|
.reduce((acc, d) => new Add(acc, d));
|
|
return new Divide(sumDiff, sumExpNode);
|
|
}
|
|
}
|
|
Lme.symbol = "lme";
|
|
|
|
const OPERATIONS = {
|
|
"+": [Add, 2],
|
|
"-": [Subtract, 2],
|
|
"*": [Multiply, 2],
|
|
"/": [Divide, 2],
|
|
negate: [Negate, 1],
|
|
pow: [Power, 2],
|
|
log: [Log, 2],
|
|
sum1: [Sum1, 1],
|
|
sum2: [Sum2, 2],
|
|
sum3: [Sum3, 3],
|
|
sum4: [Sum4, 4],
|
|
sum5: [Sum5, 5],
|
|
avg1: [Avg1, 1],
|
|
avg2: [Avg2, 2],
|
|
avg3: [Avg3, 3],
|
|
avg4: [Avg4, 4],
|
|
avg5: [Avg5, 5],
|
|
gauss: [Gauss, 4],
|
|
wrap: [Wrap, 3],
|
|
softWrap: [SoftWrap, 4],
|
|
__floor__: [Floor, 1],
|
|
__sin__: [Sin, 1],
|
|
__cos__: [Cos, 1],
|
|
__asin__: [Asin, 1],
|
|
__tanh__: [Tanh, 1],
|
|
exp: [Exp, 1],
|
|
atan12: [ArcTan12, null],
|
|
meanExp: [MeanExp, null],
|
|
sumExp: [SumExp, null],
|
|
lse: [Lse, null],
|
|
lme: [Lme, null],
|
|
};
|
|
|
|
function parse(expr) {
|
|
const tokens = expr.trim().split(/\s+/);
|
|
const stack = [];
|
|
for (const tok of tokens) {
|
|
if (tok in OPERATIONS) {
|
|
const [Ctor, arity] = OPERATIONS[tok];
|
|
const args = stack.splice(stack.length - arity, arity);
|
|
stack.push(new Ctor(...args));
|
|
} else if (tok in VAR_INDEX) {
|
|
stack.push(new Variable(tok));
|
|
} else {
|
|
stack.push(new Const(Number(tok)));
|
|
}
|
|
}
|
|
return stack[0];
|
|
}
|
|
|
|
function parsePostfix(expr) {
|
|
const tokens = expr.trim().match(BRACKET_RE) || [];
|
|
if (tokens.length === 0) {
|
|
throw new ParseError("Empty expression");
|
|
}
|
|
let pos = 0;
|
|
|
|
function peek() {
|
|
return tokens[pos];
|
|
}
|
|
function consume() {
|
|
return tokens[pos++];
|
|
}
|
|
|
|
function parseArg() {
|
|
const val = parseExpr();
|
|
if (typeof val === "string") {
|
|
throw new ParseError(`Unexpected operator '${val}' in argument position`);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
function parseExpr() {
|
|
if (pos >= tokens.length) {
|
|
throw new ParseError("Unexpected end of input");
|
|
}
|
|
const tok = peek();
|
|
|
|
if (OPEN_BRACKETS.has(tok)) {
|
|
const openBracket = consume();
|
|
const closeBracket = OPEN_TO_CLOSE[openBracket];
|
|
|
|
const args = [];
|
|
while (peek() !== closeBracket) {
|
|
if (peek() === undefined) {
|
|
throw new ParseError(`Missing closing '${closeBracket}'`);
|
|
}
|
|
const item = parseExpr();
|
|
args.push(item);
|
|
}
|
|
consume();
|
|
|
|
if (args.length === 0) {
|
|
throw new ParseError("Empty parenthesised expression");
|
|
}
|
|
|
|
const opSym = args[args.length - 1];
|
|
if (typeof opSym !== "string") {
|
|
throw new ParseError(
|
|
"Expected operator at end of parenthesised expression, got expression",
|
|
);
|
|
}
|
|
if (!(opSym in OPERATIONS)) {
|
|
throw new ParseError(`Unknown operator: '${opSym}'`);
|
|
}
|
|
|
|
const exprArgs = args.slice(0, args.length - 1);
|
|
for (let i = 0; i < exprArgs.length; i++) {
|
|
if (typeof exprArgs[i] === "string") {
|
|
throw new ParseError(
|
|
`Unexpected operator '${exprArgs[i]}' in argument position`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const [Ctor, arity] = OPERATIONS[opSym];
|
|
if (arity !== null && exprArgs.length !== arity) {
|
|
throw new ParseError(
|
|
`Operator '${opSym}' expects ${arity} argument(s), got ${exprArgs.length}`,
|
|
);
|
|
}
|
|
return new Ctor(...exprArgs);
|
|
}
|
|
|
|
if (CLOSE_BRACKETS.has(tok)) {
|
|
throw new ParseError(`Unexpected closing bracket: '${tok}'`);
|
|
}
|
|
|
|
consume();
|
|
if (tok in OPERATIONS) {
|
|
return tok;
|
|
}
|
|
if (tok in VAR_INDEX) {
|
|
return new Variable(tok);
|
|
}
|
|
const n = Number(tok);
|
|
if (isNaN(n)) {
|
|
throw new ParseError(`Unknown token: '${tok}'`);
|
|
}
|
|
return new Const(n);
|
|
}
|
|
|
|
const result = parseExpr();
|
|
|
|
if (typeof result === "string") {
|
|
throw new ParseError(`Unexpected operator token: '${result}'`);
|
|
}
|
|
if (pos < tokens.length) {
|
|
throw new ParseError(`Unexpected token after expression: '${tokens[pos]}'`);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function parsePrefix(expr) {
|
|
const tokens = expr.trim().match(BRACKET_RE) || [];
|
|
let pos = 0;
|
|
|
|
function peek() {
|
|
return tokens[pos];
|
|
}
|
|
function consume() {
|
|
return tokens[pos++];
|
|
}
|
|
|
|
function parseExpr() {
|
|
if (pos >= tokens.length) {
|
|
throw new ParseError("Unexpected end of input");
|
|
}
|
|
const tok = peek();
|
|
|
|
if (OPEN_BRACKETS.has(tok)) {
|
|
const openBracket = consume();
|
|
const closeBracket = OPEN_TO_CLOSE[openBracket];
|
|
|
|
const op = consume();
|
|
if (op === undefined) {
|
|
throw new ParseError(`Expected operator after '${openBracket}'`);
|
|
}
|
|
if (!(op in OPERATIONS)) {
|
|
throw new ParseError(`Unknown operator: '${op}'`);
|
|
}
|
|
const [Ctor, arity] = OPERATIONS[op];
|
|
|
|
const args = [];
|
|
if (arity === null) {
|
|
while (peek() !== closeBracket) {
|
|
if (peek() === undefined) {
|
|
throw new ParseError(
|
|
`Expected '${closeBracket}' but reached end of input`,
|
|
);
|
|
}
|
|
args.push(parseExpr());
|
|
}
|
|
} else {
|
|
for (let i = 0; i < arity; i++) {
|
|
args.push(parseExpr());
|
|
}
|
|
if (peek() !== closeBracket) {
|
|
throw new ParseError(
|
|
`Expected '${closeBracket}' after ${arity} argument(s) of '${op}', ` +
|
|
`got ${peek() === undefined ? "end of input" : "'" + peek() + "'"}`,
|
|
);
|
|
}
|
|
}
|
|
consume();
|
|
|
|
return new Ctor(...args);
|
|
}
|
|
|
|
consume();
|
|
if (tok in VAR_INDEX) {
|
|
return new Variable(tok);
|
|
}
|
|
if (CLOSE_BRACKETS.has(tok)) {
|
|
throw new ParseError(`Unexpected closing bracket: '${tok}'`);
|
|
}
|
|
const n = Number(tok);
|
|
if (!isNaN(n)) {
|
|
return new Const(n);
|
|
}
|
|
throw new ParseError(`Unexpected token: '${tok}'`);
|
|
}
|
|
|
|
const result = parseExpr();
|
|
|
|
if (pos < tokens.length) {
|
|
throw new ParseError(`Unexpected token after expression: '${tokens[pos]}'`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
if (typeof module !== "undefined") {
|
|
module.exports = {
|
|
Const,
|
|
Variable,
|
|
Add,
|
|
Subtract,
|
|
Multiply,
|
|
Divide,
|
|
Negate,
|
|
Power,
|
|
Log,
|
|
Sum1,
|
|
Sum2,
|
|
Sum3,
|
|
Sum4,
|
|
Sum5,
|
|
Avg1,
|
|
Avg2,
|
|
Avg3,
|
|
Avg4,
|
|
Avg5,
|
|
Gauss,
|
|
Wrap,
|
|
SoftWrap,
|
|
ArcTan12,
|
|
MeanExp,
|
|
SumExp,
|
|
Lse,
|
|
Lme,
|
|
parse,
|
|
parsePrefix,
|
|
parsePostfix,
|
|
};
|
|
}
|