@@ -357,9 +357,28 @@ public virtual with sharing class Interpreter implements Visitor {
357357 }
358358
359359 public virtual Object visit (Expr.FunctionCall function ) {
360+ attemptCapture (function );
360361 return FunctionCaller .exec (this , function );
361362 }
362363
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+
363382 public Object visit (Expr.ListLiteral listLiteral ) {
364383 inListLiteralCounter ++ ;
365384 List <Object > resultList = new List <Object >();
@@ -567,4 +586,66 @@ public virtual with sharing class Interpreter implements Visitor {
567586
568587 return new ShouldNotAdd ();
569588 }
589+
590+ private PendingCapture pendingCapture ;
591+ 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 ;
608+ }
609+
610+ 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 ;
650+ }
570651}
0 commit comments