Skip to content

Commit

Permalink
Almost works
Browse files Browse the repository at this point in the history
  • Loading branch information
julianhyde committed Mar 5, 2024
1 parent 67a4535 commit b7d515b
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 33 deletions.
4 changes: 1 addition & 3 deletions src/main/java/net/hydromatic/morel/Shell.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
import java.io.OutputStream;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -484,11 +483,10 @@ void extracted(BiConsumer<String, Binding> outBindings) {
smlParser.zero("stdIn");
statement = smlParser.statementSemicolon();
final Environment env0 = env1;
final List<CompileException> warningList = new ArrayList<>();
final Tracer tracer = Tracers.empty();
final CompiledStatement compiled =
Compiles.prepareStatement(typeSystem, session, env0,
statement, null, warningList::add, tracer);
statement, null, e -> {}, tracer);
final Use shell = new Use(env0, bindingMap);
session.withShell(shell, outLines, session1 ->
compiled.eval(session1, env0, outLines, bindingMap::add));
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/hydromatic/morel/ast/Core.java
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,7 @@ public static class Case extends Exp {

public Case copy(Exp exp, List<Match> matchList) {
return exp == this.exp && matchList.equals(this.matchList) ? this
: core.caseOf(pos, type, exp, matchList);
: core.caseOf(pos, matchList.get(0).exp.type, exp, matchList);
}
}

Expand Down
49 changes: 48 additions & 1 deletion src/main/java/net/hydromatic/morel/compile/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,27 @@

import net.hydromatic.morel.ast.AstNode;
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Pos;
import net.hydromatic.morel.ast.Visitor;
import net.hydromatic.morel.type.Binding;
import net.hydromatic.morel.type.TypeSystem;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import static net.hydromatic.morel.ast.CoreBuilder.core;

/**
* Shuttle that counts how many times each expression is used.
Expand All @@ -57,8 +67,45 @@ public static Analysis analyze(TypeSystem typeSystem, Environment env,

// Mark all top-level bindings so that they will not be removed
if (node instanceof Core.NonRecValDecl) {
analyzer.use(((Core.NonRecValDecl) node).pat).top = true;
final Core.NonRecValDecl nonRecValDecl = (Core.NonRecValDecl) node;
analyzer.use(nonRecValDecl.pat).top = true;

// If "e" uses "v1" from the environment,
// and "v1" is defined using expression "e1",
// convert "e" into "let v1 = e1 in e".
// And so forth, for any expressions used by "e1".
final Queue<Core.Exp> queue = new ArrayDeque<>();
final Set<Core.NamedPat> names = new LinkedHashSet<>();
final List<Core.NonRecValDecl> declList = new ArrayList<>();
final Visitor visitor =
new Visitor() {
@Override protected void visit(Core.Id id) {
if (names.add(id.idPat)) {
final Binding binding = env.getOpt(id.idPat);
if (binding != null && binding.exp != null) {
queue.add(binding.exp);
declList.add(
core.nonRecValDecl(Pos.ZERO, id.idPat, binding.exp));
}
}
super.visit(id);
}
};
node.accept(visitor);
for (;;) {
Core.Exp e = queue.poll();
if (e == null) {
break;
}
e.accept(visitor);
}

if (!declList.isEmpty()) {
declList.add(nonRecValDecl);
node = core.recValDecl(declList);
}
}

node.accept(analyzer);
return analyzer.result();
}
Expand Down
26 changes: 14 additions & 12 deletions src/main/java/net/hydromatic/morel/compile/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -678,13 +677,14 @@ private void compileValDecl(Context cx, Core.ValDecl valDecl,
final List<String> outs = new ArrayList<>();
try {
final Object o = code.eval(evalEnv);
final Map<Core.NamedPat, Object> pairs = new LinkedHashMap<>();
if (!Closure.bindRecurse(pat.withType(type), o, pairs::put)) {
final List<Binding> pairs = new ArrayList<>();
if (!Closure.bindRecurse(pat.withType(type), o, (pat2, o2) ->
pairs.add(Binding.of(pat2, exp, o2)))) {
throw new Codes.MorelRuntimeException(Codes.BuiltInExn.BIND, pos);
}
pairs.forEach((pat2, o2) -> {
outBindings.accept(Binding.of(pat2, o2));
if (pat2 != skipPat) {
pairs.forEach(binding -> {
outBindings.accept(binding);
if (binding.id != skipPat) {
int stringDepth = Prop.STRING_DEPTH.intValue(session.map);
int lineWidth = Prop.LINE_WIDTH.intValue(session.map);
int printDepth = Prop.PRINT_DEPTH.intValue(session.map);
Expand All @@ -693,17 +693,19 @@ private void compileValDecl(Context cx, Core.ValDecl valDecl,
new Pretty(typeSystem, lineWidth, printLength, printDepth,
stringDepth);
final Pretty.TypedVal typedVal;
if (o2 instanceof TypedValue) {
TypedValue typedValue = (TypedValue) o2;
if (binding.value instanceof TypedValue) {
TypedValue typedValue = (TypedValue) binding.value;
typedVal =
new Pretty.TypedVal(pat2.name,
new Pretty.TypedVal(binding.id.name,
typedValue.valueAs(Object.class),
Keys.toProgressive(pat2.type().key())
Keys.toProgressive(binding.id.type().key())
.toType(typeSystem));
} else {
typedVal = new Pretty.TypedVal(pat2.name, o2, pat2.type);
typedVal =
new Pretty.TypedVal(binding.id.name, binding.value,
binding.id.type);
}
pretty.pretty(buf, pat2.type, typedVal);
pretty.pretty(buf, binding.id.type, typedVal);
final String line = str(buf);
outs.add(line);
outLines.accept(line);
Expand Down
28 changes: 20 additions & 8 deletions src/main/java/net/hydromatic/morel/compile/Environments.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,30 @@ static class SubEnvironment extends Environment {
return binding.id + ", ...";
}

@Override public @Nullable Binding getOpt(String name) {
if (name.equals(binding.id.name)) {
return binding;
@Override public final @Nullable Binding getOpt(String name) {
// Manual tail recursion elimination:
// Loop rather than recurse, to save stack space.
for (SubEnvironment t = this;; t = (SubEnvironment) t.parent) {
if (name.equals(t.binding.id.name)) {
return t.binding;
}
if (!(t.parent instanceof SubEnvironment)) {
return t.parent.getOpt(name);
}
}
return parent.getOpt(name);
}

@Override public @Nullable Binding getOpt(Core.NamedPat id) {
if (id.equals(binding.id)) {
return binding;
@Override public final @Nullable Binding getOpt(Core.NamedPat id) {
// Manual tail recursion elimination:
// Loop rather than recurse, to save stack space.
for (SubEnvironment t = this;; t = (SubEnvironment) t.parent) {
if (id.equals(t.binding.id)) {
return t.binding;
}
if (!(t.parent instanceof SubEnvironment)) {
return t.parent.getOpt(id);
}
}
return parent.getOpt(id);
}

@Override protected Environment bind(Binding binding) {
Expand Down
91 changes: 89 additions & 2 deletions src/main/java/net/hydromatic/morel/compile/Inliner.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@

import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Op;
import net.hydromatic.morel.ast.Shuttle;
import net.hydromatic.morel.eval.Applicable;
import net.hydromatic.morel.eval.Code;
import net.hydromatic.morel.eval.Codes;
import net.hydromatic.morel.eval.Unit;
import net.hydromatic.morel.type.Binding;
import net.hydromatic.morel.type.FnType;
import net.hydromatic.morel.type.PrimitiveType;
import net.hydromatic.morel.type.TupleType;
import net.hydromatic.morel.type.Type;
import net.hydromatic.morel.type.TypeSystem;
import net.hydromatic.morel.type.TypeVar;

import com.google.common.collect.ImmutableMap;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;

import static net.hydromatic.morel.ast.CoreBuilder.core;
import static net.hydromatic.morel.util.Pair.forEach;
Expand Down Expand Up @@ -144,12 +150,28 @@ public static Inliner of(TypeSystem typeSystem, Environment env,
// becomes
// let x = A in E end
final Core.Fn fn = (Core.Fn) apply2.fn;
return core.let(
core.nonRecValDecl(apply2.pos, fn.idPat, apply2.arg), fn.exp);
final Core.Let let =
core.let(core.nonRecValDecl(apply2.pos, fn.idPat, apply2.arg),
fn.exp);
// Type may have become specialized. E.g. when you substitute 'x = 2'
// into '(fn x => x) 2' the type changes from "'a" to "int".
if (let.type == apply2.type) {
return let;
}
final UnaryOperator<Type> typeTransform =
unify(apply2.fn.type, apply.fn.type);
return substituteType(let, typeTransform);
}
return apply2;
}

/** Returns a copy of {@code exp}, converting every occurrence of
* {@code fromType} to {@code toType}. */
private Core.Exp substituteType(Core.Exp exp,
UnaryOperator<Type> typeTransform) {
return exp.accept(new CopyingShuttle(typeSystem, typeTransform));
}

@Override protected Core.Exp visit(Core.Case caseOf) {
final Core.Exp exp = caseOf.exp.accept(this);
final List<Core.Match> matchList = visitList(caseOf.matchList);
Expand Down Expand Up @@ -207,6 +229,71 @@ public static Inliner of(TypeSystem typeSystem, Environment env,
}
return super.visit(let);
}

/** Shuttle that copies an expression, transforming its type as it goes. */
private static class CopyingShuttle extends Shuttle {
final UnaryOperator<Type> typeTransform;
final Map<Core.NamedPat, Core.IdPat> idPatMap = new HashMap<>();

protected CopyingShuttle(TypeSystem typeSystem,
UnaryOperator<Type> typeTransform) {
super(typeSystem);
this.typeTransform = typeTransform;
}

@Override protected Core.IdPat visit(Core.IdPat idPat) {
final Core.IdPat idPat1 = idPatMap.get(idPat);
if (idPat1 != null) {
return idPat1;
}
Type type = idPat.type.copy(typeSystem, typeTransform);
if (!type.equals(idPat.type)) {
final Core.IdPat idPat2 =
core.idPat(type, typeSystem.nameGenerator);
idPatMap.put(idPat, idPat2);
return idPat2;
}
return super.visit(idPat);
}

@Override protected Core.Exp visit(Core.Id id) {
final Core.IdPat idPat = idPatMap.get(id.idPat);
if (idPat != null) {
return core.id(idPat);
}
return super.visit(id);
}
}

/** Given a source and destination type, returns the substitution
* that converted source to destination.
*
* <p>Examples:
* <ul>
* <li>{@code unify(var(0), int)} &rarr; [var(0) &rarr; int]
* <li>{@code unify(var(0), bool list)} &rarr; [var(0) &rarr; bool list]
* <li>{@code unify(var(0) * var(0), int * int)} &rarr; [var(0) &rarr; int]
* </ul>
*/
UnaryOperator<Type> unify(Type fromType, Type toType) {
if (fromType instanceof TypeVar) {
return t -> t.equals(fromType) ? toType : t;
}
if (fromType instanceof TupleType
&& toType instanceof TupleType) {
// This is wrong.
return unify(((TupleType) fromType).argType(0),
((TupleType) toType).argType(0));
}
if (fromType instanceof FnType
&& toType instanceof FnType) {
// This is wrong.
return unify(((FnType) fromType).resultType,
((FnType) toType).resultType);
}
// And so is this
return t -> t;
}
}

// End Inliner.java
4 changes: 2 additions & 2 deletions src/main/java/net/hydromatic/morel/compile/Resolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ private Resolver(TypeMap typeMap, NameGenerator nameGenerator,
/** Creates a root Resolver. */
public static Resolver of(TypeMap typeMap, Environment env,
@Nullable Session session) {
return new Resolver(typeMap, new NameGenerator(), new HashMap<>(), env,
session);
return new Resolver(typeMap, typeMap.typeSystem.nameGenerator,
new HashMap<>(), env, session);
}

/** Binds a Resolver to a new environment. */
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/hydromatic/morel/type/Binding.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public static Binding of(Core.NamedPat id, Object value) {
return new Binding(id, null, value, false);
}

public static Binding of(Core.NamedPat id, Core.Exp exp, Object value) {
return new Binding(id, exp, value, false);
}

@Override public int hashCode() {
return Objects.hash(id, exp, value);
}
Expand Down
3 changes: 1 addition & 2 deletions src/test/java/net/hydromatic/morel/Ml.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,9 @@ Ml withPrepare(Consumer<CompiledStatement> action) {
final AstNode statement = parser.statementEof();
final Environment env = Environments.empty();
final Session session = new Session(propMap);
final List<CompileException> warningList = new ArrayList<>();
final CompiledStatement compiled =
Compiles.prepareStatement(typeSystem, session, env, statement,
null, warningList::add, tracer);
null, e -> {}, tracer);
action.accept(compiled);
} catch (ParseException e) {
throw new RuntimeException(e);
Expand Down
Loading

0 comments on commit b7d515b

Please sign in to comment.