From df210b46594b4773f9863c2f0a69fce96ae5417c Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Mon, 22 Jun 2020 11:09:50 +0100 Subject: [PATCH] add checks for unary operators --- langserver/java/body-parser3.js | 11 ++- .../java/expressiontypes/UnaryOpExpression.js | 96 +++++++++++++++++++ .../java/expressiontypes/literals/Number.js | 37 ++++++- 3 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 langserver/java/expressiontypes/UnaryOpExpression.js diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index adc562a..4dabd53 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -32,6 +32,7 @@ const { MethodCallExpression } = require("./expressiontypes/MethodCallExpression const { NewArray, NewObject } = require("./expressiontypes/NewExpression"); const { TernaryOpExpression } = require("./expressiontypes/TernaryOpExpression"); const { ThisMemberExpression } = require("./expressiontypes/ThisMemberExpression"); +const { UnaryOpExpression } = require("./expressiontypes/UnaryOpExpression"); const { Variable } = require("./expressiontypes/Variable"); const { BooleanLiteral } = require('./expressiontypes/literals/Boolean'); @@ -1512,13 +1513,17 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) { matches = new ResolvedIdent(tokens.current, [NumberLiteral.from(tokens.current)]); break; case 'inc-operator': - let incop = tokens.getIfKind('inc-operator'); + let incop = tokens.consume(); matches = qualifiedTerm(tokens, mdecls, scope, imports, typemap); return new ResolvedIdent(`${incop.value}${matches.source}`, [new IncDecExpression(matches, incop, 'prefix')]) case 'plumin-operator': case 'unary-operator': - tokens.inc(); - return qualifiedTerm(tokens, mdecls, scope, imports, typemap); + let unaryop = tokens.consume(); + matches = qualifiedTerm(tokens, mdecls, scope, imports, typemap); + let unary_value = matches.variables[0] instanceof NumberLiteral + ? NumberLiteral[unaryop.value](matches.variables[0]) + : new UnaryOpExpression(matches, unaryop); + return new ResolvedIdent(`${unaryop.value}${matches.source}`, [unary_value]) case 'new-operator': return newTerm(tokens, mdecls, scope, imports, typemap); case 'open-bracket': diff --git a/langserver/java/expressiontypes/UnaryOpExpression.js b/langserver/java/expressiontypes/UnaryOpExpression.js new file mode 100644 index 0000000..502eac0 --- /dev/null +++ b/langserver/java/expressiontypes/UnaryOpExpression.js @@ -0,0 +1,96 @@ +/** + * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo + * @typedef {import('../tokenizer').Token} Token + */ +const { Expression } = require("./Expression"); +const { JavaType, PrimitiveType } = require('java-mti'); +const ParseProblem = require('../parsetypes/parse-problem'); +const { AnyType } = require('../anys'); +const { NumberLiteral } = require('./literals/Number'); + +class UnaryOpExpression extends Expression { + /** + * @param {ResolvedIdent} expression + * @param {Token} op + */ + constructor(expression, op) { + super(); + this.expression = expression; + this.op = op; + } + + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const operator = this.op.value; + const value = this.expression.resolveExpression(ri); + + if (value instanceof AnyType) { + return AnyType.Instance; + } + + if (value instanceof NumberLiteral) { + if (/^[+-]$/.test(operator)) { + return NumberLiteral[operator](value); + } + if (/^[!~]$/.test(operator) && value.type.typeSignature === 'I') { + return NumberLiteral[operator](value); + } + } + + const type = value instanceof JavaType ? value : value instanceof NumberLiteral ? value.type : null; + + if (!type) { + ri.problems.push(ParseProblem.Error(this.expression.tokens, `Expression expected`)); + return AnyType.Instance; + } + + return checkOperator(operator, ri, this.op, type); + } + + tokens() { + return [this.op, ...this.expression.tokens]; + } +} + +/** + * + * @param {string} operator + * @param {ResolveInfo} ri + * @param {Token} operator_token + * @param {JavaType} type + */ +function checkOperator(operator, ri, operator_token, type) { + + let is_valid = false; + /** @type {JavaType} */ + let return_type = AnyType.Instance; + + if (/^[+-]$/.test(operator)) { + // math operators - must be numeric + is_valid = /^[BSIJFDC]$/.test(type.typeSignature); + return_type = type; + } + + if (/^~$/.test(operator)) { + // bitwise invert operator - must be integral + is_valid = /^[BSIJC]$/.test(type.typeSignature); + return_type = PrimitiveType.map.I; + } + + if (/^!$/.test(operator)) { + // logical not operator - must be boolean + is_valid = /^Z$/.test(type.typeSignature); + return_type = PrimitiveType.map.Z; + } + + if (!is_valid) { + ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for type '${type.fullyDottedTypeName}'`)); + } + + return return_type; +} + +exports.UnaryOpExpression = UnaryOpExpression; diff --git a/langserver/java/expressiontypes/literals/Number.js b/langserver/java/expressiontypes/literals/Number.js index f935e77..918d508 100644 --- a/langserver/java/expressiontypes/literals/Number.js +++ b/langserver/java/expressiontypes/literals/Number.js @@ -77,6 +77,32 @@ class NumberLiteral extends LiteralValue { return NumberLiteral.calc(a, b, 'int-number-literal', type, val); } + /** + * @param {NumberLiteral} a + * @param {string} opvalue + * @param {(a) => Number} op + */ + static unary(a, opvalue, op) { + if (opvalue === '-') { + const ai = a.toNumber(); + if (ai === null) { + return null; + } + const val = op(ai); + const type = PrimitiveType.map[a.type.typeSignature]; + const toks = a.tokens(); + return new NumberLiteral(Array.isArray(toks) ? toks : [toks], 'int-number-literal', type, val.toString()); + } + const ai = a.toInt(); + if (ai === null) { + return null; + } + const val = op(ai); + const type = /J/.test(a.type.typeSignature) ? PrimitiveType.map.J : PrimitiveType.map.I; + const toks = a.tokens(); + return new NumberLiteral(Array.isArray(toks) ? toks : [toks], 'int-number-literal', type, val.toString()); + } + /** * @param {NumberLiteral} a * @param {NumberLiteral} b @@ -99,8 +125,15 @@ class NumberLiteral extends LiteralValue { return NumberLiteral.calc(a, b, 'int-number-literal', type, val); } - static '+'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a + b) } - static '-'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a - b) } + static '~'(value) { return NumberLiteral.unary(value, '~', (a) => ~a) } + static '+'(lhs, rhs) { return !rhs + ? lhs // unary e.g +5 + : NumberLiteral.math(lhs, rhs, (a,b) => a + b) + } + static '-'(lhs, rhs) { return !rhs + ? NumberLiteral.unary(lhs, '-', (a) => -a) + : NumberLiteral.math(lhs, rhs, (a,b) => a - b) + } static '*'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a * b) } static '/'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a / b) } static '%'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a % b) }