package scotch.compiler.analyzer;
import static java.util.Arrays.asList;
import static scotch.compiler.syntax.value.Values.apply;
import static scotch.util.TestUtil.access;
import static scotch.util.TestUtil.arg;
import static scotch.util.TestUtil.conditional;
import static scotch.util.TestUtil.fn;
import static scotch.util.TestUtil.id;
import static scotch.util.TestUtil.isConstructor;
import static scotch.util.TestUtil.let;
import static scotch.util.TestUtil.literal;
import static scotch.util.TestUtil.raise;
import static scotch.util.TestUtil.scope;
import static scotch.compiler.syntax.type.Types.t;
import java.util.Optional;
import java.util.function.Function;
import org.junit.Test;
import scotch.compiler.ClassLoaderResolver;
import scotch.compiler.Compiler;
import scotch.compiler.CompilerTest;
import scotch.compiler.syntax.definition.DefinitionGraph;
public class PatternAnalyzerIntegrationTest extends CompilerTest<ClassLoaderResolver> {
@Test
public void shouldReduceCapturesToLets() {
compile(
"module scotch.test",
"max a b = if a >= b",
" then a",
" else b"
);
shouldHaveValue("scotch.test.max", fn("scotch.test.(max#0)", asList(arg("#0", t(9)), arg("#1", t(10))),
scope("scotch.test.(max#0#0)", let(t(19), "a", arg("#0", t(12)),
let(t(18), "b", arg("#1", t(13)),
conditional(
apply(
apply(id("scotch.data.ord.(>=)", t(4)), id("a", t(3)), t(14)),
id("b", t(5)),
t(15)),
id("a", t(6)),
id("b", t(7)),
t(8)
))))
));
}
@Test
public void shouldDeeplyTagConditionals() {
compile(
"module scotch.test",
"fourth (_, (_, (_, d))) = d"
);
String tag = "scotch.data.tuple.(,)";
shouldHaveValue("scotch.test.fourth", fn("scotch.test.(fourth#0)", arg("#0", t(21)),
conditional(
apply(
apply(
id("scotch.data.bool.(&&)", t(34)),
apply(
apply(
id("scotch.data.bool.(&&)", t(31)),
isConstructor(arg("#0", t(23), tag), tag),
t(32)),
isConstructor(access(arg("#0", t(23), tag), "_1", t(25), tag), tag),
t(33)),
t(35)),
isConstructor(access(access(arg("#0", t(23), tag), "_1", t(25), tag), "_1", t(27), tag), tag),
t(36)),
scope("scotch.test.(fourth#0#0)",
let(t(37), "d", access(access(access(arg("#0", t(23), tag), "_1", t(25), tag), "_1", t(27), tag), "_1", t(29)),
id("d", t(20)))),
raise("Incomplete match", t(30)),
t(38)
)));
}
@Test
public void shouldTagDestructuredToast() {
compile(
"module scotch.test",
"data Toast { kind :: String, burnLevel :: Int }",
"isBurned? Toast { burnLevel = b } = b > 3"
);
String tag = "scotch.test.Toast";
shouldHaveValue("scotch.test.(isBurned?)", fn("scotch.test.(isBurned?#0)", arg("#0", t(7)),
conditional(
isConstructor(arg("#0", t(9), tag), tag),
scope("scotch.test.(isBurned?#0#0)",
let(t(14), "b", access(arg("#0", t(9), tag), "burnLevel", t(10)),
apply(
apply(
id("scotch.data.ord.(>)", t(6)),
id("b", t(5)),
t(11)),
literal(3),
t(12)))),
raise("Incomplete match", t(13)),
t(15)
)));
}
@Test
public void shouldTagSecondTail() {
compile(
"module scotch.test",
"secondTail (_:_:xs) = xs"
);
String tag = "scotch.data.list.(:)";
shouldHaveValue("scotch.test.secondTail", fn("scotch.test.(secondTail#0)", arg("#0", t(8)),
conditional(
apply(
apply(
id("scotch.data.bool.(&&)", t(22)),
isConstructor(arg("#0", t(16), tag), tag),
t(23)),
isConstructor(access(arg("#0", t(16), tag), "_1", t(18), tag), tag),
t(24)),
scope("scotch.test.(secondTail#0#0)",
let(t(25), "xs", access(access(arg("#0", t(16), tag), "_1", t(18), tag), "_1", t(20)), id("xs", t(7)))),
raise("Incomplete match", t(21)),
t(26)
)));
}
@Test
public void shouldDestructurePersonWithMultipleCases() {
compile(
"module scotch.test",
"data Person { age :: Int }",
"newborn? Person { age = 0 } = True",
"newborn? Person { age = _ } = False",
"run = newborn? Person { age = 1 }"
);
String tag = "scotch.test.Person";
shouldHaveValue("scotch.test.(newborn?)", fn("scotch.test.(newborn?#0)", arg("#0", t(13)),
conditional(
apply(
apply(
id("scotch.data.bool.(&&)", t(24)),
isConstructor(arg("#0", t(15), tag), tag),
t(25)),
apply(
apply(
id("scotch.data.eq.(==)", t(19)),
access(arg("#0", t(15), tag), "age", t(16)),
t(20)),
literal(0),
t(21)),
t(26)),
scope("scotch.test.(newborn?#0#0)", literal(true)),
conditional(
isConstructor(arg("#0", t(17), tag), tag),
scope("scotch.test.(newborn?#0#1)", literal(false)),
raise("Incomplete match", t(22)),
t(23)),
t(27)
)));
}
@Test
public void shouldMatchBothConsAndNilLists() {
compile(
"module scotch.test",
"filter predicate (x:xs) = something x",
"filter predicate [] = []"
);
shouldHaveValue("scotch.test.filter", fn("scotch.test.(filter#0)", asList(arg("#0", t(12)), arg("#1", t(13))),
conditional(
isConstructor(arg("#1", t(19), "scotch.data.list.(:)"), "scotch.data.list.(:)"),
scope("scotch.test.(filter#0#0)",
let(t(33), "predicate", arg("#0", t(18)),
let(t(32), "x", access(arg("#1", t(19), "scotch.data.list.(:)"), "_0", t(20)),
let(t(31), "xs", access(arg("#1", t(19), "scotch.data.list.(:)"), "_1", t(21)),
apply(id("something", t(6)), id("x", t(7)), t(22)))))),
conditional(
apply(apply(id("scotch.data.eq.(==)", t(25)), arg("#1", t(24)), t(26)), id("scotch.data.list.[]", t(10)), t(27)), // TODO should be constructor check maybe?
scope("scotch.test.(filter#0#1)",
let(t(29), "predicate", arg("#0", t(23)),
id("scotch.data.list.[]", t(11)))),
raise("Incomplete match", t(28)),
t(30)
),
t(34)
)
));
}
@Override
protected Function<Compiler, DefinitionGraph> compile() {
return Compiler::reducePatterns;
}
@Override
protected ClassLoaderResolver initResolver() {
return new ClassLoaderResolver(Optional.empty(), getClass().getClassLoader());
}
}