-
Notifications
You must be signed in to change notification settings - Fork 1
Native Javascript Compiler Architecture for Pipes
(This page describe future functionality of Pipes, and is currently not available in any Pipes release)
This page describe the upcoming native Pipes Compiler to JavaScript.
The goal of this 'subproject' is achieve high performance code execution, where a Pipes project runs at a (nearly) comparable speed as handcrafted JavaScript. The goal is to make Pipes ready for high performance production use.
The 'Export DHF Custom Module' will get an option to generate native Javascript. If you graph supports native code generation, a checkbox will be enabled which allow the business developer to select JS code generation. If the graph does not support native code generation, the checkbox is disabled (grayed out). See in a next section what supports native code generation and what not.
Graphs can be compiled to native JavaScript only if the graph:
- Has only 1 DHF Input node. E.g. an empty graph cannot be compiled.
- Has only 1 DHF output node.
- Has a path from DHF Input to DHF output node (the input and output node are (in)directly connected. E.g. a new Pipes graph with only a DHF Input node, and output node without connection cannot be compiled.
- All nodes in all paths from Input to Output have implemented a code generation hook (see below). If all nodes have a code generation function, but only 1 node has not, Javascript cannot be generated. The first beta will have support for code generation for some built in blocks.
- Subgraphs are currently in beta 1 not supported
- The graph has no cycle, that is: the output of Node A is used in node B and the output of node B is used in Node A again.
The vppBackendService
POST service will get an extra action
called compileToJavaScript
. The request body will contain the JSON graph.
We need to compile server side, because there we can have access to user defined blocks in user.sjs
.
This service returns a JSON in the form of:
{ sourceCode = "generated multiline JavaScript code.",
errors : [ { errorCode : "ERROR_CODE"
errorDescription : "ErrorDescription" }
...
]
}
Typically if there are errors, sourceCode
will be empty, and errors
will be filled, and vice-versa. This allows the Pipes client to display the errors.
The compilation process is executed as follows:
- First it tries to find the
dhf/input
block asstartState
. If it cannot find this block or it finds multiple blocks compilation is aborted. - Then it tries to find the
dhf/output
block asfinalState
. If it cannot find this block or it finds multiple blocks compilation is aborted. - It builds a graph for flow analysis
- It determines all possible paths from
startState
tofinalState
. - It removes all dead nodes. That are nodes which are unreachable from
startState
tofinalState
- It checks that there is no cycle in the remaining graph. If there is a cycle, compilation is aborted.
- It checks that all blocks support code generation (see below). If one of the blocks does not support code generation, compilation is aborted.
- Based on the flow graph and paths, it determines in which order the blocks need to be executed.
- Based on this order, it generates code for each block. During this process the compiler maintains the context in terms of input and output variables and properties/widgets.
Every block which supports code generation should implement the following function:
<name>.prototype.onCodeGeneration = function(tempVarPrefix,inputVariables,outputVariables,propertiesWidgets)
Here:
- The return value is an array of strings with generated code.
-
tempVarPrefix
is a string which this block need to use as prefix if it generates variables. This ensure the variables within the block are unique. -
inputVariables
are the names of the variables to use as input, in the form of:
{ input0 : "variableName",
input1 : "variableName2",
...
}
-
So, the alternative for
this.getInputData(0)
isinputVariables.input0
. For this construction is chosen so that during code generation you can easily check a input is connnected via"input0" in inputVariables
which will befalse
if input 0 is not connected. -
The alternative for
this.setOutputData(0)
isoutputVariable.output0
-
propertiesWidgets
will contain the value of the properties and widgets in the form of:
{ properties: { widgetKey: "widgetValue", widgetKey2: widgetValue,... },
widgets: { propertyKey: "propertyValue", propertyKey2: "propertyValue2",....}
Here is an example:
stringTemplate.prototype.onCodeGeneration = function(tempVarPrefix,inputVariables,outputVariables,propertiesWidgets) {
let code = [];
let template = propertiesWidgets.widgets.template;
if ( "input0" in inputVariables ) {
template = template.replace("v1", inputVariables.input0);
}
if ( "input1" in inputVariables ) {
template = template.replace("v2", inputVariables.input1);
}
if ("input2" in inputVariables ) {
template = template.replace("v3", inputVariables.input2);
}
if ( "v4" in propertiesWidgets.widgets ) {
code.push("let "+tempVarPrefix+"v4="+propertiesWidgets.widgets.v4+";");
template = template.replace("v4", tempVarPrefix + "v4")
}
if ( "v5" in propertiesWidgets.widgets ) {
code.push("let "+tempVarPrefix+"v5="+propertiesWidgets.widgets.v5+";");
template = template.replace("v4", tempVarPrefix + "v5")
}
code.push("let "+outputVariables.output0+" = eval(`"+template+"`);");
return code;
};
This will produce in our example:
// Start code for string/Templating - nodeId = 14
let var_14_0_newString = eval(`/final/DAMO/${var_17_0_rowNr}.json`);
And this:
xpathBlock.prototype.onCodeGeneration = function(tempVarPrefix,inputVariables,outputVariables,propertiesWidgets) {
let code = [];
let namespaces = propertiesWidgets.widgets.namespaces;
let ns = {};
if ( namespaces && namespaces.trim().length > 0 ) {
const nstokens = namepaces.trim().split(",");
if ( nstokens.length % 2 === 0 ) {
for ( let i = 0 ; i < nstokens.length ; i+=2 ) {
ns[nstokens[i].trim()] = nstokens[i+1].trim();
}
}
}
let nsString = JSON.stringify(ns);
code.push("let "+outputVariables.output0+" = "+inputVariables.input0+"[0].xpath('"+propertiesWidgets.widgets.xpath+"',"+nsString+");");
return code;
};
Will produce:
// Start code for transform/xpath - nodeId = 6
let var_6_0_nodes = var_5_0_instancefeatureMember[0].xpath('fn:name()',{});
Click the video for larger size.