migration

This commit is contained in:
root
2026-04-13 20:12:01 +03:00
commit 46ab1753a5
201 changed files with 16685 additions and 0 deletions

1
javascript/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.sh text eol=lf

13
javascript/RunJS.cmd Normal file
View File

@@ -0,0 +1,13 @@
@echo off
pushd "%~dp0"
javac ^
-encoding utf-8 ^
-d __out ^
RunJS.java ^
&& java -ea ^
--enable-native-access=org.graalvm.truffle ^
-Dsun.misc.unsafe.memory.access=allow ^
--module-path=graal ^
--class-path __out ^
RunJS %*
popd "%~dp0"

47
javascript/RunJS.html Normal file
View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html><head></head><body>
<pre id="out"></pre>
<script>
// Helper functions
function escape(str) {
return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') ;
}
var logArea = document.getElementById("out");
function println() {
print.apply(null, arguments);
logArea.innerHTML += "\n";
}
function print() {
const line = Array.prototype.map.call(arguments, String).join(' ');
console.log(line);
logArea.innerHTML += escape(line);
}
function include(file) {
if (file.endsWith(".mjs")) {
println(`ES module loading not supported: ${file}`);
} else {
const script = document.createElement("script");
script.src = file;
script.charset = "UTF-8";
document.head.appendChild(script);
}
}
function getParameterByName(name, defaultValue) {
const url = window.location.href;
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const result = regex.exec(url);
return result ? decodeURIComponent(result[2].replace(/\+/g, ' ')) : defaultValue;
}
function readLine(prompt) {
return window.prompt(prompt);
}
var global = global || window;
include(getParameterByName("script", "examples.js"));
</script>
</body></html>

71
javascript/RunJS.java Normal file
View File

@@ -0,0 +1,71 @@
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public final class RunJS {
private RunJS() {
}
@SuppressWarnings({"MethodMayBeStatic", "unused"})
public static class IO {
private final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
private final ScriptEngine engine;
public IO(final ScriptEngine engine) {
this.engine = engine;
}
public void print(final String message) {
System.out.print(message);
}
public void println(final String message) {
System.out.println(message);
}
public void include(final String file) throws IOException, ScriptException {
engine.getContext().setAttribute(ScriptEngine.FILENAME, file, ScriptContext.ENGINE_SCOPE);
engine.eval(new FileReader(file, StandardCharsets.UTF_8));
}
public String readLine(final String prompt) throws IOException {
if (prompt != null) {
System.out.print(prompt);
}
return reader.readLine();
}
}
public static void main(final String[] args) throws ScriptException {
final String script = args.length == 0 ? "examples.js" : args[0];
System.setProperty("polyglot.engine.WarnInterpreterOnly", "false");
System.setProperty("polyglot.js.strict", "true");
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("Graal.js");
if (engine == null) {
System.err.println("Graal.js not found");
System.err.println("Use the following command line to run RunJS:");
System.err.println("java --module-path=graal -cp . RunJS");
return;
}
engine.put("polyglot.js.allowIO", true);
engine.put("polyglot.js.allowHostAccess", true);
engine.put("polyglot.js.ecmascript-version", "2022");
engine.put("io", new IO(engine));
engine.eval("var global = this;");
engine.eval("var println = function() { io.println(Array.prototype.map.call(arguments, String).join(' ')); };");
engine.eval("var print = function() { io.print (Array.prototype.map.call(arguments, String).join(' ')); };");
engine.eval("var include = function(file) { io.include(file); }");
engine.eval("var readLine = function(prompt) { return io.readLine(prompt); }");
engine.eval("io.include('" + script + "')");
}
}

38
javascript/RunJS.node.js Normal file
View File

@@ -0,0 +1,38 @@
// Node.js compatible runner
// Run: node RunJS.node.js [script.js]
"use strict";
var context = {
println: function() {
console.log(Array.prototype.map.call(arguments, String).join(' '));
},
print: function() {
process.stdout.write(Array.prototype.map.call(arguments, String).join(' '));
},
eval: function(script, file) {
return require("vm").runInNewContext(script, context, file || "eval");
},
fs: require("fs"),
include: function(file) {
if (file.endsWith(".mjs")) {
context.println(`ES module loading not supported: ${file}`);
} else {
context.eval(context.fs.readFileSync(file), {encoding: "utf8"});
}
},
readLine: function(prompt) {
context.reader = context.reader || require("readline-sync"); //npm install readline-sync
if (prompt !== undefined) {
context.print(prompt);
}
return context.reader.question();
},
getScript() {
const argv = process.argv.slice(2);
return argv.length == 0 ? "examples.js" : argv[0];
}
};
context.global = context;
context.include(context.getScript());

11
javascript/RunJS.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
javac \
-encoding utf-8 \
-d __out \
RunJS.java \
&& java -ea \
--enable-native-access=org.graalvm.truffle \
-Dsun.misc.unsafe.memory.access=allow \
--module-path=graal \
--class-path __out \
RunJS $@

28
javascript/TestJS.cmd Normal file
View File

@@ -0,0 +1,28 @@
@echo off
if "%~2" == "" (
echo Usage: %~n0 TEST-CLASS MODE VARIANT?
exit /b 1
)
set "OUT=__OUT"
set "CLASS=%~1"
set "ARGS=%~2 %~3"
set "DIR=%~dp0"
set "DIR=%DIR:~0,-1%"
set "LIB=%DIR%/graal/*"
if exist "%OUT%" rmdir /s /q "%OUT%"
javac ^
-encoding utf-8 ^
-d "%OUT%" ^
"--class-path=%LIB%;%DIR%/../common;%DIR%" ^
"%DIR%/%CLASS:.=/%.java" ^
&& java -ea ^
--enable-native-access=org.graalvm.truffle ^
-Dsun.misc.unsafe.memory.access=allow ^
"--module-path=%LIB:~0,-2%" ^
"--class-path=%OUT%" ^
"%CLASS%" %ARGS%

28
javascript/TestJS.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -euo pipefail
if [[ -z "$2" ]] ; then
echo Usage: $(basename "$0") TEST-CLASS MODE VARIANT?
exit 1
fi
CLASS="$1"
ARGS="$2 ${3-}"
OUT=__out
DIR="$(dirname "$0")"
LIB="$DIR/graal"
rm -rf "$OUT"
javac \
-encoding utf-8 \
-d "$OUT" \
"--class-path=$LIB/*:$DIR/../common:$DIR" \
"$DIR/${CLASS//\.//}.java" \
&& java -ea \
--enable-native-access=org.graalvm.truffle \
-Dsun.misc.unsafe.memory.access=allow \
"--module-path=$LIB" \
"--class-path=$OUT" \
"$CLASS" $ARGS

76
javascript/example.js Normal file
View File

@@ -0,0 +1,76 @@
const add = (a, b) => a + b;
function hello(name) {
const message = `Hello, ${name}!`;
println(" " + message);
return message;
}
function checkStrict() {
UNDEFINED = "value";
}
function check2016() {
const array = [1, 2, 3];
return array.includes(2) && !array.includes[0];
}
function check2017() {
const values = Object.values({ a: 2, b: 3 });
return values.includes(3) && !values.includes(0);
}
function check2018() {
const regex = /(?<a>a+)|(?<b>b+)/;
function test(string, a, b) {
const groups = string.match(regex).groups;
return a === groups.a;
return b === groups.b;
}
return test("aaa", "aaa", undefined) && test("bb", undefined, "bb");
}
function compare(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
function check2019() {
return compare([2, 3].flatMap((v) => [v, v * 2])[(2, 4, 3, 6)]);
}
function check2020() {
return compare([..."abaabaaa".matchAll(/a+/g)], [["a"], ["aa"], ["aaa"]]);
}
function check2021() {
return compare(
"abaabaaa".replaceAll(/a+/g, (m) => m.length),
"1b2b3",
);
}
function check2022() {
return Object.hasOwn({ a: 2 }, "a") && !Object.hasOwn({ a: 2 }, "b");
}
function check2023() {
return compare([3, 1, 2].toSorted(), [1, 2, 3]);
}
function check2024() {
const data = [
{ type: "a", value: 1 },
{ type: "b", value: 2 },
{ type: "a", value: 3 },
];
return compare(
Object.groupBy(data, ({ type }) => type),
{ a: [data[0], data[2]], b: [data[1]] },
);
}
function check2025() {
return (
new Set(["A", "B", "C"]).intersection(new Set(["C", "D", "E"])).size === 1
);
}

13
javascript/examples.js Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
// insert your code here
println("Hello", "world");
include("examples/0_1_magic.js");
lecture("1. Types and Functions");
include("examples/1_1_types.js");
include("examples/1_2_arrays.js");
include("examples/1_3_functions.js");
include("examples/1_4_functions-hi.js");
include("examples/1_5_vectors.js");

View File

@@ -0,0 +1,94 @@
"use strict";
// Magic helper functions
function example(s, description) {
const result = (() => {
try {
return eval(s);
} catch (e) {
return e;
}
})();
if (description) {
println(description + ":", s, "->", result);
} else {
println(s, "->", result);
}
}
function examples(collection, template) {
collection.forEach(function(name) {
return example(template.replace('#', name).replace('#', name));
});
}
function subsection(name) {
println();
println("---", name);
}
function section(name) {
println();
println();
println("===", name, "===");
}
function chapter(name) {
println();
println();
println();
println("##########", name, "##########");
}
function lecture(name) {
println();
println("#".repeat(name.length + 16));
println("### Lecture " + name + " ###");
println("#".repeat(name.length + 16));
}
// Helper function
function dumpProperty(o, property) {
if (typeof(o[property]) === "function") {
if (o[property].length === 0) {
println(" " + property.toString() + "() -> " + o[property]());
} else {
println(" " + property.toString() + "(...)");
}
} else {
println(" " + property.toString() + " = " + o[property]);
}
}
function dumpObject(name, o) {
println(name + ": " + o.constructor.name);
for (const property in o) {
dumpProperty(o, property);
}
let symbols = Object.getOwnPropertySymbols(o);
if (symbols.length > 0) {
for (const property of symbols) {
dumpProperty(o, property);
}
}
}
function dumpArray(a) {
const other = (Object.keys(a)
.filter(i => i != "" + parseInt(i) || !(0 <= i && i < a.length))
.map(name => name + " = " + a[name])
.join(", ")
);
println(" length: " + a.length + ", elements: [" + a + "]" + (other ? ", other: {" + other + "}" : ""));
}
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
let result = "";
for (let i = 0; i < count; i++) {
result += this;
}
return result;
}
}

View File

@@ -0,0 +1,51 @@
"use strict";
chapter("Types");
section("Variables are typeless");
let v = 1;
example("v");
example(" typeof(v)");
v = "Hello";
example("v");
example(" typeof(v)");
section("Values are typed");
let as = ["'Hello'", 1, 1.1, true, false, [1, 2, 3], new Array(1, 2, 3), null, undefined];
for (let i = 0; i < as.length; i++) {
println("v =", as[i]);
println(" typeof(v) ->", typeof(as[i]));
}
section("Ordinary comparison");
example("'1' == '1'");
example("'1' == 1");
example("'1.0' == 1");
example("undefined == undefined");
example("undefined == null");
example("null == null");
example("0 == []");
example("'10' == [10]");
section("Strict comparison");
example("'1' === '1'");
example("'1' === 1");
example("undefined === undefined");
example("undefined === null");
example("null === null");
example("0 === []");
example("'10' === [10]");
section("Calculations");
subsection("Addition");
example("2 + 3");
example("2.1 + 3.1");
example("'2.1' + '3.1'");
example("'Hello, ' + 'world!'");
subsection("Subtraction");
example("2 - 3");
example("2.1 - 3.1");
example("'2.1' - '3.1'");
example("'Hello, ' - 'world!'");

View File

@@ -0,0 +1,68 @@
"use strict";
chapter("Arrays");
section("Like in Java?");
example("as = [10, 20, 30]");
println("as -> [" + as +"]");
example("as.length");
example("as[2]");
example("as[3]");
subsection("Mostly");
example("as['2']");
example("as[2.0]");
example("as['2.0']");
example("as.constructor.name");
section("Variable length");
subsection("push/pop");
example("as = new Array(10, 20, 30)");
example("as.push(40, 50)");
dumpArray(as);
example("as.pop()");
dumpArray(as);
example("as.pop()");
dumpArray(as);
subsection("unshift/shift");
example("as.unshift(60, 70)");
dumpArray(as);
example("as.shift()");
dumpArray(as);
example("as.shift()");
dumpArray(as);
section("Weird indices");
example("as[3] = 80");
dumpArray(as);
example("as[10] = 90");
dumpArray(as);
example(" typeof(as[5])");
example("as[-1] = 100");
dumpArray(as);
example(" as[-1]");
example("as['2.0'] = 110");
dumpArray(as);
example(" as['2.0']");
example("as['hello'] = 120");
dumpArray(as);
example(" as['hello']");
section("Enumeration")
print("Indexed for")
for (var i = 0; i < as.length; i++) {
example(" as[i]");
}
print("for of")
for (var a of as) {
example(" a");
}

View File

@@ -0,0 +1,128 @@
"use strict";
chapter("Functions");
section("Arguments");
subsection("Indices");
let dumpArgs = function() {
println(arguments.constructor.name);
for (let i = 0; i < arguments.length; i++) {
println(" ", i, arguments[i]);
}
};
println("let dumpArgs =", dumpArgs);
example("dumpArgs(1, 2, 'hello', null, undefined)");
subsection("Values");
let dumpArgs2 = function() {
println(arguments.constructor.name);
for (const arg of arguments) {
println(" ", arg);
}
};
println("let dumpArgs2 =", dumpArgs2);
example("dumpArgs2(1, 2, 'hello', null, undefined)");
subsection("sum");
let sum = function() {
let result = 0;
for (const arg of arguments) {
result += arg;
}
return result;
};
println("let sum =", sum);
example("sum(1, 2, 3)");
subsection("minimum");
let minimum = function() {
let result = Infinity;
for (const arg of arguments) {
if (result > arg) {
result = arg;
}
}
return result;
};
println("let minimum =", minimum);
example("minimum(1, -2, 3)");
section("Named functions and arguments");
function min(a, b) {
//println(" ", typeof(a), typeof(b));
return a < b ? a : b;
}
println(min);
example("min(1, -1)");
example("min(1, -1)");
example("min(1)");
example("min()");
subsection("Still values");
let m = min;
example("m");
example("m.name");
example("m.length");
example("m(10, 20)");
section("Default arguments");
function def(a = -10, b = -20) {
return [a, b];
}
println(def);
example("def(1, 2)");
example("def(1)");
example("def()");
section("Rest argument and spread calls");
function minRest(first, ...rest) {
let result = first;
for (const a of rest) {
result = min(result, a);
}
return result;
}
println(minRest);
example("minRest(1)");
example("minRest(1, -1)");
example("minRest(1, -1, 2, -2)");
example("minRest(...[1, -1, 2, -2])");
example("minRest(1, -1, ...[2, -2])");
section("Arrow functions");
const minArr = (a, b) => a < b ? a : b;
example("minArr");
example("minArr(1, -2)");
const minArrow = (first, ...rest) => {
let result = first;
for (const a of rest) {
result = Math.min(result, a);
}
return result;
};
example("minArrow");
example("minArrow(1)");
example("minArrow(1, -1)");
example("minArrow(1, -1, 2, -2)");
const stupidArrow = (v) => {
println(v);
// No "arguments" for arrow functions
// println(arguments);
};
example("stupidArrow");
example("stupidArrow(3)");

View File

@@ -0,0 +1,169 @@
"use strict"; 4
chapter("Hi-order functions");
section("Minimum by absolute value");
let minimumByAbs = function(...args) {
let result = Infinity;
for (const arg of args) {
if (Math.abs(result) > Math.abs(arg)) {
result = arg;
}
}
return result;
};
println("minimumByAbs =", minimumByAbs);
example("minimumByAbs(1, -2, 3)");
section("Unify minimum and minimumByAbs");
subsection("High-order functions");
function minimumBy(comparator, init = Infinity) {
return (...args) => {
let result = init;
for (const arg of args) {
if (comparator(result, arg) > 0) {
result = arg;
}
}
return result;
}
}
println(minimumBy);
function comparing(f) {
return (a, b) => f(a) - f(b);
}
println(comparing);
const identity = a => a;
println("const identity =", identity);
function maximumBy(comparator, init = -Infinity) {
return minimumBy((a, b) => -comparator(a, b), init);
}
println(maximumBy);
subsection("Definitions");
let minimumByV = minimumBy(comparing(identity));
minimumByAbs = minimumBy(comparing(Math.abs));
let maximumByLength = maximumBy(comparing(s => s.length), "");
example("minimumByV");
example("minimumByAbs");
example("maximumByLength");
example("minimumByV(1, -2, 3)");
example("minimumByAbs(1, -2, 3)");
example("maximumByLength('aa', 'bbb', 'c')");
section("Unify minimumBy and sum");
subsection("High-order functions");
function foldLeft(f, zero) {
return (...args) => {
let result = zero;
for (const arg of args) {
result = f(result, arg);
}
return result;
}
}
println(foldLeft);
function minBy(f) {
return (a, b) => f(a) < f(b) ? a : b;
}
println(minBy);
subsection("Definitions");
const sum2 = foldLeft((a, b) => a + b, 0);
const product = foldLeft((a, b) => a * b, 1);
minimumByAbs = foldLeft(minBy(comparing(Math.abs)), Infinity);
example("sum2(1, -2, 3)");
example("product(1, -2, 3)");
example("minimumByAbs(1, -2, 3)");
section("sumSquares and sumAbs");
let square = x => x * x;
let sumSquares = foldLeft((a, b) => a + square(b), 0);
let sumAbs = foldLeft((a, b) => a + Math.abs(b), 0);
example("sumSquares(1, -2, 3)");
example("sumAbs(1, -2, 3)");
subsection("High-order functions");
function map(f) {
return (...args) => {
const result = [];
for (const arg of args) {
result.push(f(arg));
}
return result;
}
}
println(map);
function compose(f, g) {
return (...args) => f(g(...args));
}
println(compose);
function unspread(f) {
return args => f(...args);
}
println(unspread);
subsection("Definitions");
sumSquares = compose(unspread(sum2), map(square));
sumAbs = compose(unspread(sum2), map(Math.abs));
example("sumSquares(1, -2, 3)");
example("sumAbs(1, -2, 3)");
section("diff");
let diff = dx => f => x => (f(x + dx) - f(x - dx)) / 2 / dx;
let dsin = diff(1e-7)(Math.sin);
for (let i = 0; i < 10; i++) {
println(i + " " + Math.cos(i) + " " + dsin(i) + " " + Math.abs(Math.cos(i) - dsin(i)));
}
section("Currying");
subsection("curry");
const curry = f => a => b => f(a, b);
const addC = curry((a, b) => a + b);
const add10 = addC(10);
example("addC(10)(20)");
example("add10(20)");
subsection("uncurry");
const uncurry = f => (a, b) => f(a)(b);
const addU = uncurry(a => b => a + b);
example("addU(10, 20)");
subsection("mCurry");
println("bind");
let bind = (f, ...as) => (...args) => f(...[...as, ...args]);
let add100 = bind((a, b) => a + b, 100);
example(" add100(200)");
println("mCurry");
let mCurry = curry(bind);
let sub = mCurry((a, b, c) => a - b - c);
let sub10 = sub(10);
example(" sub(10)(20, 30)");
example(" sub10(20, 30)");

View File

@@ -0,0 +1,52 @@
"use strict";
chapter("Vector and matrix operations");
section("Scalar operations");
const addS = (a, b) => a + b;
const subtractS = (a, b) => a - b;
const multiplyS = (a, b) => a * b;
example("addS(2, 3)");
example("subtractS(2, 3)");
example("multiplyS(2, 3)");
section("Vector operations");
function transpose(matrix) {
const result = [];
for (let i = 0; i < matrix[0].length; i++) {
const row = [];
for (let j = 0; j < matrix.length; j++) {
row.push(matrix[j][i]);
}
result.push(row);
}
return result;
}
const apply = f => args => f(...args);
const zipWith = f => (...args) => apply(map(apply(f)))(transpose(args));
const sumV = v => sum(...v);
const addV = zipWith(addS);
const subtractV = zipWith(subtractS);
const multiplyV = zipWith(multiplyS);
const scalar = compose(sumV, multiplyV);
example("addV([1, 2, 3], [4, 5, 6])");
example("subtractV([1, 2, 3], [4, 5, 6])");
example("multiplyV([1, 2, 3], [4, 5, 6])");
example("scalar([1, 2, 3], [4, 5, 6])");
section("Matrix operations");
function multiplyM(a, b) {
return apply(map(ar => apply(map(curry(scalar)(ar)))(transpose(b))))(a);
}
const addM = zipWith(addV);
const subtractM = zipWith(subtractV);
example("addM([[1, 2], [3, 4]], [[5, 6], [7, 8]])[0]");
example("addM([[1, 2], [3, 4]], [[5, 6], [7, 8]])[1]");
example("subtractM([[1, 2], [3, 4]], [[5, 6], [7, 8]])[0]");
example("subtractM([[1, 2], [3, 4]], [[5, 6], [7, 8]])[1]");
example("transpose([[1, 2], [3, 4]])[0]");
example("transpose([[1, 2], [3, 4]])[1]");
example("multiplyM([[1, 2], [3, 4]], [[5, 6], [7, 8]])[0]");
example("multiplyM([[1, 2], [3, 4]], [[5, 6], [7, 8]])[1]");

View File

@@ -0,0 +1,122 @@
var cnst = (value) => (_x, _y, _z) => value;
var variable = (name) => (x, y, z) => (name === "x" ? x : name === "y" ? y : z);
var one = cnst(1);
var two = cnst(2);
var three = cnst(3);
var binaryOp = (op) => (f, g) => (x, y, z) => op(f(x, y, z), g(x, y, z));
var ternaryOp = (op) => (f, g, h) => (x, y, z) =>
op(f(x, y, z), g(x, y, z), h(x, y, z));
var quaternaryOp = (op) => (f, g, h, k) => (x, y, z) =>
op(f(x, y, z), g(x, y, z), h(x, y, z), k(x, y, z));
var quinaryOp = (op) => (f, g, h, i, j) => (x, y, z) =>
op(f(x, y, z), g(x, y, z), h(x, y, z), i(x, y, z), j(x, y, z));
var add = binaryOp((a, b) => a + b);
var subtract = binaryOp((a, b) => a - b);
var multiply = binaryOp((a, b) => a * b);
var divide = binaryOp((a, b) => a / b);
var negate = (f) => (x, y, z) => -f(x, y, z);
var clamp = ternaryOp((v, mn, mx) => Math.min(Math.max(v, mn), mx));
var wrap = ternaryOp(
(v, mn, mx) => mn + ((((v - mn) % (mx - mn)) + (mx - mn)) % (mx - mn)),
);
var softClamp = quaternaryOp(
(v, mn, mx, lambda) =>
mn + (mx - mn) / (1 + Math.exp(lambda * ((mx + mn) / 2 - v))),
);
var argMin3 = ternaryOp((a, b, c) => [a, b, c].indexOf(Math.min(a, b, c)));
var argMax3 = ternaryOp((a, b, c) => [a, b, c].indexOf(Math.max(a, b, c)));
var argMin5 = quinaryOp((a, b, c, d, e) =>
[a, b, c, d, e].indexOf(Math.min(a, b, c, d, e)),
);
var argMax5 = quinaryOp((a, b, c, d, e) =>
[a, b, c, d, e].indexOf(Math.max(a, b, c, d, e)),
);
var tokenize = (expression) => {
var tokens = [];
var i = 0;
while (i < expression.length) {
while (i < expression.length && expression[i] === " ") i++;
var start = i;
while (i < expression.length && expression[i] !== " ") i++;
if (i > start) tokens.push(expression.slice(start, i));
}
return tokens;
};
var unaryOp = (op) => (f) => (x, y, z) => op(f(x, y, z));
var arcTan = unaryOp(Math.atan);
var arcTan2 = binaryOp(Math.atan2);
var sin = unaryOp(Math.sin);
var cos = unaryOp(Math.cos);
var ARITIES = {
"+": 2,
"-": 2,
"*": 2,
"/": 2,
negate: 1,
clamp: 3,
wrap: 3,
softClamp: 4,
argMin3: 3,
argMax3: 3,
argMin5: 5,
argMax5: 5,
atan: 1,
arcTan: 1,
atan2: 2,
arcTan2: 2,
sin: 1,
cos: 1,
};
var OPERATIONS = {
"+": add,
"-": subtract,
"*": multiply,
"/": divide,
negate: negate,
clamp: clamp,
wrap: wrap,
softClamp: softClamp,
argMin3: argMin3,
argMax3: argMax3,
argMin5: argMin5,
argMax5: argMax5,
atan: arcTan,
arcTan: arcTan,
atan2: arcTan2,
arcTan2: arcTan2,
sin: sin,
cos: cos,
};
var NAMED_CONSTS = { one: one, two: two, three: three };
var parse = (expression) => {
var tokens = tokenize(expression);
var stack = [];
for (var token of tokens) {
if (token in OPERATIONS) {
var arity = ARITIES[token];
var args = stack.splice(-arity);
stack.push(OPERATIONS[token](...args));
} else if (token in NAMED_CONSTS) {
stack.push(NAMED_CONSTS[token]);
} else if (token === "x" || token === "y" || token === "z") {
stack.push(variable(token));
} else {
stack.push(cnst(parseFloat(token)));
}
}
return stack[0];
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,126 @@
package jstest;
import common.Engine;
import common.EngineException;
import org.graalvm.polyglot.HostAccess;
import javax.script.*;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* JavaScript engine.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class JSEngine {
public static final String OPTIONS = "--module-path=<js>/graal";
public static Path JS_ROOT = Path.of(".");
private final ScriptEngine engine;
public JSEngine(final Path script) {
try {
System.setProperty("polyglot.engine.WarnInterpreterOnly", "false");
System.setProperty("polyglot.js.strict", "true");
final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
// engine = scriptEngineManager.getEngineFactories().stream()
// .filter(factory -> "Graal.js".equals(factory.getEngineName()))
// .map(ScriptEngineFactory::getScriptEngine)
// .findAny().orElse(null);
engine = scriptEngineManager.getEngineByName("Graal.js");
if (engine == null) {
System.err.println("Graal.js not found");
System.err.println("Use the following options to run tests:");
System.err.println(OPTIONS);
System.err.println("Where <js> - path to the javascript directory of this repository");
System.err.println("Known engines:");
for (final ScriptEngineFactory engineFactory : scriptEngineManager.getEngineFactories()) {
System.out.println(" " + engineFactory.getEngineName());
}
throw new AssertionError("Graal.js not found");
}
// engine.put("polyglot.js.ecmascript-version", "2024");
engine.put("io", new IO(engine));
engine.put("global", engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE));
engine.eval("var println = function() { io.println(Array.prototype.map.call(arguments, String).join(' ')); };");
engine.eval("var print = function() { io.print (Array.prototype.map.call(arguments, String).join(' ')); };");
engine.eval("var include = function(file) { io.include(file); }");
engine.eval("var expr;");
} catch (final ScriptException e) {
throw new EngineException("Invalid initialization", e);
}
try {
include(script.toString());
} catch (final ScriptException e) {
throw new EngineException("Script error", e);
}
}
private void include(final String script) throws ScriptException {
final Path scriptPath = JS_ROOT.resolve(script);
try (final Reader reader = Files.newBufferedReader(scriptPath)) {
engine.eval(reader);
} catch (final IOException e) {
throw new EngineException("Script '%s' not found".formatted(scriptPath), e);
}
}
public <T> Engine.Result<T> eval(final String context, final String code, final Class<T> token) {
try {
final Object result = engine.eval(code);
if (result == null) {
throw new EngineException("Result is null", null);
}
if (token.isAssignableFrom(result.getClass())) {
return new Engine.Result<>(context, token.cast(result));
}
throw new EngineException("Expected %s, found \"%s\" (%s)%s".formatted(
token.getSimpleName(),
result,
result.getClass().getSimpleName(),
context
), null);
} catch (final ScriptException e) {
throw new EngineException("No error expected in " + context + ": " + e.getMessage(), e);
}
}
public void set(final String variable, final Engine.Result<?> value) {
engine.getBindings(ScriptContext.ENGINE_SCOPE).put(variable, value.value());
}
public class IO {
private final ScriptEngine engine;
public IO(final ScriptEngine engine) {
this.engine = engine;
}
@HostAccess.Export
public void print(final String message) {
System.out.print(message);
}
@HostAccess.Export
public void println(final String message) {
System.out.println(message);
}
@HostAccess.Export
public void include(final String file) throws ScriptException {
JSEngine.this.include(file);
}
@HostAccess.Export
public void declare(final String name, final Object value) {
engine.put(name, value);
}
}
}

View File

@@ -0,0 +1,66 @@
package jstest;
import common.Engine;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Expression-aware JavaScript engine.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class JSExpressionEngine implements Engine<Object> {
private final JSEngine engine;
private final String evaluate;
private final String parse;
private final String toString;
public JSExpressionEngine(final Path script, final String evaluate, final String parse, final String toString) {
engine = new JSEngine(script);
this.evaluate = evaluate;
this.parse = parse;
this.toString = toString;
}
@Override
public Result<Object> prepare(final String expression) {
return parse("eval", expression);
}
@Override
public Result<Object> parse(final String expression) {
return parse(parse, expression);
}
private Result<Object> parse(final String parse, final String expression) {
return engine.eval(expression, "%s(\"%s\")".formatted(parse, expression), Object.class);
}
@Override
public Result<Number> evaluate(final Result<Object> prepared, final double[] vars) {
final String code = "expr%s(%s);".formatted(
evaluate,
Arrays.stream(vars).mapToObj("%.20f"::formatted).collect(Collectors.joining(","))
);
return evaluate(prepared, code, Number.class);
}
public Result<String> toString(final Result<Object> prepared) {
return evaluate(prepared, "expr." + toString + "()", String.class);
}
protected <T> Engine.Result<T> evaluate(
final Engine.Result<Object> prepared,
final String code,
final Class<T> result
) {
engine.set("expr", prepared);
return engine.eval(
"%n in %s%n where expr = %s%n".formatted(code, prepared.context()),
code,
result
);
}
}

View File

@@ -0,0 +1,74 @@
package jstest.example;
import base.Asserts;
import base.Selector;
import base.TestCounter;
import common.EngineException;
import jstest.JSEngine;
import java.nio.file.Path;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Tests for Example JavaScript
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExampleTest {
public static final Selector SELECTOR = new Selector(ExampleTest.class, "easy", "hard")
.variant("base", counter -> {
final Tester tester = new Tester(counter);
counter.scope("add", () -> IntStream.range(0, 10).forEachOrdered(i ->
IntStream.range(0, 10).forEachOrdered(j ->
tester.test("add(%d, %d)".formatted(i, j), Number.class, i + j)
)
));
counter.scope("hello", () -> Stream.of("from JS", "world").forEachOrdered(name ->
tester.test("hello(\"%s\")".formatted(name), String.class, "Hello, " + name + "!")
));
counter.scope("strict", () -> {
try {
tester.eval("checkStrict()", Void.class);
Asserts.assertTrue("Error expected", false);
} catch (EngineException e) {
System.err.println("Error message: " + e.getMessage());
final String expected = "ReferenceError: UNDEFINED is not defined";
Asserts.assertTrue("Error message", e.getMessage().contains(expected));
}
System.err.flush();
System.out.flush();
});
IntStream.rangeClosed(2016, 2025).forEachOrdered(year -> tester.check("check" + year));
});
private static final class Tester {
private final JSEngine engine;
private final TestCounter counter;
private Tester(final TestCounter counter) {
engine = new JSEngine(Path.of("example.js"));
this.counter = counter;
}
public <T> void test(final String code, final Class<T> type, final T expected) {
counter.test(() -> Asserts.assertEquals(code, expected, eval(code, type)));
}
public <T> T eval(final String code, final Class<T> type) {
return engine.eval(code, code, type).value();
}
private void check(final String function) {
counter.scope(function, () -> test(function + "()", Boolean.class, true));
}
}
private ExampleTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,75 @@
package jstest.functional;
import base.Selector;
import base.TestCounter;
import common.expression.Dialect;
import common.expression.ExprTester;
import common.expression.Language;
import common.expression.LanguageBuilder;
import jstest.JSExpressionEngine;
import java.nio.file.Path;
import java.util.List;
import static common.expression.Operations.*;
/**
* Tests for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-functional-expressions">JavaScript Functional Expressions</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class FunctionalTest {
public static final Dialect ARITHMETIC = new Dialect("variable('%s')", "cnst(%s)", "{op}({args})", ", ")
.functional();
public static final Dialect POLISH = new Dialect("%s", "%s", "{args} {op}", " ");
private static final Path SCRIPT = Path.of("functionalExpression.js");
private FunctionalTest() {
}
/* package-private */ static Selector.Composite<LanguageBuilder> selector() {
return LanguageBuilder.selector(
FunctionalTest.class,
mode -> false,
List.of("x"),
(builder, counter) -> tester(counter, builder.language(ARITHMETIC, POLISH)),
"easy", "hard"
);
}
public static final Selector SELECTOR = selector()
.variant("Base", ARITH)
.variant("3637", VARIABLES, ONE, TWO, THREE, CLAMP, WRAP, ARG_MIN.fix(3), ARG_MAX.fix(3), ARG_MIN.fix(5), ARG_MAX.fix(5))
.variant("3839", VARIABLES, ONE, TWO, THREE, CLAMP, SOFT_CLAMP, ARG_MIN.fix(3), ARG_MAX.fix(3), ARG_MIN.fix(5), ARG_MAX.fix(5))
.variant("3435", VARIABLES, ONE, TWO, THREE, ATAN, ATAN2)
.variant("3233", VARIABLES, ONE, TWO, THREE, SIN, COS)
.selector();
public static void main(final String... args) {
SELECTOR.main(args);
}
public static ExprTester<Object> tester(final TestCounter counter, final Language language) {
return tester(counter, language, counter.mode() >= 1, SCRIPT);
}
/* package-private */ static ExprTester<Object> tester(
final TestCounter counter,
final Language language,
final boolean testParsing,
final Path script
) {
final JSExpressionEngine engine = new JSExpressionEngine(script, "", "parse", "toString");
return new ExprTester<>(
counter,
ExprTester.RANDOM_TESTS / TestCounter.DENOMINATOR,
engine,
language,
false,
testParsing ? ExprTester.STANDARD_SPOILER : ExprTester.Generator.empty(),
ExprTester.Generator.empty()
);
}
}

View File

@@ -0,0 +1,56 @@
package jstest.object;
import base.Selector;
import common.expression.ExprTester;
import common.expression.LanguageBuilder;
import common.expression.Operation;
import jstest.functional.FunctionalTest;
import java.util.List;
import static common.expression.Operations.*;
/**
* Tests for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-object-expressions">JavaScript Object Expressions</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public enum ObjectTest {
;
/* package-private */
static Selector.Composite<LanguageBuilder> selector() {
return LanguageBuilder.selector(
ObjectTest.class,
mode -> false,
List.of("x", "y", "z"),
(builder, counter) -> ObjectTester.tester(
counter,
builder.language(ObjectTester.OBJECT, FunctionalTest.POLISH),
"toString", "parse",
ExprTester.Generator.empty(),
ExprTester.Generator.empty()
),
"easy", "", "hard", "bonus"
);
}
public static final Selector SELECTOR = selector()
.variant("Base", ARITH)
.variant("Simplify", ARITH, simplifications(new int[][]{{4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {2, 1, 1, 1}, {5, 1, 1, 1}, {4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {8, 1, 2, 1}, {5, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 5, 1}, {5, 24, 1, 1}, {3, 1, 1, 1}, {1, 1, 1, 1}, {4, 1, 1, 1}, {8, 1, 1, 4}, {18, 1, 1, 1}, {8, 1, 2, 1}, {3, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 1, 1}, {9, 1, 1, 1}, {12, 9, 1, 1}, {11, 34, 11, 1}, {16, 1, 12, 1}, {25, 1, 1, 38}}))
.variant("3637", POW, LOG, range(1, 5, SUM), simplifications(new int[][]{{4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {2, 1, 1, 1}, {5, 1, 1, 1}, {4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {8, 1, 2, 1}, {5, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 5, 1}, {5, 24, 1, 1}, {3, 1, 1, 1}, {1, 1, 1, 1}, {4, 1, 1, 1}, {8, 1, 1, 4}, {18, 1, 1, 1}, {8, 1, 2, 1}, {3, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 1, 1}, {9, 1, 1, 1}, {12, 9, 1, 1}, {11, 34, 11, 1}, {16, 1, 12, 1}, {25, 1, 1, 38}, {8, 1, 1, 1}, {7, 51, 1, 1}, {7, 1, 1, 1}, {14, 34, 34, 1}, {10, 1, 1, 17}, {16, 1, 72, 54}, {20, 53, 71, 57}, {18, 1, 1, 1}, {7, 74, 1, 1}, {1, 1, 1, 1}, {14, 107, 1, 1}, {18, 114, 63, 1}, {23, 1, 93, 79}, {13, 109, 1, 92}, {1, 1, 1, 1}, {6, 1, 1, 1}, {1, 1, 1, 1}, {10, 1, 1, 1}, {18, 1, 22, 1}, {15, 1, 1, 1}, {3, 1, 1, 1}, {8, 1, 1, 1}, {8, 1, 1, 1}, {19, 2, 1, 1}, {19, 1, 1, 2}, {3, 1, 1, 1}, {25, 1, 1, 2}, {10, 1, 1, 1}, {5, 1, 1, 1}, {10, 1, 1, 1}, {18, 1, 1, 9}, {18, 9, 1, 1}, {18, 9, 1, 1}, {13, 1, 1, 1}, {3, 1, 1, 1}, {10, 1, 1, 1}, {13, 1, 1, 1}, {33, 1, 1, 1}, {15, 1, 1, 1}, {15, 1, 1, 1}, {12, 1, 1, 1}, {3, 1, 1, 1}, {12, 1, 1, 1}, {20, 1, 1, 1}, {41, 2, 9, 1}, {84, 1, 13, 1}, {3, 1, 1, 1}, {14, 1, 1, 1}, {20, 1, 1, 1}, {17, 1, 1, 1}, {16, 1, 1, 1}, {19, 1, 1, 1}, {17, 1, 1, 1}, {14, 1, 1, 1}, {21, 1, 1, 1}, {14, 1, 1, 1}, {19, 1, 1, 1}, {14, 1, 1, 1}}))
.variant("3839", GAUSS, range(1, 5, SUM, AVG), simplifications(new int[][]{{4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {2, 1, 1, 1}, {5, 1, 1, 1}, {4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {8, 1, 2, 1}, {5, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 5, 1}, {5, 24, 1, 1}, {3, 1, 1, 1}, {1, 1, 1, 1}, {4, 1, 1, 1}, {8, 1, 1, 4}, {18, 1, 1, 1}, {8, 1, 2, 1}, {3, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 1, 1}, {9, 1, 1, 1}, {12, 9, 1, 1}, {11, 34, 11, 1}, {16, 1, 12, 1}, {25, 1, 1, 38}, {16, 59, 1, 1}, {13, 80, 50, 1}, {16, 34, 64, 57}, {1, 1, 1, 1}, {13, 51, 51, 92}, {18, 1, 57, 1}, {31, 99, 195, 202}, {72, 1, 1, 1}, {1, 1, 1, 1}, {6, 1, 1, 1}, {6, 1, 1, 1}, {6, 1, 1, 1}, {1, 1, 1, 1}, {37, 16, 1, 1}, {4, 1, 1, 1}, {6, 1, 1, 1}, {12, 1, 1, 1}, {6, 1, 1, 1}, {15, 5, 1, 21}, {11, 1, 1, 1}, {5, 1, 1, 1}, {8, 1, 1, 1}, {4, 1, 1, 1}, {21, 2, 1, 1}, {19, 1, 2, 1}, {3, 1, 1, 1}, {4, 1, 1, 1}, {5, 1, 1, 1}, {8, 3, 1, 3}, {10, 1, 1, 3}, {15, 1, 3, 4}, {12, 3, 1, 4}, {17, 1, 4, 4}, {22, 4, 4, 1}, {13, 1, 1, 1}, {10, 1, 1, 1}, {3, 1, 1, 1}, {18, 5, 5, 1}, {18, 1, 5, 9}, {21, 9, 1, 1}, {10, 1, 1, 1}, {2, 1, 1, 1}, {10, 1, 1, 1}, {22, 3, 1, 1}, {41, 3, 1, 1}, {18, 1, 1, 1}, {13, 1, 18, 18}, {13, 1, 18, 18}, {18, 47, 22, 43}, {18, 1, 43, 51}, {18, 43, 47, 22}, {10, 1, 18, 1}, {4, 1, 1, 1}, {10, 1, 1, 1}, {20, 17, 18, 1}, {41, 17, 18, 1}, {15, 1, 1, 1}, {15, 1, 1, 1}, {14, 1, 1, 1}, {5, 1, 1, 1}, {12, 1, 1, 1}, {12, 1, 1, 1}, {22, 1, 21, 1}, {59, 1, 18, 1}, {16, 1, 4, 1}, {14, 1, 4, 4}, {16, 1, 4, 1}, {3, 1, 1, 1}, {12, 1, 1, 1}, {12, 4, 4, 1}, {30, 31, 15, 30}, {65, 47, 31, 37}, {4, 1, 1, 1}, {14, 1, 1, 1}, {16, 1, 1, 1}, {14, 1, 1, 1}, {16, 1, 1, 1}, {17, 1, 1, 1}, {19, 1, 1, 1}, {17, 1, 1, 1}, {17, 1, 1, 1}, {20, 1, 1, 1}, {14, 1, 1, 1}, {14, 1, 1, 1}, {6, 1, 1, 1}, {14, 18, 3, 3}, {19, 1, 3, 1}, {17, 3, 3, 3}, {18, 3, 1, 1}, {19, 3, 1, 3}, {16, 3, 1, 1}, {14, 3, 1, 3}, {17, 3, 1, 3}, {14, 3, 3, 1}, {17, 3, 3, 3}, {19, 1, 1, 3}}))
.variant("3435", WRAP, SOFT_WRAP)
.variant("3233", WRAP)
.selector();
public static Operation simplifications(final int[]... simplifications) {
return builder -> builder.setSimplifications(List.of(simplifications));
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,55 @@
package jstest.object;
import base.TestCounter;
import common.expression.ExprTester;
import common.expression.Dialect;
import common.expression.Diff;
import common.expression.Language;
import jstest.JSExpressionEngine;
import java.nio.file.Path;
/**
* Tester for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-object-expressions">JavaScript Object Expressions</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ObjectTester {
public static final Dialect OBJECT = new Dialect("new Variable('%s')", "new Const(%s)", "new {op}({args})", ", ");
private static final Diff DIFF = new Diff(2, new Dialect(
"'%s'", "%s",
(op, args) -> "%s.%s(%s)".formatted(args.get(0), op.name(), String.join(", ", args.subList(1, args.size())))
));
private ObjectTester() {
}
public static ExprTester<Object> tester(
final TestCounter counter,
final Language language,
final String toString,
final String parse,
final ExprTester.Generator<String> spoiler,
final ExprTester.Generator<ExprTester.BadInput> corruptor
) {
final ExprTester<Object> tester = new ExprTester<>(
counter,
ExprTester.RANDOM_TESTS / TestCounter.DENOMINATOR,
new JSExpressionEngine(Path.of("objectExpression.js"), ".evaluate", parse, toString),
language,
true,
ExprTester.STANDARD_SPOILER.combine(spoiler),
corruptor
);
if (counter.mode() >= 2) {
DIFF.diff(tester, true);
}
if (counter.mode() >= 3) {
DIFF.simplify(tester);
}
return tester;
}
}

View File

@@ -0,0 +1,33 @@
package jstest.prefix;
import base.Selector;
import common.expression.Operation;
import static common.expression.Operations.*;
/**
* Tests for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ParserTest {
private static final Operation PARENTHESES = parentheses("(", ")", "{", "}", "[", "]", "<", ">");
public static final Selector SELECTOR = ParserTester.selector(
ParserTest.class,
"prefix", "parsePrefix", ParserTester.PREFIX
)
.variant("Base", ARITH)
.variant("3637", PARENTHESES, any(3, SUM_EXP, LSE))
.variant("3839", PARENTHESES, any(3, SUM_EXP, LME))
.selector();
private ParserTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,87 @@
package jstest.prefix;
import base.Functional;
import base.Selector;
import common.expression.Dialect;
import common.expression.ExprTester;
import common.expression.Language;
import common.expression.LanguageBuilder;
import jstest.object.ObjectTester;
import java.util.stream.IntStream;
/**
* Tester for
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ParserTester {
public static final Dialect PREFIX = new Dialect("%s", "%s", "({op} {args})", " ");
private ParserTester() {
}
public static Selector.Composite<LanguageBuilder> selector(
final Class<?> owner,
final String toString,
final String parse,
final Dialect unparsed,
final String... parsingTests
) {
assert parsingTests.length % 2 == 0;
return LanguageBuilder.selector(owner, mode -> true, (builder, counter) -> {
final String insertions = builder.variant().hasVarargs() ? "abc()+*/@ABC" : "xyz()+*/@ABC";
final Language language = builder.language(ObjectTester.OBJECT, unparsed);
final ExprTester<Object> tester = ObjectTester.tester(
counter,
language,
toString,
parse,
(input, expr, random, build) -> build.add(removeSpaces(input)),
corrupt(insertions)
);
tester.addStage(() -> Functional.forEachPair(
parsingTests,
(input, context) -> printParsingError(tester, input, context)
));
return tester;
}, "", "easy", "hard");
}
private static String removeSpaces(final String expression) {
return expression.replace(" (", "(").replace(") ", ")");
}
private static void printParsingError(final ExprTester<?> test, final String description, final String input) {
final String message = new ExprTester.BadInput(input, "", "").assertError(test::parse);
final int index = message.lastIndexOf("in <eval>");
System.err.format("%-15s | %-25s: %s%n", input, description, message.substring(0, index > 0 ? index : message.length()));
}
private static ExprTester.Generator<ExprTester.BadInput> corrupt(final String insertions) {
return (input, expr, random, builder) -> IntStream.range(0, 1 + Math.min(10, 200 / input.length())).boxed()
.forEach(i -> {
final int index = random.nextInt(input.length());
final char c = input.charAt(index);
if (!Character.isDigit(c) && !Character.isWhitespace(c) && "-hxyz".indexOf(c) == -1) {
builder.add(new ExprTester.BadInput(
input.substring(0, index),
"<SYMBOL REMOVED>",
input.substring(index + 1)
));
}
final char newC = insertions.charAt(random.nextInt(insertions.length()));
if (!Character.isDigit(c) && c != '-') {
builder.add(new ExprTester.BadInput(
input.substring(0, index),
"<SYMBOL INSERTED -->",
newC + input.substring(index)
));
}
});
}
}

View File

@@ -0,0 +1,54 @@
package jstest.prefix;
import base.Selector;
import common.expression.Dialect;
import common.expression.Operation;
import static common.expression.Operations.*;
/**
* Tests for Postfix* variants of
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class PostfixTest {
private static final Operation PARENTHESES = parentheses("(", ")", "{", "}", "[", "]", "<", ">");
public static final Selector SELECTOR = ParserTester.selector(
PostfixTest.class,
"postfix", "parsePostfix", new Dialect("%s", "%s", "({args} {op})", " "),
"Empty input", "",
"Unknown variable", "a",
"Invalid number", "-a",
"Missing )", "(z (x y +) *",
"Missing (", "z (x y +) *)",
"Unknown operation", "( x y @@)",
"Excessive info", "(x y +) x",
"Empty op", "()",
"Invalid unary (0 args)", "(negate)",
"Invalid unary (2 args)", "(x y negate)",
"Invalid binary (0 args)", "(+)",
"Invalid binary (1 args)", "(x +)",
"Invalid binary (3 args)", "(x y z +)",
"Variable op (0 args)", "(x)",
"Variable op (1 args)", "(1 x)",
"Variable op (2 args)", "(1 2 x)",
"Const op (0 args)", "(0)",
"Const op (1 args)", "(0 1)",
"Const op (2 args)", "(0 1 2)"
)
.variant("Base", ARITH)
.variant("3637", PARENTHESES, any(3, SUM_EXP, LSE))
.variant("3839", PARENTHESES, any(3, SUM_EXP, LME))
.selector();
private PostfixTest() {
}
public static void main(final String... args) {
ParserTest.main(args);
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,51 @@
package jstest.prefix;
import base.Selector;
import common.expression.Operation;
import static common.expression.Operations.*;
/**
* Tests for Prefix* variants
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class PrefixTest {
private static final Operation PARENTHESES = parentheses("(", ")", "{", "}", "[", "]", "<", ">", "«", "»");
public static final Selector SELECTOR = ParserTester.selector(
PrefixTest.class,
"prefix", "parsePrefix", ParserTester.PREFIX,
"Empty input", "",
"Unknown variable", "a",
"Invalid number", "-a",
"Missing )", "(* z (+ x y)",
"Unknown operation", "(@@ x y)",
"Excessive info", "(+ x y) x",
"Empty op", "()",
"Invalid unary (0 args)", "(negate)",
"Invalid unary (2 args)", "(negate x y)",
"Invalid binary (0 args)", "(+)",
"Invalid binary (1 args)", "(+ x)",
"Invalid binary (3 args)", "(+ x y z)",
"Variable op (0 args)", "(x)",
"Variable op (1 args)", "(x 1)",
"Variable op (2 args)", "(x 1 2)",
"Const op (0 args)", "(0)",
"Const op (1 args)", "(0 1)",
"Const op (2 args)", "(0 1 2)")
.variant("Base", ARITH)
.variant("3233", PARENTHESES, any(2, ATAN_12))
.variant("3435", PARENTHESES, any(3, MEAN_EXP))
.selector();
private PrefixTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,822 @@
"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,
};
}