add other flowcontrol ops. Update tests

This commit is contained in:
d98762625 2018-05-21 10:58:35 +01:00
parent 046e1ebad9
commit 8ff6596657
15 changed files with 735 additions and 402 deletions

View file

@ -0,0 +1,51 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Comment operation
*/
class Comment extends Operation {
/**
* Comment constructor
*/
constructor() {
super();
this.name = "Comment";
this.flowControl = true;
this.module = "Default";
this.description = "Provides a place to write comments within the flow of the recipe. This operation has no computational effect.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "",
"type": "text",
"value": ""
}
];
}
/**
* Comment operation.
*
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @returns {Object} The updated state of the recipe.
*/
run(state) {
return state;
}
}
export default Comment;

View file

@ -0,0 +1,89 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Dish from "../Dish";
import { getLabelIndex } from "../lib/FlowControl";
/**
* Conditional Jump operation
*/
class ConditionalJump extends Operation {
/**
* ConditionalJump constructor
*/
constructor() {
super();
this.name = "Conditional Jump";
this.flowControl = true;
this.module = "Default";
this.description = "Conditionally jump forwards or backwards to the specified Label based on whether the data matches the specified regular expression.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Match (regex)",
"type": "string",
"value": ""
},
{
"name": "Invert match",
"type": "boolean",
"value": false
},
{
"name": "Label name",
"type": "shortString",
"value": ""
},
{
"name": "Maximum jumps (if jumping backwards)",
"type": "number",
"value": 10
}
];
}
/**
* Conditional Jump operation.
*
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @param {number} state.numJumps - The number of jumps taken so far.
* @returns {Object} The updated state of the recipe.
*/
async run(state) {
const ings = state.opList[state.progress].ingValues,
dish = state.dish,
regexStr = ings[0],
invert = ings[1],
label = ings[2],
maxJumps = ings[3],
jmpIndex = getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
return state;
}
if (regexStr !== "") {
const str = await dish.get(Dish.STRING);
const strMatch = str.search(regexStr) > -1;
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;
}
}
return state;
}
}
export default ConditionalJump;

View file

@ -0,0 +1,121 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Recipe from "../Recipe";
import Dish from "../Dish";
/**
* Fork operation
*/
class Fork extends Operation {
/**
* Fork constructor
*/
constructor() {
super();
this.name = "Fork";
this.flowControl = true;
this.module = "Default";
this.description = "Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.<br><br>For example, to decode multiple Base64 strings, enter them all on separate lines then add the 'Fork' and 'From Base64' operations to the recipe. Each string will be decoded separately.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Split delimiter",
"type": "binaryShortString",
"value": "\\n"
},
{
"name": "Merge delimiter",
"type": "binaryShortString",
"value": "\\n"
},
{
"name": "Ignore errors",
"type": "boolean",
"value": false
}
];
}
/**
* Fork operation.
*
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @returns {Object} The updated state of the recipe.
*/
async run(state) {
const opList = state.opList,
inputType = opList[state.progress].inputType,
outputType = opList[state.progress].outputType,
input = await state.dish.get(inputType),
ings = opList[state.progress].ingValues,
splitDelim = ings[0],
mergeDelim = ings[1],
ignoreErrors = ings[2],
subOpList = [];
let inputs = [],
i;
if (input)
inputs = input.split(splitDelim);
// Create subOpList for each tranche to operate on
// (all remaining operations unless we encounter a Merge)
for (i = state.progress + 1; i < opList.length; i++) {
if (opList[i].name === "Merge" && !opList[i].disabled) {
break;
} else {
subOpList.push(opList[i]);
}
}
const recipe = new Recipe();
let output = "",
progress = 0;
state.forkOffset += state.progress + 1;
recipe.addOperations(subOpList);
// Take a deep(ish) copy of the ingredient values
const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));
// Run recipe over each tranche
for (i = 0; i < inputs.length; i++) {
// Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
});
const dish = new Dish();
dish.set(inputs[i], inputType);
try {
progress = await recipe.execute(dish, 0, state);
} catch (err) {
if (!ignoreErrors) {
throw err;
}
progress = err.progress + 1;
}
output += await dish.get(outputType) + mergeDelim;
}
state.dish.set(output, outputType);
state.progress += progress;
return state;
}
}
export default Fork;

View file

@ -50,13 +50,11 @@ class Jump extends Operation {
const jmpIndex = getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
return state;
}

View file

@ -0,0 +1,50 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Label operation
*/
class Label extends Operation {
/**
* Label constructor
*/
constructor() {
super();
this.name = "Label";
this.flowControl = true;
this.module = "Default";
this.description = "Provides a location for conditional and fixed jumps to redirect execution to.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Name",
"type": "shortString",
"value": ""
}
];
}
/**
* Label operation. For use with Jump and Conditional Jump
*
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @returns {Object} The updated state of the recipe.
*/
run(state) {
return state;
}
}
export default Label;

View file

@ -0,0 +1,45 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Return operation
*/
class Return extends Operation {
/**
* Return constructor
*/
constructor() {
super();
this.name = "Return";
this.flowControl = true;
this.module = "Default";
this.description = "End execution of operations at this point in the recipe.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* Return operation.
*
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @returns {Object} The updated state of the recipe.
*/
run(state) {
state.progress = state.opList.length;
return state;
}
}
export default Return;

View file

@ -227,7 +227,8 @@ const FlowControl = {
}
if (regexStr !== "") {
const strMatch = await dish.get(Dish.STRING).search(regexStr) > -1;
const str = await dish.get(Dish.STRING)
const strMatch = str.search(regexStr) > -1;
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;