migration
This commit is contained in:
1
javascript/.gitattributes
vendored
Normal file
1
javascript/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sh text eol=lf
|
||||
13
javascript/RunJS.cmd
Normal file
13
javascript/RunJS.cmd
Normal 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
47
javascript/RunJS.html
Normal 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,'&').replace(/</g,'<').replace(/>/g,'>') ;
|
||||
}
|
||||
|
||||
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
71
javascript/RunJS.java
Normal 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
38
javascript/RunJS.node.js
Normal 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
11
javascript/RunJS.sh
Normal 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
28
javascript/TestJS.cmd
Normal 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
28
javascript/TestJS.sh
Executable 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
76
javascript/example.js
Normal 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
13
javascript/examples.js
Normal 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");
|
||||
94
javascript/examples/0_1_magic.js
Normal file
94
javascript/examples/0_1_magic.js
Normal 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;
|
||||
}
|
||||
}
|
||||
51
javascript/examples/1_1_types.js
Normal file
51
javascript/examples/1_1_types.js
Normal 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!'");
|
||||
68
javascript/examples/1_2_arrays.js
Normal file
68
javascript/examples/1_2_arrays.js
Normal 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");
|
||||
}
|
||||
128
javascript/examples/1_3_functions.js
Normal file
128
javascript/examples/1_3_functions.js
Normal 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)");
|
||||
169
javascript/examples/1_4_functions-hi.js
Normal file
169
javascript/examples/1_4_functions-hi.js
Normal 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)");
|
||||
52
javascript/examples/1_5_vectors.js
Normal file
52
javascript/examples/1_5_vectors.js
Normal 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]");
|
||||
122
javascript/functionalExpression.js
Normal file
122
javascript/functionalExpression.js
Normal 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];
|
||||
};
|
||||
BIN
javascript/graal/collections-25.0.2.jar
Normal file
BIN
javascript/graal/collections-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/icu4j-25.0.2.jar
Normal file
BIN
javascript/graal/icu4j-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/jniutils-25.0.2.jar
Normal file
BIN
javascript/graal/jniutils-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/js-language-25.0.2.jar
Normal file
BIN
javascript/graal/js-language-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/js-scriptengine-25.0.2.jar
Normal file
BIN
javascript/graal/js-scriptengine-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/nativeimage-25.0.2.jar
Normal file
BIN
javascript/graal/nativeimage-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/polyglot-25.0.2.jar
Normal file
BIN
javascript/graal/polyglot-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/regex-25.0.2.jar
Normal file
BIN
javascript/graal/regex-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/truffle-api-25.0.2.jar
Normal file
BIN
javascript/graal/truffle-api-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/truffle-compiler-25.0.2.jar
Normal file
BIN
javascript/graal/truffle-compiler-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/truffle-runtime-25.0.2.jar
Normal file
BIN
javascript/graal/truffle-runtime-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/word-25.0.2.jar
Normal file
BIN
javascript/graal/word-25.0.2.jar
Normal file
Binary file not shown.
BIN
javascript/graal/xz-25.0.2.jar
Normal file
BIN
javascript/graal/xz-25.0.2.jar
Normal file
Binary file not shown.
126
javascript/jstest/JSEngine.java
Normal file
126
javascript/jstest/JSEngine.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
javascript/jstest/JSExpressionEngine.java
Normal file
66
javascript/jstest/JSExpressionEngine.java
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
74
javascript/jstest/example/ExampleTest.java
Normal file
74
javascript/jstest/example/ExampleTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
75
javascript/jstest/functional/FunctionalTest.java
Normal file
75
javascript/jstest/functional/FunctionalTest.java
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
56
javascript/jstest/object/ObjectTest.java
Normal file
56
javascript/jstest/object/ObjectTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
55
javascript/jstest/object/ObjectTester.java
Normal file
55
javascript/jstest/object/ObjectTester.java
Normal 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;
|
||||
}
|
||||
}
|
||||
33
javascript/jstest/prefix/ParserTest.java
Normal file
33
javascript/jstest/prefix/ParserTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
87
javascript/jstest/prefix/ParserTester.java
Normal file
87
javascript/jstest/prefix/ParserTester.java
Normal 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)
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
54
javascript/jstest/prefix/PostfixTest.java
Normal file
54
javascript/jstest/prefix/PostfixTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
51
javascript/jstest/prefix/PrefixTest.java
Normal file
51
javascript/jstest/prefix/PrefixTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
822
javascript/objectExpression.js
Normal file
822
javascript/objectExpression.js
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user