first commit
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user