The pipeline executor allows to execute arbitrary pipelines in a step-by-step fashion. If you are not yet in the possession of a pipeline, you can use the createPipeline function to create one for yourself, based on the steps that you want to execute.

Those steps are split into two phases or "stages" (which is the name that we will use in the following), represented by the PipelineStepStage type. These allow us to separate things that have to be done once per-file, e.g., actually parsing the AST, from those that we need to repeat 'once per request' (whatever this request may be). In other words, what can be cached between operations and what cannot.

Furthermore, this executor follows an iterable fashion to be as flexible as possible (e.g., to be instrumented with measurements). So, you can use the pipeline executor like this:

const stepper = new PipelineExecutor( ... )
while(stepper.hasNextStep()) {
await stepper.nextStep()
}

stepper.switchToRequestStage()

while(stepper.hasNextStep()) {
await stepper.nextStep()
}

const result = stepper.getResults()

Of course, you might think, that this is rather overkill if you simply want to receive the result. And this is true. Therefore, if you do not want to perform some kind of magic in-between steps, you can use the allRemainingSteps function like this:

const stepper = new PipelineExecutor( ... )
const result = await stepper.allRemainingSteps()

As the name suggests, you can combine this name with previous calls to nextStep to only execute the remaining steps in case, for whatever reason you only want to instrument some steps.

By default, the PipelineExecutor does not offer an automatic way to repeat requests (mostly to prevent accidental errors). However, you can use the updateRequest function to reset the request steps and re-execute them for a new request. This allows something like the following:

const stepper = new PipelineExecutor( ... )
const result = await stepper.allRemainingSteps()

stepper.updateRequest( ... )
const result2 = await stepper.allRemainingSteps()

Example - Slicing With the Pipeline Executor:

Suppose, you want to... you know slice a file (which was, at one point the origin of flowR), then you can either create a pipeline yourself with the respective steps, or you can use the DEFAULT_SLICING_PIPELINE (and friends). With it, slicing essentially becomes 'easy-as-pie':

const slicer = new PipelineExecutor(DEFAULT_SLICING_PIPELINE, {
shell: new RShell(),
// of course, the criterion and request given here are just examples, you can use whatever you want to slice!
criterion: ['2@b'],
request: requestFromInput('b <- 3; x <- 5\ncat(b)'),
})
const result = await slicer.allRemainingSteps()

But now, we want to slice for x in the first line as well! We can do that by adding:

stepper.updateRequest({ criterion: ['1@x'] })
const result2 = await stepper.allRemainingSteps()

Even though using the pipeline executor introduces a small performance overhead, we consider it to be the baseline for performance benchmarking. It may very well be possible to squeeze out a little bit more by directly constructing the steps in the right order. However, we consider this to be negligible when compared with the time required for, for example, the dataflow analysis of larger files.

  • PipelineExecutor#allRemainingSteps
  • PipelineExecutor#nextStep

Type Parameters

Constructors

Methods

  • Execute the next IPipelineStep|step and return the name of the IPipelineStep|step that was executed, so you can guard if the IPipelineStep|step differs from what you are interested in. Furthermore, it returns the IPipelineStep|step's result.

    Type Parameters

    Parameters

    • OptionalexpectedStepName: PassedName

      A safeguard if you want to retrieve the result. If given, it causes the execution to fail if the next step is not the one you expect.

      Without expectedStepName, please refrain from accessing the result, as you have no safeguards if the pipeline changes.

    Returns Promise<{
        name: (undefined | PassedName) extends undefined
            ? PipelineStepName
            : PassedName;
        result: (undefined | PassedName) extends undefined
            ? unknown
            : PipelineStepOutputWithName<P, PassedName>;
    }>

  • Switch to the next stage of the pipeline executor.

    This will fail if either a step change is currently not valid (as not all steps have been executed), or if there is no next stage (i.e., the pipeline is already completed or in the last stage).

    Returns void

    • PipelineExecutor
    • getCurrentStage
  • This only makes sense if you have already run a request and want to re-use the per-file results for a new one. (or if for whatever reason, you did not pass information for the pipeline with the constructor).

    Parameters

    Returns void