support token extraction in expressions

This commit is contained in:
Dave Holoway
2020-06-17 15:37:30 +01:00
parent 3c505b05be
commit 13cdd1e0bc
21 changed files with 325 additions and 48 deletions

View File

@@ -16,6 +16,7 @@ const { TokenList } = require("./TokenList");
const { AnyMethod, AnyType, AnyValue } = require("./anys");
const { Label, Local, MethodDeclarations, ResolvedIdent } = require("./body-types");
const { resolveImports, resolveSingleImport } = require('../java/import-resolver');
const { getTypeInheritanceList } = require('./expression-resolver');
const { ArrayIndexExpression } = require("./expressiontypes/ArrayIndexExpression");
const { ArrayValueExpression } = require("./expressiontypes/ArrayValueExpression");
@@ -30,6 +31,7 @@ const { MethodCallExpression } = require("./expressiontypes/MethodCallExpression
const { NewArray, NewObject } = require("./expressiontypes/NewExpression");
const { TernaryOpExpression } = require("./expressiontypes/TernaryOpExpression");
const { ThisMemberExpression } = require("./expressiontypes/ThisMemberExpression");
const { Variable } = require("./expressiontypes/Variable");
const { BooleanLiteral } = require('./expressiontypes/literals/Boolean');
const { CharacterLiteral } = require('./expressiontypes/literals/Character');
@@ -843,15 +845,15 @@ function enumValueList(type, tokens, imports, typemap) {
* @param {Map<string,CEIType>} typemap
*/
function statementBlock(tokens, mdecls, method, imports, typemap) {
const b = new Block();
const block = new Block(tokens.current);
tokens.expectValue('{');
mdecls.pushScope();
while (!tokens.isValue('}')) {
const s = statement(tokens, mdecls, method, imports, typemap);
b.statements.push(s);
block.statements.push(s);
}
mdecls.popScope();
return b;
return block;
}
/**
@@ -1538,15 +1540,16 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) {
// the result of a bracketed expression is always a value, never a variable
// - this prevents things like: (a) = 5;
return new ResolvedIdent(`(${matches.source})`, [new BracketedExpression(matches)]);
case tokens.isValue('{') && 'symbol':
case tokens.current.value === '{' && 'symbol':
// array initer
let elements = [];
let elements = [], open = tokens.current;
tokens.expectValue('{');
if (!tokens.isValue('}')) {
elements = expressionList(tokens, mdecls, scope, imports, typemap, { isArrayLiteral:true });
tokens.expectValue('}');
}
const ident = `{${elements.map(e => e.source).join(',')}}`;
return new ResolvedIdent(ident, [new ArrayValueExpression(elements)]);
return new ResolvedIdent(ident, [new ArrayValueExpression(elements, open)]);
default:
addproblem(tokens, ParseProblem.Error(tokens.current, 'Expression expected'));
return new ResolvedIdent('');
@@ -1563,9 +1566,10 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) {
* @param {Map<string,CEIType>} typemap
*/
function newTerm(tokens, mdecls, scope, imports, typemap) {
const new_token = tokens.current;
tokens.expectValue('new');
const { resolved: ctr_type } = typeIdent(tokens, scope, imports, typemap, {no_array_qualifiers:true, type_vars:[]});
let match = new ResolvedIdent(`new ${ctr_type.simpleTypeName}`, [], [], [ctr_type]);
const ctr_type = typeIdent(tokens, scope, imports, typemap, {no_array_qualifiers:true, type_vars:[]});
let match = new ResolvedIdent(`new ${ctr_type.resolved.simpleTypeName}`, [], [], [ctr_type.resolved]);
let ctr_args = [], type_body = null;
switch(tokens.current.value) {
case '[':
@@ -1575,7 +1579,7 @@ function newTerm(tokens, mdecls, scope, imports, typemap) {
// array init
rootTerm(tokens, mdecls, scope, imports, typemap);
}
return new ResolvedIdent(match.source, [new NewArray(ctr_type, match)]);
return new ResolvedIdent(match.source, [new NewArray(new_token, ctr_type, match)]);
case '(':
tokens.inc();
if (!tokens.isValue(')')) {
@@ -1592,7 +1596,7 @@ function newTerm(tokens, mdecls, scope, imports, typemap) {
addproblem(tokens, ParseProblem.Error(tokens.current, 'Constructor expression expected'));
break;
}
return new ResolvedIdent(match.source, [new NewObject(ctr_type, ctr_args, type_body)]);
return new ResolvedIdent(match.source, [new NewObject(new_token, ctr_type, ctr_args, type_body)]);
}
/**
@@ -1630,28 +1634,6 @@ function arrayElementOrConstructor(tokens, open_array, instance, index) {
return new ResolvedIdent(ident, [new ArrayIndexExpression(instance, index)]);
}
/**
* @param {CEIType} type
*/
function getTypeInheritanceList(type) {
const types = {
/** @type {JavaType[]} */
list: [type],
/** @type {Set<JavaType>} */
done: new Set(),
};
for (let type; type = types.list.shift(); ) {
if (types.done.has(type)) {
continue;
}
types.done.add(type);
if (type instanceof CEIType)
types.list.push(...type.supers);
}
return Array.from(types.done);
}
/**
* @param {ResolvedIdent} matches
* @param {TokenList} tokens
@@ -1807,7 +1789,7 @@ function arrayTypeExpression(matches) {
*/
function resolveIdentifier(tokens, mdecls, scope, imports, typemap) {
const ident = tokens.current.value;
const matches = findIdentifier(ident, mdecls, scope, imports, typemap);
const matches = findIdentifier(tokens.current, mdecls, scope, imports, typemap);
checkIdentifierFound(tokens, ident, matches);
return matches;
}
@@ -1827,20 +1809,22 @@ function checkIdentifierFound(tokens, ident, matches) {
}
/**
* @param {string} ident
* @param {Token} token
* @param {MethodDeclarations} mdecls
* @param {Scope} scope
* @param {ResolvedImport[]} imports
* @param {Map<string,CEIType>} typemap
*/
function findIdentifier(ident, mdecls, scope, imports, typemap) {
function findIdentifier(token, mdecls, scope, imports, typemap) {
const ident = token.value;
const matches = new ResolvedIdent(ident);
matches.tokens = [token];
// is it a local or parameter - note that locals must be ordered innermost-scope-first
const local = mdecls.locals.find(local => local.name === ident);
let param = scope && !(scope instanceof SourceType) && scope.parameters.find(p => p.name === ident);
if (local || param) {
matches.variables = [local || param];
matches.variables = [new Variable(token, local || param)];
} else if (scope) {
// is it a field, method or enum value in the current type (or any of the outer types or superclasses)
const scoped_type = scope instanceof SourceType ? scope : scope.owner;
@@ -1857,12 +1841,12 @@ function findIdentifier(ident, mdecls, scope, imports, typemap) {
if (!matches.variables[0]) {
const field = type.fields.find(f => f.name === ident);
if (field) {
matches.variables = [field];
matches.variables = [new Variable(token, field)];
return;
}
const enumValue = (type instanceof SourceType) && type.enumValues.find(e => e.ident.value === ident);
if (enumValue) {
matches.variables = [enumValue];
matches.variables = [new Variable(token, enumValue)];
return;
}
}
@@ -1883,7 +1867,7 @@ function findIdentifier(ident, mdecls, scope, imports, typemap) {
imp.members.forEach(member => {
if (member.name === ident) {
if (member instanceof Field) {
matches.variables.push(member);
matches.variables.push(new Variable(token, member));
} else if (member instanceof Method) {
matches.methods.push(member);
}

View File

@@ -7,7 +7,7 @@ const { Token } = require('./tokenizer');
class ResolvedIdent {
/**
* @param {string} ident
* @param {(Local|Parameter|Field|Expression)[]} variables
* @param {Expression[]} variables
* @param {Method[]} methods
* @param {JavaType[]} types
* @param {string} package_name

View File

@@ -0,0 +1,175 @@
/**
* @typedef {import('./tokenizer').Token} Token
*/
const ParseProblem = require('./parsetypes/parse-problem');
const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti');
const { AnyType } = require('./anys');
const { Local } = require('./body-types');
const { Expression } = require('./expressiontypes/Expression');
const { Variable } = require('./expressiontypes/Variable');
/**
* @param {import('./body-types').ResolvedIdent} e
* @param {JavaType} assign_type
* @param {Map<string,CEIType>} typemap
* @param {ParseProblem[]} problems
*/
function checkAssignment(e, assign_type, typemap, problems) {
const value = e.variables[0];
if (value instanceof Variable) {
checkTypeAssignable(assign_type, value.type, () => value.name_token, problems);
return;
}
if (value instanceof Expression) {
const expression_result_type = null;//value.resolveType(typemap);
checkTypeAssignable(assign_type, expression_result_type, value.tokens, problems);
return;
}
}
/**
*
* @param {JavaType} variable_type
* @param {JavaType} value_type
* @param {() => Token|Token[]} tokens
*/
function checkTypeAssignable(variable_type, value_type, tokens, problems) {
if (!isTypeAssignable(variable_type, value_type)) {
const t = tokens();
problems.push(ParseProblem.Error(t, `Incompatible types: Expression of type '${value_type.fullyDottedTypeName}' cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`));
}
}
/**
* Set of regexes to map source primitives to their destination types.
* eg, long (J) is type-assignable to long, float and double (and their boxed counterparts)
* Note that void (V) is never type-assignable to anything
*/
const valid_primitive_types = {
// conversions from a primitive to a value
from: {
B: /^[BSIJFD]$|^Ljava\/lang\/(Byte|Short|Integer|Long|Float|Double);$/,
S: /^[SIJFD]$|^Ljava\/lang\/(Short|Integer|Long|Float|Double);$/,
I: /^[IJFD]$|^Ljava\/lang\/(Integer|Long|Float|Double);$/,
J: /^[JFD]$|^Ljava\/lang\/(Long|Float|Double);$/,
F: /^[FD]$|^Ljava\/lang\/(Float|Double);$/,
D: /^D$|^Ljava\/lang\/(Double);$/,
C: /^[CIJFD]$|^Ljava\/lang\/(Character|Integer|Long|Float|Double);$/,
Z: /^Z$|^Ljava\/lang\/(Boolean);$/,
V: /$^/, // V.test() always returns false
},
// conversions to a primitive from a value
to: {
B: /^[B]$|^Ljava\/lang\/(Byte);$/,
S: /^[BS]$|^Ljava\/lang\/(Byte|Short);$/,
I: /^[BSIC]$|^Ljava\/lang\/(Byte|Short|Integer|Character);$/,
J: /^[BSIJC]$|^Ljava\/lang\/(Byte|Short|Integer|Long|Character);$/,
F: /^[BSIJCF]$|^Ljava\/lang\/(Byte|Short|Integer|Long|Character|Float);$/,
D: /^[BSIJCFD]$|^Ljava\/lang\/(Byte|Short|Integer|Long|Character|Float|Double);$/,
C: /^C$|^Ljava\/lang\/(Character);$/,
Z: /^Z$|^Ljava\/lang\/(Boolean);$/,
V: /$^/, // V.test() always returns false
}
}
/**
* Returns true if a value of value_type is assignable to a variable of dest_type
* @param {JavaType} dest_type
* @param {JavaType} value_type
*/
function isTypeAssignable(dest_type, value_type) {
let is_assignable = false;
if (dest_type.typeSignature === value_type.typeSignature) {
// exact signature match
is_assignable = true;
} else if (dest_type instanceof AnyType || value_type instanceof AnyType) {
// everything is assignable to or from AnyType
is_assignable = true;
} else if (dest_type.rawTypeSignature === 'Ljava/lang/Object;') {
// everything is assignable to Object
is_assignable = true;
} else if (value_type instanceof PrimitiveType) {
// primitive values can only be assigned to wider primitives or their class equivilents
is_assignable = valid_primitive_types.from[value_type.typeSignature].test(dest_type.typeSignature);
} else if (dest_type instanceof PrimitiveType) {
// primitive variables can only be assigned from narrower primitives or their class equivilents
is_assignable = valid_primitive_types.to[dest_type.typeSignature].test(value_type.typeSignature);
} else if (value_type instanceof NullType) {
// null is assignable to any non-primitive
is_assignable = !(dest_type instanceof PrimitiveType);
} else if (value_type instanceof ArrayType) {
// arrays are assignable to other arrays with the same dimensionality and type-assignable bases
is_assignable = dest_type instanceof ArrayType
&& dest_type.arrdims === value_type.arrdims
&& isTypeAssignable(dest_type.base, value_type.base);
} else if (value_type instanceof CEIType && dest_type instanceof CEIType) {
// class/interfaces types are assignable to any class/interface types in their inheritence tree
const valid_types = getTypeInheritanceList(value_type);
is_assignable = valid_types.includes(dest_type);
if (!is_assignable) {
// generic types are also assignable to their raw counterparts
const valid_raw_types = valid_types.map(t => t.getRawType());
is_assignable = valid_raw_types.includes(dest_type);
if (!is_assignable) {
// generic types are also assignable to compatible wildcard type bounds
const raw_type = valid_raw_types.find(rt => rt.rawTypeSignature === dest_type.rawTypeSignature);
if (raw_type instanceof CEIType && raw_type.typeVariables.length === value_type.typeVariables.length) {
is_assignable = dest_type.typeVariables.every((dest_tv, idx) => isTypeArgumentCompatible(dest_tv, value_type.typeVariables[idx].type));
}
}
}
}
return is_assignable;
}
/**
* @param {TypeVariable} dest_typevar
* @param {JavaType} value_typevar_type
*/
function isTypeArgumentCompatible(dest_typevar, value_typevar_type) {
if (dest_typevar.type instanceof WildcardType) {
if (!dest_typevar.type.bound) {
// unbounded wildcard types are compatible with everything
return true;
}
if (dest_typevar.type.bound.type === value_typevar_type) {
return true;
}
switch (dest_typevar.type.bound.kind) {
case 'extends':
return isTypeAssignable(dest_typevar.type.bound.type, value_typevar_type);
case 'super':;
return isTypeAssignable(value_typevar_type, dest_typevar.type.bound.type);
}
return false;
}
if (value_typevar_type instanceof TypeVariableType) {
// inferred type arguments of the form `x = List<>` are compatible with every destination type variable
return value_typevar_type.typeVariable instanceof InferredTypeArgument;
}
return dest_typevar.type === value_typevar_type;
}
/**
* @param {CEIType} type
*/
function getTypeInheritanceList(type) {
const types = {
/** @type {JavaType[]} */
list: [type],
/** @type {Set<JavaType>} */
done: new Set(),
};
for (let type; type = types.list.shift(); ) {
if (types.done.has(type)) {
continue;
}
types.done.add(type);
if (type instanceof CEIType)
types.list.push(...type.supers);
}
return Array.from(types.done);
}
exports.checkAssignment = checkAssignment;
exports.getTypeInheritanceList = getTypeInheritanceList;

View File

@@ -13,6 +13,10 @@ class ArrayIndexExpression extends Expression {
this.instance = instance;
this.index = index;
}
tokens() {
return [...this.instance.tokens, ...this.index.tokens];
}
}
exports.ArrayIndexExpression = ArrayIndexExpression;

View File

@@ -1,15 +1,22 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../tokenizer').Token} Token
*/
const { Expression } = require("./Expression");
class ArrayValueExpression extends Expression {
/**
* @param {ResolvedIdent[]} elements
* @param {Token} open
*/
constructor(elements) {
constructor(elements, open) {
super();
this.elements = elements;
this.open = open;
}
tokens() {
return this.open;
}
}

View File

@@ -16,6 +16,10 @@ class BinaryOpExpression extends Expression {
this.op = op;
this.rhs = rhs;
}
tokens() {
return [...this.lhs.tokens, this.op, ...this.rhs.tokens];
}
}
exports.BinaryOpExpression = BinaryOpExpression;

View File

@@ -11,6 +11,10 @@ class BracketedExpression extends Expression {
super();
this.expression = expression;
}
tokens() {
return this.expression.tokens;
}
}
exports.BracketedExpression = BracketedExpression;

View File

@@ -13,6 +13,10 @@ class CastExpression extends Expression {
this.castType = castType;
this.expression = expression;
}
tokens() {
return [...this.castType.tokens, ...this.expression.tokens];
}
}
exports.CastExpression = CastExpression;

View File

@@ -14,5 +14,9 @@ class ClassMemberExpression extends Expression {
this.instance = instance;
this.classToken = class_token;
}
tokens() {
return this.classToken;
}
}
exports.ClassMemberExpression = ClassMemberExpression;

View File

@@ -1,5 +1,12 @@
/**
* @typedef {import('../tokenizer').Token} Token
*/
class Expression {
/** @returns {Token|Token[]} */
tokens() {
throw new Error('Expression.tokens');
}
}
exports.Expression = Expression;

View File

@@ -16,6 +16,10 @@ class IncDecExpression extends Expression {
this.operator = operator;
this.which = which;
}
tokens() {
return this.operator;
}
}
exports.IncDecExpression = IncDecExpression;

View File

@@ -1,19 +1,26 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../statementtypes/Block').Block} Block
*/
const { Expression } = require("./Expression");
const { Block } = require('../statementtypes/Block');
class LambdaExpression extends Expression {
/**
*
* @param {*[]} params
* @param {Expression|Block} body
* @param {ResolvedIdent|Block} body
*/
constructor(params, body) {
super();
this.params = params;
this.body = body;
}
tokens() {
if (this.body instanceof Block) {
return this.body.open;
}
return this.body.tokens;
}
}
exports.LambdaExpression = LambdaExpression;

View File

@@ -15,6 +15,10 @@ class MemberExpression extends Expression {
// member will be null for incomplete expressions
this.member = member;
}
tokens() {
return this.member;
}
}
exports.MemberExpression = MemberExpression;

View File

@@ -13,6 +13,10 @@ class MethodCallExpression extends Expression {
this.instance = instance;
this.args = args;
}
tokens() {
return this.instance.tokens;
}
}
exports.MethodCallExpression = MethodCallExpression;

View File

@@ -1,34 +1,47 @@
/**
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../source-types').SourceTypeIdent} SourceTypeIdent
* @typedef {import('java-mti').JavaType} JavaType
*/
const { Expression } = require("./Expression");
class NewArray extends Expression {
/**
* @param {JavaType} element_type
* @param {Token} new_token
* @param {SourceTypeIdent} element_type
* @param {ResolvedIdent} dimensions
*/
constructor(element_type, dimensions) {
constructor(new_token, element_type, dimensions) {
super();
this.new_token = new_token;
this.element_type = element_type;
this.dimensions = dimensions;
}
tokens() {
return [this.new_token, ...this.element_type.tokens, ...this.dimensions.tokens];
}
}
class NewObject extends Expression {
/**
* @param {JavaType} object_type
* @param {Token} new_token
* @param {SourceTypeIdent} object_type
* @param {ResolvedIdent[]} ctr_args
* @param {Token[]} type_body
*/
constructor(object_type, ctr_args, type_body) {
constructor(new_token, object_type, ctr_args, type_body) {
super();
this.element_type = object_type;
this.new_token = new_token;
this.object_type = object_type;
this.ctr_args = ctr_args;
this.type_body = type_body;
}
tokens() {
return [this.new_token, ...this.object_type.tokens];
}
}
exports.NewArray = NewArray;

View File

@@ -15,6 +15,10 @@ class TernaryOpExpression extends Expression {
this.truthExpression = truthExpression;
this.falseExpression = falseExpression;
}
tokens() {
return [...this.test.tokens, ...this.truthExpression.tokens, ...this.falseExpression.tokens];
}
}
exports.TernaryOpExpression = TernaryOpExpression;

View File

@@ -14,6 +14,10 @@ class ThisMemberExpression extends Expression {
this.instance = instance;
this.thisToken = this_token;
}
tokens() {
return this.thisToken;
}
}
exports.ThisMemberExpression = ThisMemberExpression;

View File

@@ -0,0 +1,28 @@
/**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').Local} Local
* @typedef {import('../tokenizer').Token} Token
* @typedef {import('java-mti').Field} Field
* @typedef {import('java-mti').Parameter} Parameter
* @typedef {import('../source-types').SourceEnumValue} SourceEnumValue
*/
const { Expression } = require("./Expression");
class Variable extends Expression {
/**
* @param {Token} name_token
* @param {Local|Parameter|Field|SourceEnumValue} variable
*/
constructor(name_token, variable) {
super();
this.name_token = name_token;
this.variable = variable;
this.type = this.variable.type;
}
tokens() {
return this.name_token;
}
}
exports.Variable = Variable;

View File

@@ -11,6 +11,10 @@ class LiteralValue extends Expression {
super();
this.token = token;
}
tokens() {
return this.token;
}
}
exports.LiteralValue = LiteralValue;

View File

@@ -498,3 +498,4 @@ exports.SourceAnnotation = SourceAnnotation;
exports.SourceUnit = SourceUnit;
exports.SourcePackage = SourcePackage;
exports.SourceImport = SourceImport;
exports.SourceEnumValue = SourceEnumValue;

View File

@@ -1,8 +1,19 @@
/**
* @typedef {import('../tokenizer').Token} Token
*/
const { Statement } = require("./Statement");
class Block extends Statement {
/** @type {Statement[]} */
statements = [];
/**
* @param {Token} open
*/
constructor(open) {
super();
this.open = open;
}
}
exports.Block = Block;