MPS 2023.3 Help


This cookbook should give you quick answers and guidelines when designing dataflow for your languages. For in-depth description of the typesystem please refer to the Dataflow section of the user guide.

Reading a value

The read operation instructs the dataflow engine that a particular value is read:

data flow builder for LocalVariableReference {   (node)->void {     if (node.isVariableDefinedInThisMethod()) {       read node.localVariableDeclaration     }   } }

Writing a value

Similarly the write operation indicates that a value gets written to. In the example, a variable declaration with an initializer first executes the initializer through the code for command and then marks the node as being written the result of the initializer:

data flow builder for LocalVariableDeclaration {   (node)->void {     nop     if (node.initializer.isNotNull) {       code for node.initializer       write node = node.initializer     }   } }

Code for

As seen above in the LocalVariableDeclaration dataflow or below in the DotExpression dataflow, the code for command indicates nodes that get executed and when. In the DotExpression, for example, code for the operand runs before the actual dot operation:

data flow builder for DotExpression {   (node)->void {     code for node.operand     code for node.operation   } }


Dataflow for the TernaryOperatorExpression is a very straightforward example of using both conditional and unconditional jumps. Once the condition gets evaluated we can optionally jump to the ifFalse branch. Similarly, once the ifTrue branch is completed we unconditionally jump out of the scope of the node:

data flow builder for TernaryOperatorExpression {   (node)->void {     code for node.condition     ifjump before node.ifFalse     code for node.ifTrue     jump after node     code for node.ifFalse   } }


The WhileStatement shows a more involved usage of the dataflow language. Not also the built-in detection of boolean constants. Trying to use while(false) will thus be correctly reported by MPS as a while-loop with unreachable body. This is thanks to the unconditional jump to after node if the constant is false.

data flow builder for WhileStatement {   (node)->void {     code for node.condition     if (node.condition.isInstanceOf(BooleanConstant)) {       node<BooleanConstant> constant = node.condition : BooleanConstant;       if (!(constant.value)) {         jump after node       }     } else {       ifjump after node     }     code for node.body     { jump before node }   } }

Inserting instructions

The TryStatement has even more needs from the dataflow language. It must insert extra ifjump instructions to jump to a catch clause wherever the particular exception can be thrown in the code:

data flow builder for TryStatement {   (node)->void {     try       for (node<CatchClause> c : node.catchClause) {         ifjump before c       }       code for node.body       for (instruction instruction : get code for (node.body)) {         if (instruction.isRet || instruction.isJump || instruction.isNop) { continue; }         for (node<CatchClause> catchClause : DataFlowTryCatchUtil.getPossibleCatches(instruction.getSource, node.catchClause)) {           insert ifjump before catchClause after instruction         }         insert ifjump after afterCatches after instruction       }       { jump after afterCatches }       for (node<CatchClause> c : node.catchClause) {         code for c         { jump after afterCatches }       }       label afterCatches     finally       code for node.finallyBody     end   } }

Notice, we're using a few other helper methods and commands here - get code for to retrieve the dataflow instruction set for a node, isRet, isJump and isNop to exclude certain types of instructions (returns, jumps and no-operations respectively), label to create named places in the dataflow instruction set that we can jump to from elsewhere, and finally the insert command to insert a new command into an existing dataflow instruction set.

Last modified: 07 March 2024