Skip to content

Commit d17ecdd

Browse files
committed
Refactoring pipeline interpretation to first completely desugar it before running it through future interpreters.
1 parent 51d5866 commit d17ecdd

File tree

4 files changed

+59
-97
lines changed

4 files changed

+59
-97
lines changed

expression-src/main/src/interpreter/ContextResolver.cls

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,13 +438,11 @@ public with sharing class ContextResolver implements Visitor {
438438
}
439439

440440
public Object visit(Expr.PipelineFunction pipelineFunction) {
441-
resolve(pipelineFunction.left);
442-
resolve(pipelineFunction.body);
443-
return null;
441+
throw new UnsupportedOperationException('Pipeline functions should first be desugared before being resolved.');
444442
}
445443

446444
public Object visit(Expr.Capture capture) {
447-
return null;
445+
throw new UnsupportedOperationException('Capture expressions should first be desugared before being resolved.');
448446
}
449447

450448
private final Map<SObjectType, Fields> cachedFieldsBySObjectType = new Map<SObjectType, Fields>();

expression-src/main/src/interpreter/Interpreter.cls

Lines changed: 7 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -357,28 +357,9 @@ public virtual with sharing class Interpreter implements Visitor {
357357
}
358358

359359
public virtual Object visit(Expr.FunctionCall function) {
360-
attemptCapture(function);
361360
return FunctionCaller.exec(this, function);
362361
}
363362

364-
protected void attemptCapture(Expr.FunctionCall function) {
365-
if (pendingCapture == null || pendingCapture.isExplicit == true) {
366-
return;
367-
}
368-
369-
// If there is a pipeline capture pending, then the first function
370-
// call we encounter should use that as its first argument.
371-
if (function.arguments.isEmpty()) {
372-
function.arguments.add(pendingCapture.expression);
373-
} else {
374-
function.arguments.add(0, pendingCapture.expression);
375-
}
376-
377-
// Once captured, we clear the pending capture to avoid any additional
378-
// functions to the right-hand side of the pipeline to try and use it.
379-
pendingCapture = null;
380-
}
381-
382363
public Object visit(Expr.ListLiteral listLiteral) {
383364
inListLiteralCounter++;
384365
List<Object> resultList = new List<Object>();
@@ -586,66 +567,16 @@ public virtual with sharing class Interpreter implements Visitor {
586567

587568
return new ShouldNotAdd();
588569
}
589-
590-
private PendingCapture pendingCapture;
570+
591571
public Object visit(Expr.PipelineFunction pipelineFunction) {
592-
if (pipelineFunction.hasExplicitCapture) {
593-
// When dealing with an explicit capture, we immediately evaluate
594-
// the left-hand side of the pipeline, and store it for it to be used
595-
// whenever a capture ("_") expression is encountered within the right-hand side (body).
596-
this.pendingCapture = explicit(evaluate(pipelineFunction.left));
597-
} else {
598-
// If there is no capture on the right-hand side of the pipeline,
599-
// then it acts as syntactic sugar for passing the left-hand side
600-
// as the first argument to the function call on the right-hand side.
601-
// Thus, it does not get evaluated, but rather stored as an expression
602-
// for it to be evaluated during function call argument resolution.
603-
this.pendingCapture = implicit(pipelineFunction.left);
604-
}
605-
606-
Object toReturn = evaluate(pipelineFunction.body);
607-
return toReturn;
572+
throw new Exceptions.UnknownException(
573+
'Pipelines should first be desugared by the PipeResolver before reaching the Interpreter.'
574+
);
608575
}
609576

610577
public Object visit(Expr.Capture capture) {
611-
if (this.pendingCapture == null) {
612-
throw new Exceptions.RuntimeException(
613-
capture.captureToken,
614-
'Capture expressions can only be used inside pipeline functions.'
615-
);
616-
}
617-
618-
if (pendingCapture.isExplicit == false) {
619-
// This should be unreachable, since the PipelineFunction visitor
620-
// should have populated the pending capture as explicit.
621-
throw new Exceptions.UnknownException(
622-
capture.captureToken.position,
623-
'Internal error: expected an explicit pending capture.'
624-
);
625-
}
626-
627-
return this.pendingCapture.result;
628-
}
629-
630-
private class PendingCapture {
631-
private Boolean isExplicit;
632-
// Populated when isExplicit is false
633-
private Expr expression;
634-
// Populated when isExplicit is true
635-
private Object result;
636-
}
637-
638-
private static PendingCapture explicit(Object result) {
639-
PendingCapture capture = new PendingCapture();
640-
capture.isExplicit = true;
641-
capture.result = result;
642-
return capture;
643-
}
644-
645-
private static PendingCapture implicit(Expr expression) {
646-
PendingCapture capture = new PendingCapture();
647-
capture.isExplicit = false;
648-
capture.expression = expression;
649-
return capture;
578+
throw new Exceptions.UnknownException(
579+
'Captures should first be desugared by the PipeResolver before reaching the Interpreter.'
580+
);
650581
}
651582
}

expression-src/main/src/interpreter/PipeResolver.cls

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ public with sharing class PipeResolver implements Visitor {
6666
}
6767

6868
public Object visit(Expr.FunctionCall function) {
69+
if (this.shouldImplicitlyCapture) {
70+
// If there is a pipeline capture pending, then the first function
71+
// call we encounter should use that as its first argument.
72+
if (function.arguments.isEmpty()) {
73+
function.arguments.add(this.waitingForCapture);
74+
} else {
75+
function.arguments.add(0, this.waitingForCapture);
76+
}
77+
78+
this.resetCaptureFlagsAndState();
79+
}
80+
6981
List<Expr> newArguments = new List<Expr>();
7082
for (Expr argument : function.arguments) {
7183
newArguments.add(resolve(argument));
@@ -201,29 +213,52 @@ public with sharing class PipeResolver implements Visitor {
201213
);
202214
}
203215

204-
Boolean lookingForCapture = false;
216+
Boolean lookingForExplicitCapture = false;
217+
Boolean shouldImplicitlyCapture = false;
205218
Boolean captureFound = false;
219+
Expr waitingForCapture;
206220
public Object visit(Expr.PipelineFunction pipelineFunction) {
207221
Expr left = resolve(pipelineFunction.left);
208-
lookingForCapture = true;
222+
this.waitingForCapture = left;
223+
224+
// We now do an initial pass of the right-hand side, looking for a capture expression.
225+
this.lookingForExplicitCapture = true;
209226
Expr resolvedBody = resolve(pipelineFunction.body);
210-
lookingForCapture = false;
211-
Object toReturn = new Expr.PipelineFunction(pipelineFunction.pipeToken, left, resolvedBody)
212-
.withInnerCapture(captureFound);
213-
captureFound = false;
214-
return toReturn;
227+
this.lookingForExplicitCapture = false;
228+
229+
if (this.captureFound) {
230+
// If a capture was found, we are done. Reset all flags and return the resolved body.
231+
this.resetCaptureFlagsAndState();
232+
return resolvedBody;
233+
} else {
234+
// If no capture was found, we flag the need for an implicit capture.
235+
// And resolve again, expecting that the first function parameter will be replaced with the left expression.
236+
this.shouldImplicitlyCapture = true;
237+
resolvedBody = resolve(pipelineFunction.body);
238+
239+
// Reset all flags and return the resolved body.
240+
this.resetCaptureFlagsAndState();
241+
return resolvedBody;
242+
}
243+
}
244+
245+
void resetCaptureFlagsAndState() {
246+
this.lookingForExplicitCapture = false;
247+
this.shouldImplicitlyCapture = false;
248+
this.captureFound = false;
249+
this.waitingForCapture = null;
215250
}
216251

217252
public Object visit(Expr.Capture capture) {
218-
if (lookingForCapture) {
219-
captureFound = true;
220-
} else {
221-
throw new Exceptions.RuntimeException(
222-
capture.captureToken,
223-
'Capture expressions can only be used inside pipeline functions.'
224-
);
253+
if (this.lookingForExplicitCapture) {
254+
this.captureFound = true;
255+
return this.waitingForCapture;
225256
}
226257

227-
return capture;
258+
// If we reach here, it means a capture expression was used outside of a pipeline function.
259+
throw new Exceptions.RuntimeException(
260+
capture.captureToken,
261+
'Capture expressions can only be used inside pipeline functions.'
262+
);
228263
}
229264
}

expression-src/main/src/interpreter/query/QueryInterpreter.cls

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ public with sharing class QueryInterpreter extends Interpreter {
221221
'Functions are not supported in a Query context outside of a where clause.');
222222
}
223223

224-
attemptCapture(function);
225-
226224
Map<String, StandardFunction> dateAndTimeFunctions = new DateAndTimeFunctions.DateAndTimeFunctionsProvider().getFunctions();
227225
Map<String, StandardFunction> supportedWhereFunctions = new Map<String, StandardFunction> {
228226
'LIKE' => new LikeFn(),

0 commit comments

Comments
 (0)