migration
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
56
javascript/jstest/object/ObjectTest.java
Normal file
56
javascript/jstest/object/ObjectTest.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package jstest.object;
|
||||
|
||||
import base.Selector;
|
||||
import common.expression.ExprTester;
|
||||
import common.expression.LanguageBuilder;
|
||||
import common.expression.Operation;
|
||||
import jstest.functional.FunctionalTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static common.expression.Operations.*;
|
||||
|
||||
/**
|
||||
* Tests for
|
||||
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-object-expressions">JavaScript Object Expressions</a>
|
||||
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public enum ObjectTest {
|
||||
;
|
||||
|
||||
/* package-private */
|
||||
static Selector.Composite<LanguageBuilder> selector() {
|
||||
return LanguageBuilder.selector(
|
||||
ObjectTest.class,
|
||||
mode -> false,
|
||||
List.of("x", "y", "z"),
|
||||
(builder, counter) -> ObjectTester.tester(
|
||||
counter,
|
||||
builder.language(ObjectTester.OBJECT, FunctionalTest.POLISH),
|
||||
"toString", "parse",
|
||||
ExprTester.Generator.empty(),
|
||||
ExprTester.Generator.empty()
|
||||
),
|
||||
"easy", "", "hard", "bonus"
|
||||
);
|
||||
}
|
||||
|
||||
public static final Selector SELECTOR = selector()
|
||||
.variant("Base", ARITH)
|
||||
.variant("Simplify", ARITH, simplifications(new int[][]{{4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {2, 1, 1, 1}, {5, 1, 1, 1}, {4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {8, 1, 2, 1}, {5, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 5, 1}, {5, 24, 1, 1}, {3, 1, 1, 1}, {1, 1, 1, 1}, {4, 1, 1, 1}, {8, 1, 1, 4}, {18, 1, 1, 1}, {8, 1, 2, 1}, {3, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 1, 1}, {9, 1, 1, 1}, {12, 9, 1, 1}, {11, 34, 11, 1}, {16, 1, 12, 1}, {25, 1, 1, 38}}))
|
||||
.variant("3637", POW, LOG, range(1, 5, SUM), simplifications(new int[][]{{4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {2, 1, 1, 1}, {5, 1, 1, 1}, {4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {8, 1, 2, 1}, {5, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 5, 1}, {5, 24, 1, 1}, {3, 1, 1, 1}, {1, 1, 1, 1}, {4, 1, 1, 1}, {8, 1, 1, 4}, {18, 1, 1, 1}, {8, 1, 2, 1}, {3, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 1, 1}, {9, 1, 1, 1}, {12, 9, 1, 1}, {11, 34, 11, 1}, {16, 1, 12, 1}, {25, 1, 1, 38}, {8, 1, 1, 1}, {7, 51, 1, 1}, {7, 1, 1, 1}, {14, 34, 34, 1}, {10, 1, 1, 17}, {16, 1, 72, 54}, {20, 53, 71, 57}, {18, 1, 1, 1}, {7, 74, 1, 1}, {1, 1, 1, 1}, {14, 107, 1, 1}, {18, 114, 63, 1}, {23, 1, 93, 79}, {13, 109, 1, 92}, {1, 1, 1, 1}, {6, 1, 1, 1}, {1, 1, 1, 1}, {10, 1, 1, 1}, {18, 1, 22, 1}, {15, 1, 1, 1}, {3, 1, 1, 1}, {8, 1, 1, 1}, {8, 1, 1, 1}, {19, 2, 1, 1}, {19, 1, 1, 2}, {3, 1, 1, 1}, {25, 1, 1, 2}, {10, 1, 1, 1}, {5, 1, 1, 1}, {10, 1, 1, 1}, {18, 1, 1, 9}, {18, 9, 1, 1}, {18, 9, 1, 1}, {13, 1, 1, 1}, {3, 1, 1, 1}, {10, 1, 1, 1}, {13, 1, 1, 1}, {33, 1, 1, 1}, {15, 1, 1, 1}, {15, 1, 1, 1}, {12, 1, 1, 1}, {3, 1, 1, 1}, {12, 1, 1, 1}, {20, 1, 1, 1}, {41, 2, 9, 1}, {84, 1, 13, 1}, {3, 1, 1, 1}, {14, 1, 1, 1}, {20, 1, 1, 1}, {17, 1, 1, 1}, {16, 1, 1, 1}, {19, 1, 1, 1}, {17, 1, 1, 1}, {14, 1, 1, 1}, {21, 1, 1, 1}, {14, 1, 1, 1}, {19, 1, 1, 1}, {14, 1, 1, 1}}))
|
||||
.variant("3839", GAUSS, range(1, 5, SUM, AVG), simplifications(new int[][]{{4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {2, 1, 1, 1}, {5, 1, 1, 1}, {4, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {8, 1, 2, 1}, {5, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 5, 1}, {5, 24, 1, 1}, {3, 1, 1, 1}, {1, 1, 1, 1}, {4, 1, 1, 1}, {8, 1, 1, 4}, {18, 1, 1, 1}, {8, 1, 2, 1}, {3, 1, 1, 1}, {5, 1, 2, 1}, {5, 1, 1, 1}, {9, 1, 1, 1}, {12, 9, 1, 1}, {11, 34, 11, 1}, {16, 1, 12, 1}, {25, 1, 1, 38}, {16, 59, 1, 1}, {13, 80, 50, 1}, {16, 34, 64, 57}, {1, 1, 1, 1}, {13, 51, 51, 92}, {18, 1, 57, 1}, {31, 99, 195, 202}, {72, 1, 1, 1}, {1, 1, 1, 1}, {6, 1, 1, 1}, {6, 1, 1, 1}, {6, 1, 1, 1}, {1, 1, 1, 1}, {37, 16, 1, 1}, {4, 1, 1, 1}, {6, 1, 1, 1}, {12, 1, 1, 1}, {6, 1, 1, 1}, {15, 5, 1, 21}, {11, 1, 1, 1}, {5, 1, 1, 1}, {8, 1, 1, 1}, {4, 1, 1, 1}, {21, 2, 1, 1}, {19, 1, 2, 1}, {3, 1, 1, 1}, {4, 1, 1, 1}, {5, 1, 1, 1}, {8, 3, 1, 3}, {10, 1, 1, 3}, {15, 1, 3, 4}, {12, 3, 1, 4}, {17, 1, 4, 4}, {22, 4, 4, 1}, {13, 1, 1, 1}, {10, 1, 1, 1}, {3, 1, 1, 1}, {18, 5, 5, 1}, {18, 1, 5, 9}, {21, 9, 1, 1}, {10, 1, 1, 1}, {2, 1, 1, 1}, {10, 1, 1, 1}, {22, 3, 1, 1}, {41, 3, 1, 1}, {18, 1, 1, 1}, {13, 1, 18, 18}, {13, 1, 18, 18}, {18, 47, 22, 43}, {18, 1, 43, 51}, {18, 43, 47, 22}, {10, 1, 18, 1}, {4, 1, 1, 1}, {10, 1, 1, 1}, {20, 17, 18, 1}, {41, 17, 18, 1}, {15, 1, 1, 1}, {15, 1, 1, 1}, {14, 1, 1, 1}, {5, 1, 1, 1}, {12, 1, 1, 1}, {12, 1, 1, 1}, {22, 1, 21, 1}, {59, 1, 18, 1}, {16, 1, 4, 1}, {14, 1, 4, 4}, {16, 1, 4, 1}, {3, 1, 1, 1}, {12, 1, 1, 1}, {12, 4, 4, 1}, {30, 31, 15, 30}, {65, 47, 31, 37}, {4, 1, 1, 1}, {14, 1, 1, 1}, {16, 1, 1, 1}, {14, 1, 1, 1}, {16, 1, 1, 1}, {17, 1, 1, 1}, {19, 1, 1, 1}, {17, 1, 1, 1}, {17, 1, 1, 1}, {20, 1, 1, 1}, {14, 1, 1, 1}, {14, 1, 1, 1}, {6, 1, 1, 1}, {14, 18, 3, 3}, {19, 1, 3, 1}, {17, 3, 3, 3}, {18, 3, 1, 1}, {19, 3, 1, 3}, {16, 3, 1, 1}, {14, 3, 1, 3}, {17, 3, 1, 3}, {14, 3, 3, 1}, {17, 3, 3, 3}, {19, 1, 1, 3}}))
|
||||
.variant("3435", WRAP, SOFT_WRAP)
|
||||
.variant("3233", WRAP)
|
||||
.selector();
|
||||
|
||||
public static Operation simplifications(final int[]... simplifications) {
|
||||
return builder -> builder.setSimplifications(List.of(simplifications));
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
55
javascript/jstest/object/ObjectTester.java
Normal file
55
javascript/jstest/object/ObjectTester.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package jstest.object;
|
||||
|
||||
import base.TestCounter;
|
||||
import common.expression.ExprTester;
|
||||
import common.expression.Dialect;
|
||||
import common.expression.Diff;
|
||||
import common.expression.Language;
|
||||
import jstest.JSExpressionEngine;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Tester for
|
||||
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-object-expressions">JavaScript Object Expressions</a>
|
||||
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ObjectTester {
|
||||
public static final Dialect OBJECT = new Dialect("new Variable('%s')", "new Const(%s)", "new {op}({args})", ", ");
|
||||
|
||||
private static final Diff DIFF = new Diff(2, new Dialect(
|
||||
"'%s'", "%s",
|
||||
(op, args) -> "%s.%s(%s)".formatted(args.get(0), op.name(), String.join(", ", args.subList(1, args.size())))
|
||||
));
|
||||
|
||||
private ObjectTester() {
|
||||
}
|
||||
|
||||
public static ExprTester<Object> tester(
|
||||
final TestCounter counter,
|
||||
final Language language,
|
||||
final String toString,
|
||||
final String parse,
|
||||
final ExprTester.Generator<String> spoiler,
|
||||
final ExprTester.Generator<ExprTester.BadInput> corruptor
|
||||
) {
|
||||
final ExprTester<Object> tester = new ExprTester<>(
|
||||
counter,
|
||||
ExprTester.RANDOM_TESTS / TestCounter.DENOMINATOR,
|
||||
new JSExpressionEngine(Path.of("objectExpression.js"), ".evaluate", parse, toString),
|
||||
language,
|
||||
true,
|
||||
ExprTester.STANDARD_SPOILER.combine(spoiler),
|
||||
corruptor
|
||||
);
|
||||
if (counter.mode() >= 2) {
|
||||
DIFF.diff(tester, true);
|
||||
}
|
||||
if (counter.mode() >= 3) {
|
||||
DIFF.simplify(tester);
|
||||
}
|
||||
return tester;
|
||||
}
|
||||
}
|
||||
33
javascript/jstest/prefix/ParserTest.java
Normal file
33
javascript/jstest/prefix/ParserTest.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package jstest.prefix;
|
||||
|
||||
import base.Selector;
|
||||
import common.expression.Operation;
|
||||
|
||||
import static common.expression.Operations.*;
|
||||
|
||||
/**
|
||||
* Tests for
|
||||
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
|
||||
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ParserTest {
|
||||
private static final Operation PARENTHESES = parentheses("(", ")", "{", "}", "[", "]", "<", ">");
|
||||
|
||||
public static final Selector SELECTOR = ParserTester.selector(
|
||||
ParserTest.class,
|
||||
"prefix", "parsePrefix", ParserTester.PREFIX
|
||||
)
|
||||
.variant("Base", ARITH)
|
||||
.variant("3637", PARENTHESES, any(3, SUM_EXP, LSE))
|
||||
.variant("3839", PARENTHESES, any(3, SUM_EXP, LME))
|
||||
.selector();
|
||||
|
||||
private ParserTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
87
javascript/jstest/prefix/ParserTester.java
Normal file
87
javascript/jstest/prefix/ParserTester.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package jstest.prefix;
|
||||
|
||||
import base.Functional;
|
||||
import base.Selector;
|
||||
import common.expression.Dialect;
|
||||
import common.expression.ExprTester;
|
||||
import common.expression.Language;
|
||||
import common.expression.LanguageBuilder;
|
||||
import jstest.object.ObjectTester;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Tester for
|
||||
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
|
||||
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ParserTester {
|
||||
public static final Dialect PREFIX = new Dialect("%s", "%s", "({op} {args})", " ");
|
||||
|
||||
private ParserTester() {
|
||||
}
|
||||
|
||||
public static Selector.Composite<LanguageBuilder> selector(
|
||||
final Class<?> owner,
|
||||
final String toString,
|
||||
final String parse,
|
||||
final Dialect unparsed,
|
||||
final String... parsingTests
|
||||
) {
|
||||
assert parsingTests.length % 2 == 0;
|
||||
|
||||
return LanguageBuilder.selector(owner, mode -> true, (builder, counter) -> {
|
||||
final String insertions = builder.variant().hasVarargs() ? "abc()+*/@ABC" : "xyz()+*/@ABC";
|
||||
final Language language = builder.language(ObjectTester.OBJECT, unparsed);
|
||||
final ExprTester<Object> tester = ObjectTester.tester(
|
||||
counter,
|
||||
language,
|
||||
toString,
|
||||
parse,
|
||||
(input, expr, random, build) -> build.add(removeSpaces(input)),
|
||||
corrupt(insertions)
|
||||
);
|
||||
tester.addStage(() -> Functional.forEachPair(
|
||||
parsingTests,
|
||||
(input, context) -> printParsingError(tester, input, context)
|
||||
));
|
||||
return tester;
|
||||
}, "", "easy", "hard");
|
||||
}
|
||||
|
||||
private static String removeSpaces(final String expression) {
|
||||
return expression.replace(" (", "(").replace(") ", ")");
|
||||
}
|
||||
|
||||
private static void printParsingError(final ExprTester<?> test, final String description, final String input) {
|
||||
final String message = new ExprTester.BadInput(input, "", "").assertError(test::parse);
|
||||
final int index = message.lastIndexOf("in <eval>");
|
||||
|
||||
System.err.format("%-15s | %-25s: %s%n", input, description, message.substring(0, index > 0 ? index : message.length()));
|
||||
}
|
||||
|
||||
private static ExprTester.Generator<ExprTester.BadInput> corrupt(final String insertions) {
|
||||
return (input, expr, random, builder) -> IntStream.range(0, 1 + Math.min(10, 200 / input.length())).boxed()
|
||||
.forEach(i -> {
|
||||
final int index = random.nextInt(input.length());
|
||||
final char c = input.charAt(index);
|
||||
if (!Character.isDigit(c) && !Character.isWhitespace(c) && "-hxyz".indexOf(c) == -1) {
|
||||
builder.add(new ExprTester.BadInput(
|
||||
input.substring(0, index),
|
||||
"<SYMBOL REMOVED>",
|
||||
input.substring(index + 1)
|
||||
));
|
||||
}
|
||||
final char newC = insertions.charAt(random.nextInt(insertions.length()));
|
||||
if (!Character.isDigit(c) && c != '-') {
|
||||
builder.add(new ExprTester.BadInput(
|
||||
input.substring(0, index),
|
||||
"<SYMBOL INSERTED -->",
|
||||
newC + input.substring(index)
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
54
javascript/jstest/prefix/PostfixTest.java
Normal file
54
javascript/jstest/prefix/PostfixTest.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package jstest.prefix;
|
||||
|
||||
import base.Selector;
|
||||
import common.expression.Dialect;
|
||||
import common.expression.Operation;
|
||||
|
||||
import static common.expression.Operations.*;
|
||||
|
||||
/**
|
||||
* Tests for Postfix* variants of
|
||||
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
|
||||
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class PostfixTest {
|
||||
private static final Operation PARENTHESES = parentheses("(", ")", "{", "}", "[", "]", "<", ">");
|
||||
|
||||
public static final Selector SELECTOR = ParserTester.selector(
|
||||
PostfixTest.class,
|
||||
"postfix", "parsePostfix", new Dialect("%s", "%s", "({args} {op})", " "),
|
||||
"Empty input", "",
|
||||
"Unknown variable", "a",
|
||||
"Invalid number", "-a",
|
||||
"Missing )", "(z (x y +) *",
|
||||
"Missing (", "z (x y +) *)",
|
||||
"Unknown operation", "( x y @@)",
|
||||
"Excessive info", "(x y +) x",
|
||||
"Empty op", "()",
|
||||
"Invalid unary (0 args)", "(negate)",
|
||||
"Invalid unary (2 args)", "(x y negate)",
|
||||
"Invalid binary (0 args)", "(+)",
|
||||
"Invalid binary (1 args)", "(x +)",
|
||||
"Invalid binary (3 args)", "(x y z +)",
|
||||
"Variable op (0 args)", "(x)",
|
||||
"Variable op (1 args)", "(1 x)",
|
||||
"Variable op (2 args)", "(1 2 x)",
|
||||
"Const op (0 args)", "(0)",
|
||||
"Const op (1 args)", "(0 1)",
|
||||
"Const op (2 args)", "(0 1 2)"
|
||||
)
|
||||
.variant("Base", ARITH)
|
||||
.variant("3637", PARENTHESES, any(3, SUM_EXP, LSE))
|
||||
.variant("3839", PARENTHESES, any(3, SUM_EXP, LME))
|
||||
.selector();
|
||||
|
||||
private PostfixTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
ParserTest.main(args);
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
51
javascript/jstest/prefix/PrefixTest.java
Normal file
51
javascript/jstest/prefix/PrefixTest.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package jstest.prefix;
|
||||
|
||||
import base.Selector;
|
||||
import common.expression.Operation;
|
||||
|
||||
import static common.expression.Operations.*;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for Prefix* variants
|
||||
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#js-expression-parsing">JavaScript Expression Parsing</a>
|
||||
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class PrefixTest {
|
||||
private static final Operation PARENTHESES = parentheses("(", ")", "{", "}", "[", "]", "<", ">", "«", "»");
|
||||
|
||||
public static final Selector SELECTOR = ParserTester.selector(
|
||||
PrefixTest.class,
|
||||
"prefix", "parsePrefix", ParserTester.PREFIX,
|
||||
"Empty input", "",
|
||||
"Unknown variable", "a",
|
||||
"Invalid number", "-a",
|
||||
"Missing )", "(* z (+ x y)",
|
||||
"Unknown operation", "(@@ x y)",
|
||||
"Excessive info", "(+ x y) x",
|
||||
"Empty op", "()",
|
||||
"Invalid unary (0 args)", "(negate)",
|
||||
"Invalid unary (2 args)", "(negate x y)",
|
||||
"Invalid binary (0 args)", "(+)",
|
||||
"Invalid binary (1 args)", "(+ x)",
|
||||
"Invalid binary (3 args)", "(+ x y z)",
|
||||
"Variable op (0 args)", "(x)",
|
||||
"Variable op (1 args)", "(x 1)",
|
||||
"Variable op (2 args)", "(x 1 2)",
|
||||
"Const op (0 args)", "(0)",
|
||||
"Const op (1 args)", "(0 1)",
|
||||
"Const op (2 args)", "(0 1 2)")
|
||||
.variant("Base", ARITH)
|
||||
.variant("3233", PARENTHESES, any(2, ATAN_12))
|
||||
.variant("3435", PARENTHESES, any(3, MEAN_EXP))
|
||||
.selector();
|
||||
|
||||
private PrefixTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user