diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index d3f0c032..b095cbc8 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -92,7 +92,7 @@ async function bake(data) { } catch (err) { self.postMessage({ action: "bakeError", - data: err.message + data: err.message.split(":").slice(1).join(":").slice(1) // Cut off worker blurb }); } } @@ -176,3 +176,22 @@ self.setOption = function(option, value) { } }); }; + + +/** + * Send register values back to the app. + * + * @param {number} opIndex + * @param {number} numPrevRegisters + * @param {string[]} registers + */ +self.setRegisters = function(opIndex, numPrevRegisters, registers) { + self.postMessage({ + action: "setRegisters", + data: { + opIndex: opIndex, + numPrevRegisters: numPrevRegisters, + registers: registers + } + }); +}; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index 9e01ef0a..4a94ffdf 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -90,6 +90,74 @@ const FlowControl = { }, + /** + * Register 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. + */ + runRegister: function(state) { + const ings = state.opList[state.progress].getIngValues(), + extractorStr = ings[0], + i = ings[1], + m = ings[2]; + + let modifiers = ""; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + + const extractor = new RegExp(extractorStr, modifiers), + input = state.dish.get(Dish.STRING), + registers = input.match(extractor); + + if (!registers) return state; + + if (ENVIRONMENT_IS_WORKER()) { + self.setRegisters(state.progress, state.numRegisters, registers.slice(1)); + } + + /** + * Replaces references to registers (e.g. $R0) with the contents of those registers. + * + * @param {string} str + * @returns {string} + */ + function replaceRegister(str) { + // Replace references to registers ($Rn) with contents of registers + return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { + const index = parseInt(regNum, 10) + 1; + if (index <= state.numRegisters || index >= state.numRegisters + registers.length) + return match; + if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape + return slashes + registers[index - state.numRegisters]; + }); + } + + // Step through all subsequent ops and replace registers in args with extracted content + for (let i = state.progress + 1; i < state.opList.length; i++) { + if (state.opList[i].isDisabled()) continue; + + let args = state.opList[i].getIngValues(); + args = args.map(arg => { + if (typeof arg !== "string" && typeof arg !== "object") return arg; + + if (typeof arg === "object" && arg.hasOwnProperty("string")) { + arg.string = replaceRegister(arg.string); + return arg; + } + return replaceRegister(arg); + }); + state.opList[i].setIngValues(args); + } + + state.numRegisters += registers.length - 1; + return state; + }, + + /** * Jump operation. * diff --git a/src/core/Recipe.js b/src/core/Recipe.js index d703ac1e..fdd06943 100755 --- a/src/core/Recipe.js +++ b/src/core/Recipe.js @@ -145,7 +145,7 @@ Recipe.prototype.lastOpIndex = function(startIndex) { */ Recipe.prototype.execute = async function(dish, startFrom) { startFrom = startFrom || 0; - let op, input, output, numJumps = 0; + let op, input, output, numJumps = 0, numRegisters = 0; for (let i = startFrom; i < this.opList.length; i++) { op = this.opList[i]; @@ -162,15 +162,17 @@ Recipe.prototype.execute = async function(dish, startFrom) { if (op.isFlowControl()) { // Package up the current state let state = { - "progress": i, - "dish": dish, - "opList": this.opList, - "numJumps": numJumps + "progress": i, + "dish": dish, + "opList": this.opList, + "numJumps": numJumps, + "numRegisters": numRegisters }; state = await op.run(state); i = state.progress; numJumps = state.numJumps; + numRegisters = state.numRegisters; } else { output = await op.run(input, op.getIngValues()); dish.set(output, op.outputType); diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js index d1ed8132..234f6919 100755 --- a/src/core/config/Categories.js +++ b/src/core/config/Categories.js @@ -317,6 +317,7 @@ const Categories = [ ops: [ "Fork", "Merge", + "Register", "Jump", "Conditional Jump", "Return", diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index d302abee..8f24e5f2 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -116,6 +116,30 @@ const OperationConfig = { flowControl: true, args: [] }, + "Register": { + module: "Default", + description: "Extract data from the input and store it in registers which can then be passed into subsequent operations as arguments. Regular expression capture groups are used to select the data to extract.

To use registers in arguments, refer to them using the notation $Rn where n is the register number, starting at 0.

For example:
Input: Test
Extractor: (.*)
Argument: $R0 becomes Test

Registers can be escaped in arguments using a backslash. e.g. \\$R0 would become $R0 rather than Test.", + inputType: "string", + outputType: "string", + flowControl: true, + args: [ + { + name: "Extractor", + type: "binaryString", + value: "([\\s\\S]*)" + }, + { + name: "Case insensitive", + type: "boolean", + value: true + }, + { + name: "Multiline matching", + type: "boolean", + value: false + }, + ] + }, "Jump": { module: "Default", description: "Jump forwards or backwards over the specified number of operations.", diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index 9af165d0..6e51367b 100644 --- a/src/core/config/modules/Default.js +++ b/src/core/config/modules/Default.js @@ -154,6 +154,7 @@ OpModules.Default = { "Generate HOTP": OTP.runHOTP, "Fork": FlowControl.runFork, "Merge": FlowControl.runMerge, + "Register": FlowControl.runRegister, "Jump": FlowControl.runJump, "Conditional Jump": FlowControl.runCondJump, "Return": FlowControl.runReturn, diff --git a/src/web/RecipeWaiter.js b/src/web/RecipeWaiter.js index 121b64d2..3400fa32 100755 --- a/src/web/RecipeWaiter.js +++ b/src/web/RecipeWaiter.js @@ -1,5 +1,6 @@ import HTMLOperation from "./HTMLOperation.js"; import Sortable from "sortablejs"; +import Utils from "../core/Utils.js"; /** @@ -435,4 +436,30 @@ RecipeWaiter.prototype.opRemove = function(e) { window.dispatchEvent(this.manager.statechange); }; + +/** + * Sets register values. + * + * @param {number} opIndex + * @param {number} numPrevRegisters + * @param {string[]} registers + */ +RecipeWaiter.prototype.setRegisters = function(opIndex, numPrevRegisters, registers) { + const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`), + prevRegList = op.querySelector(".register-list"); + + // Remove previous div + if (prevRegList) prevRegList.remove(); + + let registerList = []; + for (let i = 0; i < registers.length; i++) { + registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); + } + const registerListEl = `
+ ${registerList.join("
")} +
`; + + op.insertAdjacentHTML("beforeend", registerListEl); +}; + export default RecipeWaiter; diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js index 362ab128..6fc69e40 100644 --- a/src/web/WorkerWaiter.js +++ b/src/web/WorkerWaiter.js @@ -61,6 +61,9 @@ WorkerWaiter.prototype.handleChefMessage = function(e) { case "optionUpdate": this.app.options[r.data.option] = r.data.value; break; + case "setRegisters": + this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers); + break; case "highlightsCalculated": this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); break; diff --git a/src/web/stylesheets/components/_operation.css b/src/web/stylesheets/components/_operation.css index 93bc0ddf..d596ed05 100644 --- a/src/web/stylesheets/components/_operation.css +++ b/src/web/stylesheets/components/_operation.css @@ -124,6 +124,12 @@ button.dropdown-toggle { background-color: var(--secondary-background-colour); } +.register-list { + background-color: var(--fc-operation-border-colour); + font-family: var(--fixed-width-font-family); + padding: 10px; +} + .op-icon { float: right; margin-left: 10px; @@ -195,3 +201,13 @@ button.dropdown-toggle { background-color: var(--disabled-bg-colour) !important; border-color: var(--disabled-border-colour) !important; } + +.break .register-list { + color: var(--fc-breakpoint-operation-font-colour) !important; + background-color: var(--fc-breakpoint-operation-border-colour) !important; +} + +.disabled .register-list { + color: var(--disabled-font-colour) !important; + background-color: var(--disabled-border-colour) !important; +}