2017-03-23 17:52:20 +00:00
import Recipe from "./Recipe.js" ;
import Dish from "./Dish.js" ;
2018-01-14 16:07:39 +00:00
import Magic from "./lib/Magic.js" ;
2018-01-22 23:34:24 +00:00
import Utils from "./Utils.js" ;
2017-03-21 22:41:44 +00:00
2016-11-28 10:42:58 +00:00
/**
* Flow Control operations.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @namespace
*/
2017-03-23 17:52:20 +00:00
const FlowControl = {
2016-11-28 10:42:58 +00:00
/**
* 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.
2017-01-31 18:24:56 +00:00
* @param {Operation[]} state.opList - The list of operations in the recipe.
2016-11-28 10:42:58 +00:00
* @returns {Object} The updated state of the recipe.
*/
2017-04-21 17:48:42 -04:00
runFork : async function ( state ) {
2017-04-13 18:08:50 +01:00
let opList = state . opList ,
2017-01-31 18:24:56 +00:00
inputType = opList [ state . progress ]. inputType ,
outputType = opList [ state . progress ]. outputType ,
input = state . dish . get ( inputType ),
ings = opList [ state . progress ]. getIngValues (),
splitDelim = ings [ 0 ],
mergeDelim = ings [ 1 ],
ignoreErrors = ings [ 2 ],
subOpList = [],
2017-04-13 18:31:26 +01:00
inputs = [],
i ;
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
if ( input )
2017-01-31 18:24:56 +00:00
inputs = input . split ( splitDelim );
2017-02-09 15:09:33 +00:00
2017-01-31 18:24:56 +00:00
// Create subOpList for each tranche to operate on
2016-11-28 10:42:58 +00:00
// (all remaining operations unless we encounter a Merge)
2017-04-13 18:31:26 +01:00
for ( i = state . progress + 1 ; i < opList . length ; i ++ ) {
2017-01-31 18:24:56 +00:00
if ( opList [ i ]. name === "Merge" && ! opList [ i ]. isDisabled ()) {
2016-11-28 10:42:58 +00:00
break ;
} else {
2017-01-31 18:24:56 +00:00
subOpList . push ( opList [ i ]);
2016-11-28 10:42:58 +00:00
}
}
2017-02-09 15:09:33 +00:00
2017-04-13 18:08:50 +01:00
let recipe = new Recipe (),
2016-11-28 10:42:58 +00:00
output = "" ,
2017-01-16 15:58:38 +00:00
progress = 0 ;
2017-02-09 15:09:33 +00:00
2017-01-31 18:24:56 +00:00
recipe . addOperations ( subOpList );
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
// Run recipe over each tranche
for ( i = 0 ; i < inputs . length ; i ++ ) {
2017-12-29 17:32:23 +00:00
log . debug ( `Entering tranche ${ i + 1 } of ${ inputs . length } ` );
2017-04-13 18:08:50 +01:00
const dish = new Dish ( inputs [ i ], inputType );
2017-01-16 15:58:38 +00:00
try {
2017-04-21 17:48:42 -04:00
progress = await recipe . execute ( dish , 0 );
2017-02-09 15:09:33 +00:00
} catch ( err ) {
2017-01-31 18:24:56 +00:00
if ( ! ignoreErrors ) {
2017-01-16 15:58:38 +00:00
throw err ;
}
progress = err . progress + 1 ;
}
2017-01-31 18:24:56 +00:00
output += dish . get ( outputType ) + mergeDelim ;
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2017-01-31 18:24:56 +00:00
state . dish . set ( output , outputType );
2016-11-28 10:42:58 +00:00
state . progress += progress ;
return state ;
},
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
/**
* Merge 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.
2017-01-31 18:24:56 +00:00
* @param {Operation[]} state.opList - The list of operations in the recipe.
2016-11-28 10:42:58 +00:00
* @returns {Object} The updated state of the recipe.
*/
2017-01-31 18:24:56 +00:00
runMerge : function ( state ) {
2016-11-28 10:42:58 +00:00
// No need to actually do anything here. The fork operation will
// merge when it sees this operation.
return state ;
},
2017-02-09 15:09:33 +00:00
2017-09-28 16:27:39 +00:00
/**
* 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 );
2017-09-28 17:35:52 +00:00
if ( ! registers ) return state ;
if ( ENVIRONMENT_IS_WORKER ()) {
2017-09-28 18:39:35 +00:00
self . setRegisters ( state . progress , state . numRegisters , registers . slice ( 1 ));
2017-09-28 17:35:52 +00:00
}
2017-09-28 16:27:39 +00:00
/**
* 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
2017-09-28 19:24:28 +00:00
return str . replace ( /(\\*)\$R(\d{1,2})/g , ( match , slashes , regNum ) => {
2017-09-28 16:27:39 +00:00
const index = parseInt ( regNum , 10 ) + 1 ;
2017-09-28 19:24:28 +00:00
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 ];
});
2017-09-28 16:27:39 +00:00
}
// Step through all subsequent ops and replace registers in args with extracted content
for ( let i = state . progress + 1 ; i < state . opList . length ; i ++ ) {
2017-09-28 18:39:35 +00:00
if ( state . opList [ i ]. isDisabled ()) continue ;
2017-09-28 16:27:39 +00:00
let args = state . opList [ i ]. getIngValues ();
args = args . map ( arg => {
if ( typeof arg !== "string" && typeof arg !== "object" ) return arg ;
2017-09-28 18:39:35 +00:00
if ( typeof arg === "object" && arg . hasOwnProperty ( "string" )) {
2017-09-28 16:27:39 +00:00
arg . string = replaceRegister ( arg . string );
return arg ;
}
return replaceRegister ( arg );
});
state . opList [ i ]. setIngValues ( args );
}
2017-09-28 18:39:35 +00:00
state . numRegisters += registers . length - 1 ;
2017-09-28 16:27:39 +00:00
return state ;
},
2016-11-28 10:42:58 +00:00
/**
* 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.
2017-01-31 18:24:56 +00:00
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @param {number} state.numJumps - The number of jumps taken so far.
2016-11-28 10:42:58 +00:00
* @returns {Object} The updated state of the recipe.
*/
2017-01-31 18:24:56 +00:00
runJump : function ( state ) {
2018-01-22 22:06:26 +00:00
const ings = state . opList [ state . progress ]. getIngValues (),
2017-12-29 17:32:23 +00:00
label = ings [ 0 ],
maxJumps = ings [ 1 ],
jmpIndex = FlowControl . _getLabelIndex ( label , state );
2017-02-09 15:09:33 +00:00
2017-11-24 10:31:26 -08:00
if ( state . numJumps >= maxJumps || jmpIndex === - 1 ) {
2017-12-29 17:32:23 +00:00
log . debug ( "Maximum jumps reached or label cannot be found" );
2017-01-16 16:00:44 +00:00
return state ;
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2017-11-24 10:12:08 -08:00
state . progress = jmpIndex ;
2017-01-31 18:24:56 +00:00
state . numJumps ++ ;
2017-12-29 17:32:23 +00:00
log . debug ( `Jumping to label ' ${ label } ' at position ${ jmpIndex } (jumps = ${ state . numJumps } )` );
2016-11-28 10:42:58 +00:00
return state ;
},
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
/**
* 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.
2017-01-31 18:24:56 +00:00
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @param {number} state.numJumps - The number of jumps taken so far.
2016-11-28 10:42:58 +00:00
* @returns {Object} The updated state of the recipe.
*/
2017-01-31 18:24:56 +00:00
runCondJump : function ( state ) {
2018-01-22 22:06:26 +00:00
const ings = state . opList [ state . progress ]. getIngValues (),
2017-01-31 18:24:56 +00:00
dish = state . dish ,
regexStr = ings [ 0 ],
2017-11-24 05:48:40 -08:00
invert = ings [ 1 ],
2017-12-29 17:32:23 +00:00
label = ings [ 2 ],
maxJumps = ings [ 3 ],
jmpIndex = FlowControl . _getLabelIndex ( label , state );
2017-02-09 15:09:33 +00:00
2017-11-24 10:31:26 -08:00
if ( state . numJumps >= maxJumps || jmpIndex === - 1 ) {
2017-12-29 17:32:23 +00:00
log . debug ( "Maximum jumps reached or label cannot be found" );
2017-01-16 16:00:44 +00:00
return state ;
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2017-11-24 05:48:40 -08:00
if ( regexStr !== "" ) {
let strMatch = dish . get ( Dish . STRING ). search ( regexStr ) > - 1 ;
if ( ! invert && strMatch || invert && ! strMatch ) {
2017-11-24 10:12:08 -08:00
state . progress = jmpIndex ;
2017-11-24 05:48:40 -08:00
state . numJumps ++ ;
2017-12-29 17:32:23 +00:00
log . debug ( `Jumping to label ' ${ label } ' at position ${ jmpIndex } (jumps = ${ state . numJumps } )` );
2017-11-24 10:12:08 -08:00
}
2016-11-28 10:42:58 +00:00
}
2017-02-09 15:09:33 +00:00
2016-11-28 10:42:58 +00:00
return state ;
},
2017-02-09 15:09:33 +00:00
2017-11-24 10:12:08 -08:00
2016-11-28 10:42:58 +00:00
/**
* 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.
2017-01-31 18:24:56 +00:00
* @param {Operation[]} state.opList - The list of operations in the recipe.
2016-11-28 10:42:58 +00:00
* @returns {Object} The updated state of the recipe.
*/
2017-01-31 18:24:56 +00:00
runReturn : function ( state ) {
state . progress = state . opList . length ;
2016-11-28 10:42:58 +00:00
return state ;
},
2017-02-09 15:09:33 +00:00
2017-04-27 13:05:29 +00:00
/**
* Comment operation.
2017-04-27 13:12:45 +00:00
*
2017-04-27 13:05:29 +00:00
* @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.
*/
runComment : function ( state ) {
return state ;
},
2017-12-19 13:18:25 +00:00
2018-01-14 16:07:39 +00:00
/**
* Magic 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.
*/
2018-01-22 22:06:26 +00:00
runMagic : async function ( state ) {
const ings = state . opList [ state . progress ]. getIngValues (),
depth = ings [ 0 ],
2018-02-14 16:08:59 +00:00
intensive = ings [ 1 ],
extLang = ings [ 2 ],
2018-01-22 22:06:26 +00:00
dish = state . dish ,
2018-01-22 23:34:24 +00:00
currentRecipeConfig = state . opList . map ( op => op . getConfig ()),
magic = new Magic ( dish . get ( Dish . ARRAY_BUFFER )),
2018-02-14 16:08:59 +00:00
options = await magic . speculativeExecution ( depth , extLang , intensive );
2018-01-22 23:34:24 +00:00
let output = `<table
class='table table-hover table-condensed table-bordered'
style='table-layout: fixed;'>
<tr>
<th>Recipe (click to load)</th>
2018-02-10 15:31:50 +00:00
<th>Result snippet</th>
2018-02-10 15:10:53 +00:00
<th>Properties</th>
2018-01-22 23:34:24 +00:00
</tr>` ;
2018-02-19 17:25:28 +00:00
/**
* Returns a CSS colour value based on an integer input.
*
* @param {number} val
* @returns {string}
*/
function chooseColour ( val ) {
if ( val < 3 ) return "green" ;
if ( val < 5 ) return "goldenrod" ;
return "red" ;
}
2018-01-22 23:34:24 +00:00
options . forEach ( option => {
// Construct recipe URL
// Replace this Magic op with the generated recipe
const recipeConfig = currentRecipeConfig . slice ( 0 , state . progress )
. concat ( option . recipe )
. concat ( currentRecipeConfig . slice ( state . progress + 1 )),
recipeURL = "recipe=" + Utils . encodeURIFragment ( Utils . generatePrettyRecipe ( recipeConfig ));
2018-02-10 15:31:50 +00:00
let language = "" ,
fileType = "" ,
matchingOps = "" ,
2018-02-15 13:39:55 +00:00
useful = "" ,
2018-02-19 17:25:28 +00:00
entropy = `<span data-toggle="tooltip" data-container="body" title="Shannon Entropy is measured from 0 to 8. High entropy suggests encrypted or compressed data. Normal text is usually around 3.5 to 5.">Entropy: <span style="color: ${ chooseColour ( option . entropy ) } "> ${ option . entropy . toFixed ( 2 ) } </span></span>` ,
validUTF8 = option . isUTF8 ? "<span data-toggle='tooltip' data-container='body' title='The data could be a valid UTF8 string based on its encoding.'>Valid UTF8</span>\n" : "" ;
2018-02-10 15:10:53 +00:00
2018-02-15 13:39:55 +00:00
if ( option . languageScores [ 0 ]. probability > 0 ) {
let likelyLangs = option . languageScores . filter ( l => l . probability > 0 );
2018-02-10 17:53:59 +00:00
if ( likelyLangs . length < 1 ) likelyLangs = [ option . languageScores [ 0 ]];
2018-02-19 17:25:28 +00:00
language = "<span data-toggle='tooltip' data-container='body' title='Based on a statistical comparison of the frequency of bytes in various languages. Ordered by likelihood.'>" +
"Possible languages:\n " + likelyLangs . map ( lang => {
return Magic . codeToLanguage ( lang . lang );
}). join ( "\n " ) + "</span>\n" ;
2018-02-10 15:10:53 +00:00
}
2018-01-22 23:34:24 +00:00
if ( option . fileType ) {
2018-02-19 17:25:28 +00:00
fileType = `<span data-toggle="tooltip" data-container="body" title="Based on the presence of magic bytes.">File type: ${ option . fileType . mime } ( ${ option . fileType . ext } )</span>\n` ;
2018-02-10 15:31:50 +00:00
}
if ( option . matchingOps . length ) {
matchingOps = `Matching ops: ${ [... new Set ( option . matchingOps . map ( op => op . op ))]. join ( ", " ) } \n` ;
2018-01-22 23:34:24 +00:00
}
2018-01-14 16:07:39 +00:00
2018-02-15 13:39:55 +00:00
if ( option . useful ) {
2018-02-19 17:25:28 +00:00
useful = "<span data-toggle='tooltip' data-container='body' title='This could be an operation that displays data in a useful way, such as rendering an image.'>Useful op detected</span>\n" ;
2018-02-15 13:39:55 +00:00
}
2018-01-22 23:34:24 +00:00
output += `<tr>
<td><a href="# ${ recipeURL } "> ${ Utils . generatePrettyRecipe ( option . recipe , true ) } </a></td>
2018-01-23 01:15:13 +00:00
<td> ${ Utils . escapeHtml ( Utils . printable ( Utils . truncate ( option . data , 99 ))) } </td>
2018-02-19 17:25:28 +00:00
<td> ${ language }${ fileType }${ matchingOps }${ useful }${ validUTF8 }${ entropy } </td>
2018-01-22 23:34:24 +00:00
</tr>` ;
});
2018-01-14 16:07:39 +00:00
2018-02-19 17:25:28 +00:00
output += "</table><script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>" ;
2018-02-14 16:08:59 +00:00
if ( ! options . length ) {
output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?" ;
}
2018-01-22 23:34:24 +00:00
dish . set ( output , Dish . HTML );
2018-01-14 16:07:39 +00:00
return state ;
},
2017-12-19 13:18:25 +00:00
/**
* Returns the index of a label.
*
2017-12-29 17:32:23 +00:00
* @private
2017-12-19 13:18:25 +00:00
* @param {Object} state
* @param {string} name
* @returns {number}
*/
_getLabelIndex : function ( name , state ) {
for ( let o = 0 ; o < state . opList . length ; o ++ ) {
let operation = state . opList [ o ];
if ( operation . name === "Label" ){
let ings = operation . getIngValues ();
if ( name === ings [ 0 ]) {
return o ;
}
}
}
return - 1 ;
},
2016-11-28 10:42:58 +00:00
};
2017-03-23 17:52:20 +00:00
export default FlowControl ;