generate source types before parsing

This commit is contained in:
Dave Holoway
2020-06-15 14:34:29 +01:00
parent 01ae51d91a
commit 549473d765
3 changed files with 117 additions and 12 deletions

View File

@@ -78,6 +78,76 @@ function addproblem(tokens, problem) {
tokens.problems.push(problem); tokens.problems.push(problem);
} }
/**
* @param {TokenList} tokens
* @param {*} typemap
*/
function extractSourceTypes(tokens, typemap) {
// first strip out any comments, chars, strings etc which might confuse the parsing
const normalised_source = tokens.tokens.map(t => {
return /wsc|string-literal|char-literal/.test(t.kind)
? ' '.repeat(t.length)
: t.source
}).join('')
// look for scope boundaries, package and type declarations
const re = /(\{)|(\})|\bpackage +(\w+(?: *\. *\w+)*)|\b(class|enum|interface|@ *interface) +(\w+)/g;
let package_name = null;
let type_stack = [];
let code_balance = 0;
const source_types = [];
function findTokenAt(idx) {
return tokens.tokens.find(t => t.range.start === idx);
}
for (let m; m = re.exec(normalised_source);) {
if (code_balance) {
if (m[1]) code_balance += 1;
else if (m[2]) code_balance -= 1;
continue;
}
if (m[1]) {
// open brace
if (!type_stack[0]) {
continue; // ignore - we haven't started a type yet
}
if (!type_stack[0].type_open) {
type_stack[0].type_open = true; // start of type body
continue;
}
// start of method body or array expression
code_balance = 1;
} else if (m[2]) {
// close brace
if (!type_stack[0]) {
continue; // we're outside any type
}
type_stack.shift();
} else if (m[3]) {
// package name
if (package_name !== null) {
continue; // ignore - we already have a package name or started parsing types
}
package_name = m[3].replace(/ +/g, '');
} else if (m[4]) {
// named type decl
package_name = package_name || '';
const typeKind = m[4].replace(/ +/g, ''),
kind_token = findTokenAt(m.index),
name_token = findTokenAt(m.index + m[0].match(/\w+$/).index),
outer_type = type_stack[0] && type_stack[0].source_type,
source_type = new SourceType(package_name, outer_type, '', [], typeKind, kind_token, name_token, typemap);
type_stack.unshift({
source_type,
type_open: false,
});
source_types.unshift(source_type);
}
}
console.log(source_types.map(t => t.shortSignature))
return source_types;
}
/** /**
* @param {string} source * @param {string} source
* @param {Map<string,JavaType>} typemap * @param {Map<string,JavaType>} typemap
@@ -89,6 +159,12 @@ function parse(source, typemap) {
console.time('tokenize'); console.time('tokenize');
tokens = new TokenList(tokenize(source)); tokens = new TokenList(tokenize(source));
console.timeEnd('tokenize'); console.timeEnd('tokenize');
// in order to resolve types as we parse, we must extract the set of source types first
const source_types = extractSourceTypes(tokens, typemap);
// add them to the type map
source_types.forEach(t => typemap.set(t.shortSignature, t));
console.time('parse'); console.time('parse');
parseUnit(tokens, unit, typemap); parseUnit(tokens, unit, typemap);
console.timeEnd('parse'); console.timeEnd('parse');
@@ -592,7 +668,17 @@ function typeDeclaration(package_name, scope, modifiers, typeKind, kind_token, t
addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`)); addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`));
return; return;
} }
const type = new SourceType(package_name, scope, '', modifiers, typeKind, kind_token, name, typemap); const type_short_sig = SourceType.getShortSignature(package_name, scope, name.value);
// the source type object should already exist in the type map
/** @type {SourceType} */
// @ts-ignore
let type = typemap.get(type_short_sig);
if (type instanceof SourceType) {
// update the missing parts
type.setModifierTokens(modifiers);
} else {
type = new SourceType(package_name, scope, '', 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)
: []; : [];

View File

@@ -3,21 +3,21 @@ const { Token } = require('./tokenizer');
/** /**
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser|string} scope_or_package_name * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser|string} scope_or_package_name
* @param {Token} name * @param {string} name
*/ */
function generateShortSignature(scope_or_package_name, name) { function generateShortSignature(scope_or_package_name, name) {
if (scope_or_package_name instanceof SourceType) { if (scope_or_package_name instanceof SourceType) {
const type = scope_or_package_name; const type = scope_or_package_name;
return `${type._rawShortSignature}$${name.value}`; return `${type._rawShortSignature}$${name}`;
} }
if (scope_or_package_name instanceof SourceMethod if (scope_or_package_name instanceof SourceMethod
|| scope_or_package_name instanceof SourceConstructor || scope_or_package_name instanceof SourceConstructor
|| scope_or_package_name instanceof SourceInitialiser) { || scope_or_package_name instanceof SourceInitialiser) {
const method = scope_or_package_name; const method = scope_or_package_name;
return `${method.owner._rawShortSignature}$${method.owner.localTypeCount += 1}${name.value}`; return `${method.owner._rawShortSignature}$${method.owner.localTypeCount += 1}${name}`;
} }
const pkgname = scope_or_package_name; const pkgname = scope_or_package_name;
return pkgname ?`${pkgname.replace(/\./g, '/')}/${name.value}` : name.value; return pkgname ?`${pkgname.replace(/\./g, '/')}/${name}` : name;
} }
class SourceType extends CEIType { class SourceType extends CEIType {
@@ -32,7 +32,7 @@ class SourceType extends CEIType {
*/ */
constructor(packageName, outer_scope, docs, modifiers, typeKind, kind_token, name_token, typemap) { constructor(packageName, outer_scope, docs, modifiers, typeKind, kind_token, name_token, typemap) {
// @ts-ignore // @ts-ignore
super(generateShortSignature(outer_scope || packageName, name_token), typeKind, modifiers.map(m => m.source), docs); super(generateShortSignature(outer_scope || packageName, name_token.value), typeKind, modifiers.map(m => m.source), docs);
super.packageName = packageName; super.packageName = packageName;
this.modifierTokens = modifiers; this.modifierTokens = modifiers;
this.kind_token = kind_token; this.kind_token = kind_token;
@@ -58,6 +58,23 @@ class SourceType extends CEIType {
this.initers = []; this.initers = [];
} }
/**
* @param {string} package_name
* @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope
* @param {string} name
*/
static getShortSignature(package_name, outer_scope, name) {
return generateShortSignature(outer_scope || package_name || '', name);
}
/**
* @param {Token[]} mods
*/
setModifierTokens(mods) {
this.modifierTokens = mods;
this.modifiers = mods.map(m => m.source);
}
get supers() { get supers() {
const supertypes = [...this.extends_types, ...this.implements_types].map(x => x.resolved); const supertypes = [...this.extends_types, ...this.implements_types].map(x => x.resolved);
if (this.typeKind === 'enum') { if (this.typeKind === 'enum') {

View File

@@ -43,18 +43,20 @@ let connection = createConnection(ProposedFeatures.all);
///** @type {LiveParseInfo[]} */ ///** @type {LiveParseInfo[]} */
//const liveParsers = []; //const liveParsers = [];
/** @type {{content: string, uri: string, result: SourceUnit, positionAt:(n) => Position, indexAt:(p:Position) => number}} */ /** @type {{content: string, uri: string, result: SourceUnit, typemap:Map<string,JavaType>, positionAt:(n) => Position, indexAt:(p:Position) => number}} */
let parsed = null; let parsed = null;
function reparse(uri, content) { function reparse(uri, content) {
if (androidLibrary instanceof Promise) { if (androidLibrary instanceof Promise) {
return; return;
} }
const result = parse(content, new Map(androidLibrary)); const typemap = new Map(androidLibrary);
const result = parse(content, typemap);
parsed = { parsed = {
content, content,
uri, uri,
result, result,
typemap,
positionAt(n) { positionAt(n) {
let line = 0, let line = 0,
last_nl_idx = 0, last_nl_idx = 0,
@@ -259,7 +261,7 @@ async function validateTextDocument(textDocument) {
if (parsed && parsed.result) { if (parsed && parsed.result) {
try { try {
problems = validate(parsed.result, androidLibrary); problems = validate(parsed.result, parsed.typemap);
} catch(err) { } catch(err) {
console.error(err); console.error(err);
} }
@@ -351,7 +353,7 @@ connection.onCompletion(
if (androidLibrary instanceof Promise) { if (androidLibrary instanceof Promise) {
androidLibrary = await androidLibrary; androidLibrary = await androidLibrary;
} }
const lib = androidLibrary; const lib = (parsed && parsed.typemap) || androidLibrary;
if (!lib) return []; if (!lib) return [];
const typeKindMap = { const typeKindMap = {
class: CompletionItemKind.Class, class: CompletionItemKind.Class,
@@ -367,12 +369,12 @@ connection.onCompletion(
kind: CompletionItemKind.Keyword, kind: CompletionItemKind.Keyword,
data: -1, data: -1,
})), })),
...'public private protected static final abstract volatile native'.split(' ').map((t) => ({ ...'public private protected static final abstract volatile native transient strictfp'.split(' ').map((t) => ({
label: t, label: t,
kind: CompletionItemKind.Keyword, kind: CompletionItemKind.Keyword,
data: -1, data: -1,
})), })),
...'false true null'.split(' ').map((t) => ({ ...'false true null this super'.split(' ').map((t) => ({
label: t, label: t,
kind: CompletionItemKind.Value, kind: CompletionItemKind.Value,
data: -1, data: -1,