first commit

This commit is contained in:
me
2026-04-08 21:25:17 +03:00
parent 3681b8eccd
commit 371b14c5e3
173 changed files with 14126 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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