different attempt to parse using collapsable text

ranges
This commit is contained in:
Dave Holoway
2020-05-23 13:20:51 +01:00
parent bdc5b1d4cd
commit 23dc6d3871
18 changed files with 1904 additions and 345 deletions

View File

@@ -0,0 +1,93 @@
const { ModuleBlock, FieldBlock, TypeDeclBlock } = require('../parser9');
const ParseProblem = require('../parsetypes/parse-problem');
/**
*
* @param {TypeDeclBlock} type
* @param {ParseProblem[]} probs
*/
function checkDuplicateFieldName(type, probs) {
/** @type {Map<string,FieldBlock>} */
let names = new Map();
type.fields.forEach(field => {
if (!field.name) {
return;
}
const value = names.get(field.name);
if (value === undefined) {
names.set(field.name, field);
} else {
if (value !== null) {
probs.push(ParseProblem.Error(value, `Duplicate field: ${field.name}`));
names.set(field.name, null);
}
probs.push(ParseProblem.Error(field, `Duplicate field: ${field.name}`));
}
})
// check enclosed types
type.types.forEach(type => checkDuplicateFieldName(type, probs));
}
/**
* @param {string} outername
* @param {TypeDeclBlock[]} types
* @param {ParseProblem[]} probs
*/
function checkDuplicateTypeNames(outername, types, probs) {
/** @type {Map<string,TypeDeclBlock>} */
let names = new Map();
types.forEach(type => {
const name = type.simpleName;
if (!name) {
return;
}
const value = names.get(name);
if (value === undefined) {
names.set(name, type);
} else {
if (value !== null) {
probs.push(ParseProblem.Error(value.name_token, `Duplicate type: ${outername}${name}`));
names.set(name, null);
}
probs.push(ParseProblem.Error(type.name_token, `Duplicate type: ${outername}${name}`));
}
})
// check enclosed types
types.forEach(type => {
checkDuplicateTypeNames(`${outername}${type.simpleName}.`, type.types, probs);
});
}
/**
* @param {TypeDeclBlock} type
* @param {ParseProblem[]} probs
*/
function checkDuplicateTypeVariableName(type, probs) {
type.typevars.forEach((tv, i) => {
const name = tv.name;
if (tv.name === '?') {
return;
}
if (type.typevars.findIndex(tv => tv.name === name) < i) {
probs.push(ParseProblem.Error(tv.decl, `Duplicate type variable: ${name}`));
}
})
// check enclosed types
type.types.forEach(type => {
checkDuplicateTypeVariableName(type, probs);
});
}
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod) {
const probs = [];
mod.types.forEach(type => {
checkDuplicateFieldName(type, probs);
checkDuplicateTypeVariableName(type, probs);
});
checkDuplicateTypeNames('', mod.types, probs);
return probs;
}

View File

@@ -0,0 +1,138 @@
const { TextBlock, ModuleBlock, FieldBlock, MethodBlock, ConstructorBlock, InitialiserBlock, TypeDeclBlock } = require('../parser9');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {TextBlock[]} mods
* @param {ParseProblem[]} probs
*/
function checkDuplicate(mods, probs) {
if (mods.length <= 1) {
return;
}
const m = new Map();
for (let mod of mods) {
const firstmod = m.get(mod.source);
if (firstmod === undefined) {
m.set(mod.source, mod);
} else {
probs.push(ParseProblem.Error(mod, 'Duplicate modifier'));
if (firstmod !== null) {
probs.push(ParseProblem.Error(firstmod, 'Duplicate modifier'));
m.set(mod.source, null);
}
}
}
}
/**
* @param {TextBlock[]} mods
* @param {ParseProblem[]} probs
*/
function checkConflictingAccess(mods, probs) {
if (mods.length <= 1) {
return;
}
const allmods = mods.map(m => m.source).join(' ');
for (let mod of mods) {
let match;
switch (mod.source) {
case 'private':
match = allmods.match(/protected|public/);
break;
case 'protected':
match = allmods.match(/private|public/);
break;
case 'public':
match = allmods.match(/private|protected/);
break;
}
if (match) {
probs.push(ParseProblem.Error(mod, `Access modifier '${mod.source}' conflicts with '${match[0]}'`));
}
}
}
/**
* @param {FieldBlock} field
* @param {ParseProblem[]} probs
*/
function checkFieldModifiers(field, probs) {
checkDuplicate(field.modifiers, probs);
checkConflictingAccess(field.modifiers, probs);
for (let mod of field.modifiers) {
switch (mod.source) {
case 'abstract':
probs.push(ParseProblem.Error(mod, 'Field declarations cannot be abstract'));
break;
case 'native':
probs.push(ParseProblem.Error(mod, 'Field declarations cannot be native'));
break;
}
}
}
/**
* @param {Set<string>} ownertypemods
* @param {MethodBlock} method
* @param {ParseProblem[]} probs
*/
function checkMethodModifiers(ownertypemods, method, probs) {
checkDuplicate(method.modifiers, probs);
checkConflictingAccess(method.modifiers, probs);
const allmods = new Map(method.modifiers.map(m => [m.source, m]));
if (allmods.has('abstract') && allmods.has('final')) {
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations cannot be abstract and final'));
}
if (allmods.has('abstract') && allmods.has('native')) {
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations cannot be abstract and native'));
}
if (allmods.has('abstract') && method.body().simplified.startsWith('B')) {
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations marked as abstract cannot have a method body'));
}
if (!allmods.has('abstract') && !allmods.has('native') && !method.body().simplified.startsWith('B')) {
probs.push(ParseProblem.Error(method, `Method '${method.name}' must have an implementation or be defined as abstract or native`));
}
if (allmods.has('abstract') && !ownertypemods.has('abstract')) {
probs.push(ParseProblem.Error(method, `Method '${method.name}' cannot be declared abstract inside a non-abstract type`));
}
if (allmods.has('native') && method.body().simplified.startsWith('B')) {
probs.push(ParseProblem.Error(allmods.get('native'), 'Method declarations marked as native cannot have a method body'));
}
}
/**
* @param {ConstructorBlock} field
* @param {ParseProblem[]} probs
*/
function checkConstructorModifiers(field, probs) {
}
/**
* @param {InitialiserBlock} initialiser
* @param {ParseProblem[]} probs
*/
function checkInitialiserModifiers(initialiser, probs) {
}
/**
* @param {TypeDeclBlock} type
* @param {ParseProblem[]} probs
*/
function checkTypeModifiers(type, probs) {
const typemods = new Set(type.modifiers.map(m => m.source));
type.fields.forEach(field => checkFieldModifiers(field, probs));
type.methods.forEach(method => checkMethodModifiers(typemods, method, probs));
type.constructors.forEach(ctr => checkConstructorModifiers(ctr, probs));
//type.initialisers.forEach(initer => checkInitModifiers(initer, probs));
// check enclosed types
type.types.forEach(type => checkTypeModifiers(type, probs));
}
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod) {
const probs = [];
mod.types.forEach(type => checkTypeModifiers(type, probs));
return probs;
}

View File

@@ -0,0 +1,13 @@
const { ModuleBlock } = require('./../parser9');
const ParseProblem = require('./../parsetypes/parse-problem');
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod) {
return mod.packages.slice(1).map(
pkg => {
return ParseProblem.Error(pkg, 'Additional package declaration');
}
)
}

View File

@@ -0,0 +1,29 @@
const { ModuleBlock, TypeDeclBlock, MethodBlock } = require('../parser9');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {TypeDeclBlock} type
* @param {ParseProblem[]} probs
*/
function checkTypeParseErrors(type, probs) {
type.parseErrors.forEach(err => probs.push(ParseProblem.Error(err, `Invalid, incomplete or unsupported declaration`)));
type.methods.filter(m => m.parseErrors).forEach(m => checkMethodParseErrors(m, probs));
type.types.forEach(type => checkTypeParseErrors(type, probs));
}
/**
* @param {MethodBlock} method
* @param {ParseProblem[]} probs
*/
function checkMethodParseErrors(method, probs) {
method.parseErrors.forEach(err => probs.push(ParseProblem.Error(err, `Invalid, incomplete or unsupported declaration`)));
}
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod) {
const probs = [];
mod.types.forEach(type => checkTypeParseErrors(type, probs));
return probs;
}

View File

@@ -0,0 +1,255 @@
/**
* @typedef {import('../parsetypes/resolved-import')} ResolvedImport
*/
const { ModuleBlock, TypeDeclBlock, DeclaredVariableBlock, MethodBlock, ParameterBlock, TextBlock } = require('../parser9');
const ParseProblem = require('../parsetypes/parse-problem');
const { resolveTypes } = require('../type-resolver');
const ResolvedType = require('../parsetypes/resolved-type');
/**
* @param {DeclaredVariableBlock|MethodBlock|TextBlock[] & {typeTokens: *[]}} decl
* @param {ResolvedType|ResolvedType[]} resolved
* @param {ParseProblem[]} probs
*/
function checkResolvedTypes(decl, resolved, probs) {
if (Array.isArray(resolved)) {
resolved.forEach(resolved => checkResolvedTypes(decl, resolved, probs));
return;
}
if (resolved.error) {
probs.push(ParseProblem.Error(decl, resolved.error));
return;
}
// the parser will detect varargs (...) on all variable declarations
if (decl instanceof DeclaredVariableBlock && decl.isVarArgs && !(decl instanceof ParameterBlock)) {
probs.push(ParseProblem.Error(decl.varBlock.varargs_token, `Variable-arity can only be applied to parameter declarations.`));
}
// void arrays are illegal
if (/^void\[/.test(resolved.rawlabel)) {
probs.push(ParseProblem.Error(decl.typeTokens, `Invalid type: ${resolved.rawlabel}`));
return;
}
// void can only be used for method declarations
if (resolved.rawlabel === 'void' && decl instanceof DeclaredVariableBlock) {
probs.push(ParseProblem.Error(decl.typeTokens, `'void' is not a valid type for fields, parameters or variables`));
return;
}
// no primitive type arguments
if (resolved.isTypeArg && resolved.isPrimitive) {
probs.push(ParseProblem.Error(decl.typeTokens, `Primitive types cannot be used as type arguments.`));
return;
}
switch (resolved.mtis.length) {
case 0:
probs.push(ParseProblem.Error(decl.typeTokens, `Unresolved type: '${resolved.rawlabel}'`));
break;
case 1:
break;
default:
const matchlist = resolved.mtis.map(m => `'${m.fullyDottedRawName}'`).join(', ');
probs.push(ParseProblem.Error(decl.typeTokens, `Ambiguous type: '${resolved.rawlabel}'. Possible matches: ${matchlist}.`));
break;
}
// check type arguments
resolved.parts
.filter(typepart => typepart.typeargs)
.forEach(typepart => {
checkResolvedTypes(decl, typepart.typeargs, probs);
// check number of type arguments match
if (resolved.mtis.length === 1 && typepart.typeargs.length !== resolved.mtis[0].typevars.length) {
const msg = resolved.mtis[0].typevars.length === 0
? `Type '${resolved.mtis[0].fullyDottedRawName}' is not declared as a parameterized type and cannot be used with type arguments.`
: `Wrong number of type arguments for: '${resolved.mtis[0].fullyDottedRawName}'. Expected ${resolved.mtis[0].typevars.length} but found ${typepart.typeargs.length}.`;
probs.push(ParseProblem.Error(decl.typeTokens, msg));
}
});
}
/**
* @param {string} outername
* @param {TypeDeclBlock} owner_type
* @param {''|'.'|'$'} qualifier
* @param {ResolvedImport[]} resolved_imports
* @param {Map<string, import('../mti').Type>} typemap
* @param {ParseProblem[]} probs
*/
function resolveFieldTypes(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
const fieldtypes = owner_type.fields.map(f => f.type);
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
const resolved = resolveTypes(fieldtypes, fully_qualified_scope_name, resolved_imports, typemap);
owner_type.fields.forEach((field,i) => {
checkResolvedTypes(field, resolved[i], probs);
})
// check enclosed types
owner_type.types.forEach(type => {
resolveFieldTypes(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
});
}
function extractTypeList(decl) {
if (!decl) {
return [];
}
const types = [];
const re = /[WD]( *[WDT.])*/g;
decl = decl.blockArray();
const sm = decl.sourcemap();
for (let m; m = re.exec(sm.simplified);) {
const start = sm.map[m.index], end = sm.map[m.index + m[0].length-1];
const block_range = decl.blocks.slice(start, end+1);
const typename = block_range.map(b => b.source).join('');
block_range.typename = typename;
block_range.typeTokens = block_range;
types.push(block_range);
}
return types;
}
/**
* @param {string} outername
* @param {TypeDeclBlock} owner_type
* @param {''|'.'|'$'} qualifier
* @param {ResolvedImport[]} resolved_imports
* @param {Map<string, import('../mti').Type>} typemap
* @param {ParseProblem[]} probs
*/
function resolveExtends(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
if (!owner_type.extends_token) {
return;
}
// the scope for extends and implements needs to include any type variables, but not enclosed types
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
if (!/^(class|interface)/.test(owner_type.kind())) {
probs.push(ParseProblem.Error(owner_type.extends_token, `extends declaration is not valid for ${owner_type.kind()} type: ${fully_qualified_scope_name}`));
return;
}
const eit_types = extractTypeList(owner_type.extends_token);
const resolved = resolveTypes(eit_types.map(x => x.typename), fully_qualified_scope_name, resolved_imports, typemap);
eit_types.forEach((eit_type,i) => {
checkResolvedTypes(eit_type, resolved[i], probs);
})
switch(owner_type.kind()) {
case 'class':
if (eit_types[0] && resolved[0].mtis.length === 1 && resolved[0].mtis[0].typeKind !== 'class') {
probs.push(ParseProblem.Error(eit_types[0], `Class '${fully_qualified_scope_name}' cannot extend from ${resolved[0].mtis[0].typeKind} type '${resolved[0].mtis[0].fullyDottedRawName}'`));
}
if (eit_types.length > 1) {
probs.push(ParseProblem.Error(eit_types[1], `Class types cannot extend from more than one type`));
}
break;
case "interface":
eit_types.forEach((eit_type, i) => {
const mti = resolved[i].mtis[0];
if (resolved[i].mtis.length === 1 && mti.typeKind !== 'interface') {
probs.push(ParseProblem.Error(eit_type, `Interface '${fully_qualified_scope_name}' cannot extend from ${mti.typeKind} type '${mti.fullyDottedRawName}'`));
}
// check for repeated types
if (resolved[i].mtis.length === 1) {
const name = resolved[i].mtis[0].fullyDottedRawName;
if (resolved.findIndex(r => r.mtis.length === 1 && r.mtis[0].fullyDottedRawName === name) < i) {
probs.push(ParseProblem.Error(eit_types[1], `Repeated type: ${name}`));
}
}
})
break;
}
// check enclosed types
owner_type.types.forEach(type => {
resolveExtends(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
});
}
/**
* @param {string} outername
* @param {TypeDeclBlock} owner_type
* @param {''|'.'|'$'} qualifier
* @param {ResolvedImport[]} resolved_imports
* @param {Map<string, import('../mti').Type>} typemap
* @param {ParseProblem[]} probs
*/
function resolveImplements(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
if (!owner_type.implements_token) {
return;
}
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
if (!/class/.test(owner_type.kind())) {
probs.push(ParseProblem.Error(owner_type.implements_token, `implements declaration is not valid for ${owner_type.kind()} type: ${fully_qualified_scope_name}`));
return;
}
const eit_types = extractTypeList(owner_type.implements_token);
// the scope for extends and implements needs to include any type variables, but not enclosed types
const resolved = resolveTypes(eit_types.map(x => x.typename), fully_qualified_scope_name, resolved_imports, typemap);
eit_types.forEach((eit_type,i) => {
checkResolvedTypes(eit_type, resolved[i], probs);
})
eit_types.forEach((eit_type, i) => {
const mti = resolved[i].mtis[0];
if (resolved[i].mtis.length === 1 && mti.typeKind !== 'interface') {
probs.push(ParseProblem.Error(eit_type, `Interface '${fully_qualified_scope_name}' cannot extend from ${mti.typeKind} type '${mti.fullyDottedRawName}'`));
}
// check for repeated types
if (resolved[i].mtis.length === 1) {
const name = resolved[i].mtis[0].fullyDottedRawName;
if (resolved.findIndex(r => r.mtis.length === 1 && r.mtis[0].fullyDottedRawName === name) < i) {
probs.push(ParseProblem.Error(eit_types[1], `Repeated type: ${name}`));
}
}
})
// check enclosed types
owner_type.types.forEach(type => {
resolveImplements(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
});
}
/**
* @param {string} outername
* @param {TypeDeclBlock} owner_type
* @param {''|'.'|'$'} qualifier
* @param {ResolvedImport[]} resolved_imports
* @param {Map<string, import('../mti').Type>} typemap
* @param {ParseProblem[]} probs
*/
function resolveMethodTypes(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
const method_type_names = [];
owner_type.methods.forEach(m => {
method_type_names.push(m.type);
m.parameters.forEach(p => {
method_type_names.push(p.type);
});
});
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
const resolved = resolveTypes(method_type_names, fully_qualified_scope_name, resolved_imports, typemap);
let i = 0;
owner_type.methods.forEach(method => {
checkResolvedTypes(method, resolved[i++], probs);
method.parameters.forEach((parameter, idx, arr) => {
checkResolvedTypes(parameter, resolved[i++], probs);
if (parameter.isVarArgs && idx !== arr.length-1) {
probs.push(ParseProblem.Error(parameter, `Variable-arity parameters must be declared last.`));
}
});
})
// check enclosed types
owner_type.types.forEach(type => {
resolveMethodTypes(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
});
}
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod, imports) {
/** @type {ParseProblem[]} */
const probs = [];
mod.types.forEach(type => {
const qualifier = mod.packageName ? '.' : '';
resolveExtends(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
resolveImplements(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
resolveFieldTypes(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
resolveMethodTypes(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
});
return probs;
}

View File

@@ -0,0 +1,33 @@
const { ModuleBlock, PackageBlock, ImportBlock, TypeDeclBlock } = require('../parser9');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod) {
let have_imports, have_type;
const problems = [];
for (let decl of mod.decls()) {
let p;
switch (true) {
case decl instanceof PackageBlock:
if (have_imports || have_type) {
p = ParseProblem.Error(decl, 'package must be declared before import and type declarations');
}
break;
case decl instanceof ImportBlock:
if (have_type) {
p = ParseProblem.Error(decl, 'imports must be declared before type declarations');
}
have_imports = true;
break;
case decl instanceof TypeDeclBlock:
have_type = true;
break;
}
if (p) {
problems.push(p)
}
}
return problems;
}

View File

@@ -0,0 +1,16 @@
const { ModuleBlock } = require('../parser9');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {ModuleBlock} mod
*/
module.exports = function(mod, imports) {
/** @type {ParseProblem[]} */
const probs = [];
imports.unresolved.forEach(i => {
probs.push(ParseProblem.Warning(i, `Unresolved import: ${i.name}`));
})
return probs;
}