add support for parsing and validating anonymous types

This commit is contained in:
Dave Holoway
2020-06-29 23:48:29 +01:00
parent 1b2cd957ec
commit 7d1d215b79
5 changed files with 146 additions and 49 deletions

View File

@@ -6,7 +6,7 @@
*/ */
const { CEIType, PrimitiveType, ArrayType, UnresolvedType, TypeVariable, Field, Method } = require('java-mti'); const { CEIType, PrimitiveType, ArrayType, UnresolvedType, TypeVariable, Field, Method } = require('java-mti');
const { SourceType, SourceTypeIdent, SourceField, SourceMethod, SourceConstructor, SourceInitialiser, SourceParameter, SourceAnnotation, const { SourceType, SourceTypeIdent, SourceField, SourceMethod, SourceConstructor, SourceInitialiser, SourceParameter, SourceAnnotation,
SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType } = require('./source-types'); SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType, NamedSourceType, AnonymousSourceType } = require('./source-types');
const ResolvedImport = require('./parsetypes/resolved-import'); const ResolvedImport = require('./parsetypes/resolved-import');
const ParseProblem = require('./parsetypes/parse-problem'); const ParseProblem = require('./parsetypes/parse-problem');
const { tokenize, Token } = require('./tokenizer'); const { tokenize, Token } = require('./tokenizer');
@@ -86,6 +86,23 @@ function flattenBlocks(blocks, isMethod) {
}, []) }, [])
} }
/**
* @param {SourceType} type
* @param {ResolvedImport[]} imports
* @param {Map<string,CEIType>} typemap
*/
function parseTypeMethods(type, imports, typemap) {
type.initers.forEach(i => {
i.parsed = parseBody(i, imports, typemap);
})
type.constructors.forEach(c => {
c.parsed = parseBody(c, imports, typemap);
})
type.sourceMethods.forEach(m => {
m.parsed = parseBody(m, imports, typemap);
})
}
/** /**
* @param {SourceMethod | SourceConstructor | SourceInitialiser} method * @param {SourceMethod | SourceConstructor | SourceInitialiser} method
* @param {ResolvedImport[]} imports * @param {ResolvedImport[]} imports
@@ -97,16 +114,15 @@ function parseBody(method, imports, typemap) {
return null; return null;
} }
const tokenlist = new TokenList(flattenBlocks(body_tokens, true)); const tokenlist = new TokenList(flattenBlocks(body_tokens, true));
let block = null;
let mdecls = new MethodDeclarations(); let mdecls = new MethodDeclarations();
try { try {
block = statementBlock(tokenlist, mdecls, method, imports, typemap); method.body.block = statementBlock(tokenlist, mdecls, method, imports, typemap);
checkStatementBlock(block, method, typemap, tokenlist.problems); checkStatementBlock(method.body.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}`));
} }
return { return {
block, block: method.body.block,
problems: tokenlist.problems, problems: tokenlist.problems,
} }
} }
@@ -168,7 +184,7 @@ function extractSourceTypes(tokens, typemap) {
kind_token = findTokenAt(m.index), kind_token = findTokenAt(m.index),
name_token = findTokenAt(m.index + m[0].match(/\w+$/).index), name_token = findTokenAt(m.index + m[0].match(/\w+$/).index),
outer_type = type_stack[0] && type_stack[0].source_type, outer_type = type_stack[0] && type_stack[0].source_type,
source_type = new SourceType(package_name, outer_type, '', [], typeKind, kind_token, name_token, typemap); source_type = new NamedSourceType(package_name, outer_type, '', [], typeKind, kind_token, name_token, typemap);
type_stack.unshift({ type_stack.unshift({
source_type, source_type,
@@ -690,17 +706,17 @@ function typeDeclaration(package_name, scope, docs, modifiers, typeKind, kind_to
addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`)); addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`));
return; return;
} }
const type_short_sig = SourceType.getShortSignature(package_name, scope, name.value); const type_short_sig = NamedSourceType.getShortSignature(package_name, scope, name.value);
// the source type object should already exist in the type map // the source type object should already exist in the type map
/** @type {SourceType} */ /** @type {NamedSourceType} */
// @ts-ignore // @ts-ignore
let type = typemap.get(type_short_sig); let type = typemap.get(type_short_sig);
if (type instanceof SourceType) { if (type instanceof NamedSourceType) {
// update the missing parts // update the missing parts
type.setModifierTokens(modifiers); type.setModifierTokens(modifiers);
type.docs = docs; type.docs = docs;
} else { } else {
type = new SourceType(package_name, scope, docs, modifiers, typeKind, kind_token, name, typemap); type = new NamedSourceType(package_name, scope, docs, modifiers, typeKind, kind_token, name, typemap);
} }
type.typeVariables = tokens.current.value === '<' type.typeVariables = tokens.current.value === '<'
? typeVariableList(type, tokens, scope, imports, typemap) ? typeVariableList(type, tokens, scope, imports, typemap)
@@ -1608,8 +1624,14 @@ function newTerm(tokens, mdecls, scope, imports, typemap) {
newtokens = tokens.markEnd(); newtokens = tokens.markEnd();
// @ts-ignore // @ts-ignore
if (tokens.current.value === '{') { if (tokens.current.value === '{') {
// anonymous type - just skip for now // anonymous type
type_body = skipBody(tokens); tokens.consume();
type_body = new AnonymousSourceType(ctr_type, scope, typemap);
typemap.set(type_body.shortSignature, type_body);
typeBody(type_body, tokens, mdecls, imports, typemap);
tokens.expectValue('}');
// perform an immediate parse of all the methods in the anonymous class
parseTypeMethods(type_body, imports, typemap);
} }
return new ResolvedIdent(match.source, [new NewObject(new_token, ctr_type, open_bracket, ctr_args, commas, type_body)], [], [], '', newtokens); return new ResolvedIdent(match.source, [new NewObject(new_token, ctr_type, open_bracket, ctr_args, commas, type_body)], [], [], '', newtokens);
} }
@@ -1907,6 +1929,6 @@ function findIdentifier(token, mdecls, scope, imports, typemap) {
exports.addproblem = addproblem; exports.addproblem = addproblem;
exports.parseBody = parseBody; exports.parseTypeMethods = parseTypeMethods;
exports.parse = parse; exports.parse = parse;
exports.flattenBlocks = flattenBlocks; exports.flattenBlocks = flattenBlocks;

View File

@@ -2,6 +2,7 @@
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../body-types').ResolveInfo} ResolveInfo
* @typedef {import('../source-types').AnonymousSourceType} AnonymousSourceType
* @typedef {import('../source-types').SourceTypeIdent} SourceTypeIdent * @typedef {import('../source-types').SourceTypeIdent} SourceTypeIdent
* @typedef {import('java-mti').JavaType} JavaType * @typedef {import('java-mti').JavaType} JavaType
*/ */
@@ -64,7 +65,7 @@ class NewObject extends Expression {
* @param {Token} open_bracket * @param {Token} open_bracket
* @param {ResolvedIdent[]} ctr_args * @param {ResolvedIdent[]} ctr_args
* @param {Token[]} commas * @param {Token[]} commas
* @param {Token[]} type_body * @param {AnonymousSourceType} type_body
*/ */
constructor(new_token, object_type, open_bracket, ctr_args, commas, type_body) { constructor(new_token, object_type, open_bracket, ctr_args, commas, type_body) {
super(); super();

View File

@@ -1,4 +1,5 @@
const { CEIType, JavaType, PrimitiveType, ArrayType, TypeVariableType, Field, Method, MethodBase, Constructor, Parameter, TypeVariable, TypeArgument } = require('java-mti'); const { CEIType, JavaType, PrimitiveType, ArrayType, TypeVariableType, Field, Method, MethodBase, Constructor, Parameter, TypeVariable, TypeArgument } = require('java-mti');
const { AnyType } = require('./anys');
const { Token } = require('./tokenizer'); const { Token } = require('./tokenizer');
/** /**
@@ -59,24 +60,14 @@ function createImplicitEnumMethods(enum_type, typemap) {
class SourceType extends CEIType { class SourceType extends CEIType {
/** /**
* @param {string} packageName * @param {string} rawShortSignature
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope * @param {'class'|'interface'|'enum'|'@interface'} typeKind
* @param {string[]|number} modifiers
* @param {string} docs * @param {string} docs
* @param {Token[]} modifiers
* @param {string} typeKind
* @param {Token} kind_token
* @param {Token} name_token
* @param {Map<string,CEIType>} typemap * @param {Map<string,CEIType>} typemap
*/ */
constructor(packageName, outer_scope, docs, modifiers, typeKind, kind_token, name_token, typemap) { constructor(rawShortSignature, typeKind, modifiers, docs, typemap) {
// @ts-ignore super(rawShortSignature, typeKind, modifiers, docs);
super(generateShortSignature(outer_scope || packageName, name_token.value), typeKind, modifiers.map(m => m.source), docs);
super.packageName = packageName;
this.modifierTokens = modifiers;
this.kind_token = kind_token;
this.nameToken = name_token;
this.scope = outer_scope;
this.typemap = typemap;
/** /**
* Number of local/anonymous types declared in the scope of this type * Number of local/anonymous types declared in the scope of this type
* The number is used when naming them. * The number is used when naming them.
@@ -98,14 +89,7 @@ class SourceType extends CEIType {
this.initers = []; this.initers = [];
/** @type {SourceEnumValue[]} */ /** @type {SourceEnumValue[]} */
this.enumValues = []; this.enumValues = [];
} this.typemap = typemap;
/**
* @returns {SourceMethod[]}
*/
get sourceMethods() {
// @ts-ignore
return this.methods.filter(m => m instanceof SourceMethod);// [...this.implicitMethods, ...this.sourceMethods];
} }
/** /**
@@ -118,6 +102,95 @@ class SourceType extends CEIType {
this.enumValues.push(new SourceEnumValue(this, docs, ident, ctr_args, anonymousType)); this.enumValues.push(new SourceEnumValue(this, docs, ident, ctr_args, anonymousType));
} }
/**
* @returns {SourceMethod[]}
*/
get sourceMethods() {
// @ts-ignore
return this.methods.filter(m => m instanceof SourceMethod);// [...this.implicitMethods, ...this.sourceMethods];
}
}
class AnonymousSourceType extends SourceType {
/**
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} scope
*/
static genSignature(scope) {
const type = scope instanceof SourceType ? scope : scope.owner;
return `${type._rawShortSignature}$${type.localTypeCount += 1}`;
}
/**
* @param {SourceTypeIdent} typeident
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope
* @param {Map<string,CEIType>} typemap
*/
constructor(typeident, outer_scope, typemap) {
super(AnonymousSourceType.genSignature(outer_scope), 'class', [], '', typemap);
this.simpleTypeName = typeident.resolved.simpleTypeName;
this.typeIdent = typeident;
}
get dottedTypeName() {
return this.typeIdent.resolved.dottedTypeName;
}
get fullyDottedRawName() {
return this.dottedTypeName;
}
get fullyDottedTypeName() {
return this.dottedTypeName;
}
get label() {
return `new ${this.dottedTypeName}`;
}
/** @type {JavaType[]} */
get supers() {
if (this.typeIdent.resolved instanceof AnyType || this.typeIdent.resolved.typeKind !== 'class') {
return [this.typemap.get('java/lang/Object')]
}
return [this.typeIdent.resolved];
}
get shortSignature() {
return this._rawShortSignature;
}
get rawTypeSignature() {
return `L${this._rawShortSignature};`;
}
get typeSignature() {
return this.rawTypeSignature;
}
}
class NamedSourceType extends SourceType {
/**
* @param {string} packageName
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope
* @param {string} docs
* @param {Token[]} modifiers
* @param {string} typeKind
* @param {Token} kind_token
* @param {Token} name_token
* @param {Map<string,CEIType>} typemap
*/
constructor(packageName, outer_scope, docs, modifiers, typeKind, kind_token, name_token, typemap) {
// @ts-ignore
super(generateShortSignature(outer_scope || packageName, name_token.value), typeKind, modifiers.map(m => m.source), docs, typemap);
super.packageName = packageName;
this.modifierTokens = modifiers;
this.kind_token = kind_token;
this.nameToken = name_token;
this.scope = outer_scope;
}
/** /**
* @param {string} package_name * @param {string} package_name
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope
@@ -372,6 +445,8 @@ class SourceConstructor extends Constructor {
tokens: body_tokens, tokens: body_tokens,
/** @type {import('./body-types').Local[]} */ /** @type {import('./body-types').Local[]} */
locals: [], locals: [],
/** @type {import('./statementtypes/Block').Block} */
block: null,
} }
this.parsed = null; this.parsed = null;
} }
@@ -423,6 +498,8 @@ class SourceMethod extends Method {
tokens: body_tokens, tokens: body_tokens,
/** @type {import('./body-types').Local[]} */ /** @type {import('./body-types').Local[]} */
locals: [], locals: [],
/** @type {import('./statementtypes/Block').Block} */
block: null,
} }
this.parsed = null; this.parsed = null;
} }
@@ -467,6 +544,8 @@ class SourceInitialiser extends MethodBase {
tokens: body_tokens, tokens: body_tokens,
/** @type {import('./body-types').Local[]} */ /** @type {import('./body-types').Local[]} */
locals: [], locals: [],
/** @type {import('./statementtypes/Block').Block} */
block: null,
} }
this.parsed = null; this.parsed = null;
} }
@@ -673,3 +752,5 @@ exports.SourceImport = SourceImport;
exports.SourceEnumValue = SourceEnumValue; exports.SourceEnumValue = SourceEnumValue;
exports.SourceArrayType = SourceArrayType; exports.SourceArrayType = SourceArrayType;
exports.FixedLengthArrayType = FixedLengthArrayType; exports.FixedLengthArrayType = FixedLengthArrayType;
exports.NamedSourceType = NamedSourceType;
exports.AnonymousSourceType = AnonymousSourceType;

View File

@@ -16,6 +16,9 @@ const { ValidateInfo } = require('./body-types');
* @param {ParseProblem[]} problems * @param {ParseProblem[]} problems
*/ */
function checkStatementBlock(block, method, typemap, problems) { function checkStatementBlock(block, method, typemap, problems) {
if (!block) {
return;
}
block.validate(new ValidateInfo(typemap, problems, method)); block.validate(new ValidateInfo(typemap, problems, method));
} }

View File

@@ -1,7 +1,7 @@
const { CEIType } = require('java-mti'); const { CEIType } = require('java-mti');
const { resolveImports } = require('../java/import-resolver'); const { resolveImports } = require('../java/import-resolver');
const { SourceUnit } = require('./source-types'); const { SourceUnit } = require('./source-types');
const { parseBody } = require('./body-parser'); const { parseTypeMethods } = require('./body-parser');
/** /**
* @param {SourceUnit} unit * @param {SourceUnit} unit
@@ -12,17 +12,7 @@ function parseMethodBodies(unit, typemap) {
...resolveImports(typemap, unit.packageName), ...resolveImports(typemap, unit.packageName),
...unit.imports.filter(i => i.resolved).map(i => i.resolved), ...unit.imports.filter(i => i.resolved).map(i => i.resolved),
] ]
unit.types.forEach(t => { unit.types.forEach(t => parseTypeMethods(t, resolved_types, typemap));
t.initers.forEach(i => {
i.parsed = parseBody(i, resolved_types, typemap);
})
t.constructors.forEach(c => {
c.parsed = parseBody(c, resolved_types, typemap);
})
t.sourceMethods.forEach(m => {
m.parsed = parseBody(m, resolved_types, typemap);
})
})
} }
/** /**