Files
paradigms/javascript/objectExpression.js
2026-04-13 20:12:01 +03:00

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