adder-script
Version:
Python like language to execute untrusted codes in browsers and Node.js.
296 lines (243 loc) • 10.7 kB
JavaScript
"use strict";
/**
* The main API class that spawn programs and load code.
* This is the main class you need to use.
*
* Author: Ronen Ness.
* Since: 2016
*/
// include jsface for classes
var jsface = require("./../dependencies/jsface"),
Class = jsface.Class,
extend = jsface.extend;
// include the alternative console
var Console = require("./../console");
// include the program class
var Program = require("./program");
// include the compiler class
var Compiler = require("./../compiler");
// include core objects
var Core = require("./../core");
// include errors
var Errors = require("./../errors");
// include utils
var Utils = require("./../utils");
// include language stuff
var Language = require("./../language");
// the environment class.
var Environment = Class({
// the environment is a singleton class
$singleton: true,
// add access to errors and utils
Errors: Errors,
Utils: Utils,
// Environment constructor.
// @param params is a dictionary with all environment params. contains:
// flags - compiler and interpreter flags.
// modules - a list of modules to load by default. Can also be ['ALL'] to load all builtin modules.
// outputFunc - a function to handle output from script execution (print calls).
// showDebugConsole - if true, will output debug prints to console.
init: function(params)
{
// default params
params = params || {};
// store interpreter flags
this._flags = params.flags || {};
this._modules = params.modules || ["SAFE"];
this._outputFunc = params.outputFunc || null;
// all custom modules
this._customModules = {};
// set debug console
if (params.showDebugConsole) {
Console.bindToNativeConsole();
}
// show basic info
Console.info("Created a new environment!", this._flags, this._modules);
// create the compiler instance
this._compiler = new Compiler(this._flags);
},
// compile code and return the compiled code
compile: function(code) {
return this._compiler.compile(code);
},
// spawn a program from compiled code
newProgram: function(compiledCode) {
// create and return the new program
var program = new Program(compiledCode, this._modules, this._flags, this._outputFunc);
// add custom modules
for (var key in this._customModules) {
program.addModule(key, this._customModules[key]);
}
// return the newly created program
return program;
},
// convert data dictionary to a builtin function or an object
__toBuiltin: function(data, key, containerName) {
// if its a function convert to a function instance and return
if (typeof data === "object" && data.func) {
data = Core.BuiltinFunc.create(data.func, data.requiredParams, data.optionalParams, data.deterministic || false);
data.identifier = containerName + ".functions." + key;
data.convertParamsToNativeJs = true;
return data;
}
// else just return the object
return data;
},
// add a built-in function to Adder. This will only affect future programs, not already existing ones.
// @param data is a dictionary with the following keys:
// name: builtin function name.
// func: function to register.
// requiredParams: minimum amount of required params. set null any number of params.
// optionalParams: number of optional params. default to 0.
// deterministic: if for input X output will always be Y, eg the function is deterministic and predictable,
// set this to true. this will allow Adder to cache results and greatly optimize using this function.
// note: default to false.
addBuiltinFunction: function(data) {
// add to builtin functions dictionary
Language.Builtins.Functions[data.name] = this.__toBuiltin(data, data.name, "custom.builtin");
},
// remove a built-in function
removeBuiltinFunction: function(name) {
delete Language.Builtins.Functions[name];
},
// create and add a builtin module.
// @param name - module name.
// @param moduleApi - a dictionary with module's API.
// to add a const value just add key value.
// to add a function add a dictionary with 'func', 'requiredParams', and 'optionalParams'.
// see addBuiltinFunction() for options.
//
// Usage example:
// AdderScript.addBuiltinModule("Test", {
// "foo": {
// func: function(x) {alert(x)},
// requiredParams: 1,
// optionalParams: 0
// },
// "bar": 5,
// });
addBuiltinModule: function(name, moduleApi) {
// iterate over module api and convert to items
for (var key in moduleApi) {
// get current item and convert to builtin object
var curr = moduleApi[key];
curr = this.__toBuiltin(curr, key, name);
// set back into api
moduleApi[key] = curr;
}
// create the module and add it
var CustomModule = Class(Core.Module, {
api: moduleApi,
name: name,
version: "1.0.0",
});
this._customModules[name] = CustomModule;
},
// remove a built-in module
removeBuiltinModule: function(name) {
delete this._customModules[name];
},
// define a built-in object (like list, dict, set..) you can return and use in your modules and builtin functions.
// @param name - object type name (for example when doing type(obj) this string will be returned).
// @param api - a dictionary with object's API.
// to add a const value just add key value.
// to add a function add a dictionary with 'func', 'requiredParams', and 'optionalParams'.
// see addBuiltinFunction() for options.
//
// Usage example:
// createFunc = AdderScript.defineBuiltinObject("Person", {
// "say_hello": {
// func: function() {alert("Hello World!")},
// requiredParams: 0,
// optionalParams: 0
// },
// "race": "human",
// });
//
// to create a new object instance:
// var newInstance = createFunc(this);
//
// Where 'this' is an interpreter instance.
//
// Note: by default users won't be able to create instances of this object on their own, you'll need to provide a function to generate it.
//
defineBuiltinObject: function(name, api) {
// iterate over module api and convert to items
for (var key in api) {
// get current item and convert to builtin object
var curr = api[key];
curr = this.__toBuiltin(curr, key, name);
// set back into api
api[key] = curr;
}
// create the object type and return the function to create new instance
var ret = (function() {
// create the object type
var _ObjType = Class(Core.Object, {
// set api
api: api,
// convert to string
toString: function()
{
return this.type;
},
// convert to repr
toRepr: function()
{
return "<" + this.type + ">";
},
// convert to a native javascript object
toNativeJs: function()
{
return this;
},
// object identifier
name: name,
type: name,
});
// create the function to return the object instance
return function(parent) {
var context = parent._context || parent._interpreter._context;
if (!context) throw "Invalid parent param, must be interpreter or program!";
return new _ObjType(context);
}
})();
// return the new object creation function
return ret;
},
// Convert a JavaScript object into a simple Adder object.
// You can use this to return complex objects without having to define them as builtins first. For example:
//
// function someFunc() {
// return AdderScript.toAdderObject("Target", {type: "car", hp: 5, isEnemy: true});
// }
//
// and later Adder script can simple use this object's API, ie:
//
// if target.type == "car":
// print ("its a car!")
//
// @param name - object type name (for example when doing type(obj) this string will be returned).
// @param api - a dictionary with object's API.
// to add a const value just add key value.
// to add a function add a dictionary with 'func', 'requiredParams', and 'optionalParams'.
// see addBuiltinFunction() for options.
//
// Usage example:
// var retObj = AdderScript.toAdderObject("Person", {
// "say_hello": {
// func: function() {alert("Hello World!")},
// requiredParams: 0,
// optionalParams: 0
// },
// "race": "human",
// });
//
// Note: calls defineBuiltinObject() internally.
//
toAdderObject: function(name, api, program) {
return this.defineBuiltinObject(name, api)(program);
},
});
// export the Environment class
module.exports = Environment;