
net.hydromatic.morel.compile.Inliner Maven / Gradle / Ivy
Show all versions of morel Show documentation
/*
* 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 static java.util.Objects.requireNonNull;
import static net.hydromatic.morel.ast.CoreBuilder.core;
import static net.hydromatic.morel.util.Pair.forEach;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.ast.Op;
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.TypeSystem;
import org.checkerframework.checker.nullness.qual.Nullable;
/** Shuttle that inlines constant values. */
public class Inliner extends EnvShuttle {
private final Analyzer.@Nullable Analysis analysis;
/** Private constructor. */
private Inliner(
TypeSystem typeSystem, Environment env, Analyzer.Analysis analysis) {
super(typeSystem, env);
this.analysis = analysis;
}
/**
* Creates an Inliner.
*
* If {@code analysis} is null, no variables are inlined.
*/
public static Inliner of(
TypeSystem typeSystem,
Environment env,
Analyzer.@Nullable Analysis analysis) {
return new Inliner(typeSystem, env, analysis);
}
@Override
protected Inliner push(Environment env) {
return new Inliner(typeSystem, env, analysis);
}
@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
: requireNonNull(analysis.map.get(id.idPat));
switch (use) {
case ATOMIC:
case ONCE_SAFE:
return binding.exp.accept(this);
}
}
Object v = binding.value;
if (v instanceof Macro) {
final Macro macro = (Macro) binding.value;
final Core.Exp x =
macro.expand(typeSystem, env, ((FnType) id.type).paramType);
if (x instanceof Core.Literal) {
return x;
}
}
if (v != Unit.INSTANCE) {
switch (id.type.op()) {
case ID:
assert id.type instanceof PrimitiveType;
return core.literal((PrimitiveType) id.type, v);
case FUNCTION_TYPE:
assert v instanceof Applicable || v instanceof Macro : v;
final BuiltIn builtIn = Codes.BUILT_IN_MAP.get(v);
if (builtIn != null) {
return core.functionLiteral(typeSystem, builtIn);
}
// Applicable (including Closure) that does not map to a BuiltIn
// is not considered 'constant', mainly because it creates messy
// plans.
break;
default:
if (v instanceof Code) {
v = ((Code) v).eval(Compiler.EMPTY_ENV);
if (v == null) {
// Cannot inline SYS_FILE; it requires a session.
break;
}
}
return core.valueLiteral(id, v);
}
}
}
return super.visit(id);
}
@Override
protected Core.Exp visit(Core.Apply apply) {
final Core.Apply apply2 = (Core.Apply) super.visit(apply);
if (apply2.fn.op == Op.RECORD_SELECTOR
&& apply2.arg.op == Op.VALUE_LITERAL) {
final Core.RecordSelector selector = (Core.RecordSelector) apply2.fn;
@SuppressWarnings("rawtypes")
final List list = ((Core.Literal) apply2.arg).unwrap(List.class);
final Object o = list.get(selector.slot);
if (o instanceof Applicable || o instanceof Macro) {
// E.g. apply is '#filter List', o is Codes.LIST_FILTER,
// builtIn is BuiltIn.LIST_FILTER.
final BuiltIn builtIn = Codes.BUILT_IN_MAP.get(o);
if (builtIn != null) {
return core.functionLiteral(typeSystem, builtIn);
}
}
return core.valueLiteral(apply2, o);
}
if (apply2.fn.op == Op.FN) {
// Beta-reduction:
// (fn x => E) A
// 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);
}
return apply2;
}
@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();
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
? requireNonNull(
analysis.map.get(((Core.NonRecValDecl) let.decl).pat))
: Analyzer.Use.MULTI_UNSAFE;
switch (use) {
case DEAD:
// This declaration has no uses; remove it
return let.exp.accept(this);
case ATOMIC:
case ONCE_SAFE:
// This declaration has one use; remove the declaration, and replace its
// use inside the expression.
final List bindings = new ArrayList<>();
Compiles.bindPattern(typeSystem, bindings, let.decl);
return let.exp.accept(bind(bindings));
}
return super.visit(let);
}
}
// End Inliner.java