first commit
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
54
javascript/jstest/object/ObjectTest.java
Normal file
54
javascript/jstest/object/ObjectTest.java
Normal file
@@ -0,0 +1,54 @@
|
||||
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}}))
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
56
javascript/jstest/object/ObjectTester.java
Normal file
56
javascript/jstest/object/ObjectTester.java
Normal file
@@ -0,0 +1,56 @@
|
||||
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",
|
||||
(name, args) -> "%s.%s(%s)".formatted(args.get(0), 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;
|
||||
}
|
||||
}
|
||||
|
||||
552
javascript/objectExpression.js
Normal file
552
javascript/objectExpression.js
Normal file
@@ -0,0 +1,552 @@
|
||||
"use strict";
|
||||
|
||||
class Expression {
|
||||
evaluate() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
toString() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
diff() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
simplify() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class Const extends Expression {
|
||||
constructor(value) {
|
||||
super();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
evaluate(_x, _y, _z) {
|
||||
return this._value;
|
||||
}
|
||||
toString() {
|
||||
return String(this._value);
|
||||
}
|
||||
diff(_varName) {
|
||||
return ZERO;
|
||||
}
|
||||
simplify() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
const ZERO = new Const(0);
|
||||
const ONE = new Const(1);
|
||||
|
||||
const VAR_INDEX = { x: 0, y: 1, z: 2 };
|
||||
|
||||
class Variable extends Expression {
|
||||
constructor(name) {
|
||||
super();
|
||||
this._name = name;
|
||||
this._index = VAR_INDEX[name];
|
||||
}
|
||||
|
||||
evaluate(...args) {
|
||||
return args[this._index];
|
||||
}
|
||||
toString() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
return varName === this._name ? ONE : ZERO;
|
||||
}
|
||||
|
||||
simplify() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function exprEquals(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
if (a instanceof Const) return a._value === b._value;
|
||||
if (a instanceof Variable) return a._name === b._name;
|
||||
if (a instanceof BinaryOp)
|
||||
return exprEquals(a._left, b._left) && exprEquals(a._right, b._right);
|
||||
if (a instanceof UnaryOp) return exprEquals(a._operand, b._operand);
|
||||
if (a instanceof NaryOp) {
|
||||
if (a._args.length !== b._args.length) return false;
|
||||
return a._args.every((arg, i) => exprEquals(arg, b._args[i]));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class BinaryOp extends Expression {
|
||||
constructor(left, right) {
|
||||
super();
|
||||
this._left = left;
|
||||
this._right = right;
|
||||
}
|
||||
|
||||
evaluate(x, y, z) {
|
||||
return this._op(
|
||||
this._left.evaluate(x, y, z),
|
||||
this._right.evaluate(x, y, z),
|
||||
);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this._left.toString()} ${this._right.toString()} ${this._symbol}`;
|
||||
}
|
||||
|
||||
simplify() {
|
||||
const l = this._left.simplify();
|
||||
const r = this._right.simplify();
|
||||
const lConst = l instanceof Const;
|
||||
const rConst = r instanceof Const;
|
||||
|
||||
const special = this._simplifySpecial(l, r, lConst, rConst);
|
||||
if (special !== null) return special;
|
||||
|
||||
if (lConst && rConst) {
|
||||
return new Const(this._op(l._value, r._value));
|
||||
}
|
||||
|
||||
return this._rebuild(l, r);
|
||||
}
|
||||
|
||||
_rebuild(l, r) {
|
||||
return new this.constructor(l, r);
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryOp extends Expression {
|
||||
constructor(operand) {
|
||||
super();
|
||||
this._operand = operand;
|
||||
}
|
||||
|
||||
evaluate(x, y, z) {
|
||||
return this._op(this._operand.evaluate(x, y, z));
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this._operand.toString()} ${this._symbol}`;
|
||||
}
|
||||
|
||||
simplify() {
|
||||
const inner = this._operand.simplify();
|
||||
if (inner instanceof Const) {
|
||||
return new Const(this._op(inner._value));
|
||||
}
|
||||
return new this.constructor(inner);
|
||||
}
|
||||
}
|
||||
|
||||
// Base class for N-ary operations (N = 1..5)
|
||||
class NaryOp extends Expression {
|
||||
constructor(...args) {
|
||||
super();
|
||||
this._args = args;
|
||||
}
|
||||
|
||||
evaluate(x, y, z) {
|
||||
return this._op(...this._args.map((a) => a.evaluate(x, y, z)));
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this._args.map((a) => a.toString()).join(" ")} ${this._symbol}`;
|
||||
}
|
||||
|
||||
simplify() {
|
||||
const simplified = this._args.map((a) => a.simplify());
|
||||
if (simplified.every((a) => a instanceof Const)) {
|
||||
return new Const(this._op(...simplified.map((a) => a._value)));
|
||||
}
|
||||
return new this.constructor(...simplified);
|
||||
}
|
||||
}
|
||||
|
||||
class Add extends BinaryOp {
|
||||
get _symbol() {
|
||||
return "+";
|
||||
}
|
||||
_op(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
return new Add(this._left.diff(varName), this._right.diff(varName));
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
if (lConst && l._value === 0) return r;
|
||||
if (rConst && r._value === 0) return l;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Subtract extends BinaryOp {
|
||||
get _symbol() {
|
||||
return "-";
|
||||
}
|
||||
_op(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
return new Subtract(this._left.diff(varName), this._right.diff(varName));
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
if (rConst && r._value === 0) return l;
|
||||
if (lConst && l._value === 0) return new Negate(r).simplify();
|
||||
if (exprEquals(l, r)) return ZERO;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function cancelCommonFactor(num, den) {
|
||||
if (exprEquals(num, den)) return [ONE, ONE];
|
||||
|
||||
if (den instanceof Multiply) {
|
||||
const [dA, dB] = [den._left, den._right];
|
||||
if (exprEquals(num, dA)) return [ONE, dB];
|
||||
if (exprEquals(num, dB)) return [ONE, dA];
|
||||
if (num instanceof Multiply) {
|
||||
const [nA, nB] = [num._left, num._right];
|
||||
if (exprEquals(nA, dA)) return [nB, dB];
|
||||
if (exprEquals(nA, dB)) return [nB, dA];
|
||||
if (exprEquals(nB, dA)) return [nA, dB];
|
||||
if (exprEquals(nB, dB)) return [nA, dA];
|
||||
}
|
||||
}
|
||||
|
||||
if (num instanceof Multiply) {
|
||||
const [nA, nB] = [num._left, num._right];
|
||||
if (exprEquals(den, nA)) return [nB, ONE];
|
||||
if (exprEquals(den, nB)) return [nA, ONE];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class Multiply extends BinaryOp {
|
||||
get _symbol() {
|
||||
return "*";
|
||||
}
|
||||
_op(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
return new Add(
|
||||
new Multiply(this._left.diff(varName), this._right),
|
||||
new Multiply(this._left, this._right.diff(varName)),
|
||||
);
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
if ((lConst && l._value === 0) || (rConst && r._value === 0)) return ZERO;
|
||||
if (lConst && l._value === 1) return r;
|
||||
if (rConst && r._value === 1) return l;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Divide extends BinaryOp {
|
||||
get _symbol() {
|
||||
return "/";
|
||||
}
|
||||
_op(a, b) {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
return new Divide(
|
||||
new Subtract(
|
||||
new Multiply(this._left.diff(varName), this._right),
|
||||
new Multiply(this._left, this._right.diff(varName)),
|
||||
),
|
||||
new Multiply(this._right, this._right),
|
||||
);
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
if (lConst && l._value === 0) return ZERO;
|
||||
if (rConst && r._value === 1) return l;
|
||||
|
||||
const cancelled = cancelCommonFactor(l, r);
|
||||
if (cancelled !== null) {
|
||||
const [newNum, newDen] = cancelled;
|
||||
return new Divide(newNum, newDen).simplify();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Negate extends UnaryOp {
|
||||
get _symbol() {
|
||||
return "negate";
|
||||
}
|
||||
_op(a) {
|
||||
return -a;
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
return new Negate(this._operand.diff(varName));
|
||||
}
|
||||
|
||||
simplify() {
|
||||
const inner = this._operand.simplify();
|
||||
if (inner instanceof Const) return new Const(-inner._value);
|
||||
return new Negate(inner);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Power ──────────────────────────────────────────────────────────────────
|
||||
// pow(f, g) = f ^ g
|
||||
// (f ^ g)' = f ^ g * (g' * ln|f| + g * f' / f)
|
||||
class Power extends BinaryOp {
|
||||
get _symbol() {
|
||||
return "pow";
|
||||
}
|
||||
_op(a, b) {
|
||||
return Math.pow(a, b);
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
const f = this._left;
|
||||
const g = this._right;
|
||||
|
||||
// Simplify f and g first so that e.g. Negate(Const(493)) is treated as Const(-493)
|
||||
const gs = g.simplify();
|
||||
const fs = f.simplify();
|
||||
|
||||
// Also compute derivatives and simplify them to detect zero
|
||||
const fdiff = f.diff(varName).simplify();
|
||||
const gdiff = g.diff(varName).simplify();
|
||||
const fdiffZero = fdiff instanceof Const && fdiff._value === 0;
|
||||
const gdiffZero = gdiff instanceof Const && gdiff._value === 0;
|
||||
|
||||
if (gs instanceof Const || gdiffZero) {
|
||||
// Power rule: (f^n)' = n * f^(n-1) * f'
|
||||
// Works when exponent is constant OR doesn't depend on varName
|
||||
const n = gs instanceof Const ? gs._value : g;
|
||||
const nExpr = gs instanceof Const ? new Const(gs._value) : g;
|
||||
const nMinus1 = gs instanceof Const ? new Const(gs._value - 1) : new Subtract(g, ONE);
|
||||
return new Multiply(
|
||||
new Multiply(nExpr, new Power(f, nMinus1)),
|
||||
fdiff,
|
||||
);
|
||||
}
|
||||
|
||||
if (fs instanceof Const || fdiffZero) {
|
||||
// Exponential rule: (a^g)' = a^g * ln(|a|) * g'
|
||||
// Works when base is constant OR doesn't depend on varName
|
||||
return new Multiply(
|
||||
new Multiply(new Power(f, g), new Log(new Const(Math.E), f)),
|
||||
gdiff,
|
||||
);
|
||||
}
|
||||
|
||||
// General case: both base and exponent truly depend on variable
|
||||
// (f^g)' = f^g * (g' * ln|f| + g * f'/f)
|
||||
return new Multiply(
|
||||
new Power(f, g),
|
||||
new Add(
|
||||
new Multiply(gdiff, new Log(new Const(Math.E), f)),
|
||||
new Multiply(g, new Divide(fdiff, f)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
if (rConst && r._value === 0) return ONE;
|
||||
if (rConst && r._value === 1) return l;
|
||||
if (lConst && l._value === 1) return ONE;
|
||||
if (lConst && l._value === 0) return ZERO;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Log ────────────────────────────────────────────────────────────────────
|
||||
// log(base, x) = ln|x| / ln|base|
|
||||
// Matches the postfix token "log": `base x log`
|
||||
//
|
||||
// Full quotient-rule derivative (base may depend on the variable):
|
||||
// log_b(f) = ln|f| / ln|b|
|
||||
// d/dx = (f'/f * ln|b| - b'/b * ln|f|) / (ln|b|)^2
|
||||
// = f'/(f * ln|b|) - b' * ln|f| / (b * (ln|b|)^2)
|
||||
class Log extends BinaryOp {
|
||||
get _symbol() {
|
||||
return "log";
|
||||
}
|
||||
_op(base, x) {
|
||||
return Math.log(Math.abs(x)) / Math.log(Math.abs(base));
|
||||
}
|
||||
|
||||
diff(varName) {
|
||||
const b = this._left; // base expression
|
||||
const f = this._right; // argument expression
|
||||
|
||||
// Compute derivatives and simplify to detect zeros
|
||||
const bdiff = b.diff(varName).simplify();
|
||||
const fdiff = f.diff(varName).simplify();
|
||||
const bdiffZero = bdiff instanceof Const && bdiff._value === 0;
|
||||
const fdiffZero = fdiff instanceof Const && fdiff._value === 0;
|
||||
|
||||
// log_b(f) = ln|f| / ln|b|
|
||||
// Quotient rule: d/dx = (f'/f * ln|b| - b'/b * ln|f|) / (ln|b|)^2
|
||||
// = f'/(f * ln|b|) - b'*ln|f| / (b * (ln|b|)^2)
|
||||
|
||||
// Special case: log_b(b) = 1 always => d/dx = 0
|
||||
if (exprEquals(b, f)) return ZERO;
|
||||
|
||||
const lnB = new Log(new Const(Math.E), b);
|
||||
|
||||
// Special case: base is constant (b' = 0) => d/dx = f'/(f * ln|b|)
|
||||
if (bdiffZero) {
|
||||
return new Divide(fdiff, new Multiply(f, lnB));
|
||||
}
|
||||
|
||||
// Special case: argument is constant (f' = 0) => d/dx = -log_b(f) / (b * ln|b|)
|
||||
// derived from general formula with fdiff=0: -(b'/b * log_b(f)) / lnB
|
||||
// restructured to avoid 1/b factor: -Log(b,f) / (b * lnB)
|
||||
if (fdiffZero) {
|
||||
return new Negate(new Divide(new Log(b, f), new Multiply(b, lnB)));
|
||||
}
|
||||
|
||||
// General case: use formula (f'/f - b'/b * log_b(f)) / ln|b|
|
||||
// This avoids introducing ln|f| separately (which would create structurally
|
||||
// different but numerically equal subexpressions that don't cancel).
|
||||
const inner = new Subtract(
|
||||
new Divide(fdiff, f),
|
||||
new Multiply(new Divide(bdiff, b), new Log(b, f)),
|
||||
);
|
||||
return new Divide(inner, lnB);
|
||||
}
|
||||
|
||||
_simplifySpecial(l, r, lConst, rConst) {
|
||||
// log_b(1) = 0
|
||||
if (rConst && r._value === 1) return ZERO;
|
||||
// log_b(b) = 1 (when base and argument are structurally equal)
|
||||
if (exprEquals(l, r)) return ONE;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── SumN ───────────────────────────────────────────────────────────────────
|
||||
// sum1..sum5 – sum of N arguments
|
||||
// Derivative: sum of derivatives of each argument.
|
||||
function makeSumN(n) {
|
||||
return class extends NaryOp {
|
||||
get _symbol() {
|
||||
return `sum${n}`;
|
||||
}
|
||||
_op(...vals) {
|
||||
return vals.reduce((acc, v) => acc + v, 0);
|
||||
}
|
||||
diff(varName) {
|
||||
return this._args
|
||||
.map((a) => a.diff(varName))
|
||||
.reduce((acc, d) => new Add(acc, d));
|
||||
}
|
||||
simplify() {
|
||||
const simplified = this._args.map((a) => a.simplify());
|
||||
// Drop zero summands
|
||||
const nonZero = simplified.filter(
|
||||
(a) => !(a instanceof Const && a._value === 0),
|
||||
);
|
||||
if (nonZero.length === 0) return ZERO;
|
||||
if (nonZero.length === 1) return nonZero[0];
|
||||
if (nonZero.every((a) => a instanceof Const)) {
|
||||
return new Const(nonZero.reduce((acc, a) => acc + a._value, 0));
|
||||
}
|
||||
return new this.constructor(...simplified);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const Sum1 = makeSumN(1);
|
||||
const Sum2 = makeSumN(2);
|
||||
const Sum3 = makeSumN(3);
|
||||
const Sum4 = makeSumN(4);
|
||||
const Sum5 = makeSumN(5);
|
||||
|
||||
// ── Operation registry & parser ────────────────────────────────────────────
|
||||
const OPERATIONS = {
|
||||
"+": Add,
|
||||
"-": Subtract,
|
||||
"*": Multiply,
|
||||
"/": Divide,
|
||||
negate: Negate,
|
||||
pow: Power,
|
||||
log: Log,
|
||||
sum1: Sum1,
|
||||
sum2: Sum2,
|
||||
sum3: Sum3,
|
||||
sum4: Sum4,
|
||||
sum5: Sum5,
|
||||
};
|
||||
|
||||
// Arity for each token (BinaryOp = 2, UnaryOp = 1, NaryOp = N)
|
||||
const ARITY = {
|
||||
"+": 2,
|
||||
"-": 2,
|
||||
"*": 2,
|
||||
"/": 2,
|
||||
negate: 1,
|
||||
pow: 2,
|
||||
log: 2,
|
||||
sum1: 1,
|
||||
sum2: 2,
|
||||
sum3: 3,
|
||||
sum4: 4,
|
||||
sum5: 5,
|
||||
};
|
||||
|
||||
function parse(expr) {
|
||||
const tokens = expr
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter((t) => t.length > 0);
|
||||
const stack = [];
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token in OPERATIONS) {
|
||||
const Cls = OPERATIONS[token];
|
||||
const arity = ARITY[token];
|
||||
const operands = stack.splice(stack.length - arity, arity);
|
||||
stack.push(new Cls(...operands));
|
||||
} else if (token in VAR_INDEX) {
|
||||
stack.push(new Variable(token));
|
||||
} else {
|
||||
stack.push(new Const(Number(token)));
|
||||
}
|
||||
}
|
||||
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = {
|
||||
Const,
|
||||
Variable,
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Negate,
|
||||
Power,
|
||||
Log,
|
||||
Sum1,
|
||||
Sum2,
|
||||
Sum3,
|
||||
Sum4,
|
||||
Sum5,
|
||||
parse,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user