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

@@ -18,6 +18,15 @@ class TokenList {
this.marks = []; this.marks = [];
} }
/**
* Returns and consumes the current token
*/
consume() {
const tok = this.current;
this.inc();
return tok;
}
inc() { inc() {
for (; ;) { for (; ;) {
this.current = this.tokens[this.idx += 1]; this.current = this.tokens[this.idx += 1];

View File

@@ -14,9 +14,10 @@ const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resol
const { genericTypeArgs, typeIdent, typeIdentList } = require('./typeident'); const { genericTypeArgs, typeIdent, typeIdentList } = require('./typeident');
const { TokenList } = require("./TokenList"); const { TokenList } = require("./TokenList");
const { AnyMethod, AnyType, AnyValue } = require("./anys"); const { AnyMethod, AnyType, AnyValue } = require("./anys");
const { Label, Local, MethodDeclarations, ResolvedIdent } = require("./body-types"); const { Label, Local, MethodDeclarations, ResolvedIdent, ResolveInfo } = require("./body-types");
const { resolveImports, resolveSingleImport } = require('../java/import-resolver'); const { resolveImports, resolveSingleImport } = require('../java/import-resolver');
const { checkAssignment, getTypeInheritanceList } = require('./expression-resolver'); const { checkAssignment, getTypeInheritanceList } = require('./expression-resolver');
const { checkStatementBlock } = require('./statement-validater');
const { ArrayIndexExpression } = require("./expressiontypes/ArrayIndexExpression"); const { ArrayIndexExpression } = require("./expressiontypes/ArrayIndexExpression");
const { ArrayValueExpression } = require("./expressiontypes/ArrayValueExpression"); const { ArrayValueExpression } = require("./expressiontypes/ArrayValueExpression");
@@ -46,9 +47,11 @@ const { BreakStatement } = require("./statementtypes/BreakStatement");
const { ContinueStatement } = require("./statementtypes/ContinueStatement"); const { ContinueStatement } = require("./statementtypes/ContinueStatement");
const { DoStatement } = require("./statementtypes/DoStatement"); const { DoStatement } = require("./statementtypes/DoStatement");
const { EmptyStatement } = require("./statementtypes/EmptyStatement"); const { EmptyStatement } = require("./statementtypes/EmptyStatement");
const { ExpressionStatement } = require("./statementtypes/ExpressionStatement");
const { ForStatement } = require("./statementtypes/ForStatement"); const { ForStatement } = require("./statementtypes/ForStatement");
const { IfStatement } = require("./statementtypes/IfStatement"); const { IfStatement } = require("./statementtypes/IfStatement");
const { InvalidStatement } = require("./statementtypes/InvalidStatement"); const { InvalidStatement } = require("./statementtypes/InvalidStatement");
const { LocalDeclStatement } = require("./statementtypes/LocalDeclStatement");
const { ReturnStatement } = require("./statementtypes/ReturnStatement"); const { ReturnStatement } = require("./statementtypes/ReturnStatement");
const { Statement } = require("./statementtypes/Statement"); const { Statement } = require("./statementtypes/Statement");
const { SwitchStatement } = require("./statementtypes/SwitchStatement"); const { SwitchStatement } = require("./statementtypes/SwitchStatement");
@@ -98,6 +101,7 @@ function parseBody(method, imports, typemap) {
let mdecls = new MethodDeclarations(); let mdecls = new MethodDeclarations();
try { try {
block = statementBlock(tokenlist, mdecls, method, imports, typemap); block = statementBlock(tokenlist, mdecls, method, imports, typemap);
checkStatementBlock(block, method, typemap, tokenlist.problems);
} catch (err) { } catch (err) {
addproblem(tokenlist, ParseProblem.Information(tokenlist.current, `Parse failed: ${err.message}`)); addproblem(tokenlist, ParseProblem.Information(tokenlist.current, `Parse failed: ${err.message}`));
@@ -214,8 +218,9 @@ function parse(source, typemap) {
timeEnd('parse'); timeEnd('parse');
// once all the types have been parsed, resolve any field initialisers // once all the types have been parsed, resolve any field initialisers
const ri = new ResolveInfo(typemap, tokens.problems);
unit.types.forEach(t => { unit.types.forEach(t => {
t.fields.filter(f => f.init).forEach(f => checkAssignment(f.init, f.type, typemap, tokens.problems)); t.fields.filter(f => f.init).forEach(f => checkAssignment(ri, f.type, f.init));
}); });
} catch(err) { } catch(err) {
@@ -380,7 +385,7 @@ function addLocals(tokens, mdecls, new_locals) {
* @param {SourceMC} method * @param {SourceMC} method
* @param {ResolvedImport[]} imports * @param {ResolvedImport[]} imports
* @param {Map<string,CEIType>} typemap * @param {Map<string,CEIType>} typemap
* @returns {ResolvedIdent|Local[]|Statement} * @returns {Statement}
*/ */
function statement(tokens, mdecls, method, imports, typemap) { function statement(tokens, mdecls, method, imports, typemap) {
let s, modifiers = []; let s, modifiers = [];
@@ -399,10 +404,10 @@ function statement(tokens, mdecls, method, imports, typemap) {
// modifiers are only allowed on local variable decls // modifiers are only allowed on local variable decls
if (modifiers.length) { if (modifiers.length) {
const type = typeIdent(tokens, method, imports, typemap); const type = typeIdent(tokens, method, imports, typemap);
s = var_ident_list(modifiers, type, null, tokens, mdecls, method, imports, typemap) const locals = var_ident_list(modifiers, type, null, tokens, mdecls, method, imports, typemap)
addLocals(tokens, mdecls, s); addLocals(tokens, mdecls, locals);
semicolon(tokens); semicolon(tokens);
return s; return new LocalDeclStatement(locals);
} }
switch(tokens.current.kind) { switch(tokens.current.kind) {
@@ -422,9 +427,12 @@ function statement(tokens, mdecls, method, imports, typemap) {
} }
// fall-through to expression_or_var_decl // fall-through to expression_or_var_decl
case 'primitive-type': case 'primitive-type':
s = expression_or_var_decl(tokens, mdecls, method, imports, typemap); const exp_or_vardecl = expression_or_var_decl(tokens, mdecls, method, imports, typemap);
if (Array.isArray(s)) { if (Array.isArray(exp_or_vardecl)) {
addLocals(tokens, mdecls, s); addLocals(tokens, mdecls, exp_or_vardecl);
s = new LocalDeclStatement(exp_or_vardecl);
} else {
s = new ExpressionStatement(exp_or_vardecl);
} }
semicolon(tokens); semicolon(tokens);
return s; return s;
@@ -438,7 +446,8 @@ function statement(tokens, mdecls, method, imports, typemap) {
case 'unary-operator': case 'unary-operator':
case 'open-bracket': case 'open-bracket':
case 'new-operator': case 'new-operator':
s = expression(tokens, mdecls, method, imports, typemap); const e = expression(tokens, mdecls, method, imports, typemap);
s = new ExpressionStatement(e);
semicolon(tokens); semicolon(tokens);
return s; return s;
} }
@@ -858,7 +867,7 @@ function statementBlock(tokens, mdecls, method, imports, typemap) {
const s = statement(tokens, mdecls, method, imports, typemap); const s = statement(tokens, mdecls, method, imports, typemap);
block.statements.push(s); block.statements.push(s);
} }
mdecls.popScope(); block.decls = mdecls.popScope();
return block; return block;
} }
@@ -898,21 +907,13 @@ function statementKeyword(tokens, mdecls, method, imports, typemap) {
s.statement = statement(tokens, mdecls, method, imports, typemap); s.statement = statement(tokens, mdecls, method, imports, typemap);
break; break;
case 'break': case 'break':
tokens.inc(); s = new BreakStatement(tokens.consume());
s = new BreakStatement(); s.target = tokens.getIfKind('ident');
if (tokens.current.kind === 'ident') {
s.target = tokens.current;
tokens.inc();
}
semicolon(tokens); semicolon(tokens);
break; break;
case 'continue': case 'continue':
tokens.inc(); s = new ContinueStatement(tokens.consume());
s = new ContinueStatement(); s.target = tokens.getIfKind('ident');
if (tokens.current.kind === 'ident') {
s.target = tokens.current;
tokens.inc();
}
semicolon(tokens); semicolon(tokens);
break; break;
case 'switch': case 'switch':
@@ -934,8 +935,8 @@ function statementKeyword(tokens, mdecls, method, imports, typemap) {
tryStatement(s, tokens, mdecls, method, imports, typemap); tryStatement(s, tokens, mdecls, method, imports, typemap);
break; break;
case 'return': case 'return':
s = new ReturnStatement(tokens.current);
tokens.inc(); tokens.inc();
s = new ReturnStatement();
s.expression = isExpressionStart(tokens.current) ? expression(tokens, mdecls, method, imports, typemap) : null; s.expression = isExpressionStart(tokens.current) ? expression(tokens, mdecls, method, imports, typemap) : null;
semicolon(tokens); semicolon(tokens);
break; break;
@@ -1234,25 +1235,24 @@ function caseBlock(s, tokens, mdecls, method, imports, typemap) {
* @param {Map<string,CEIType>} typemap * @param {Map<string,CEIType>} typemap
*/ */
function caseExpressionList(cases, tokens, mdecls, method, imports, typemap) { function caseExpressionList(cases, tokens, mdecls, method, imports, typemap) {
let c = caseExpression(cases, tokens, mdecls, method, imports, typemap); let c = caseExpression(tokens, mdecls, method, imports, typemap);
if (!c) { if (!c) {
return; return;
} }
while (c) { while (c) {
cases.push(c); cases.push(c);
c = caseExpression(cases, tokens, mdecls, method, imports, typemap); c = caseExpression(tokens, mdecls, method, imports, typemap);
} }
} }
/** /**
* @param {(ResolvedIdent|boolean)[]} cases
* @param {TokenList} tokens * @param {TokenList} tokens
* @param {MethodDeclarations} mdecls * @param {MethodDeclarations} mdecls
* @param {SourceMC} method * @param {SourceMC} method
* @param {ResolvedImport[]} imports * @param {ResolvedImport[]} imports
* @param {Map<string,CEIType>} typemap * @param {Map<string,CEIType>} typemap
*/ */
function caseExpression(cases, tokens, mdecls, method, imports, typemap) { function caseExpression(tokens, mdecls, method, imports, typemap) {
/** @type {boolean|ResolvedIdent} */ /** @type {boolean|ResolvedIdent} */
let e = tokens.isValue('default'); let e = tokens.isValue('default');
if (!e) { if (!e) {
@@ -1558,7 +1558,7 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) {
return new ResolvedIdent(ident, [new ArrayValueExpression(elements, open)]); return new ResolvedIdent(ident, [new ArrayValueExpression(elements, open)]);
default: default:
addproblem(tokens, ParseProblem.Error(tokens.current, 'Expression expected')); addproblem(tokens, ParseProblem.Error(tokens.current, 'Expression expected'));
return new ResolvedIdent(''); return new ResolvedIdent('', [new AnyValue('')]);
} }
tokens.inc(); tokens.inc();
return matches; return matches;

View File

@@ -121,8 +121,18 @@ class ResolveInfo {
} }
} }
class ValidateInfo extends ResolveInfo {
constructor(typemap, problems, method) {
super(typemap, problems);
this.method = method;
/** @type {('if'|'else'|'for'|'while'|'do'|'switch'|'try'|'synchronized')[]} */
this.statementStack = [];
}
}
exports.Label = Label; exports.Label = Label;
exports.Local = Local; exports.Local = Local;
exports.MethodDeclarations = MethodDeclarations; exports.MethodDeclarations = MethodDeclarations;
exports.ResolvedIdent = ResolvedIdent; exports.ResolvedIdent = ResolvedIdent;
exports.ResolveInfo = ResolveInfo; exports.ResolveInfo = ResolveInfo;
exports.ValidateInfo = ValidateInfo;

View File

@@ -7,28 +7,16 @@ const ParseProblem = require('./parsetypes/parse-problem');
const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti'); const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti');
const { AnyType, ArrayValueType, MultiValueType } = require('./anys'); const { AnyType, ArrayValueType, MultiValueType } = require('./anys');
const { ResolveInfo } = require('./body-types'); const { ResolveInfo } = require('./body-types');
const { LiteralValue } = require('./expressiontypes/literals/LiteralValue');
const { NumberLiteral } = require('./expressiontypes/literals/Number'); const { NumberLiteral } = require('./expressiontypes/literals/Number');
const { Expression } = require('./expressiontypes/Expression');
const { Variable } = require('./expressiontypes/Variable');
/** /**
* @param {import('./body-types').ResolvedIdent} e * @param {ResolveInfo} ri
* @param {ResolvedIdent} expression
* @param {JavaType} assign_type * @param {JavaType} assign_type
* @param {Map<string,CEIType>} typemap
* @param {ParseProblem[]} problems
*/ */
function checkAssignment(e, assign_type, typemap, problems) { function checkAssignment(ri, assign_type, expression) {
const value = e.variables[0]; const value = expression.resolveExpression(ri);
if (value instanceof Variable) { checkTypeAssignable(assign_type, value, () => expression.tokens, ri.problems);
checkTypeAssignable(assign_type, value.type, () => value.name_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);
return;
}
} }
/** /**
@@ -180,9 +168,14 @@ const valid_primitive_types = {
/** /**
* Returns true if a value of value_type is assignable to a variable of dest_type * Returns true if a value of value_type is assignable to a variable of dest_type
* @param {JavaType} dest_type * @param {JavaType} dest_type
* @param {JavaType} value_type * @param {JavaType|NumberLiteral} value_type
*/ */
function isTypeAssignable(dest_type, value_type) { function isTypeAssignable(dest_type, value_type) {
if (value_type instanceof NumberLiteral) {
return value_type.isCompatibleWith(dest_type);
}
let is_assignable = false; let is_assignable = false;
if (dest_type.typeSignature === value_type.typeSignature) { if (dest_type.typeSignature === value_type.typeSignature) {
// exact signature match // exact signature match
@@ -255,6 +248,23 @@ function isTypeArgumentCompatible(dest_typevar, value_typevar_type) {
return isTypeAssignable(dest_typevar.type, value_typevar_type); return isTypeAssignable(dest_typevar.type, value_typevar_type);
} }
/**
*
* @param {ResolvedValue} value
* @param {() => Token[]} tokens
* @param {ParseProblem[]} problems
*/
function checkBooleanBranchCondition(value, tokens, problems) {
if (value instanceof JavaType) {
if (!isTypeAssignable(PrimitiveType.map.Z, value)) {
problems.push(ParseProblem.Error(tokens(), `Boolean expression expected, but type '${value.fullyDottedTypeName}' found.`));
}
return;
}
problems.push(ParseProblem.Error(tokens(), `Boolean expression expected.`));
}
/** /**
* @param {CEIType} type * @param {CEIType} type
*/ */
@@ -276,6 +286,9 @@ function getTypeInheritanceList(type) {
return Array.from(types.done); return Array.from(types.done);
} }
exports.checkAssignment = checkAssignment;
exports.checkArrayIndex = checkArrayIndex; exports.checkArrayIndex = checkArrayIndex;
exports.checkAssignment = checkAssignment;
exports.checkBooleanBranchCondition = checkBooleanBranchCondition;
exports.checkTypeAssignable = checkTypeAssignable;
exports.getTypeInheritanceList = getTypeInheritanceList; exports.getTypeInheritanceList = getTypeInheritanceList;
exports.isTypeAssignable = isTypeAssignable;

View File

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

View File

@@ -4,10 +4,11 @@
* @typedef {import('../anys').ResolvedValue} ResolvedValue * @typedef {import('../anys').ResolvedValue} ResolvedValue
*/ */
const { Expression } = require("./Expression"); const { Expression } = require("./Expression");
const { AnyType, TypeIdentType } = require('../anys'); const { AnyType, MultiValueType, TypeIdentType } = require('../anys');
const ParseProblem = require('../parsetypes/parse-problem'); const ParseProblem = require('../parsetypes/parse-problem');
const { JavaType, PrimitiveType, NullType, CEIType, ArrayType } = require('java-mti'); const { JavaType, PrimitiveType, NullType, CEIType, ArrayType } = require('java-mti');
const { getTypeInheritanceList } = require('../expression-resolver'); const { getTypeInheritanceList } = require('../expression-resolver');
const { NumberLiteral } = require('../expressiontypes/literals/Number');
class CastExpression extends Expression { class CastExpression extends Expression {
/** /**
@@ -55,6 +56,14 @@ function checkCastable(cast, cast_type, expr_type, problems) {
} }
return; 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`)); 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 * @typedef {import('../tokenizer').Token} Token
*/ */
const { Expression } = require("./Expression"); const { Expression } = require("./Expression");
const { CEIType } = require('java-mti'); const { JavaType, CEIType } = require('java-mti');
const { AnyType, MethodType, PackageNameType, TypeIdentType } = require('../anys'); const { AnyType, MethodType, PackageNameType, TypeIdentType } = require('../anys');
const { getTypeInheritanceList } = require('../expression-resolver'); const { getTypeInheritanceList } = require('../expression-resolver');
const { resolveNextPackage } = require('../type-resolver'); const { resolveNextPackage } = require('../type-resolver');
@@ -46,7 +46,7 @@ class MemberExpression extends Expression {
: AnyType.Instance; : AnyType.Instance;
} }
if (!(instance instanceof CEIType)) { if (!(instance instanceof JavaType)) {
ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`)); ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`));
return AnyType.Instance; return AnyType.Instance;
} }
@@ -54,6 +54,12 @@ class MemberExpression extends Expression {
if (field) { if (field) {
return field.type; return field.type;
} }
if (!(instance instanceof CEIType)) {
ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`));
return AnyType.Instance;
}
let methods = new Map(); let methods = new Map();
getTypeInheritanceList(instance).forEach(type => { getTypeInheritanceList(instance).forEach(type => {
type.methods.forEach(m => { type.methods.forEach(m => {

View File

@@ -81,30 +81,29 @@ class NumberLiteral extends LiteralValue {
* @param {NumberLiteral} a * @param {NumberLiteral} a
* @param {NumberLiteral} b * @param {NumberLiteral} b
* @param {(a,b) => Number} op * @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(); const ai = a.toNumber(), bi = b.toNumber();
if (bi === 0 && divmod) {
return null;
}
let val = op(ai, bi); 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) { if (!/[FD]/.test(typekey)) {
val = Math.trunc(val); val = Math.trunc(val);
} }
const type = typekey.includes('D') ? PrimitiveType.map.D const type = typekey.includes('D') ? PrimitiveType.map.D
: typekey.includes('F') ? PrimitiveType.map.F : typekey.includes('F') ? PrimitiveType.map.F
: typekey.includes('J') ? PrimitiveType.map.J : typekey.includes('J') ? PrimitiveType.map.J
: PrimitiveType.map.I; : 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); 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) }
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) }
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.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) } 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) }

View File

@@ -0,0 +1,34 @@
const ParseProblem = require('./parsetypes/parse-problem');
const { CEIType } = require('java-mti')
const { SourceMethod, SourceConstructor, SourceInitialiser } = require('./source-types');
const { Block } = require("./statementtypes/Block");
const { Statement } = require("./statementtypes/Statement");
const { LocalDeclStatement } = require("./statementtypes/LocalDeclStatement");
const { ValidateInfo } = require('./body-types');
/**
* @param {Block} block
* @param {SourceMethod | SourceConstructor | SourceInitialiser} method
* @param {Map<string,CEIType>} typemap
* @param {ParseProblem[]} problems
*/
function checkStatementBlock(block, method, typemap, problems) {
block.validate(new ValidateInfo(typemap, problems, method));
}
/**
* @param {Statement} statement
* @param {ValidateInfo} vi
*/
function checkNonVarDeclStatement(statement, vi) {
if (statement instanceof LocalDeclStatement) {
vi.problems.push(ParseProblem.Error(statement.locals[0].decltoken, `Local variables cannot be declared as single conditional statements`));
};
statement.validate(vi);
}
exports.checkStatementBlock = checkStatementBlock;
exports.checkNonVarDeclStatement = checkNonVarDeclStatement;

View File

@@ -1,8 +1,37 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
const { isTypeAssignable } = require('../expression-resolver');
const { JavaType, PrimitiveType } = require('java-mti');
class AssertStatement extends Statement { class AssertStatement extends Statement {
/** @type {ResolvedIdent} */
expression = null; expression = null;
/** @type {ResolvedIdent} */
message = null; message = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (this.expression) {
const value = this.expression.resolveExpression(vi);
if (!(value instanceof JavaType) || !isTypeAssignable(PrimitiveType.map.Z, value)) {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `Boolean expression expected`));
}
}
if (this.message) {
const msg_value = this.message.resolveExpression(vi);
if (!(msg_value instanceof JavaType) || !isTypeAssignable(vi.typemap.get('java/lang/String'), msg_value)) {
vi.problems.push(ParseProblem.Error(this.message.tokens, `String expression expected`));
}
}
}
} }
exports.AssertStatement = AssertStatement; exports.AssertStatement = AssertStatement;

View File

@@ -1,12 +1,21 @@
/** /**
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').Local} Local
* @typedef {import('../body-types').Label} Label
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../source-types').SourceType} SourceType
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
const { checkAssignment } = require('../expression-resolver');
class Block extends Statement { class Block extends Statement {
/** @type {Statement[]} */ /** @type {Statement[]} */
statements = []; statements = [];
/** @type {{locals: Local[], labels: Label[], types: SourceType[]}} */
decls = null;
/** /**
* @param {Token} open * @param {Token} open
*/ */
@@ -14,6 +23,23 @@ class Block extends Statement {
super(); super();
this.open = open; this.open = open;
} }
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (this.decls) {
const locals = this.decls.locals.reverse();
locals.forEach(local => {
if (locals.find(l => l.name === local.name) !== local) {
vi.problems.push(ParseProblem.Error(local.decltoken, `Variable redeclared: ${local.name}`))
}
});
}
for (let statement of this.statements) {
statement.validate(vi);
}
}
} }
exports.Block = Block; exports.Block = Block;

View File

@@ -1,11 +1,30 @@
/** /**
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
class BreakStatement extends Statement { class BreakStatement extends Statement {
/** @type {Token} */ /** @type {Token} */
target = null; target = null;
/**
* @param {Token} token
*/
constructor(token) {
super();
this.break_token = token;
}
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (!vi.statementStack.find(s => /^(for|do|while|switch)$/.test(s))) {
vi.problems.push(ParseProblem.Error(this.break_token, `break can only be specified inside loop/switch statements`));
}
}
} }
exports.BreakStatement = BreakStatement; exports.BreakStatement = BreakStatement;

View File

@@ -1,11 +1,30 @@
/** /**
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
class ContinueStatement extends Statement { class ContinueStatement extends Statement {
/** @type {Token} */ /** @type {Token} */
target = null; target = null;
/**
* @param {Token} token
*/
constructor(token) {
super();
this.continue_token = token;
}
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (!vi.statementStack.find(s => /^(for|do|while)$/.test(s))) {
vi.problems.push(ParseProblem.Error(this.continue_token, `continue can only be specified inside loop statements`));
}
}
} }
exports.ContinueStatement = ContinueStatement; exports.ContinueStatement = ContinueStatement;

View File

@@ -1,14 +1,31 @@
/** /**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('./Block').Block} Block * @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../expressiontypes/Expression').Expression} Expression
* @typedef {import('../statementtypes/Block').Block} Block
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const { checkBooleanBranchCondition } = require('../expression-resolver');
class DoStatement extends Statement { class DoStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
test = null; test = null;
/** @type {Block} */ /** @type {Block} */
block = null; block = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (this.block) {
vi.statementStack.unshift('do');
this.block.validate(vi);
vi.statementStack.shift();
}
const value = this.test.resolveExpression(vi);
checkBooleanBranchCondition(value, () => this.test.tokens, vi.problems);
}
} }
exports.DoStatement = DoStatement; exports.DoStatement = DoStatement;

View File

@@ -0,0 +1,40 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../expressiontypes/Expression').Expression} Expression
*/
const { Statement } = require("./Statement");
const { BinaryOpExpression } = require('../expressiontypes/BinaryOpExpression');
const { MethodCallExpression } = require('../expressiontypes/MethodCallExpression');
const { NewObject } = require('../expressiontypes/NewExpression');
const { IncDecExpression } = require('../expressiontypes/IncDecExpression');
const ParseProblem = require('../parsetypes/parse-problem');
class ExpressionStatement extends Statement {
/**
* @param {ResolvedIdent} expression
*/
constructor(expression) {
super();
this.expression = expression;
}
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
// only method calls, new objects, increments and assignments are allowed as expression statements
const e = this.expression.variables[0];
let is_statement = e instanceof MethodCallExpression || e instanceof NewObject || e instanceof IncDecExpression;
if (e instanceof BinaryOpExpression) {
is_statement = e.op.kind === 'assignment-operator';
}
if (!is_statement) {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `Statement expected`));
}
this.expression.resolveExpression(vi);
}
}
exports.ExpressionStatement = ExpressionStatement;

View File

@@ -1,9 +1,11 @@
/** /**
* @typedef {import('../body-types').Local} Local * @typedef {import('../body-types').Local} Local
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const { checkNonVarDeclStatement } = require('../statement-validater');
class ForStatement extends Statement { class ForStatement extends Statement {
/** @type {ResolvedIdent[] | Local[]} */ /** @type {ResolvedIdent[] | Local[]} */
@@ -16,6 +18,18 @@ class ForStatement extends Statement {
iterable = null; iterable = null;
/** @type {Statement} */ /** @type {Statement} */
statement = null; statement = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (this.statement) {
vi.statementStack.unshift('for');
checkNonVarDeclStatement(this.statement, vi);
vi.statementStack.shift();
}
}
} }
exports.ForStatement = ForStatement; exports.ForStatement = ForStatement;

View File

@@ -1,7 +1,10 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const { checkBooleanBranchCondition } = require('../expression-resolver');
const { checkNonVarDeclStatement } = require('../statement-validater');
class IfStatement extends Statement { class IfStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
@@ -10,6 +13,25 @@ class IfStatement extends Statement {
statement = null; statement = null;
/** @type {Statement} */ /** @type {Statement} */
elseStatement = null; elseStatement = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
const value = this.test.resolveExpression(vi);
checkBooleanBranchCondition(value, () => this.test.tokens, vi.problems);
if (this.statement) {
vi.statementStack.unshift('if');
checkNonVarDeclStatement(this.statement, vi);
vi.statementStack.shift();
}
if (this.elseStatement) {
vi.statementStack.unshift('else');
checkNonVarDeclStatement(this.statement, vi);
vi.statementStack.shift();
}
}
} }
exports.IfStatement = IfStatement; exports.IfStatement = IfStatement;

View File

@@ -0,0 +1,33 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').Local} Local
* @typedef {import('../body-types').Label} Label
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../source-types').SourceType} SourceType
*/
const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
const { checkAssignment } = require('../expression-resolver');
class LocalDeclStatement extends Statement {
/**
* @param {Local[]} locals
*/
constructor(locals) {
super();
this.locals = locals;
}
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
this.locals.forEach(local => {
if (local.init) {
checkAssignment(vi, local.type, local.init);
}
});
}
}
exports.LocalDeclStatement = LocalDeclStatement;

View File

@@ -1,11 +1,59 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../tokenizer').Token} Token
*/ */
const { JavaType, PrimitiveType } = require('java-mti');
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
const { isTypeAssignable } = require('../expression-resolver');
const { NumberLiteral } = require('../expressiontypes/literals/Number');
const { MultiValueType } = require('../anys');
class ReturnStatement extends Statement { class ReturnStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
expression = null; expression = null;
/**
* @param {Token} return_token
*/
constructor(return_token) {
super();
this.return_token = return_token;
}
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
const method_return_type = vi.method.returnType;
if (!this.expression) {
if (method_return_type !== PrimitiveType.map.V) {
vi.problems.push(ParseProblem.Error(this.return_token, `Method must return a value of type '${method_return_type.fullyDottedTypeName}'`));
}
return;
}
if (method_return_type === PrimitiveType.map.V) {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `void method cannot return a value`));
return;
}
const type = this.expression.resolveExpression(vi);
checkType(type);
function checkType(type) {
if (type instanceof JavaType || type instanceof NumberLiteral) {
if (!isTypeAssignable(method_return_type, type)) {
const expr_type = type instanceof NumberLiteral ? type.type : type;
vi.problems.push(ParseProblem.Error(this.expression.tokens, `Incompatible types: expression of type '${expr_type.fullyDottedTypeName}' cannot be returned from a method of type '${method_return_type.fullyDottedTypeName}'`));
}
} else if (type instanceof MultiValueType) {
// ternary, eg. return x > 0 ? 1 : 2;
type.types.forEach(type => checkType(type));
} else {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `'${method_return_type.fullyDottedTypeName}' type expression expected`));
}
}
}
} }
exports.ReturnStatement = ReturnStatement; exports.ReturnStatement = ReturnStatement;

View File

@@ -1,4 +1,7 @@
class Statement { class Statement {
validate(vi) {}
} }
exports.Statement = Statement; exports.Statement = Statement;

View File

@@ -1,13 +1,68 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('../tokenizer').Token} Token
*/ */
const { JavaType, PrimitiveType } = require('java-mti');
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
const { isTypeAssignable } = require('../expression-resolver');
const { NumberLiteral } = require('../expressiontypes/literals/Number');
class SwitchStatement extends Statement { class SwitchStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
test = null; test = null;
/** @type {(ResolvedIdent|boolean)[]} */
cases = []; cases = [];
/** @type {{cases: (ResolvedIdent|boolean)[], statements: Statement[]} []} */
caseBlocks = []; caseBlocks = [];
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
let test_type = null;
if (this.test) {
test_type = this.test.resolveExpression(vi);
if (test_type instanceof NumberLiteral) {
test_type = test_type.type;
}
if (test_type instanceof JavaType) {
if (!isTypeAssignable(vi.typemap.get('java/lang/String'), test_type)) {
if (!isTypeAssignable(PrimitiveType.map.I, test_type)) {
test_type = null;
}
}
} else {
test_type = null;
}
if (!test_type) {
vi.problems.push(ParseProblem.Error(this.test.tokens, `Switch expression must be of type 'int' or 'java.lang.String'`));
}
}
vi.statementStack.unshift('switch');
this.caseBlocks.forEach(caseblock => {
caseblock.cases.forEach(c => {
if (typeof c === 'boolean') {
// default case
return;
}
const case_value = c.resolveExpression(vi);
if (case_value instanceof JavaType || case_value instanceof NumberLiteral) {
if (test_type && !isTypeAssignable(test_type, case_value)) {
const case_type = case_value instanceof JavaType ? case_value : case_value.type;
vi.problems.push(ParseProblem.Error(c.tokens, `Incomparable types: expression of type '${case_type.fullyDottedTypeName}' is not comparable with type '${test_type.fullyDottedTypeName}'`));
}
} else {
vi.problems.push(ParseProblem.Error(c.tokens, `Expression expected`));
}
})
})
vi.statementStack.shift();
}
} }
exports.SwitchStatement = SwitchStatement; exports.SwitchStatement = SwitchStatement;

View File

@@ -1,13 +1,34 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/ */
const { CEIType } = require('java-mti');
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
class SynchronizedStatement extends Statement { class SynchronizedStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
expression = null; expression = null;
/** @type {Statement} */ /** @type {Statement} */
statement = null; statement = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (this.expression) {
const value = this.expression.resolveExpression(vi);
// locks must be a reference type
if (!(value instanceof CEIType)) {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `Lock expression must be a reference type`));
}
}
if (this.statement) {
vi.statementStack.unshift('synchronized');
this.statement.validate(vi);
vi.statementStack.shift();
}
}
} }
exports.SynchronizedStatement = SynchronizedStatement; exports.SynchronizedStatement = SynchronizedStatement;

View File

@@ -1,11 +1,32 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/ */
const { JavaType } = require('java-mti');
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const { isTypeAssignable } = require('../expression-resolver');
const ParseProblem = require('../parsetypes/parse-problem');
class ThrowStatement extends Statement { class ThrowStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
expression = null; expression = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
if (!this.expression) {
return;
}
const throw_value = this.expression.resolveExpression(vi);
if (throw_value instanceof JavaType) {
if (!isTypeAssignable(vi.typemap.get('java/lang/Throwable'), throw_value)) {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `throw expression does not inherit from java.lang.Throwable`));
}
} else {
vi.problems.push(ParseProblem.Error(this.expression.tokens, `Throwable expression expected`));
}
}
} }
exports.ThrowStatement = ThrowStatement; exports.ThrowStatement = ThrowStatement;

View File

@@ -1,9 +1,11 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ValidateInfo} ValidateInfo
* @typedef {import('./Block').Block} Block * @typedef {import('./Block').Block} Block
* @typedef {import('../body-types').Local} Local * @typedef {import('../body-types').Local} Local
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const { ResolvedIdent } = require('../body-types');
const ParseProblem = require('../parsetypes/parse-problem');
class TryStatement extends Statement { class TryStatement extends Statement {
/** @type {(ResolvedIdent|Local[])[]} */ /** @type {(ResolvedIdent|Local[])[]} */
@@ -11,6 +13,23 @@ class TryStatement extends Statement {
/** @type {Block} */ /** @type {Block} */
block = null; block = null;
catches = []; catches = [];
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
this.resources.forEach(r => {
if (r instanceof ResolvedIdent) {
r.resolveExpression(vi);
}
});
if (this.block) {
vi.statementStack.unshift('try');
this.block.validate(vi);
vi.statementStack.shift();
}
}
} }
exports.TryStatement = TryStatement; exports.TryStatement = TryStatement;

View File

@@ -1,14 +1,29 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('./Block').Block} Block * @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/ */
const { Statement } = require("./Statement"); const { Statement } = require("./Statement");
const { checkBooleanBranchCondition } = require('../expression-resolver');
class WhileStatement extends Statement { class WhileStatement extends Statement {
/** @type {ResolvedIdent} */ /** @type {ResolvedIdent} */
test = null; test = null;
/** @type {Statement} */ /** @type {Statement} */
statement = null; statement = null;
/**
* @param {ValidateInfo} vi
*/
validate(vi) {
const value = this.test.resolveExpression(vi);
checkBooleanBranchCondition(value, () => this.test.tokens, vi.problems);
if (this.statement) {
vi.statementStack.unshift('while');
this.statement.validate(vi);
vi.statementStack.shift();
}
}
} }
exports.WhileStatement = WhileStatement; exports.WhileStatement = WhileStatement;