const { JavaType, ArrayType, PrimitiveType, Method, Parameter, Field } = require('java-mti'); const { Token } = require('./tokenizer'); class ResolvedIdent { /** * @param {string} ident * @param {(Local|Parameter|Field|ArrayElement|ValueBase)[]} variables * @param {Method[]} methods * @param {JavaType[]} types * @param {string} package_name */ constructor(ident, variables = [], methods = [], types = [], package_name = '') { this.source = ident; this.variables = variables; this.methods = methods; this.types = types; this.package_name = package_name; /** @type {Token[]} */ this.tokens = []; } } /** * AnyType is a special type that's used to fill in types that are missing. * To prevent cascading errors, AnyType should be fully assign/cast/type-compatible * with any other type */ class AnyType extends JavaType { /** * * @param {String} label */ constructor(label) { super("class", [], ''); super.simpleTypeName = label || ''; } static Instance = new AnyType(''); get rawTypeSignature() { return 'U'; } get typeSignature() { return 'U'; } } class AnyMethod extends Method { /** * @param {string} name */ constructor(name) { super(null, name, [], ''); } get returnType() { return AnyType.Instance; } } class Local { /** * @param {Token[]} modifiers * @param {string} name * @param {Token} decltoken * @param {import('./source-type').SourceTypeIdent} typeIdent * @param {number} postnamearrdims * @param {ResolvedIdent} init */ constructor(modifiers, name, decltoken, typeIdent, postnamearrdims, init) { this.finalToken = modifiers.find(m => m.source === 'final') || null; this.name = name; this.decltoken = decltoken; if (postnamearrdims > 0) { typeIdent.resolved = new ArrayType(typeIdent.resolved, postnamearrdims); } this.typeIdent = typeIdent; this.init = init; } get type() { return this.typeIdent.resolved; } } class Label { /** * @param {Token} token */ constructor(token) { this.name_token = token; } } class MethodDeclarations { /** @type {Local[]} */ locals = []; /** @type {Label[]} */ labels = []; /** @type {import('./source-types2').SourceType[]} */ types = []; _scopeStack = []; pushScope() { this._scopeStack.push([this.locals, this.labels, this.types]); this.locals = this.locals.slice(); this.labels = this.labels.slice(); this.types = this.types.slice(); } popScope() { [this.locals, this.labels, this.types] = this._scopeStack.pop(); } } class ArrayElement { /** * * @param {Local|Parameter|Field|ArrayElement|Value} array_variable * @param {ResolvedIdent} index */ constructor(array_variable, index) { this.array_variable = array_variable; this.index = index; if (!(this.array_variable.type instanceof ArrayType)) { throw new Error('Array element cannot be created from non-array type'); } this.name = `${array_variable.name}[${index.source}]`; /** @type {JavaType} */ this.type = this.array_variable.type.elementType; } } class ValueBase {} class Value extends ValueBase { /** * @param {string} name * @param {JavaType} type */ constructor(name, type) { super(); this.name = name; this.type = type; } /** * @param {string} ident * @param {ResolvedIdent} lhs * @param {ResolvedIdent} rhs * @param {JavaType} type */ static build(ident, lhs, rhs, type) { if (!lhs.variables[0] || !rhs.variables[0]) { return new Value(ident, type); } if (lhs.variables[0] instanceof LiteralValue && rhs.variables && rhs.variables[0] instanceof LiteralValue) { new LiteralValue(ident, type); } return new Value(ident, type); } } class AnyValue extends Value { constructor(name) { super(name, AnyType.Instance); } } class LiteralValue extends Value { } /** * LiteralNumberType is a value representing literal numbers (like 0, 5.3, -0.1e+12, etc). * * It's used to allow literal numbers to be type-assignable to variables with different primitive types. * For example, 200 is type-assignable to short, int, long, float and double, but not byte. */ class LiteralNumber extends LiteralValue { /** * @param {string} value * @param {string} kind * @param {PrimitiveType} default_type */ constructor(value, kind, default_type) { super(value, default_type); this.numberValue = value; this.numberKind = kind; } static shift(a, b, op) { const ai = a.toInt(), bi = b.toInt(); if (ai === null || bi === null) { return null; } const val = op(ai, bi); const type = a.type.typeSignature === 'J' ? PrimitiveType.map.J : PrimitiveType.map.I; return new LiteralNumber(val.toString(), 'int-number-literal', type); } static bitwise(a, b, op) { const ai = a.toInt(), bi = b.toInt(); if (ai === null || bi === null) { return null; } const val = op(ai, bi); const typekey = a.type.typeSignature+ b.type.typeSignature; let type = /J/.test(typekey) ? PrimitiveType.map.J : PrimitiveType.map.I; return new LiteralNumber(val.toString(), 'int-number-literal', type); } static math(a, b, op, divmod) { const ai = a.toNumber(), bi = b.toNumber(); if (bi === 0 && divmod) { return null; } let val = op(ai, bi); const typekey = a.type.typeSignature+ b.type.typeSignature; if (!/[FD]/.test(typekey) && divmod) { val = Math.trunc(val); } let type; if (/^(D|F[^D]|J[^FD])/.test(typekey)) { type = a.type; } else { type = b.type; } return new LiteralNumber(val.toString(), 'int-number-literal', type); } static '+'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a + b) } static '-'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a - b) } static '*'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a * b) } static '/'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a / b, true) } static '%'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a % b, true) } static '&'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a & b) } static '|'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a | b) } static '^'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a ^ b) } static '>>'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => a >> b) } static '>>>'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => { // unsigned shift (>>>) is not supported by bigints // @ts-ignore return (a >> b) & ~(-1n << (64n - b)); }) } static '<<'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => a << b) } toInt() { switch (this.numberKind) { case 'hex-number-literal': case 'int-number-literal': // unlike parseInt, BigInt doesn't like invalid characters, so // ensure we strip any trailing long specifier return BigInt(this.name.match(/(.+?)[lL]?$/)[1]); } return null; } toNumber() { return parseFloat(this.name); } /** * @param {JavaType} type */ isCompatibleWith(type) { if (this.type === type) { return true; } switch(this.type.simpleTypeName) { case 'double': return /^([D]|Ljava\/lang\/(Double);)$/.test(type.typeSignature); case 'float': return /^([FD]|Ljava\/lang\/(Float|Double);)$/.test(type.typeSignature); } // all integral types are all compatible with long, float and double variables if (/^([JFD]|Ljava\/lang\/(Long|Float|Double);)$/.test(type.typeSignature)) { return true; } // the desintation type must be a number primitive or one of the corresponding boxed classes if (!/^([BSIJFDC]|Ljava\/lang\/(Byte|Short|Integer|Long|Float|Double|Character);)$/.test(type.typeSignature)) { return false; } let number = 0; if (this.numberKind === 'hex-number-literal') { if (this.numberValue !== '0x') { const non_leading_zero_digits = this.numberValue.match(/0x0*(.+)/)[1]; number = non_leading_zero_digits.length > 8 ? Number.MAX_SAFE_INTEGER : parseInt(non_leading_zero_digits, 16); } } else if (this.numberKind === 'int-number-literal') { const non_leading_zero_digits = this.numberValue.match(/0*(.+)/)[1]; number = non_leading_zero_digits.length > 10 ? Number.MAX_SAFE_INTEGER : parseInt(non_leading_zero_digits, 10); } if (number >= -128 && number <= 127) { return true; // byte values are compatible with all other numbers } if (number >= -32768 && number <= 32767) { return !/^([B]|Ljava\/lang\/(Byte);)$/.test(type.typeSignature); // anything except byte } return !/^([BSC]|Ljava\/lang\/(Byte|Short|Character);)$/.test(type.typeSignature); // anything except byte, short and character } /** * @param {Token} token */ static from(token) { function suffix(which) { switch(which.indexOf(token.value.slice(-1))) { case 0: case 1: return PrimitiveType.map.F; case 2: case 3: return PrimitiveType.map.D; case 4: case 5: return PrimitiveType.map.J; } } switch(token.kind) { case 'dec-exp-number-literal': case 'dec-number-literal': return new LiteralNumber(token.value, token.kind, suffix('FfDdLl') || PrimitiveType.map.D); case 'hex-number-literal': return new LiteralNumber(token.value, token.kind, suffix(' Ll') || PrimitiveType.map.I); case 'int-number-literal': default: return new LiteralNumber(token.value, token.kind, suffix('FfDdLl') || PrimitiveType.map.I); } } } class MethodCall extends Value { /** * @param {string} name * @param {ResolvedIdent} instance * @param {Method} method */ constructor(name, instance, method) { super(name, method.returnType); this.instance = instance; this.method = method; } } class ConstructorCall extends Value { /** * @param {string} name * @param {JavaType} type */ constructor(name, type) { super(name, type); } } class ArrayLiteral extends LiteralValue { /** * @param {string} name * @param {ResolvedIdent[]} elements */ constructor(name, elements) { super(name, null); this.elements = elements; } } class TernaryValue extends Value { /** * @param {string} name * @param {JavaType} true_type * @param {Token} colon * @param {Value} false_value */ constructor(name, true_type, colon, false_value) { super(name, true_type); this.colon = colon; this.falseValue = false_value; } } exports.AnyMethod = AnyMethod; exports.AnyType = AnyType; exports.AnyValue = AnyValue; exports.ArrayElement = ArrayElement; exports.ArrayLiteral = ArrayLiteral; exports.ConstructorCall = ConstructorCall; exports.Label = Label; exports.LiteralNumber = LiteralNumber; exports.LiteralValue = LiteralValue; exports.Local = Local; exports.MethodCall = MethodCall; exports.MethodDeclarations = MethodDeclarations; exports.ResolvedIdent = ResolvedIdent; exports.TernaryValue = TernaryValue; exports.Value = Value; exports.ValueBase = ValueBase;