implement method body and ststement validation

This commit is contained in:
Dave Holoway
2020-06-21 13:47:56 +01:00
parent a034a90735
commit 6badc9fdb6
25 changed files with 575 additions and 87 deletions

View File

@@ -8,6 +8,7 @@ const { JavaType, PrimitiveType } = require('java-mti');
const ParseProblem = require('../parsetypes/parse-problem');
const { AnyType, TypeIdentType } = require('../anys');
const { NumberLiteral } = require('./literals/Number');
const { checkTypeAssignable } = require('../expression-resolver');
class BinaryOpExpression extends Expression {
/**
@@ -27,46 +28,43 @@ class BinaryOpExpression extends Expression {
*/
resolveExpression(ri) {
const operator = this.op.value;
let lhstype = this.lhs.resolveExpression(ri);
let rhstype = this.rhs.resolveExpression(ri);
const lhsvalue = this.lhs.resolveExpression(ri);
const rhsvalue = this.rhs.resolveExpression(ri);
if (lhstype instanceof AnyType || rhstype instanceof AnyType) {
if (lhsvalue instanceof AnyType || rhsvalue instanceof AnyType) {
return AnyType.Instance;
}
if (lhstype instanceof NumberLiteral || rhstype instanceof NumberLiteral) {
if (lhstype instanceof NumberLiteral && rhstype instanceof NumberLiteral) {
if (lhsvalue instanceof NumberLiteral || rhsvalue instanceof NumberLiteral) {
if (lhsvalue instanceof NumberLiteral && rhsvalue instanceof NumberLiteral) {
// if they are both literals, compute the result
if (/^[*/%+-]$/.test(operator)) {
return NumberLiteral[operator](lhstype, rhstype);
return NumberLiteral[operator](lhsvalue, rhsvalue);
}
if (/^([&|^]|<<|>>>?)$/.test(operator) && !/[FD]/.test(`${lhstype.type.typeSignature}${rhstype.type.typeSignature}`)) {
return NumberLiteral[operator](lhstype, rhstype);
if (/^([&|^]|<<|>>>?)$/.test(operator) && !/[FD]/.test(`${lhsvalue.type.typeSignature}${rhsvalue.type.typeSignature}`)) {
return NumberLiteral[operator](lhsvalue, rhsvalue);
}
}
if (lhstype instanceof NumberLiteral) {
lhstype = lhstype.type;
}
if (rhstype instanceof NumberLiteral) {
rhstype = rhstype.type;
}
}
const lhstype = lhsvalue instanceof JavaType ? lhsvalue : lhsvalue instanceof NumberLiteral ? lhsvalue.type : null;
const rhstype = rhsvalue instanceof JavaType ? rhsvalue : rhsvalue instanceof NumberLiteral ? rhsvalue.type : null;
if (operator === 'instanceof') {
if (!(rhstype instanceof TypeIdentType)) {
if (!(rhsvalue instanceof TypeIdentType)) {
ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Type expected`));
}
if (!(lhstype instanceof JavaType)) {
if (!lhstype) {
ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`));
}
return PrimitiveType.map.Z;
}
if (!(lhstype instanceof JavaType) || !(rhstype instanceof JavaType)) {
if (!(lhstype instanceof JavaType)) {
if (!lhstype || !rhstype) {
if (!lhstype) {
ri.problems.push(ParseProblem.Error(this.lhs.tokens, `Expression expected`));
}
if (!(rhstype instanceof JavaType)) {
if (!rhstype) {
ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`));
}
return AnyType.Instance;
@@ -74,13 +72,17 @@ class BinaryOpExpression extends Expression {
const typekey = `${lhstype.typeSignature}#${rhstype.typeSignature}`;
if (operator === '+' && typekey.startsWith('Ljava/lang/String;')) {
if (operator === '+' && /(^|#)Ljava\/lang\/String;/.test(typekey)) {
// string appending is compatible with all types
return lhstype;
return ri.typemap.get('java/lang/String');
}
if (/^([*/%&|^+-]?=|<<=|>>>?=)$/.test(operator)) {
checkOperator(operator.slice(0,-1), ri, this.op, typekey, lhstype, rhstype);
let src_type = rhsvalue;
if (operator.length > 1) {
src_type = checkOperator(operator.slice(0,-1), ri, this.op, typekey, lhstype, rhstype);
}
checkTypeAssignable(lhstype, src_type, () => this.rhs.tokens, ri.problems);
// result of assignments are lhs
return lhstype;
}
@@ -104,9 +106,14 @@ class BinaryOpExpression extends Expression {
*/
function checkOperator(operator, ri, operator_token, typekey, lhstype, rhstype) {
if (operator === '+' && /(^|#)Ljava\/lang\/String;/.test(typekey)) {
// string appending is compatible with all types
return ri.typemap.get('java/lang/String');
}
if (/^[*/%+-]$/.test(operator)) {
// math operators - must be numeric
if (!/^[BSIJFD]#[BSIJFD]$/.test(typekey)) {
if (!/^[BSIJFDC]#[BSIJFDC]$/.test(typekey)) {
ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`));
}
if (/^(D|F#[^D]|J#[^FD]|I#[^JFD])/.test(typekey)) {
@@ -120,7 +127,7 @@ function checkOperator(operator, ri, operator_token, typekey, lhstype, rhstype)
if (/^(<<|>>>?)$/.test(operator)) {
// shift operators - must be integral
if (!/^[BSIJ]#[BSIJ]$/.test(typekey)) {
if (!/^[BSIJC]#[BSIJC]$/.test(typekey)) {
ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`));
}
if (/^J/.test(typekey)) {
@@ -131,7 +138,7 @@ function checkOperator(operator, ri, operator_token, typekey, lhstype, rhstype)
if (/^[&|^]$/.test(operator)) {
// bitwise or logical operators
if (!/^[BSIJ]#[BSIJ]$|^Z#Z$/.test(typekey)) {
if (!/^[BSIJC]#[BSIJC]$|^Z#Z$/.test(typekey)) {
ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`));
}
if (/^[JZ]/.test(typekey)) {

View File

@@ -4,10 +4,11 @@
* @typedef {import('../anys').ResolvedValue} ResolvedValue
*/
const { Expression } = require("./Expression");
const { AnyType, TypeIdentType } = require('../anys');
const { AnyType, MultiValueType, TypeIdentType } = require('../anys');
const ParseProblem = require('../parsetypes/parse-problem');
const { JavaType, PrimitiveType, NullType, CEIType, ArrayType } = require('java-mti');
const { getTypeInheritanceList } = require('../expression-resolver');
const { NumberLiteral } = require('../expressiontypes/literals/Number');
class CastExpression extends Expression {
/**
@@ -55,6 +56,14 @@ function checkCastable(cast, cast_type, expr_type, problems) {
}
return;
}
if (expr_type instanceof NumberLiteral) {
checkCastable(cast, cast_type, expr_type.type, problems);
return;
}
if (expr_type instanceof MultiValueType) {
expr_type.types.forEach(type => checkCastable(cast, cast_type, type, problems));
return;
}
problems.push(ParseProblem.Error(cast.expression.tokens, `Invalid cast: expression is not a value or variable`));
}

View File

@@ -4,7 +4,7 @@
* @typedef {import('../tokenizer').Token} Token
*/
const { Expression } = require("./Expression");
const { CEIType } = require('java-mti');
const { JavaType, CEIType } = require('java-mti');
const { AnyType, MethodType, PackageNameType, TypeIdentType } = require('../anys');
const { getTypeInheritanceList } = require('../expression-resolver');
const { resolveNextPackage } = require('../type-resolver');
@@ -46,7 +46,7 @@ class MemberExpression extends Expression {
: AnyType.Instance;
}
if (!(instance instanceof CEIType)) {
if (!(instance instanceof JavaType)) {
ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`));
return AnyType.Instance;
}
@@ -54,6 +54,12 @@ class MemberExpression extends Expression {
if (field) {
return field.type;
}
if (!(instance instanceof CEIType)) {
ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`));
return AnyType.Instance;
}
let methods = new Map();
getTypeInheritanceList(instance).forEach(type => {
type.methods.forEach(m => {

View File

@@ -81,30 +81,29 @@ class NumberLiteral extends LiteralValue {
* @param {NumberLiteral} a
* @param {NumberLiteral} b
* @param {(a,b) => Number} op
* @param {boolean} [divmod]
*/
static math(a, b, op, divmod) {
static math(a, b, op) {
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) {
if (!/[FD]/.test(typekey)) {
val = Math.trunc(val);
}
const type = typekey.includes('D') ? PrimitiveType.map.D
: typekey.includes('F') ? PrimitiveType.map.F
: typekey.includes('J') ? PrimitiveType.map.J
: PrimitiveType.map.I;
// note: Java allows integer division by zero at compile-time - it will
// always cause an ArithmeticException at runtime, so the result here (inf or nan)
// is largely meaningless
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 '*'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a * b) }
static '/'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a / b, true) }
static '%'(lhs, rhs) { return NumberLiteral.math(lhs, rhs, (a,b) => a % b, true) }
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.bitwise(lhs, rhs, (a,b) => a & b) }
static '|'(lhs, rhs) { return NumberLiteral.bitwise(lhs, rhs, (a,b) => a | b) }
static '^'(lhs, rhs) { return NumberLiteral.bitwise(lhs, rhs, (a,b) => a ^ b) }