src/Value.js
'use strict';
/* @flow */
const CompletionRecord = require('./CompletionRecord');
const GenDash = require('./GenDash');
let undef, nil, tru, fals, nan, emptyString, zero, one, negone, negzero, smallIntValues;
let cache = new WeakMap();
let bookmarks = new WeakMap();
let ObjectValue, PrimitiveValue, StringValue, NumberValue, BridgeValue, Evaluator;
let serial = 0;
/**
* Represents a value a variable could take.
*/
class Value {
/**
* Convert a native javascript primative value to a Value
* @param {any} value - The value to convert
*/
static fromPrimativeNative(value) {
if ( !value ) {
if ( value === undefined ) return undef;
if ( value === null ) return nil;
if ( value === false ) return fals;
if ( value === '' ) return emptyString;
}
if ( value === true ) return tru;
if ( typeof value === 'number' ) {
if ( value === 0 ) {
return 1 / value > 0 ? zero : negzero;
}
if ( value | 0 === value ) {
let snv = smallIntValues[value + 1];
if ( snv ) return snv;
}
return new NumberValue(value);
}
if ( typeof value === 'string' ) return new StringValue(value);
if ( typeof value === 'boolean' ) return new PrimitiveValue(value);
}
static hasBookmark(native) { return bookmarks.has(native); }
static getBookmark(native) { return bookmarks.get(native); }
/**
* Convert a native javascript value to a Value
*
* @param {any} value - The value to convert
* @param {Realm} realm - The realm of the new value.
*/
static fromNative(value, realm) {
if ( value instanceof Value ) return value;
let prim = Value.fromPrimativeNative(value);
if ( prim ) return prim;
if ( value instanceof Error ) {
if ( !realm ) throw new Error('We needed a realm, but we didnt have one. We were sad :(');
if ( value instanceof TypeError ) return realm.TypeError.makeFrom(value);
if ( value instanceof ReferenceError ) return realm.ReferenceError.makeFrom(value);
if ( value instanceof SyntaxError ) return realm.SyntaxError.makeFrom(value);
else return realm.Error.makeFrom(value);
}
if ( Value.hasBookmark(value) ) {
return Value.getBookmark(value);
}
throw new TypeError('Tried to load an unsafe native value into the interperter:' + typeof value + ' / ' + value);
//TODO: Is this cache dangerous?
if ( !cache.has(value) ) {
let nue = new BridgeValue(realm, value);
cache.set(value, nue);
return nue;
}
return cache.get(value);
}
/**
* Holds a value representing `undefined`
*
* @returns {UndefinedValue}
*/
static get undef() {
return undef;
}
/**
* Holds a value representing `null`
*
* @returns {NullValue}
*/
static get null() {
return nil;
}
/**
* Holds a value representing `true`
*
* @returns {BooleanValue} true
*/
static get true() {
return tru;
}
/**
* Holds a value representing `fasle`
*
* @returns {BooleanValue} false
*/
static get false() {
return fals;
}
/**
* Holds a value representing `NaN`
*
* @returns {NumberValue} NaN
*/
static get nan() {
return nan;
}
/**
* Holds a value representing `''`
*
* @returns {StringValue} ''
*/
static get emptyString() {
return emptyString;
}
/**
* Holds a value representing `0`
*
* @returns {NumberValue} 0
*/
static get zero() { return zero; }
static createNativeBookmark(v, realm) {
var out;
let thiz = this;
if ( typeof v.call === 'function' ) {
switch ( realm.options.bookmarkInvocationMode ) {
case 'loop':
out = function Bookmark() {
let Evaluator = require('./Evaluator');
let cthis = realm.makeForForeignObject(this);
let c = v.call(cthis, Array.from(arguments).map((v) => realm.makeForForeignObject(v)), realm.globalScope);
let evalu = new Evaluator(realm, null, realm.globalScope);
evalu.pushFrame({type: 'program', generator: c, scope: realm.globalScope});
let gen = evalu.generator();
let result;
do {
result = gen.next();
} while ( !result.done );
return result.value.toNative();
};
break;
default:
out = function Bookmark() { throw new Error('Atempted to invoke bookmark for ' + v.debugString); };
}
} else {
out = {};
}
Object.defineProperties(out, {
toString: {value: function() { return v.debugString; }, writable: true},
inspect: {value: function() { return v.debugString; }, writable: true},
esperValue: {get: function() { return v; } },
});
bookmarks.set(out, v);
return out;
}
constructor() {
this.serial = serial++;
}
/**
* Converts this value to a native javascript value.
*
* @abstract
* @returns {*}
*/
toNative() {
throw new Error('Unimplemented: Value#toNative');
}
/**
* Deep copy this value to a native javascript value.
*
* @returns {*}
*/
toJS() {
return this.toNative();
}
/**
* A string representation of this Value suitable for display when
* debugging.
* @abstract
* @returns {string}
*/
get debugString() {
let native = this.toNative();
return native ? native.toString() : '???';
}
inspect() { return this.debugString; }
//TODO: Kill this
fromNative(other, realm) {
realm = realm || this.realm;
if ( realm ) return realm.fromNative(other);
return Value.fromNative(other);
}
/**
* Indexes the value to get the value of a property.
* i.e. `value[name]`
* @param {String} name
* @param {Realm} realm
* @abstract
* @returns {Value}
*/
*get(name, realm) {
let err = "Can't access get " + name + ' of that type: ' + require('util').inspect(this);
return CompletionRecord.makeTypeError(realm, err);
}
getImmediate(name) {
return GenDash.syncGenHelper(this.get(name));
}
/**
* Computes the javascript expression `!value`
* @returns {Value}
*/
*not() {
return !this.truthy ? Value.true : Value.false;
}
/**
* Computes the javascript expression `+value`
* @returns {Value}
*/
*unaryPlus() {
return Value.fromNative(+(yield * this.toNumberValue()));
}
/**
* Computes the javascript expression `-value`
* @returns {Value}
*/
*unaryMinus() {
return Value.fromNative(-(yield * this.toNumberValue()));
}
/**
* Computes the javascript expression `typeof value`
* @returns {Value}
*/
*typeOf() {
return Value.fromNative(this.jsTypeName);
}
/**
* Computes the javascript expression `!(value == other)`
* @param {Value} other - The other value
* @param {Realm} realm - The realm to use when creating resuls.
* @returns {Value}
*/
*notEquals(other, realm) {
var result = yield * this.doubleEquals(other, realm);
return yield * result.not();
}
/**
* Computes the javascript expression `!(value === other)`
* @param {Value} other - The other value
* @param {Realm} realm - The realm to use when creating resuls.
* @returns {Value}
*/
*doubleNotEquals(other, realm) {
var result = yield * this.tripleEquals(other, realm);
return yield * result.not();
}
/**
* Computes the javascript expression `value === other`
* @param {Value} other - The other value
* @param {Realm} realm - The realm to use when creating resuls.
* @returns {Value}
*/
*tripleEquals(other, realm) {
return other === this ? Value.true : Value.false;
}
getPrototypeProperty() {
let p = this.properties['prototype'];
if ( !p ) return;
return p.value;
}
*makeThisForNew(realm) {
var nue = new ObjectValue(realm);
var p = this.getPrototypeProperty();
if ( p ) nue.setPrototype(p);
return nue;
}
/**
* Computes the javascript expression `value > other`
* @param {Value} other - The other value
* @returns {Value}
*/
*gt(other) { return this.fromNative((yield * this.toNumberNative()) > (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value < other`
* @param {Value} other - The other value
* @returns {Value}
*/
*lt(other) { return this.fromNative((yield * this.toNumberNative()) < (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value >= other`
* @param {Value} other - The other value
* @returns {Value}
*/
*gte(other) { return this.fromNative((yield * this.toNumberNative()) >= (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value <= other`
* @param {Value} other - The other value
* @returns {Value}
*/
*lte(other) { return this.fromNative((yield * this.toNumberNative()) <= (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value - other`
* @param {Value} other - The other value
* @returns {Value}
*/
*subtract(other) { return this.fromNative((yield * this.toNumberNative()) - (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value / other`
* @param {Value} other - The other value
* @returns {Value}
*/
*divide(other) { return this.fromNative((yield * this.toNumberNative()) / (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value * other`
* @param {Value} other - The other value
* @returns {Value}
*/
*multiply(other) { return this.fromNative((yield * this.toNumberNative()) * (yield * other.toNumberNative())); }
/**
* Computes the javascript expression `value % other`
* @param {Value} other - The other value
* @returns {Value}
*/
*mod(other) { return this.fromNative((yield * this.toNumberNative()) % (yield * other.toNumberNative())); }
*bitNot() { return this.fromNative(~(yield * this.toNumberNative())); }
*shiftLeft(other) { return this.fromNative((yield * this.toNumberNative()) << (yield * other.toNumberNative())); }
*shiftRight(other) { return this.fromNative((yield * this.toNumberNative()) >> (yield * other.toNumberNative())); }
*shiftRightZF(other) { return this.fromNative((yield * this.toNumberNative()) >>> (yield * other.toNumberNative())); }
*bitAnd(other) { return this.fromNative((yield * this.toNumberNative()) & (yield * other.toNumberNative())); }
*bitOr(other) { return this.fromNative((yield * this.toNumberNative()) | (yield * other.toNumberNative())); }
*bitXor(other) { return this.fromNative((yield * this.toNumberNative()) ^ (yield * other.toNumberNative())); }
/**
* Computes the `value` raised to the `other` power (`value ** other`)
* @param {Value} other - The other value
* @returns {Value}
*/
*pow(other) { return this.fromNative(Math.pow(yield * this.toNumberNative(),yield * other.toNumberNative())); }
*inOperator(other) {
let err = "Cannot use 'in' operator to search for 'thing' in 'thing'";
return new CompletionRecord(CompletionRecord.THROW, {
type: "TypeError",
message: err
});
}
/**
* Is the value is truthy, i.e. `!!value`
*
* @abstract
* @type {boolean}
*/
get truthy() {
throw new Error('Unimplemented: Value#truthy');
}
get jsTypeName() {
throw new Error('Unimplemented: Value#jsTypeName');
}
get specTypeName() {
return this.jsTypeName;
}
get isCallable() {
return ( typeof this.call === 'function' );
}
*toNumberValue() { throw new Error('Unimplemented: Value#toNumberValue'); }
*toStringValue() { throw new Error('Unimplemented: Value#StringValue'); }
*toStringNative() { return (yield * this.toStringValue()).native; }
*toBooleanValue() { return this.truthy ? tru : fals; }
*toUIntNative() {
let nv = yield * this.toNumberValue();
return Math.floor(nv.native);
}
*toIntNative() {
let nv = yield * this.toNumberValue();
return Math.floor(nv.native);
}
*toNumberNative() {
let nv = yield * this.toNumberValue();
return nv.native;
}
*toPrimitiveValue(preferedType) { throw new Error('Unimplemented: Value#toPrimitiveValue'); }
*toPrimitiveNative(preferedType) { return (yield * this.toPrimitiveValue(preferedType)).native; }
/**
* Quickly make a generator for this value
*/
*fastGen() { return this; }
}
module.exports = Value;
ObjectValue = require('./values/ObjectValue');
PrimitiveValue = require('./values/PrimitiveValue');
StringValue = require('./values/StringValue');
NumberValue = require('./values/NumberValue');
const UndefinedValue = require('./values/UndefinedValue');
const NullValue = require('./values/NullValue');
undef = new UndefinedValue();
nil = new NullValue();
tru = new PrimitiveValue(true);
fals = new PrimitiveValue(false);
nan = new PrimitiveValue(NaN);
emptyString = new StringValue('');
zero = new NumberValue(0);
negzero = new NumberValue(-0);
one = new NumberValue(1);
negone = new NumberValue(-1);
smallIntValues = [
negone, zero,
one, new NumberValue(2), new NumberValue(3), new NumberValue(4), new NumberValue(5),
new NumberValue(6), new NumberValue(7), new NumberValue(8), new NumberValue(9)
];