From b86dceb3c6a01d168f3062fd9be0811379296bb0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Sep 2017 16:27:39 +0000 Subject: [PATCH 1/5] Added Register operation --- src/core/FlowControl.js | 60 ++++++++++++++++++++++++++++++ src/core/config/Categories.js | 1 + src/core/config/OperationConfig.js | 24 ++++++++++++ src/core/config/modules/Default.js | 1 + 4 files changed, 86 insertions(+) diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index 9e01ef0a..aba64ab1 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -90,6 +90,66 @@ 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); + + /** + * 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 + str = str.replace(/((?:^|[^\\])(?:\\.|[^\\])*?)\$R(\d{1,2})/g, (match, pre, regNum) => { + const index = parseInt(regNum, 10) + 1; + return (index >= registers.length) ? match : pre + registers[index]; + }); + + // Unescape remaining register references + return str.replace(/\\\$R(\d{1,2})/, "$R$1"); + } + + // Step through all subsequent ops and replace registers in args with extracted content + for (let i = state.progress + 1; i < state.opList.length; i++) { + let args = state.opList[i].getIngValues(); + args = args.map(arg => { + if (typeof arg !== "string" && typeof arg !== "object") return arg; + + if (typeof arg === "object" && arg.string) { + arg.string = replaceRegister(arg.string); + return arg; + } + + return replaceRegister(arg); + }); + state.opList[i].setIngValues(args); + } + + return state; + }, + + /** * Jump operation. * 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, From e2ac2971021b173890e62abd440714236493595e Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Sep 2017 17:35:52 +0000 Subject: [PATCH 2/5] Register values now displayed in the recipe --- src/core/ChefWorker.js | 17 ++++++++++++ src/core/FlowControl.js | 6 +++++ src/web/RecipeWaiter.js | 26 +++++++++++++++++++ src/web/WorkerWaiter.js | 3 +++ src/web/stylesheets/components/_operation.css | 6 +++++ 5 files changed, 58 insertions(+) diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index d3f0c032..d29a3cae 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -176,3 +176,20 @@ self.setOption = function(option, value) { } }); }; + + +/** + * Send register values back to the app. + * + * @param {number} opIndex + * @param {string[]} registers + */ +self.setRegisters = function(opIndex, registers) { + self.postMessage({ + action: "setRegisters", + data: { + opIndex: opIndex, + registers: registers + } + }); +}; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index aba64ab1..6cc2b5d4 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -113,6 +113,12 @@ const FlowControl = { input = state.dish.get(Dish.STRING), registers = input.match(extractor); + if (!registers) return state; + + if (ENVIRONMENT_IS_WORKER()) { + self.setRegisters(state.progress, registers.slice(1)); + } + /** * Replaces references to registers (e.g. $R0) with the contents of those registers. * diff --git a/src/web/RecipeWaiter.js b/src/web/RecipeWaiter.js index 121b64d2..ac6bdfd9 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,29 @@ RecipeWaiter.prototype.opRemove = function(e) { window.dispatchEvent(this.manager.statechange); }; + +/** + * Sets register values. + * + * @param {number} opIndex + * @param {string[]} registers + */ +RecipeWaiter.prototype.setRegisters = function(opIndex, 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${i} = ${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..e8c0f1ce 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.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..c3a80a59 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; From 877ab57f0a22d8efc7a9c29b3d98aee0b0aa2f05 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Sep 2017 18:39:35 +0000 Subject: [PATCH 3/5] Multiple Register operations can now be called in a single recipe --- src/core/ChefWorker.js | 6 ++++-- src/core/FlowControl.js | 17 ++++++++++------- src/core/Recipe.js | 12 +++++++----- src/web/RecipeWaiter.js | 7 ++++--- src/web/WorkerWaiter.js | 2 +- src/web/stylesheets/components/_operation.css | 10 ++++++++++ 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index d29a3cae..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 }); } } @@ -182,13 +182,15 @@ 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, 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 6cc2b5d4..aa4082e5 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -116,7 +116,7 @@ const FlowControl = { if (!registers) return state; if (ENVIRONMENT_IS_WORKER()) { - self.setRegisters(state.progress, registers.slice(1)); + self.setRegisters(state.progress, state.numRegisters, registers.slice(1)); } /** @@ -127,31 +127,34 @@ const FlowControl = { */ function replaceRegister(str) { // Replace references to registers ($Rn) with contents of registers - str = str.replace(/((?:^|[^\\])(?:\\.|[^\\])*?)\$R(\d{1,2})/g, (match, pre, regNum) => { + str = str ? str.replace(/((?:^|[^\\])(?:\\.|[^\\])*?)\$R(\d{1,2})/g, (match, pre, regNum) => { const index = parseInt(regNum, 10) + 1; - return (index >= registers.length) ? match : pre + registers[index]; - }); + return (index <= state.numRegisters || index >= state.numRegisters + registers.length) ? + match : pre + registers[index - state.numRegisters]; + }) : str; // Unescape remaining register references - return str.replace(/\\\$R(\d{1,2})/, "$R$1"); + return str ? str.replace(/\\\$R(\d{1,2})/, "$R$1") : str; } // 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.string) { + 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; }, 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/web/RecipeWaiter.js b/src/web/RecipeWaiter.js index ac6bdfd9..8e64eb4b 100755 --- a/src/web/RecipeWaiter.js +++ b/src/web/RecipeWaiter.js @@ -441,18 +441,19 @@ RecipeWaiter.prototype.opRemove = function(e) { * Sets register values. * * @param {number} opIndex + * @param {number} numPrevRegisters * @param {string[]} registers */ -RecipeWaiter.prototype.setRegisters = function(opIndex, 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(); - +window.Utils = Utils; let registerList = []; for (let i = 0; i < registers.length; i++) { - registerList.push(`$R${i} = ${Utils.truncate(Utils.printable(registers[i]), 100)}`); + registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); } const registerListEl = `
${registerList.join("
")} diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js index e8c0f1ce..6fc69e40 100644 --- a/src/web/WorkerWaiter.js +++ b/src/web/WorkerWaiter.js @@ -62,7 +62,7 @@ WorkerWaiter.prototype.handleChefMessage = function(e) { this.app.options[r.data.option] = r.data.value; break; case "setRegisters": - this.manager.recipe.setRegisters(r.data.opIndex, r.data.registers); + 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); diff --git a/src/web/stylesheets/components/_operation.css b/src/web/stylesheets/components/_operation.css index c3a80a59..d596ed05 100644 --- a/src/web/stylesheets/components/_operation.css +++ b/src/web/stylesheets/components/_operation.css @@ -201,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; +} From ab7cc878c3d4c70845d8ddf9c41dc0a55267f39e Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Sep 2017 18:40:29 +0000 Subject: [PATCH 4/5] Removed debug code --- src/web/RecipeWaiter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/RecipeWaiter.js b/src/web/RecipeWaiter.js index 8e64eb4b..3400fa32 100755 --- a/src/web/RecipeWaiter.js +++ b/src/web/RecipeWaiter.js @@ -450,7 +450,7 @@ RecipeWaiter.prototype.setRegisters = function(opIndex, numPrevRegisters, regist // Remove previous div if (prevRegList) prevRegList.remove(); -window.Utils = Utils; + let registerList = []; for (let i = 0; i < registers.length; i++) { registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); From ee57a92daabdd65dc2530e64f81c0d726158cc37 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Sep 2017 19:24:28 +0000 Subject: [PATCH 5/5] Improved register reference regex --- src/core/FlowControl.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index aa4082e5..4a94ffdf 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -127,14 +127,13 @@ const FlowControl = { */ function replaceRegister(str) { // Replace references to registers ($Rn) with contents of registers - str = str ? str.replace(/((?:^|[^\\])(?:\\.|[^\\])*?)\$R(\d{1,2})/g, (match, pre, regNum) => { + return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { const index = parseInt(regNum, 10) + 1; - return (index <= state.numRegisters || index >= state.numRegisters + registers.length) ? - match : pre + registers[index - state.numRegisters]; - }) : str; - - // Unescape remaining register references - return str ? str.replace(/\\\$R(\d{1,2})/, "$R$1") : str; + 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