args) {
+ /** Creates a TuplePat.
+ *
+ * Type is {@link PrimitiveType#UNIT} if {@code args} is empty,
+ * otherwise a {@link TupleType}. */
+ TuplePat(RecordLikeType type, ImmutableList args) {
super(Op.TUPLE_PAT, type);
this.args = requireNonNull(args);
+ checkArgument(args.size() == type.argNameTypes().size());
+ checkArgument(args.isEmpty()
+ ? type == PrimitiveType.UNIT
+ : type instanceof TupleType);
+ }
+
+ @Override public RecordLikeType type() {
+ return (RecordLikeType) type;
}
@Override AstWriter unparse(AstWriter w, int left, int right) {
@@ -407,6 +417,17 @@ public TuplePat copy(TypeSystem typeSystem, List args) {
return args.equals(this.args) ? this
: core.tuplePat(typeSystem, args);
}
+
+ /** Returns the names of all components that are named. */
+ public List fieldNames() {
+ final ImmutableList.Builder names = ImmutableList.builder();
+ for (Pat arg : args) {
+ if (arg instanceof NamedPat) {
+ names.add(((NamedPat) arg).name);
+ }
+ }
+ return names.build();
+ }
}
/** List pattern.
@@ -1127,7 +1148,11 @@ public static class From extends Exp {
}
}
- public Exp copy(TypeSystem typeSystem, Environment env,
+ /** Copies this {@code From} with a new set of steps.
+ *
+ * Returns this {@code From} if the steps are the same.
+ * If {@code env} is not null, performs additional checking. */
+ public Exp copy(TypeSystem typeSystem, @Nullable Environment env,
List steps) {
return steps.equals(this.steps)
? this
@@ -1165,17 +1190,28 @@ public static class Scan extends FromStep {
Scan(Op op, ImmutableList bindings, Pat pat, Exp exp,
Exp condition) {
super(op, bindings);
- switch (op) {
- case INNER_JOIN:
- case SUCH_THAT:
- break;
- default:
+ if (op != Op.INNER_JOIN) {
// SCAN and CROSS_JOIN are valid in ast, not core.
- throw new AssertionError("not a join type " + op);
+ throw new IllegalArgumentException("not a join type " + op);
}
this.pat = requireNonNull(pat, "pat");
this.exp = requireNonNull(exp, "exp");
this.condition = requireNonNull(condition, "condition");
+ if (!(exp.type instanceof ListType)) {
+ throw new IllegalArgumentException("scan expression must be list: "
+ + exp.type);
+ }
+ final ListType listType = (ListType) exp.type;
+ if (!canAssign(listType.elementType, pat.type)) {
+ throw new IllegalArgumentException(exp.type + " + " + pat.type);
+ }
+ }
+
+ /** Returns whether you can assign a value of {@code fromType} to a variable
+ * of type {@code toType}. */
+ private static boolean canAssign(Type fromType, Type toType) {
+ return fromType.equals(toType)
+ || toType.isProgressive();
}
@Override public Scan accept(Shuttle shuttle) {
@@ -1188,16 +1224,17 @@ public static class Scan extends FromStep {
@Override protected AstWriter unparse(AstWriter w, From from, int ordinal,
int left, int right) {
- final String prefix = ordinal == 0 ? " "
- : op == Op.SUCH_THAT ? " join "
- : op.padded;
- final String infix = op == Op.SUCH_THAT ? " suchthat "
- : " in ";
- w.append(prefix)
- // for these purposes 'in' and 'suchthat' have same precedence as '='
- .append(pat, 0, Op.EQ.left)
- .append(infix)
- .append(exp, Op.EQ.right, 0);
+ w.append(ordinal == 0 ? " " : op.padded)
+ // for these purposes 'in' has same precedence as '='
+ .append(pat, 0, Op.EQ.left);
+ if (Extents.isInfinite(exp)) {
+ // Print "from x : int" rather "from x in extent 'int'"
+ w.append(" : ")
+ .append(((ListType) exp.type).elementType.moniker());
+ } else {
+ w.append(" in ")
+ .append(exp, Op.EQ.right, 0);
+ }
if (!isLiteralTrue()) {
w.append("on").append(condition, 0, 0);
}
diff --git a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
index 5e473b55f..fa335046e 100644
--- a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
+++ b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
@@ -20,6 +20,7 @@
import net.hydromatic.morel.compile.BuiltIn;
import net.hydromatic.morel.compile.Environment;
+import net.hydromatic.morel.compile.Extents;
import net.hydromatic.morel.compile.NameGenerator;
import net.hydromatic.morel.eval.Unit;
import net.hydromatic.morel.type.Binding;
@@ -39,6 +40,7 @@
import net.hydromatic.morel.util.PairList;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableRangeSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
@@ -56,9 +58,11 @@
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import static net.hydromatic.morel.type.RecordType.ORDERING;
import static net.hydromatic.morel.util.Pair.forEach;
+import static net.hydromatic.morel.util.Pair.forEachIndexed;
import static net.hydromatic.morel.util.Static.transform;
import static com.google.common.collect.Iterables.getOnlyElement;
@@ -253,11 +257,12 @@ public Core.Con0Pat con0Pat(DataType type, String tyCon) {
return new Core.Con0Pat(type, tyCon);
}
- public Core.TuplePat tuplePat(Type type, Iterable extends Core.Pat> args) {
+ public Core.TuplePat tuplePat(RecordLikeType type,
+ Iterable extends Core.Pat> args) {
return new Core.TuplePat(type, ImmutableList.copyOf(args));
}
- public Core.TuplePat tuplePat(Type type, Core.Pat... args) {
+ public Core.TuplePat tuplePat(RecordLikeType type, Core.Pat... args) {
return new Core.TuplePat(type, ImmutableList.copyOf(args));
}
@@ -540,12 +545,32 @@ public Core.Yield yield_(TypeSystem typeSystem, Core.Exp exp) {
switch (exp.type.op()) {
case RECORD_TYPE:
case TUPLE_TYPE:
- ((RecordLikeType) exp.type).argNameTypes().forEach((name, type) ->
- bindings.add(
- Binding.of(idPat(type, name, typeSystem.nameGenerator))));
+ forEachIndexed(((RecordLikeType) exp.type).argNameTypes(),
+ (i, name, type) -> {
+ final Core.NamedPat idPat;
+ if (exp.op == Op.TUPLE
+ && exp.arg(i) instanceof Core.Id
+ && ((Core.Id) exp.arg(i)).idPat.name.equals(name)) {
+ // Use an existing IdPat if we can, rather than generating a new
+ // IdPat with a different sequence number. (The underlying problem
+ // is that the fields of record types have only names, no sequence
+ // numbers.)
+ idPat = ((Core.Id) exp.arg(i)).idPat;
+ } else {
+ idPat = idPat(type, name, typeSystem.nameGenerator);
+ }
+ bindings.add(Binding.of(idPat));
+ });
break;
+
default:
- bindings.add(Binding.of(idPat(exp.type, typeSystem.nameGenerator)));
+ switch (exp.op) {
+ case ID:
+ bindings.add(Binding.of(((Core.Id) exp).idPat));
+ break;
+ default:
+ bindings.add(Binding.of(idPat(exp.type, typeSystem.nameGenerator)));
+ }
}
return yield_(bindings, exp);
}
@@ -578,20 +603,32 @@ public Core.Exp list(TypeSystem typeSystem, Core.Exp arg0, Core.Exp... args) {
* fall into a given range-set. The range-set might consist of just
* {@link Range#all()}, in which case, the list returns all values of the
* type. */
- @SuppressWarnings({"UnstableApiUsage", "rawtypes"})
+ @SuppressWarnings({"rawtypes", "unchecked"})
public Core.Exp extent(TypeSystem typeSystem, Type type,
- RangeSet rangeSet) {
+ RangeSet rangeSet) {
+ final Map map;
+ if (rangeSet.complement().isEmpty()) {
+ map = ImmutableMap.of();
+ } else {
+ map = ImmutableMap.of("/", ImmutableRangeSet.copyOf(rangeSet));
+ }
+ return extent(typeSystem, type, map);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Core.Exp extent(TypeSystem typeSystem, Type type,
+ Map rangeSetMap) {
final ListType listType = typeSystem.listType(type);
// Store an ImmutableRangeSet value inside a literal of type 'unit'.
// The value of such literals is usually Unit.INSTANCE, but we cheat.
return core.apply(Pos.ZERO, listType,
core.functionLiteral(typeSystem, BuiltIn.Z_EXTENT),
- core.internalLiteral(new RangeExtent(rangeSet, type)));
+ core.internalLiteral(new RangeExtent(typeSystem, type, rangeSetMap)));
}
- @SuppressWarnings({"UnstableApiUsage", "rawtypes", "unchecked"})
- public Pair> mergeExtents(TypeSystem typeSystem,
- List extends Core.Exp> exps, boolean intersect) {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public Pair> intersectExtents(TypeSystem typeSystem,
+ List extends Core.Exp> exps) {
switch (exps.size()) {
case 0:
throw new AssertionError();
@@ -600,28 +637,61 @@ public Pair> mergeExtents(TypeSystem typeSystem,
return Pair.of(simplify(typeSystem, exps.get(0)), ImmutableList.of());
default:
- ImmutableRangeSet rangeSet = intersect
- ? ImmutableRangeSet.of(Range.all())
- : ImmutableRangeSet.of();
+ final List