package scotch.compiler.intermediate; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static me.qmx.jitescript.util.CodegenUtils.sig; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static scotch.compiler.Compiler.compiler; import static scotch.compiler.intermediate.Intermediates.access; import static scotch.compiler.intermediate.Intermediates.apply; import static scotch.compiler.intermediate.Intermediates.assign; import static scotch.compiler.intermediate.Intermediates.conditional; import static scotch.compiler.intermediate.Intermediates.constantReference; import static scotch.compiler.intermediate.Intermediates.constructor; import static scotch.compiler.intermediate.Intermediates.data; import static scotch.compiler.intermediate.Intermediates.field; import static scotch.compiler.intermediate.Intermediates.function; import static scotch.compiler.intermediate.Intermediates.instanceOf; import static scotch.compiler.intermediate.Intermediates.instanceRef; import static scotch.compiler.intermediate.Intermediates.literal; import static scotch.compiler.intermediate.Intermediates.raise; import static scotch.compiler.intermediate.Intermediates.valueRef; import static scotch.compiler.intermediate.Intermediates.variable; import static scotch.compiler.syntax.reference.DefinitionReference.dataRef; import static scotch.compiler.syntax.reference.DefinitionReference.valueRef; import static scotch.symbol.FieldSignature.fieldSignature; import static scotch.symbol.MethodSignature.constructor; import static scotch.symbol.MethodSignature.staticMethod; import static scotch.symbol.Symbol.symbol; import static scotch.compiler.syntax.type.Types.sum; import static scotch.compiler.syntax.type.Types.var; import static scotch.compiler.text.TextUtil.quote; import java.util.Optional; import org.junit.Test; import scotch.compiler.ClassLoaderResolver; import scotch.runtime.Applicable; import scotch.runtime.Callable; import scotch.compiler.syntax.type.Type; public class IntermediateGeneratorTest { private IntermediateGraph graph; @Test public void shouldCreateIntermediateCode() { compile( "module scotch.test", "import scotch.data.num", "", "run = 2 + 2" ); shouldHaveValue("scotch.test.run", apply( emptyList(), apply( emptyList(), apply( emptyList(), valueRef( "scotch.data.num.(+)", staticMethod("scotch/data/num/Num", "add", sig(Applicable.class))), instanceRef("scotch.data.num.Num", "scotch.data.num", "scotch.data.int.Int", staticMethod("scotch/data/num/NumInt", "instance", sig(Callable.class))) ), literal(2) ), literal(2) )); } @Test public void shouldPreserveType() { compile( "module scotch.test", "import scotch.data.num", "", "run = 2 + 2" ); shouldHaveValue("scotch.test.run", sum("scotch.data.int.Int")); } @Test public void shouldCreateIntermediateConditional() { compile( "module scotch.test", "max = if 2 >= 1", " then 2", " else 1" ); shouldHaveValue("scotch.test.max", conditional( apply(emptyList(), apply(emptyList(), apply(emptyList(), apply(emptyList(), valueRef( "scotch.data.ord.(>=)", staticMethod("scotch/data/ord/Ord", "greaterThanEquals", sig(Applicable.class))), instanceRef("scotch.data.eq.Eq", "scotch.data.eq", "scotch.data.int.Int", staticMethod("scotch/data/eq/EqInt", "instance", sig(Callable.class)))), instanceRef("scotch.data.ord.Ord", "scotch.data.ord", "scotch.data.int.Int", staticMethod("scotch/data/ord/OrdInt", "instance", sig(Callable.class)))), literal(2)), literal(1)), literal(2), literal(1) )); } @Test public void shouldCreateIntermediatePattern() { compile( "module scotch.test", "max a b = if a >= b", " then a", " else b" ); shouldHaveValue("scotch.test.max", function(emptyList(), "#0i", function(asList("#0i"), "#1i", function(asList("#0i", "#1i"), "#0", function(asList("#0i", "#1i", "#0"), "#1", assign("a", variable("#0"), assign("b", variable("#1"), conditional( apply(asList("#0i", "#1i", "a", "b"), apply(asList("#0i", "#1i", "a"), apply(asList("#0i", "#1i"), apply(asList("#0i"), valueRef( "scotch.data.ord.(>=)", staticMethod("scotch/data/ord/Ord", "greaterThanEquals", sig(Applicable.class))), variable("#0i")), variable("#1i")), variable("a")), variable("b")), variable("a"), variable("b") )))))))); } @Test public void shouldGenerateEmptyListEquals() { compile( "module scotch.test", "bothEqual? = [] == []" ); shouldHaveValue("scotch.test.(bothEqual?)", apply(emptyList(), apply(emptyList(), apply(emptyList(), valueRef( "scotch.data.eq.(==)", staticMethod("scotch/data/eq/Eq", "eq", sig(Applicable.class))), instanceRef("scotch.data.eq.Eq", "scotch.data.list", sum("scotch.data.list.[]", asList(sum("scotch.data.list.[]"))), staticMethod("scotch/data/list/EqList", "instance", sig(Callable.class)))), valueRef("scotch.data.list.[]", staticMethod("scotch/data/list/ConsList", "empty", sig(Callable.class)))), valueRef("scotch.data.list.[]", staticMethod("scotch/data/list/ConsList", "empty", sig(Callable.class))))); } @Test public void shouldGenerateDataDefinition() { compile( "module scotch.test", "data Thing n { value :: n }" ); assertThat(graph.getDefinition(dataRef(symbol("scotch.test.Thing"))), is(Optional.of(data("scotch.test.Thing", asList(var("n")), asList( constructor("scotch.test.Thing", asList( field("value", var("n")) )) ))))); } @Test public void shouldGenerateConstructor() { compile( "module scotch.test", "data Thing n { value :: n }" ); shouldHaveValue("scotch.test.Thing", function(emptyList(), "value", constructor("scotch.test.Thing", "scotch/test/Thing$Thing", constructor("scotch/test/Thing$Thing:<init>:" + sig(void.class, Callable.class)), asList(variable("value"))))); } @Test public void shouldGenerateNullaryConstructor() { compile( "module scotch.test", "data Maybe a = Nothing | Just a" ); assertThat(graph.getDefinition(dataRef(symbol("scotch.test.Maybe"))), is(Optional.of(data("scotch.test.Maybe", asList(var("a")), asList( constructor("scotch.test.Nothing", "scotch.test.Maybe", emptyList()), constructor("scotch.test.Just", "scotch.test.Maybe", asList( field("_0", var("a")) )) ))))); shouldHaveValue("scotch.test.Nothing", constantReference("scotch.test.Nothing", "scotch.test.Maybe", fieldSignature("scotch/test/Maybe$Nothing", ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "INSTANCE", "Lscotch/runtime/Callable;"))); } @Test public void shouldGenerateTupleDestructure() { compile( "module scotch.test", "second (_, b) = b" ); shouldHaveValue("scotch.test.second", function(emptyList(), "#0", conditional( instanceOf(variable("#0"), "scotch/data/tuple/Tuple2$Tuple2Data"), assign("b", access(asList("#0"), variable("#0"), "_1", "get_1"), variable("b")), raise("Incomplete match") ))); } @Test public void shouldGenerateNestedTupleDestructure() { compile( "module scotch.test", "third (_, (_, c)) = c" ); shouldHaveValue("scotch.test.third", function(emptyList(), "#0", conditional( apply(asList("#0"), apply(asList("#0"), valueRef("scotch.data.bool.(&&)", staticMethod("scotch/data/bool/Bool", "and", sig(Applicable.class))), instanceOf(variable("#0"), "scotch/data/tuple/Tuple2$Tuple2Data")), instanceOf(access(asList("#0"), variable("#0"), "_1", "get_1"), "scotch/data/tuple/Tuple2$Tuple2Data")), assign("c", access(asList("#0"), access(asList("#0"), variable("#0"), "_1", "get_1"), "_1", "get_1"), variable("c")), raise("Incomplete match") ))); } private void compile(String... lines) { ClassLoader classLoader = IntermediateGeneratorTest.class.getClassLoader(); ClassLoaderResolver symbolResolver = new ClassLoaderResolver(Optional.empty(), classLoader); graph = compiler(symbolResolver, lines).generateIntermediateCode(); } private IntermediateDefinition getDefinition(String name) { return graph.getDefinition(valueRef(symbol(name))) .orElseThrow(() -> new IllegalArgumentException("Value " + quote(name) + " does not exist")); } private void shouldHaveValue(String name, IntermediateValue value) { assertThat(((IntermediateValueDefinition) getDefinition(name)).getValue(), is(value)); } private void shouldHaveValue(String name, Type type) { assertThat(((IntermediateValueDefinition) getDefinition(name)).getType(), is(type)); } }