diff --git a/src/main/java/net/hydromatic/morel/compile/Analyzer.java b/src/main/java/net/hydromatic/morel/compile/Analyzer.java index da9a4845..dfc280b3 100644 --- a/src/main/java/net/hydromatic/morel/compile/Analyzer.java +++ b/src/main/java/net/hydromatic/morel/compile/Analyzer.java @@ -78,7 +78,7 @@ private Analysis result() { use(idPat); } - @Override public void visit(Core.Id id) { + @Override protected void visit(Core.Id id) { use(id.idPat).useCount++; super.visit(id); } diff --git a/src/main/java/net/hydromatic/morel/compile/Compiles.java b/src/main/java/net/hydromatic/morel/compile/Compiles.java index a2749ce1..95b3bde3 100644 --- a/src/main/java/net/hydromatic/morel/compile/Compiles.java +++ b/src/main/java/net/hydromatic/morel/compile/Compiles.java @@ -360,11 +360,11 @@ private static class PatternBinder extends Visitor { this.bindings = bindings; } - @Override public void visit(Core.IdPat idPat) { + @Override protected void visit(Core.IdPat idPat) { bindPattern(typeSystem, bindings, idPat); } - @Override public void visit(Core.AsPat asPat) { + @Override protected void visit(Core.AsPat asPat) { bindPattern(typeSystem, bindings, asPat); super.visit(asPat); } diff --git a/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java b/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java index 7a93c03a..5ae6eb31 100644 --- a/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java +++ b/src/main/java/net/hydromatic/morel/compile/EnvShuttle.java @@ -74,26 +74,26 @@ protected EnvShuttle bind(List bindingList) { return core.match(match.pos, pat2, match.exp.accept(bind(bindings))); } - @Override public Core.Exp visit(Core.Let let) { + @Override protected Core.Exp visit(Core.Let let) { final List bindings = new ArrayList<>(); Compiles.bindPattern(typeSystem, bindings, let.decl); return let.copy(let.decl.accept(this), let.exp.accept(bind(bindings))); } - @Override public Core.Exp visit(Core.Local local) { + @Override protected Core.Exp visit(Core.Local local) { final List bindings = new ArrayList<>(); Compiles.bindDataType(typeSystem, bindings, local.dataType); return local.copy(local.dataType, local.exp.accept(bind(bindings))); } - @Override public Core.RecValDecl visit(Core.RecValDecl recValDecl) { + @Override protected Core.RecValDecl visit(Core.RecValDecl recValDecl) { final List bindings = new ArrayList<>(); recValDecl.list.forEach(decl -> Compiles.bindPattern(typeSystem, bindings, decl.pat)); return recValDecl.copy(bind(bindings).visitList(recValDecl.list)); } - @Override public Core.Exp visit(Core.From from) { + @Override protected Core.Exp visit(Core.From from) { List bindings = ImmutableList.of(); final List steps = new ArrayList<>(); for (Core.FromStep step : from.steps) { diff --git a/src/main/java/net/hydromatic/morel/compile/Inliner.java b/src/main/java/net/hydromatic/morel/compile/Inliner.java index 5bb9c0e3..547a50b2 100644 --- a/src/main/java/net/hydromatic/morel/compile/Inliner.java +++ b/src/main/java/net/hydromatic/morel/compile/Inliner.java @@ -28,18 +28,24 @@ import net.hydromatic.morel.type.FnType; import net.hydromatic.morel.type.PrimitiveType; import net.hydromatic.morel.type.TypeSystem; +import net.hydromatic.morel.util.Pair; + +import com.google.common.collect.ImmutableMap; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import java.util.Map; import static net.hydromatic.morel.ast.CoreBuilder.core; +import static java.util.Objects.requireNonNull; + /** * Shuttle that inlines constant values. */ public class Inliner extends EnvShuttle { - private final @Nullable Analyzer.Analysis analysis; + private final Analyzer.@Nullable Analysis analysis; /** Private constructor. */ private Inliner(TypeSystem typeSystem, Environment env, @@ -52,7 +58,7 @@ private Inliner(TypeSystem typeSystem, Environment env, * *

If {@code analysis} is null, no variables are inlined. */ public static Inliner of(TypeSystem typeSystem, Environment env, - @Nullable Analyzer.Analysis analysis) { + Analyzer.@Nullable Analysis analysis) { return new Inliner(typeSystem, env, analysis); } @@ -60,14 +66,14 @@ public static Inliner of(TypeSystem typeSystem, Environment env, return new Inliner(typeSystem, env, analysis); } - @Override public Core.Exp visit(Core.Id id) { + @Override protected Core.Exp visit(Core.Id id) { final Binding binding = env.getOpt(id.idPat); if (binding != null && !binding.parameter) { if (binding.exp != null) { final Analyzer.Use use = analysis == null ? Analyzer.Use.MULTI_UNSAFE - : analysis.map.get(id.idPat); + : requireNonNull(analysis.map.get(id.idPat)); switch (use) { case ATOMIC: case ONCE_SAFE: @@ -143,12 +149,47 @@ public static Inliner of(TypeSystem typeSystem, Environment env, return apply2; } - @Override public Core.Exp visit(Core.Let let) { + @Override protected Core.Exp visit(Core.Case caseOf) { + final Core.Exp exp = caseOf.exp.accept(this); + final List matchList = visitList(caseOf.matchList); + if (matchList.size() == 1) { + final Map substitution = + getSub(exp, matchList.get(0)); + if (substitution != null) { + return Replacer.substitute(typeSystem, substitution, + matchList.get(0).exp); + } + } + return caseOf.copy(exp, matchList); + } + + private @Nullable Map getSub(Core.Exp exp, + Core.Match match) { + if (exp.op == Op.ID && match.pat.op == Op.ID_PAT) { + return ImmutableMap.of(core.id((Core.IdPat) match.pat), (Core.Id) exp); + } + if (exp.op == Op.TUPLE && match.pat.op == Op.TUPLE_PAT) { + final Core.Tuple tuple = (Core.Tuple) exp; + final Core.TuplePat tuplePat = (Core.TuplePat) match.pat; + if (tuple.args.stream().allMatch(arg -> arg.op == Op.ID) + && tuplePat.args.stream().allMatch(arg -> arg.op == Op.ID_PAT)) { + final ImmutableMap.Builder builder = + ImmutableMap.builder(); + Pair.forEach(tuple.args, tuplePat.args, (arg, pat) -> + builder.put(core.id((Core.IdPat) pat), (Core.Id) arg)); + return builder.build(); + } + } + return null; + } + + @Override protected Core.Exp visit(Core.Let let) { final Analyzer.Use use = analysis == null ? Analyzer.Use.MULTI_UNSAFE : let.decl instanceof Core.NonRecValDecl - ? analysis.map.get(((Core.NonRecValDecl) let.decl).pat) + ? requireNonNull( + analysis.map.get(((Core.NonRecValDecl) let.decl).pat)) : Analyzer.Use.MULTI_UNSAFE; switch (use) { case DEAD: diff --git a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java index d272435f..6d3cdc8d 100644 --- a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java +++ b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java @@ -113,9 +113,9 @@ private Core.From toFrom(Core.Exp exp) { } } - @Override public Core.Exp visit(Core.From from) { + @Override protected Core.Exp visit(Core.From from) { final Core.From from2 = (Core.From) super.visit(from); - if (from2.steps.size() > 0) { + if (!from2.steps.isEmpty()) { final Core.FromStep step = from2.steps.get(0); if (step instanceof Core.Scan && ((Core.Scan) step).exp.op == Op.FROM diff --git a/src/main/java/net/hydromatic/morel/compile/Replacer.java b/src/main/java/net/hydromatic/morel/compile/Replacer.java new file mode 100644 index 00000000..e1b6c315 --- /dev/null +++ b/src/main/java/net/hydromatic/morel/compile/Replacer.java @@ -0,0 +1,57 @@ +/* + * Licensed to Julian Hyde under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Julian Hyde licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package net.hydromatic.morel.compile; + +import net.hydromatic.morel.ast.Core; +import net.hydromatic.morel.type.TypeSystem; + +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +/** + * Replaces identifiers with other identifiers. + */ +public class Replacer extends EnvShuttle { + private final Map substitution; + + private Replacer(TypeSystem typeSystem, Environment env, + Map substitution) { + super(typeSystem, env); + this.substitution = requireNonNull(substitution); + } + + static Core.Exp substitute(TypeSystem typeSystem, + Map substitution, Core.Exp exp) { + final Replacer replacer = + new Replacer(typeSystem, Environments.empty(), substitution); + return exp.accept(replacer); + } + + @Override protected Replacer push(Environment env) { + return new Replacer(typeSystem, env, substitution); + } + + @Override protected Core.Exp visit(Core.Id id) { + final Core.Id id2 = substitution.get(id); + return id2 != null ? id2 : id; + } +} + +// End Replacer.java diff --git a/src/test/java/net/hydromatic/morel/InlineTest.java b/src/test/java/net/hydromatic/morel/InlineTest.java index abb4a0d4..fd0177cf 100644 --- a/src/test/java/net/hydromatic/morel/InlineTest.java +++ b/src/test/java/net/hydromatic/morel/InlineTest.java @@ -339,6 +339,46 @@ private String v(int i) { hasToString(core1)) .assertEval(isUnordered(list(list(Unit.INSTANCE, 10)))); } + + /** Tests that a singleton {@code case} is inlined. */ + @Test void testInlineCase() { + final String ml = "let\n" + + " val f = fn x => case x of y => y + 2\n" + + "in\n" + + " f 3\n" + + "end"; + ml(ml) + .assertCore(0, + hasToString("val it = " + + "let val f = fn x => case x of y => y + 2 in f 3 end")) + .assertCore(2, hasToString("val it = let val x = 3 in x + 2 end")) + .assertEval(is(5)); + } + + /** Tests that a singleton {@code case} is inlined. */ + @Test void testInlineCase2() { + final String ml = "let\n" + + " val f = fn (x, y) => case (x, y) of (x1, y1) => x1 - y1\n" + + "in\n" + + " f (13, 5)\n" + + "end"; + ml(ml) + .assertCore(0, + hasToString("val it = " + + "let" + + " val f = fn v0 => " + + "case v0 of (x, y) => " + + "case (x, y) of (x1, y1) => x1 - y1 " + + "in" + + " f (13, 5) " + + "end")) + .assertCore(2, + hasToString("val it = " + + "let val v0 = (13, 5) " + + "in case v0 of (x, y) => -:int (x, y) " + + "end")) + .assertEval(is(8)); + } } // End InlineTest.java