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

@@ -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 ParseProblem = require('../parsetypes/parse-problem');
const { isTypeAssignable } = require('../expression-resolver');
const { JavaType, PrimitiveType } = require('java-mti');
class AssertStatement extends Statement {
/** @type {ResolvedIdent} */
expression = null;
/** @type {ResolvedIdent} */
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;

View File

@@ -1,12 +1,21 @@
/**
* @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 Block extends Statement {
/** @type {Statement[]} */
statements = [];
/** @type {{locals: Local[], labels: Label[], types: SourceType[]}} */
decls = null;
/**
* @param {Token} open
*/
@@ -14,6 +23,23 @@ class Block extends Statement {
super();
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;

View File

@@ -1,11 +1,30 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
class BreakStatement extends Statement {
/** @type {Token} */
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;

View File

@@ -1,11 +1,30 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
class ContinueStatement extends Statement {
/** @type {Token} */
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;

View File

@@ -1,14 +1,31 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @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 { checkBooleanBranchCondition } = require('../expression-resolver');
class DoStatement extends Statement {
/** @type {ResolvedIdent} */
test = null;
/** @type {Block} */
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;

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

View File

@@ -1,7 +1,10 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { Statement } = require("./Statement");
const { checkBooleanBranchCondition } = require('../expression-resolver');
const { checkNonVarDeclStatement } = require('../statement-validater');
class IfStatement extends Statement {
/** @type {ResolvedIdent} */
@@ -10,6 +13,25 @@ class IfStatement extends Statement {
statement = null;
/** @type {Statement} */
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;

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').ValidateInfo} ValidateInfo
* @typedef {import('../tokenizer').Token} Token
*/
const { JavaType, PrimitiveType } = require('java-mti');
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 {
/** @type {ResolvedIdent} */
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;

View File

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

View File

@@ -1,13 +1,68 @@
/**
* @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 ParseProblem = require('../parsetypes/parse-problem');
const { isTypeAssignable } = require('../expression-resolver');
const { NumberLiteral } = require('../expressiontypes/literals/Number');
class SwitchStatement extends Statement {
/** @type {ResolvedIdent} */
test = null;
/** @type {(ResolvedIdent|boolean)[]} */
cases = [];
/** @type {{cases: (ResolvedIdent|boolean)[], statements: Statement[]} []} */
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;

View File

@@ -1,13 +1,34 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { CEIType } = require('java-mti');
const { Statement } = require("./Statement");
const ParseProblem = require('../parsetypes/parse-problem');
class SynchronizedStatement extends Statement {
/** @type {ResolvedIdent} */
expression = null;
/** @type {Statement} */
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;

View File

@@ -1,11 +1,32 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { JavaType } = require('java-mti');
const { Statement } = require("./Statement");
const { isTypeAssignable } = require('../expression-resolver');
const ParseProblem = require('../parsetypes/parse-problem');
class ThrowStatement extends Statement {
/** @type {ResolvedIdent} */
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;

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('../body-types').Local} Local
*/
const { Statement } = require("./Statement");
const { ResolvedIdent } = require('../body-types');
const ParseProblem = require('../parsetypes/parse-problem');
class TryStatement extends Statement {
/** @type {(ResolvedIdent|Local[])[]} */
@@ -11,6 +13,23 @@ class TryStatement extends Statement {
/** @type {Block} */
block = null;
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;

View File

@@ -1,14 +1,29 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('./Block').Block} Block
* @typedef {import('../body-types').ValidateInfo} ValidateInfo
*/
const { Statement } = require("./Statement");
const { checkBooleanBranchCondition } = require('../expression-resolver');
class WhileStatement extends Statement {
/** @type {ResolvedIdent} */
test = null;
/** @type {Statement} */
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;