'use strict'; const JSBI = require('jsbi'); const ZERO = JSBI.BigInt(0); /* Used when evaling runtime compiled methods */ const nullStr = require('./null_s.js'); const Null = require('./null.js'); const globalContext = require('./global-context.js'); const constants = require('./constants.js'); /* Needed for setting defaults values of attrs for objects */ /* Needed for throwing exceptions from generated code */ const NQPException = require('./nqp-exception.js'); const nativeArgs = require('./native-args.js'); const NativeStrArg = nativeArgs.NativeStrArg; /*async*/ function findMethod(ctx, obj, name) { if (obj.$$STable.methodCache) { const method = obj.$$STable.methodCache.get(name); if (method !== undefined) { return method; } if (obj.$$STable.modeFlags & constants.METHOD_CACHE_AUTHORITATIVE) { return Null; } } if (/*await*/ obj.$$STable.HOW.$$can(ctx, 'find_method')) { return /*await*/ obj.$$STable.HOW.p6$find_method(ctx, null, obj.$$STable.HOW, obj, new NativeStrArg(name)); } else { return Null; } } function scwb() { const compilingSCs = globalContext.context.compilingSCs; if (compilingSCs.length == 0 || globalContext.context.scwbDisableDepth || globalContext.context.neverRepossess.has(this)) { return; } if (compilingSCs[compilingSCs.length - 1] !== this.$$SC) { const owned = this.$$SC.ownedObjects.get(this); compilingSCs[compilingSCs.length - 1].repossessObject(owned === undefined ? this : owned); } } module.exports.scwb = scwb; class STable { constructor(REPR, HOW) { this.REPR = REPR; this.HOW = HOW; this.modeFlags = 0; this.lazyMethodCache = false; this.$$SC = undefined; this.ObjConstructor = REPR.createObjConstructor(this); /* HACK - it's a bit hackish - think how correct it is */ this.ObjConstructor.prototype.$$clone = function() { const clone = new this.$$STable.ObjConstructor(); for (const i in this) { if (Object.prototype.hasOwnProperty.call(this, i) && i != '$$SC') { clone[i] = this[i]; } } return clone; }; /* Default boolification mode 5 */ this.ObjConstructor.prototype.$$toBool = function(ctx) { return this.$$typeObject ? 0 : 1; }; this.ObjConstructor.prototype.$$decont = function(ctx) { return this; }; this.ObjConstructor.prototype.$$isrwcont = function() { return 0; }; this.ObjConstructor.prototype.$$typeObject = 0; this.ObjConstructor.prototype.$$call = undefined; this.ObjConstructor.prototype.$$scwb = scwb; this.ObjConstructor.prototype.$$istype = /*async*/ function(ctx, type) { const cache = this.$$STable.typeCheckCache; if (cache) { for (let i = 0; i < cache.length; i++) { if (cache[i] === type) { return 1; } } } else { /* If we get here, need to call .^type_check on the value we're * checking. */ const HOW = this.$$STable.HOW; /* This "hack" is stolen from the JVM */ if (!/*await*/ HOW.$$can(ctx, 'type_check')) { return 0; } const typeCheckResult = /*await*/ HOW.p6$type_check(ctx, null, HOW, this, type); if (typeof typeCheckResult === 'number' ? typeCheckResult : typeCheckResult.$$toBool(ctx)) { return 1; } } const TYPE_CHECK_NEEDS_ACCEPTS = 2; if (type.$$STable.modeFlags & TYPE_CHECK_NEEDS_ACCEPTS) { return (/*await*/ type.$$STable.HOW.p6$accepts_type(ctx, null, type.$$STable.HOW, type, this)).$$toBool(ctx); } return 0; }; this.ObjConstructor.prototype.$$can = /*async*/ function(ctx, name) { return (/*await*/ findMethod(ctx, this, name)) === Null ? 0 : 1; }; if (this.REPR.setupSTable) { this.REPR.setupSTable(this); } } lookupParametric(params) { const lookup = this.parameterizerCache; for (let i = 0; i < lookup.length; i++) { if (params.length == lookup[i].params.length) { let match = true; for (let j = 0; j < params.length; j++) { /* XXX More cases to consider here. - copied over from the jvm backend, need to consider what they are*/ if (params[j] !== lookup[i].params[j]) { match = false; break; } } if (match) { return lookup[i].type; } } } } setboolspec(mode, method) { this.boolificationSpec = {mode: mode, method: method}; if (mode == 0) { this.ObjConstructor.prototype.$$toBool = /*async*/ function(ctx) { const ret = /*await*/ method.$$call(ctx, {}, this); return (typeof ret === 'number' ? (ret === 0 ? 0 : 1) : ret.$$decont().$$toBool(ctx)); }; } else if (mode == 1) { this.ObjConstructor.prototype.$$toBool = function(ctx) { return this.$$typeObject || this.$$getInt() == 0 ? 0 : 1; }; } else if (mode == 2) { this.ObjConstructor.prototype.$$toBool = function(ctx) { return this.$$typeObject || this.$$getNum() == 0 ? 0 : 1; }; } else if (mode == 3) { this.ObjConstructor.prototype.$$toBool = function(ctx) { return (this.$$typeObject || this.$$getStr() === nullStr || this.$$getStr() === '') ? 0 : 1; }; } else if (mode == 4) { this.ObjConstructor.prototype.$$toBool = function(ctx) { if (this.$$typeObject) return 0; const str = this.$$getStr(); return (str === nullStr || str == '' || str == '0') ? 0 : 1; }; } else if (mode == 5) { // this is the default - do nothing } else if (mode == 6) { this.ObjConstructor.prototype.$$toBool = function(ctx) { return (this.$$typeObject || JSBI.equal(this.$$getBignum(), ZERO)) ? 0 : 1; }; } else if (mode == 7) { // STUB } else if (mode == 8) { this.ObjConstructor.prototype.$$toBool = function(ctx) { return this.$$elems() ? 1 : 0; }; } else { throw new NQPException('setboolspec with mode: ' + mode + ' NYI'); } } setinvokespec(classHandle, attrName, invocationHandler) { if (this.REPR.hardcodedInvokeSpec) { return; } if (classHandle !== Null) { /* TODO - think if we can use direct access here */ const getter = this.REPR.getterForAttr(classHandle, attrName); this.ObjConstructor.prototype.$$call = function() { const value = this[getter](); return value.$$call.apply(value, arguments); }; this.ObjConstructor.prototype.$$apply = function(args) { return this[getter]().$$apply(args); }; } else { this.ObjConstructor.prototype.$$call = function() { const args = []; args.push(arguments[0]); args.push(arguments[1]); args.push(this); for (let i = 2; i < arguments.length; i++) { args.push(arguments[i]); } return invocationHandler.$$apply(args); }; this.ObjConstructor.prototype.$$apply = function(args) { const newArgs = []; newArgs.push(args[0]); newArgs.push(args[1]); newArgs.push(this); for (let i = 2; i < args.length; i++) { newArgs.push(args[i]); } return invocationHandler.$$apply(newArgs); }; } this.invocationSpec = {classHandle: classHandle, attrName: attrName, invocationHandler: invocationHandler}; } createTypeObject() { const obj = new this.ObjConstructor(); obj.$$typeObject = 1; obj.$$atkey = function(key) { return Null; }; obj.$$atpos = function(index) { return Null; }; obj.$$decont = function(ctx) { return this; }; obj.$$clone = function(ctx) { return this; }; obj.$$getInt = function() { throw new NQPException(`Cannot unbox a type object (${this.$$STable.debugName}) to an int.`); }; obj.$$getNum = function() { throw new NQPException(`Cannot unbox a type object (${this.$$STable.debugName}) to an num.`); }; obj.$$getStr = function() { throw new NQPException(`Cannot unbox a type object (${this.$$STable.debugName}) to an str.`); }; obj.$$getattr = function(classHandle, attrName) { throw new NQPException(`Cannot look up attributes in a ${this.$$STable.debugName} type object`); }; obj.$$bindattr = function(classHandle, attrName, value) { throw new NQPException(`Cannot bind attributes in a ${this.$$STable.debugName} type object`); }; return obj; } setLazyMethodCache(methodCache) { this.methodCache = methodCache; this.lazyMethodCache = true; } setMethodCache(methodCache) { // TODO delete old methods this.methodCache = methodCache; this.lazyMethodCache = false; const proto = this.ObjConstructor.prototype; methodCache.forEach(function(method, name, map) { proto['p6$' + name] = function() { return method.$$call.apply(method, arguments); }; }); } setPositionalDelegate(attr) { this.ObjConstructor.prototype.$$bindpos = function(index, value) { return this[attr].$$bindpos(index, value); }; this.ObjConstructor.prototype.$$atpos = function(index) { return this[attr].$$atpos(index); }; this.ObjConstructor.prototype.$$unshift = function(value) { return this[attr].$$unshift(value); }; this.ObjConstructor.prototype.$$pop = function(value) { return this[attr].$$pop(value); }; this.ObjConstructor.prototype.$$push = function(value) { return this[attr].$$push(value); }; this.ObjConstructor.prototype.$$shift = function(value) { return this[attr].$$shift(value); }; this.ObjConstructor.prototype.$$elems = function(value) { return this[attr].$$elems(); }; } setAssociativeDelegate(attr) { this.ObjConstructor.prototype.$$bindkey = function(key, value) { return this[attr].$$bindkey(key, value); }; this.ObjConstructor.prototype.$$atkey = function(key) { return this[attr].$$atkey(key); }; this.ObjConstructor.prototype.$$existskey = function(key) { return this[attr].$$existskey(key); }; this.ObjConstructor.prototype.$$deletekey = function(key) { return this[attr].$$deletekey(key); }; } addInternalMethod(name, func) { this.ObjConstructor.prototype[name] = func; } addInternalMethods(klass) { for (const methodName of Object.getOwnPropertyNames(klass.prototype)) { this.addInternalMethod(methodName, klass.prototype[methodName]); } } addCustomInspection(callback) { this.addInternalMethod(Symbol.for('nodejs.util.inspect.custom'), callback); } compileAccessor(accessor, code, setup) { this.code = this.code || ''; this.setup = this.setup || ''; if (setup) this.setup += setup; this.code += 'STable.addInternalMethod("' + accessor + '",' + code + ');\n'; } evalGatheredCode() { if (this.code) { const STable = this; // eslint-disable-line no-unused-vars eval(this.setup + this.code); this.code = ''; } } scwb() { const compilingSCs = globalContext.context.compilingSCs; if (compilingSCs.length == 0 || globalContext.context.scwbDisableDepth) return; if (compilingSCs[compilingSCs.length - 1] !== this.$$SC) { compilingSCs[compilingSCs.length - 1].repossessSTable(this); } } }; module.exports.STable = STable; module.exports.findMethod = findMethod;