-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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