diff --git a/langserver/java/anys.js b/langserver/java/anys.js index 8a80610..a2720da 100644 --- a/langserver/java/anys.js +++ b/langserver/java/anys.js @@ -130,7 +130,8 @@ class MultiValueType { } /** - * @typedef {JavaType|MethodType|LambdaType|ArrayValueType|TypeIdentType|MultiValueType} ResolvedValue + * @typedef {import('./expressiontypes/literals/Number').NumberLiteral} NumberLiteral + * @typedef {JavaType|MethodType|LambdaType|ArrayValueType|TypeIdentType|MultiValueType|NumberLiteral} ResolvedValue **/ exports.AnyMethod = AnyMethod; diff --git a/langserver/java/expression-resolver.js b/langserver/java/expression-resolver.js index 5916697..9767418 100644 --- a/langserver/java/expression-resolver.js +++ b/langserver/java/expression-resolver.js @@ -24,16 +24,6 @@ function checkAssignment(e, assign_type, typemap, problems) { checkTypeAssignable(assign_type, value.type, () => value.name_token, problems); return; } - if (value instanceof NumberLiteral) { - if (!value.isCompatibleWith(assign_type)) { - incompatibleTypesError(assign_type, value.type, () => value.token, problems); - } - return; - } - if (value instanceof LiteralValue) { - checkTypeAssignable(assign_type, value.type, () => value.token, problems); - return; - } if (value instanceof Expression) { const expression_result_type = value.resolveExpression(new ResolveInfo(typemap, problems)); checkTypeAssignable(assign_type, expression_result_type, () => value.tokens(), problems); @@ -44,22 +34,28 @@ function checkAssignment(e, assign_type, typemap, problems) { /** * * @param {JavaType} variable_type - * @param {ResolvedValue} value_type + * @param {ResolvedValue} value * @param {() => Token|Token[]} tokens * @param {ParseProblem[]} problems */ -function checkTypeAssignable(variable_type, value_type, tokens, problems) { - if (value_type instanceof MultiValueType) { - value_type.types.forEach(t => checkTypeAssignable(variable_type, t, tokens, problems)); +function checkTypeAssignable(variable_type, value, tokens, problems) { + if (value instanceof NumberLiteral) { + if (!value.isCompatibleWith(variable_type)) { + incompatibleTypesError(variable_type, value.type, () => value.tokens(), problems); + } return; } - if (value_type instanceof ArrayValueType) { - checkArrayLiteral(variable_type, value_type, tokens, problems); + if (value instanceof MultiValueType) { + value.types.forEach(t => checkTypeAssignable(variable_type, t, tokens, problems)); return; } - if (value_type instanceof JavaType) { - if (!isTypeAssignable(variable_type, value_type)) { - incompatibleTypesError(variable_type, value_type, tokens, problems); + if (value instanceof ArrayValueType) { + checkArrayLiteral(variable_type, value, tokens, problems); + return; + } + if (value instanceof JavaType) { + if (!isTypeAssignable(variable_type, value)) { + incompatibleTypesError(variable_type, value, tokens, problems); } return; } @@ -104,6 +100,12 @@ function checkArrayLiteral(variable_type, value_type, tokens, problems) { * @param {Token[]} tokens */ function checkArrayElement(element_type, value_type, tokens) { + if (value_type instanceof NumberLiteral) { + if (!value_type.isCompatibleWith(element_type)) { + incompatibleTypesError(element_type, value_type.type, () => tokens, problems); + } + return; + } if (value_type instanceof JavaType) { if (!isTypeAssignable(element_type, value_type)) { incompatibleTypesError(element_type, value_type, () => tokens, problems); @@ -125,6 +127,15 @@ function checkArrayLiteral(variable_type, value_type, tokens, problems) { */ function checkArrayIndex(ri, d, kind) { const idx = d.resolveExpression(ri); + if (idx instanceof NumberLiteral) { + if (!idx.isCompatibleWith(PrimitiveType.map.I)) { + ri.problems.push(ParseProblem.Error(d.tokens, `Value '${idx.toNumber()}' is not valid as an array ${kind}`)); + } + else if (idx.toNumber() < 0) { + ri.problems.push(ParseProblem.Error(d.tokens, `Negative array ${kind}: ${idx.toNumber()}`)); + } + return; + } if (idx instanceof PrimitiveType) { if (!/^[BSI]$/.test(idx.typeSignature)) { ri.problems.push(ParseProblem.Error(d.tokens, `Expression of type '${idx.label}' is not valid as an array ${kind}`)); diff --git a/langserver/java/expressiontypes/BinaryOpExpression.js b/langserver/java/expressiontypes/BinaryOpExpression.js index 8158aa7..59ecc26 100644 --- a/langserver/java/expressiontypes/BinaryOpExpression.js +++ b/langserver/java/expressiontypes/BinaryOpExpression.js @@ -7,6 +7,7 @@ const { Expression } = require("./Expression"); const { JavaType, PrimitiveType } = require('java-mti'); const ParseProblem = require('../parsetypes/parse-problem'); const { AnyType, TypeIdentType } = require('../anys'); +const { NumberLiteral } = require('./literals/Number'); class BinaryOpExpression extends Expression { /** @@ -26,13 +27,31 @@ class BinaryOpExpression extends Expression { */ resolveExpression(ri) { const operator = this.op.value; - const lhstype = this.lhs.resolveExpression(ri); - const rhstype = this.rhs.resolveExpression(ri); + let lhstype = this.lhs.resolveExpression(ri); + let rhstype = this.rhs.resolveExpression(ri); if (lhstype instanceof AnyType || rhstype instanceof AnyType) { return AnyType.Instance; } + if (lhstype instanceof NumberLiteral || rhstype instanceof NumberLiteral) { + if (lhstype instanceof NumberLiteral && rhstype instanceof NumberLiteral) { + // if they are both literals, compute the result + if (/^[*/%+-]$/.test(operator)) { + return NumberLiteral[operator](lhstype, rhstype); + } + if (/^([&|^]|<<|>>>?)$/.test(operator) && !/[FD]/.test(`${lhstype.type.typeSignature}${rhstype.type.typeSignature}`)) { + return NumberLiteral[operator](lhstype, rhstype); + } + } + if (lhstype instanceof NumberLiteral) { + lhstype = lhstype.type; + } + if (rhstype instanceof NumberLiteral) { + rhstype = rhstype.type; + } + } + if (operator === 'instanceof') { if (!(rhstype instanceof TypeIdentType)) { ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Type expected`)); diff --git a/langserver/java/expressiontypes/literals/LiteralValue.js b/langserver/java/expressiontypes/literals/LiteralValue.js index aef6421..897fd88 100644 --- a/langserver/java/expressiontypes/literals/LiteralValue.js +++ b/langserver/java/expressiontypes/literals/LiteralValue.js @@ -2,29 +2,31 @@ * @typedef {import('java-mti').JavaType} JavaType * @typedef {import('../../body-types').ResolveInfo} ResolveInfo * @typedef {import('../../tokenizer').Token} Token + * @typedef {import('../../anys').ResolvedValue} ResolvedValue */ const { Expression } = require('../Expression'); class LiteralValue extends Expression { /** - * @param {Token} token + * @param {Token|Token[]} tokens * @param {JavaType} known_type */ - constructor(token, known_type) { + constructor(tokens, known_type) { super(); - this.token = token; + this._tokens = tokens; this.type = known_type; } /** * @param {ResolveInfo} ri + * @returns {ResolvedValue} */ resolveExpression(ri) { return this.type; } tokens() { - return this.token; + return this._tokens; } } diff --git a/langserver/java/expressiontypes/literals/Number.js b/langserver/java/expressiontypes/literals/Number.js index f7c2bf3..bfa4fb8 100644 --- a/langserver/java/expressiontypes/literals/Number.js +++ b/langserver/java/expressiontypes/literals/Number.js @@ -1,6 +1,7 @@ /** * @typedef {import('../../tokenizer').Token} Token * @typedef {import('java-mti').JavaType} JavaType + * @typedef {import('../../body-types').ResolveInfo} ResolveInfo */ const { LiteralValue } = require('./LiteralValue'); const { PrimitiveType } = require('java-mti'); @@ -13,15 +14,43 @@ const { PrimitiveType } = require('java-mti'); */ class NumberLiteral extends LiteralValue { /** - * @param {Token} value + * @param {Token[]} tokens * @param {string} kind * @param {PrimitiveType} default_type + * @param {string} [value] */ - constructor(value, kind, default_type) { - super(value, default_type); + constructor(tokens, kind, default_type, value = tokens[0].value) { + super(tokens, default_type); + this.value = value; this.numberKind = kind; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + return this; + } + + /** + * @param {NumberLiteral} a + * @param {NumberLiteral} b + * @param {string} kind + * @param {PrimitiveType} type + * @param {number} value + */ + static calc(a, b, kind, type, value) { + let atoks = a.tokens(), btoks = b.tokens(); + atoks = Array.isArray(atoks) ? atoks : [atoks]; + btoks = Array.isArray(btoks) ? btoks : [btoks]; + return new NumberLiteral([...atoks, ...btoks], kind, type, value.toString()); + } + + /** + * @param {NumberLiteral} a + * @param {NumberLiteral} b + * @param {(a,b) => Number} op + */ static shift(a, b, op) { const ai = a.toInt(), bi = b.toInt(); if (ai === null || bi === null) { @@ -29,9 +58,14 @@ class NumberLiteral extends LiteralValue { } const val = op(ai, bi); const type = a.type.typeSignature === 'J' ? PrimitiveType.map.J : PrimitiveType.map.I; - return new NumberLiteral(val.toString(), 'int-number-literal', type); + return NumberLiteral.calc(a, b, 'int-number-literal', type, val); } + /** + * @param {NumberLiteral} a + * @param {NumberLiteral} b + * @param {(a,b) => Number} op + */ static bitwise(a, b, op) { const ai = a.toInt(), bi = b.toInt(); if (ai === null || bi === null) { @@ -39,27 +73,31 @@ class NumberLiteral extends LiteralValue { } 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 NumberLiteral(val.toString(), 'int-number-literal', type); + const type = /J/.test(typekey) ? PrimitiveType.map.J : PrimitiveType.map.I; + return NumberLiteral.calc(a, b, 'int-number-literal', type, val); } + /** + * @param {NumberLiteral} a + * @param {NumberLiteral} b + * @param {(a,b) => Number} op + * @param {boolean} [divmod] + */ 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; + 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 NumberLiteral(val.toString(), 'int-number-literal', type); + const type = typekey.includes('D') ? PrimitiveType.map.D + : typekey.includes('F') ? PrimitiveType.map.F + : typekey.includes('J') ? PrimitiveType.map.J + : PrimitiveType.map.I; + return NumberLiteral.calc(a, b, 'int-number-literal', type, val); } static '+'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a + b) } @@ -84,13 +122,13 @@ class NumberLiteral extends LiteralValue { case 'int-number-literal': // unlike parseInt, BigInt doesn't like invalid characters, so // ensure we strip any trailing long specifier - return BigInt(this.token.value.match(/(.+?)[lL]?$/)[1]); + return BigInt(this.value.match(/(.+?)[lL]?$/)[1]); } return null; } toNumber() { - return parseFloat(this.token.value); + return parseFloat(this.value); } /** @@ -116,12 +154,12 @@ class NumberLiteral extends LiteralValue { } let number = 0; if (this.numberKind === 'hex-number-literal') { - if (this.token.value !== '0x') { - const non_leading_zero_digits = this.token.value.match(/0x0*(.+)/)[1]; + if (this.value !== '0x') { + const non_leading_zero_digits = this.value.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.token.value.match(/0*(.+)/)[1]; + const non_leading_zero_digits = this.value.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) { @@ -153,12 +191,12 @@ class NumberLiteral extends LiteralValue { switch(token.kind) { case 'dec-exp-number-literal': case 'dec-number-literal': - return new NumberLiteral(token, token.kind, suffix('FfDdLl') || PrimitiveType.map.D); + return new NumberLiteral([token], token.kind, suffix('FfDdLl') || PrimitiveType.map.D); case 'hex-number-literal': - return new NumberLiteral(token, token.kind, suffix(' Ll') || PrimitiveType.map.I); + return new NumberLiteral([token], token.kind, suffix(' Ll') || PrimitiveType.map.I); case 'int-number-literal': default: - return new NumberLiteral(token, token.kind, suffix('FfDdLl') || PrimitiveType.map.I); + return new NumberLiteral([token], token.kind, suffix('FfDdLl') || PrimitiveType.map.I); } } }