Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use a babel middleware to transform more human-readable OS code into execution code #16

Open
kapil1garg opened this issue Apr 23, 2022 · 2 comments
Labels
enhancement New feature or request

Comments

@kapil1garg
Copy link
Contributor

Due to the way the execution environment works and the need for most of the OS language functions to be asynchronous (since they query an external Studio API), writing code for orchestration scripts can be syntactically messy, requiring the user to add async/await flags and this.keywords for any OS functions. For instance, this is what a simple trigger to send a message during a SIG meeting currently looks like:

async function feedbackOpportunity() {
  return await this.during(await this.venue("SIG"));
}

One way to clean this up is to create a custom plugin with Babel that takes code that is more human-readable and workable, and transforms it in a middleware layer before it's saved to the database. The user only ever interacts with the nice to read/work with code, while the backend system gets the representation that's easy for it to execute.

Here's a tester implementation that gets most of the way there (see TODOs in the code for what needs to still be done):

import babel from '@babel/core';
import * as t from "@babel/types";

// define a babel configuration
const babelTransformConfig = {
  plugins: [
    function orchestrationScriptTransformer() {
      return {
        visitor: {
          FunctionDeclaration(path) {
            path.node.async = true;
          },

          VariableDeclaration(path) {
            // TODO: may also need to add a check for variables that are stored as objects in the PL
          },

          Identifier(path) {
            // don't work on the highest-level identifier
            // TODO: is there a more elegant way to do this? (maybe check parent?)
            if (path.node.name === "detector") {
              return;
            }

            // TODO: need to check if the expressions are anything in our library
            // add this keyword to member expression
            if (t.isCallExpression(path.parentPath.node)) {
              path.replaceWith(
              t.memberExpression(
                t.thisExpression(),
                path.node
              )
            );
            }

            // skip children so we don't repeat
            path.skip();
          },

          CallExpression(path) {
            // TODO: need to check if the expressions are anything in our library
            // make any calls to OS functions async
            if (!t.isAwaitExpression(path.parentPath.node)) {
              path.replaceWith(
                t.awaitExpression(path.node)
              );
            }
          }
        },
      };
    },
  ],

  // keep any white space so code stays pretty
  retainLines: true
};

const transformOSCode = function (code, config) {
  let output = babel.transformSync(code, config);
  return output.code;
}

Going back to the example above, here's what the input code can now become, and the output generated though the transformation above:

// input code (what a mentor would write)
function feedbackOpportunity() {
    return during(venue("SIG"));
  }

// output code (what the engine will use to execute)
async function feedbackOpportunity() {
  return await this.during(await this.venue("SIG"));
}

Some helpful links:

Writing Transformations
Writing a babel transform
Custom plugins in babel
Dealing with replaced nodes
More on stopping code for added nodes

AST Explorers + Info
Babel-specific
General
ASTs in JS

Babel Documentation
Babel Plugin Handbook
@babel/parser
@babel/generator
@babel/traverse
@babel/types
More on babel types
Babel options

@kapil1garg kapil1garg added the enhancement New feature or request label Apr 23, 2022
@kapil1garg
Copy link
Contributor Author

// input code (what a mentor would write)
function feedbackOpportunity() {
    return during(venue("SIG"));
  }

With this input code, it's actually possible (and pretty easy) to remove the return as well and just have the expression code there. In the transform, a ReturnStatement with the node starting from FunctionDeclaration would need to be added

@kapil1garg
Copy link
Contributor Author

See the orchestration-scripts-ast-transformer repo for the development code. It'll be easier to play around there and get the system working as intended before adding it into the engine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant