Creating the Code Generator

So far we have constructed a meta-model for our electrical-circuit domain. It enabled us to create models that resembles circuits. Up to this point though there is no actual meaning to the circuits. As humans we can infer certain properties from these circuits, but the models themselves don’t impose any particular interpretation. The meta-model infers the structural semantics, but no behavioural semantics.

There are many potential interpretations and interpreters of our circuit models. In this tutorial we will focus on generating Modelica code that can be used to simulate the dynamic behavior of the circuit in question.

How to Implement the Interpreter?

In webgme the typical extension point for writing interpreters are plugins. The plugin framework and API are designed to enable both server- and browser-side execution of the same code. At the point where a plugin is executed it will have access to the context it was invoked and through various webgme APIs; Core, Project, BlobClient etc.

In this tutorial we will create two plugins;

  • ModelicaCodeGenerator - Responsible for traversing model hierarchy and extract the data needed for generating Modelica code corresponding to the circuit being interpreted.
  • SimulateModelica - Responsible for invoking a Modelica tool that can simulate the dynamical behavior of the circuits. This plugin will also be responsible for communicating back the results to the users.

There a couple of reasons why this is a favourable division. To generate the Modelica code there is no restrictions on where the plugin is executed. The server does not have to have any 3rd party dependencies installed (or have any connected workers with available resources) and the plugin can even run in the browser. For some deployments restricting the set of features to only generate the Modelica code might be favorable. When it comes to writing tests it is typically also easier to divide functionality into separate implementations.

We will start with the ModelicaCodeGenerator here and continue with the SimulateModelica in the Integrating Analysis Tools section.

Generating a Plugin Template

To get a quick start we use the webgme-cli tool in order to create a new plugin. Navigate to the root of the repository created at the beginning of this tutorial and invoke either:

webgme new plugin ModelicaCodeGenerator

or for a Python plugin:

webgme new plugin ModelicaCodeGenerator --language Python

This should generate a range of new files. In both cases these files will be added:

src/plugins/ModelicaCodeGenerator/metadata.json
This json-structure contains information about the plugin and is used by the GUI and plugin-framework. Details about what goes in here is explained in the wikipages.
src/plugins/ModelicaCodeGenerator/ModelicaCodeGenerator.js
This is the code of the plugin itself. The very first lines show the dependencies needed for this code to run and is using requirejs hence the syntax define(['path'], function (Module){ ... return ModelicaCodeGenerator;});. The last return statement is the module that this file defines when required by another module for instance the webgme framework must be able to load our plugin. (If you generated a Python plugin - this will still be entry point for the webgme framework.)
test/plugins/ModelicaCodeGenerator/ModelicaCodeGenerator.spec.js
This is the outline of a mocha test suite for the plugin and shows how to build up a test context and invoke a plugin from a unit-test.

You might also have noticed that the config/config.webgme.js was modified… In order for the webgme plugin framework to find our plugin the path to it is added to the configuration file. Note that both config.default.js and config.test.js load and reuse the added configuration parameters from this file.

Python Plugin

If you generated a python plugin, three python files are generated in addition to the files above:

src/plugins/ModelicaCodeGenerator/run_plugin.py
This script is called by the plugin-wrapper, ModelicaCodeGenerator.js, which passes down the plugin context via arguments.
src/plugins/ModelicaCodeGenerator/run_debug.py
This file can be used as the entry point when debugging the python portion of the plugin.
src/plugins/ModelicaCodeGenerator/ModelicaCodeGenerator/__init__.py
This is where the implementation of the plugin logic goes. The ModelicaCodeGenerator-class is imported and used from both run_plugin.py and run_debug.py.

It’s important to note that a Python plugin can only run on the server (and not inside the client’s browsers). In your ./config/config.default.js add the following line (right before the module.exports = config; statement:

config.plugin.allowServerExecution = true;

For an overview of how the python bindings is connected, go to the webgme-bindings repository.

The Python API is generated in snake_case rather than camelCase and is completely synchronous so there’s no need to deal with callbacks or promises. When following this tutorial keep this in mind and more importantly use the load* equivalents to get*path, e.g. loadChildren-vs-getChildrenPaths and loadPointer-vs-getPointerPath.

First cut on Plugin

The video below shows how to generate the new plugin and modify it so we have a map of all the nodes in the subtree of the activeNode. The activeNode is the invocation point of a plugin and in the next sub-section we will register our plugin so it’s invokable at Circuits. (With the node map it is possible to retrieve nodes without any asynchronous function calls. The asynchronous API functions in webgme do return promises which typically makes the asynchronous functions a bit easier to deal with.)

Click here to open the video in a browser..


Registering the Plugin at Circuits

The generated plugin is available from the browser and the server, however in order to present it to the user on the GUI, we must register it at the appropriate nodes. In our case we want the ModelicaCodeGenerator to be invoked from nodes of meta-type Circuit so we edit the value at the meta-node and the registered value will propagate down the inheritance chain to all Circuits.

This video shows how we register the plugin and how we can enable the gme-logger for the ModelicaCodeGenerator in the browser. (Note that after updating the localStorage the page must be refreshed. The page must also be refreshed each time we update the plugin code on the server.)

Click here to open the video in a browser..


Querying the Model

At this point we have the context setup up for our plugin. The activeNode for the plugin will be a Circuit and all nodes in the sub-tree are pre-loaded in a map where keys are the path (a unique id) to the nodes and values are the node objects.

To extract data from the model we will be using the Core-API, and it’s highly recommended to read the section on how to use the API at the link.

Before we start extracting the necessary data from the model we need to pin down what we need from our models in order to, in this case, generate Modelica code. The figure below shows the mapping from the Circuit to Modelica code and the related Core-API calls. (For simplicity we will leave out the Modelica parameters and use the default values from MSL.)

../_images/map_to_modelica.png

Mapping from a Circuit in our webgme model to Modelica code

We will break up the task into two parts; 1) Extracting the data and 2) Generating the modelica code. The high-level outline of the first part is shown below in the code block where all the extracted data will be stored in serializable JavaScript object.

ExtractName(activeNode)
for all (Node child in activeNode) do
    if (child.metaType is Component) then
        ExtractNameAndModelicaURI(child)
    else if (child.metaType is Connection) then
        [srcNode, dstNode] = GetConnectedNodes(child)
        srcParent = GetParentNode(srcNode)
        dstParent = GetParentNode(dstNode)
        ExtractNames([srcNode, dstNode, srcParent, dstParent])
    end if
end for

The first video shows how to iterate over the children of the Circuit and check their meta-type. Important concepts here are; self.META property of the plugin which is a mapping from the name of a meta-node to the actual core-node, and the helper-method isMetaTypeOf which checks if the first node is of type of the second node.

Click here to open the video in a browser..


Next we need to implement the extraction of the data we need in order to generate the Modelica code. For this we will implement two helper functions that populates the modelJson.components and modelJson.connections array respectively. At the end we will serialize the data using the standard built-in JSON.stringify function.

Click here to open the video in a browser..


Generating the Code

In the previous section we extracted the data needed to generate the Modelica code in an easily accessible format…

{
  "name": "aCircuit",
  "components": [
    {
      "URI": "Modelica.Electrical.Analog.Basic.Resistor",
      "name": "Resistor2",
      "parameters": {}
    },
    ...
  ],
  "connections": [
    {
      "src": "Resistor2.n",
      "dst": "Capacitor.n"
    },
    ...
  ]
}

To generate the actual code we can use a templating engine such as ejs (an example of how to use this in webgme can be found here). There are also other more fitting templating engines available in JavaScript. For simplicity here, we will make a simple string concatenation to generate the Modelica code.

Click here to open the video in a browser..


Uploading the Generated File

After generating the modelica file we would like to make it available for the user to download. Webgme provides a storage for files through the blob-storage.

In the video below we will show how to upload the generated file onto the storage and how to provide a download link to the invoker of the plugin.

Click here to open the video in a browser..