diff --git a/docs/reference.md b/docs/reference.md
index f5dd1bb5..aca0d58c 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -139,7 +139,7 @@ In Standard ML but not in Morel:
conditional
| case exp of match case analysis
| fn match function
- | from [ scan1 , ... , scans ] step*
+ | from [ scan1 , ... , scans ] step*
relational expression (s ≥ 0)
exprow → exprowItem [, exprowItem ]*
expression row
@@ -147,10 +147,12 @@ In Standard ML but not in Morel:
match → matchItem [ '|' matchItem ]*
match
matchItem → pat => exp
-scan → pat [ in | = ] exp
+scan → pat in exp [ on exp ] iteration
+ | pat = exp [ on exp ] single iteration
| var unbounded variable
step → where exp filter clause
- | join scan [ on exp ] join clause
+ | join scan1 [ , ... , scans ]
+ join clause
| group groupKey1 , ... , groupKeyg
[ compute agg1 , ... , agga ]
group clause (g ≥ 0, a ≥ 1)
diff --git a/src/main/java/net/hydromatic/morel/ast/Ast.java b/src/main/java/net/hydromatic/morel/ast/Ast.java
index 17a8e9da..4b72d852 100644
--- a/src/main/java/net/hydromatic/morel/ast/Ast.java
+++ b/src/main/java/net/hydromatic/morel/ast/Ast.java
@@ -1509,7 +1509,6 @@ && getLast(steps) instanceof Ast.Yield) {
final Set nextFields = new HashSet<>();
for (FromStep step : steps) {
switch (step.op) {
- case INNER_JOIN:
case SCAN:
final Scan scan = (Scan) step;
nextFields.clear();
@@ -1578,8 +1577,12 @@ public Exp accept(Shuttle shuttle) {
} else {
w.append("from");
forEachIndexed(steps, (step, i) -> {
- if (step.op == Op.SCAN && i > 0 && steps.get(i - 1).op == Op.SCAN) {
- w.append(",");
+ if (step.op == Op.SCAN && i > 0) {
+ if (steps.get(i - 1).op == Op.SCAN) {
+ w.append(",");
+ } else {
+ w.append(" join");
+ }
}
step.unparse(w, 0, 0);
});
@@ -1620,17 +1623,8 @@ public static class Scan extends FromStep {
public final @Nullable Exp exp;
public final @Nullable Exp condition;
- Scan(Pos pos, Op op, Pat pat, @Nullable Exp exp, @Nullable Exp condition) {
- super(pos, op);
- switch (op) {
- case INNER_JOIN:
- break;
- case SCAN:
- checkArgument(condition == null);
- break;
- default:
- throw new AssertionError("not a join type " + op);
- }
+ Scan(Pos pos, Pat pat, @Nullable Exp exp, @Nullable Exp condition) {
+ super(pos, Op.SCAN);
this.pat = pat;
this.exp = exp;
this.condition = condition;
@@ -1668,7 +1662,7 @@ public Scan copy(Pat pat, @Nullable Exp exp, @Nullable Exp condition) {
&& Objects.equals(this.exp, exp)
&& Objects.equals(this.condition, condition)
? this
- : new Scan(pos, op, pat, exp, condition);
+ : new Scan(pos, pat, exp, condition);
}
}
diff --git a/src/main/java/net/hydromatic/morel/ast/AstBuilder.java b/src/main/java/net/hydromatic/morel/ast/AstBuilder.java
index e81b481f..6b4cc2a3 100644
--- a/src/main/java/net/hydromatic/morel/ast/AstBuilder.java
+++ b/src/main/java/net/hydromatic/morel/ast/AstBuilder.java
@@ -457,9 +457,9 @@ public Ast.Exp map(Pos pos, Ast.Exp e1, Ast.Exp e2) {
return apply(apply(ref(pos, BuiltIn.LIST_MAP), e1), e2);
}
- public Ast.Scan scan(Pos pos, Op op, Ast.Pat pat, Ast.Exp exp,
+ public Ast.Scan scan(Pos pos, Ast.Pat pat, Ast.Exp exp,
@Nullable Ast.Exp condition) {
- return new Ast.Scan(pos, op, pat, exp, condition);
+ return new Ast.Scan(pos, pat, exp, condition);
}
public Ast.Order order(Pos pos, Iterable orderItems) {
diff --git a/src/main/java/net/hydromatic/morel/ast/Core.java b/src/main/java/net/hydromatic/morel/ast/Core.java
index dce8ad2b..83075d68 100644
--- a/src/main/java/net/hydromatic/morel/ast/Core.java
+++ b/src/main/java/net/hydromatic/morel/ast/Core.java
@@ -1187,13 +1187,9 @@ public static class Scan extends FromStep {
public final Exp exp;
public final Exp condition;
- Scan(Op op, ImmutableList bindings, Pat pat, Exp exp,
+ Scan(ImmutableList bindings, Pat pat, Exp exp,
Exp condition) {
- super(op, bindings);
- if (op != Op.INNER_JOIN) {
- // SCAN and CROSS_JOIN are valid in ast, not core.
- throw new IllegalArgumentException("not a join type " + op);
- }
+ super(Op.SCAN, bindings);
this.pat = requireNonNull(pat, "pat");
this.exp = requireNonNull(exp, "exp");
this.condition = requireNonNull(condition, "condition");
@@ -1224,7 +1220,7 @@ private static boolean canAssign(Type fromType, Type toType) {
@Override protected AstWriter unparse(AstWriter w, From from, int ordinal,
int left, int right) {
- w.append(ordinal == 0 ? " " : op.padded)
+ w.append(ordinal == 0 ? " " : " join ")
// for these purposes 'in' has same precedence as '='
.append(pat, 0, Op.EQ.left);
if (Extents.isInfinite(exp)) {
@@ -1252,7 +1248,7 @@ public Scan copy(List bindings, Pat pat, Exp exp, Exp condition) {
&& condition == this.condition
&& bindings.equals(this.bindings)
? this
- : core.scan(op, bindings, pat, exp, condition);
+ : core.scan(bindings, pat, exp, condition);
}
}
diff --git a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
index fa335046..5f4ea372 100644
--- a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
+++ b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java
@@ -415,8 +415,7 @@ public Core.Exp implicitYieldExp(TypeSystem typeSystem,
}
}
- public static List lastBindings(
- List extends Core.FromStep> steps) {
+ public List lastBindings(List extends Core.FromStep> steps) {
return steps.isEmpty()
? ImmutableList.of()
: Iterables.getLast(steps).bindings;
@@ -492,10 +491,9 @@ public Core.DatatypeDecl datatypeDecl(Iterable dataTypes) {
return new Core.DatatypeDecl(ImmutableList.copyOf(dataTypes));
}
- public Core.Scan scan(Op op, List bindings, Core.Pat pat,
+ public Core.Scan scan(List bindings, Core.Pat pat,
Core.Exp exp, Core.Exp condition) {
- return new Core.Scan(op, ImmutableList.copyOf(bindings), pat, exp,
- condition);
+ return new Core.Scan(ImmutableList.copyOf(bindings), pat, exp, condition);
}
public Core.Aggregate aggregate(Type type, Core.Exp aggregate,
diff --git a/src/main/java/net/hydromatic/morel/ast/FromBuilder.java b/src/main/java/net/hydromatic/morel/ast/FromBuilder.java
index 4ac6c382..e18ba466 100644
--- a/src/main/java/net/hydromatic/morel/ast/FromBuilder.java
+++ b/src/main/java/net/hydromatic/morel/ast/FromBuilder.java
@@ -201,7 +201,7 @@ && getLast(((Core.From) exp).steps).bindings.size() == 1
return yield_(uselessIfLast, bindings, core.record(typeSystem, nameExps));
}
Compiles.acceptBinding(typeSystem, pat, bindings);
- return addStep(core.scan(Op.INNER_JOIN, bindings, pat, exp, condition));
+ return addStep(core.scan(bindings, pat, exp, condition));
}
public FromBuilder addAll(Iterable extends Core.FromStep> steps) {
@@ -367,7 +367,7 @@ private Core.Exp build(boolean simplify) {
}
if (simplify
&& steps.size() == 1
- && steps.get(0).op == Op.INNER_JOIN) {
+ && steps.get(0).op == Op.SCAN) {
final Core.Scan scan = (Core.Scan) steps.get(0);
if (scan.pat.op == Op.ID_PAT) {
return scan.exp;
diff --git a/src/main/java/net/hydromatic/morel/ast/Op.java b/src/main/java/net/hydromatic/morel/ast/Op.java
index 0dfef84b..5ae30abc 100644
--- a/src/main/java/net/hydromatic/morel/ast/Op.java
+++ b/src/main/java/net/hydromatic/morel/ast/Op.java
@@ -130,7 +130,6 @@ public enum Op {
CASE,
FROM,
SCAN(" "),
- INNER_JOIN(" join "),
WHERE,
GROUP,
COMPUTE,
diff --git a/src/main/java/net/hydromatic/morel/ast/Shuttle.java b/src/main/java/net/hydromatic/morel/ast/Shuttle.java
index 09333a64..de8287b0 100644
--- a/src/main/java/net/hydromatic/morel/ast/Shuttle.java
+++ b/src/main/java/net/hydromatic/morel/ast/Shuttle.java
@@ -231,7 +231,7 @@ protected AstNode visit(Ast.OrderItem orderItem) {
}
protected Ast.Scan visit(Ast.Scan scan) {
- return ast.scan(scan.pos, scan.op, scan.pat.accept(this),
+ return ast.scan(scan.pos, scan.pat.accept(this),
scan.exp.accept(this),
scan.condition == null ? null : scan.condition.accept(this));
}
diff --git a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
index eee61b12..a0a68dc3 100644
--- a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
+++ b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
@@ -404,7 +404,7 @@ private static void harmonizeRowTypes(RelBuilder relBuilder, int inputCount) {
private RelContext step(RelContext cx, int i, Core.FromStep fromStep) {
switch (fromStep.op) {
- case INNER_JOIN:
+ case SCAN:
return join(cx, i, (Core.Scan) fromStep);
case WHERE:
return where(cx, (Core.Where) fromStep);
@@ -687,7 +687,7 @@ private RelContext join(RelContext cx, int i, Core.Scan scan) {
private static JoinRelType joinRelType(Op op) {
switch (op) {
- case INNER_JOIN:
+ case SCAN:
return JoinRelType.INNER;
default:
throw new AssertionError(op);
diff --git a/src/main/java/net/hydromatic/morel/compile/Compiler.java b/src/main/java/net/hydromatic/morel/compile/Compiler.java
index 9a69786c..35f5f9e1 100644
--- a/src/main/java/net/hydromatic/morel/compile/Compiler.java
+++ b/src/main/java/net/hydromatic/morel/compile/Compiler.java
@@ -329,7 +329,7 @@ && getOnlyElement(bindings).id.type.equals(elementType)) {
createRowSinkFactory(cx, firstStep.bindings, skip(steps),
elementType);
switch (firstStep.op) {
- case INNER_JOIN:
+ case SCAN:
final Core.Scan scan = (Core.Scan) firstStep;
final Code code = compile(cx, scan.exp);
final Code conditionCode = compile(cx, scan.condition);
diff --git a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java
index 6d3cdc8d..ebd6cb72 100644
--- a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java
+++ b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java
@@ -108,7 +108,7 @@ private Core.From toFrom(Core.Exp exp) {
final List bindings = new ArrayList<>();
Compiles.acceptBinding(typeSystem, id, bindings);
final Core.Scan scan =
- core.scan(Op.INNER_JOIN, bindings, id, exp, core.boolLiteral(true));
+ core.scan(bindings, id, exp, core.boolLiteral(true));
return core.from(typeSystem, ImmutableList.of(scan));
}
}
diff --git a/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java b/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java
index 3e9c6e16..82da5002 100644
--- a/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java
+++ b/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java
@@ -110,7 +110,6 @@ Core.From visit(Core.From from) {
final Core.FromStep step = steps.get(i);
switch (step.op) {
case SCAN:
- case INNER_JOIN:
final Core.Scan scan = (Core.Scan) step;
if (Extents.isInfinite(scan.exp)) {
final int idPatCount = idPats.size();
diff --git a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java
index c100d700..0212d14f 100644
--- a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java
+++ b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java
@@ -480,7 +480,6 @@ private Pair deduceStepType(TypeEnv env,
Map fieldVars, List fromSteps) {
switch (step.op) {
case SCAN:
- case INNER_JOIN:
final Ast.Scan scan = (Ast.Scan) step;
final Ast.Exp scanExp;
final boolean eq;
diff --git a/src/main/java/net/hydromatic/morel/eval/Codes.java b/src/main/java/net/hydromatic/morel/eval/Codes.java
index cf2bfe97..7a4025e9 100644
--- a/src/main/java/net/hydromatic/morel/eval/Codes.java
+++ b/src/main/java/net/hydromatic/morel/eval/Codes.java
@@ -3092,7 +3092,7 @@ static class ScanRowSink extends BaseRowSink {
ScanRowSink(Op op, Core.Pat pat, Code code, Code conditionCode,
RowSink rowSink) {
super(rowSink);
- checkArgument(op == Op.INNER_JOIN);
+ checkArgument(op == Op.SCAN);
this.op = op;
this.pat = pat;
this.code = code;
@@ -3101,8 +3101,7 @@ static class ScanRowSink extends BaseRowSink {
@Override public Describer describe(Describer describer) {
return describer.start("join", d ->
- d.arg("op", op.padded.trim())
- .arg("pat", pat)
+ d.arg("pat", pat)
.arg("exp", code)
.argIf("condition", conditionCode, !isConstantTrue(conditionCode))
.arg("sink", rowSink));
diff --git a/src/main/javacc/MorelParser.jj b/src/main/javacc/MorelParser.jj
index 8e8c415d..bcac7317 100644
--- a/src/main/javacc/MorelParser.jj
+++ b/src/main/javacc/MorelParser.jj
@@ -330,8 +330,8 @@ Exp from() :
}
{
{ span = Span.of(pos()); }
- [ fromFirstStep(steps)
- ( fromFirstStep(steps) )*
+ [ fromFirstScan(steps)
+ ( fromScan(steps) )*
]
( fromStep(steps) )*
{
@@ -339,7 +339,7 @@ Exp from() :
}
}
-void fromFirstStep(List steps) :
+void fromFirstScan(List steps) :
{
final Pair patExp;
}
@@ -349,30 +349,22 @@ void fromFirstStep(List steps) :
patExp.right != null
? Span.of(patExp.left, patExp.right)
: Span.of(patExp.left);
- steps.add(ast.scan(span.pos(), Op.SCAN, patExp.left, patExp.right, null));
+ steps.add(ast.scan(span.pos(), patExp.left, patExp.right, null));
}
}
-void fromStep(List steps) :
+void fromScan(List steps) :
{
- final Span span;
- final Op op;
final Pair patExp;
final Exp condition;
- final Exp filterExp;
- final Exp skipExp;
- final Exp takeExp;
- final Exp yieldExp;
- final PairList groupExps;
- final List aggregates;
- final List orderItems;
}
{
- {
- span = Span.of(pos());
- op = Op.INNER_JOIN;
-}
- patExp = fromSource()
+ patExp = fromSource() {
+ final Span span =
+ patExp.right != null
+ ? Span.of(patExp.left, patExp.right)
+ : Span.of(patExp.left);
+ }
(
LOOKAHEAD(2)
condition = expression()
@@ -380,10 +372,26 @@ void fromStep(List steps) :
{ condition = null; }
)
{
- steps.add(
- ast.scan(span.end(this), op, patExp.left, patExp.right,
- condition));
+ steps.add(ast.scan(span.end(this), patExp.left, patExp.right, condition));
}
+}
+
+void fromStep(List steps) :
+{
+ final Span span;
+ final Op op;
+ final Exp filterExp;
+ final Exp skipExp;
+ final Exp takeExp;
+ final Exp yieldExp;
+ final PairList groupExps;
+ final List aggregates;
+ final List orderItems;
+}
+{
+
+ fromScan(steps)
+ ( fromScan(steps) )*
|
{ span = Span.of(pos()); } filterExp = expression() {
steps.add(ast.where(span.end(this), filterExp));
diff --git a/src/test/java/net/hydromatic/morel/MainTest.java b/src/test/java/net/hydromatic/morel/MainTest.java
index 5ba69a28..01ae0a4c 100644
--- a/src/test/java/net/hydromatic/morel/MainTest.java
+++ b/src/test/java/net/hydromatic/morel/MainTest.java
@@ -1705,11 +1705,18 @@ private static List