src/Realm.js
'use strict';
const Scope = require('./Scope');
const Value = require('./Value');
const CompletionRecord = require('./CompletionRecord');
const ObjectValue = require('./values/ObjectValue');
const PrimitiveValue = require('./values/PrimitiveValue.js');
const StringValue = require('./values/StringValue');
const LinkValue = require('./values/LinkValue');
const SmartLinkValue = require('./values/SmartLinkValue');
const BridgeValue = require('./values/BridgeValue');
const ASTPreprocessor = require('./ASTPreprocessor');
const EasyNativeFunction = require('./values/EasyNativeFunction');
const PropertyDescriptor = require('./values/PropertyDescriptor');
const Evaluator = require('./Evaluator');
const EvaluatorInstruction = require('./EvaluatorInstruction');
const ObjectPrototype = require('./stdlib/ObjectPrototype');
const FunctionPrototype = require('./stdlib/FunctionPrototype');
const ObjectClass = require('./stdlib/Object');
const FunctionClass = require('./stdlib/Function');
const NumberPrototype = require('./stdlib/NumberPrototype');
const StringPrototype = require('./stdlib/StringPrototype');
const ArrayPrototype = require('./stdlib/ArrayPrototype');
const ArrayClass = require('./stdlib/Array');
const StringClass = require('./stdlib/String');
const NumberClass = require('./stdlib/Number');
const BooleanPrototype = require('./stdlib/BooleanPrototype');
const BooleanClass = require('./stdlib/Boolean');
const RegExpPrototype = require('./stdlib/RegExpPrototype');
const RegExpClass = require('./stdlib/RegExp');
const EsperClass = require('./stdlib/Esper');
const ErrorPrototype = require('./stdlib/ErrorPrototype');
const ErrorClass = require('./stdlib/Error');
const AssertClass = require('./stdlib/Assert');
const MathClass = require('./stdlib/Math.js');
const ConsoleClass = require('./stdlib/Console');
const JSONClass = require('./stdlib/JSON');
const ProxyClass = require('./stdlib/Proxy')
const esper = require('./index.js');
class EvalFunction extends ObjectValue {
constructor(realm) {
super(realm);
this.setPrototype(realm.FunctionPrototype);
}
*call(thiz, args, scope) {
let cv = Value.undef;
if ( args.length > 0 ) cv = args[0];
if ( !(cv instanceof StringValue) ) return cv;
let code = yield * cv.toStringNative();
let ast;
try {
let oast = scope.realm.parser(code, {loc: true});
ast = ASTPreprocessor.process(oast);
} catch ( e ) {
var eo;
let desc = e.description || e.message;
if ( e.name == 'ReferenceError' || /Invalid left-hand side in/.test(desc) ) eo = new ReferenceError(e.description, e.fileName, e.lineNumber);
else eo = new SyntaxError(e.description, e.fileName, e.lineNumber);
if ( e.stack ) eo.stack = e.stack;
return new CompletionRecord(CompletionRecord.THROW, Value.fromNative(eo, scope.realm));
}
//TODO: Dont run in the parent scope if we are called indirectly
let bak = yield EvaluatorInstruction.branch('eval', ast, scope);
//console.log("EVALED: ", bak);
return bak;
}
}
let timeoutIds = 0;;
class SetTimeoutFunction extends ObjectValue {
constructor(realm, runtime, isSetInterval) {
super(realm);
this.setPrototype(realm.FunctionPrototype);
this.runtime = runtime;
this.isSetInterval = isSetInterval;
}
*call(thiz, args, scope) {
let engine = scope.realm.engine;
let ev = args[0];
let evaluator = new Evaluator(scope.realm, null, scope.global);
let time = args.length > 1 ? 0 + args[1].toNative() : 0;
let id = timeoutIds++;
let isSetInterval = this.isSetInterval;
if ( !ev || ev.jsTypeName !== "function" && ev.jsTypeName !== "string" ) {
return new CompletionRecord(CompletionRecord.THROW, Value.fromNative(new TypeError('"callback" argument must be a function'), scope.realm));
}
evaluator.pushFrame({generator: (function*() {
while ( true ) {
yield esper.FutureValue.make(yield * engine.runtime.wait(time));
if ( ev.jsTypeName == "function" ) {
let tv = scope.strict ? esper.undef : scope.global.thiz;
yield * ev.call(tv, args.slice(2), scope.global);
} else if ( ev.jsTypeName == "string" ) {
let oast = scope.realm.parser(yield * ev.toStringNative(), {loc: true});
let ast = ASTPreprocessor.process(oast);
yield EvaluatorInstruction.branch('eval', ast, scope.global);
}
if ( !isSetInterval ) break;
}
return Value.undef;
})(), type: 'invoke'});
let o = evaluator.generator();
evaluator.id = id;
engine.threads.push(o);
return Value.fromNative(id);
}
}
class ClearTimeoutFunction extends ObjectValue {
constructor(realm, runtime) {
super(realm);
this.setPrototype(realm.FunctionPrototype);
this.runtime = runtime;
}
*call(thiz, args, scope) {
if ( args.length < 1 ) return Value.undef;
let target = yield * args[0].toIntNative();
let engine = scope.realm.engine;
for ( let i = 0; i < engine.threads.length; ++i ) {
if ( engine.threads[i].evaluator.id == target ) {
let thr = engine.threads.splice(i, 1);
thr[0].evaluator.dispose.map((x) => x());
return Value.fromNative(true);
}
}
return Value.fromNative(false);
}
}
/**
* Represents a javascript execution environment including
* it's scopes and standard libraries.
*/
class Realm {
print() {
console.log.apply(console, arguments);
}
write() {
this.print.apply(this, arguments);
}
parser(code, options) {
if ( !esper.languages[this.language] ) {
throw new Error(`Unknown language ${this.language}. Load the lang-${this.language} plugin?`);
}
return esper.languages[this.language].parser(code, options);
}
makeLiteralValue(v, n) {
let lang = esper.languages[this.language];
if ( lang && lang.makeLiteralValue ) {
let langv = lang.makeLiteralValue(v, this, n);
if ( langv ) return langv;
}
return this.fromNative(v, n);
}
constructor(options, engine) {
this.engine = engine;
this.options = options || {};
this.language = options.language || 'javascript';
/** @type {Value} */
this.ObjectPrototype = new ObjectPrototype(this);
this.FunctionPrototype = new FunctionPrototype(this);
this.Object = new ObjectClass(this);
this.ObjectPrototype._init(this);
this.FunctionPrototype._init(this);
this.Object.setPrototype(this.ObjectPrototype);
this.FunctionPrototype.setPrototype(this.ObjectPrototype);
//TODO: Do this when we can make the property non enumerable.
this.ObjectPrototype.rawSetProperty('constructor', new PropertyDescriptor(this.Object, false));
this.Function = new FunctionClass(this);
this.FunctionPrototype.rawSetProperty('constructor', new PropertyDescriptor(this.Function, false));
/** @type {Math} */
this.Math = new MathClass(this);
/** @type {NumberPrototype} */
this.NumberPrototype = new NumberPrototype(this);
/** @type {StringPrototype} */
this.StringPrototype = new StringPrototype(this);
this.ArrayPrototype = new ArrayPrototype(this);
this.Array = new ArrayClass(this);
this.String = new StringClass(this);
this.Number = new NumberClass(this);
this.BooleanPrototype = new BooleanPrototype(this);
this.Boolean = new BooleanClass(this);
this.RegExpPrototype = new RegExpPrototype(this);
this.RegExp = new RegExpClass(this);
this.Proxy = new ProxyClass(this);
this.Esper = new EsperClass(this);
this.ErrorPrototype = new ErrorPrototype(this);
this.Error = new ErrorClass(this);
this.ErrorPrototype.rawSetProperty('constructor', new PropertyDescriptor(this.Error, false));
/** @type {Value} */
this.console = new ConsoleClass(this);
let scope = new Scope(this);
scope.object.clazz = 'global';
scope.strict = options.strict || false;
let that = this;
var printer = EasyNativeFunction.makeForNative(this, function() {
that.print.apply(that, arguments);
});
scope.set('print', printer);
scope.set('log', printer);
scope.addConst('NaN', this.fromNative(NaN));
scope.addConst('Infinity', this.fromNative(Infinity));
scope.set('console', this.console);
scope.set('JSON', new JSONClass(this));
if ( options.exposeEsperGlobal ) {
scope.set('Esper', this.Esper);
}
scope.set('Math', this.Math);
scope.set('Number', this.Number);
scope.set('Boolean', this.Boolean);
scope.set('Object', this.Object);
scope.set('Function', this.Function);
scope.set('Array', this.Array);
scope.set('String', this.String);
scope.set('RegExp', this.RegExp);
scope.set('Proxy', this.Proxy);
scope.set('Error', this.Error);
scope.set('TypeError', this.TypeError = this.Error.makeErrorType(TypeError));
scope.set('SyntaxError', this.SyntaxError = this.Error.makeErrorType(SyntaxError));
scope.set('ReferenceError', this.ReferenceError = this.Error.makeErrorType(ReferenceError));
scope.set('RangeError', this.RangeError = this.Error.makeErrorType(RangeError));
scope.set('EvalError', this.EvalError = this.Error.makeErrorType(EvalError));
scope.set('URIError', this.URIError = this.Error.makeErrorType(URIError));
scope.set('parseInt', EasyNativeFunction.makeForNative(this, parseInt));
scope.set('parseFloat', EasyNativeFunction.makeForNative(this, parseFloat));
scope.set('isNaN', EasyNativeFunction.makeForNative(this, isNaN));
scope.set('isFinite', EasyNativeFunction.makeForNative(this, isFinite));
//scope.set('Date', this.fromNative(Date));
scope.set('eval', new EvalFunction(this));
scope.set('assert', new AssertClass(this));
if ( options.runtime ) {
scope.set("setTimeout", new SetTimeoutFunction(this, options.runtime, false));
scope.set("setInterval", new SetTimeoutFunction(this, options.runtime, true));
scope.set("clearTimeout", new ClearTimeoutFunction(this, options.runtime));
scope.set("clearInterval", new ClearTimeoutFunction(this, options.runtime));
}
scope.thiz = scope.object;
this.importCache = new WeakMap();
/** @type {Scope} */
this.globalScope = scope;
let lang = esper.languages[this.language];
if ( lang && lang.setupRealm ) lang.setupRealm(this);
}
lookupWellKnown(v) {
if ( v === Object ) return this.Object;
if ( v === Object.prototype ) return this.ObjectPrototype;
if ( v === Function ) return this.Function;
if ( v === Function.prototype ) return this.FunctionPrototype;
if ( v === Math ) return this.Math;
if ( v === Number ) return this.Number;
if ( v === Number.prototype ) return this.NumberPrototype;
if ( v === String ) return this.String;
if ( v === String.prototype ) return this.StringPrototype;
if ( v === Array ) return this.Array;
if ( v === Array.prototype ) return this.ArrayPrototype;
if ( v === RegExp ) return this.RegExp;
if ( v === RegExp.prototype ) return this.RegExpPrototype;
if ( typeof console !== 'undefined' && v === console ) return this.console;
}
lookupWellKnownByName(v) {
switch ( v ) {
case '%Object%': return this.Object;
case '%ObjectPrototype%': return this.ObjectPrototype;
case '%Function%': return this.Function;
case '%FunctionPrototype%': return this.FunctionPrototype;
case '%Math%': return this.Math;
case '%Number%': return this.Number;
case '%NumberPrototype%': return this.NumberPrototype;
case '%Array%': return this.Array;
case '%ArrayPrototype%': return this.ArrayPrototype;
case '%RegExp%': return this.RegExp;
case '%RegExpPrototype%': return this.RegExpPrototype;
}
}
fromNative(native, x) {
return Value.fromNative(native, this);
}
import(native, modeHint) {
if ( native instanceof Value ) return native;
if ( native === undefined ) return Value.undef;
let prim = Value.fromPrimativeNative(native);
if ( prim ) return prim;
//if ( this.importCache.has(native) ) {
// return this.importCache.get(native);
//}
if ( Value.hasBookmark(native) ) {
return Value.getBookmark(native);
}
let result;
switch ( modeHint || this.options.foreignObjectMode ) {
case 'bridge':
result = BridgeValue.make(native, this);
break;
case 'smart':
result = SmartLinkValue.make(native, this);
break;
case 'link':
default:
result = LinkValue.make(native, this);
break;
}
//this.importCache.set(native, result);
return result;
}
}
Realm.prototype.makeForForeignObject = Realm.prototype.import;
module.exports = Realm;