mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 09:59:25 +00:00
refactor to prepare for merging with type parsing
This commit is contained in:
72
langserver/java/TokenList.js
Normal file
72
langserver/java/TokenList.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {import('./tokenizer').Token} Token
|
||||||
|
*/
|
||||||
|
const ParseProblem = require('./parsetypes/parse-problem');
|
||||||
|
|
||||||
|
class TokenList {
|
||||||
|
/**
|
||||||
|
* @param {Token[]} tokens
|
||||||
|
*/
|
||||||
|
constructor(tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
this.idx = -1;
|
||||||
|
/** @type {Token} */
|
||||||
|
this.current = null;
|
||||||
|
this.inc();
|
||||||
|
/** @type {ParseProblem[]} */
|
||||||
|
this.problems = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
inc() {
|
||||||
|
for (; ;) {
|
||||||
|
this.current = this.tokens[this.idx += 1];
|
||||||
|
if (!this.current || this.current.kind !== 'wsc') {
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check if the current token matches the specified value and consumes it
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
isValue(value) {
|
||||||
|
if (this.current.value === value) {
|
||||||
|
this.inc();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current token matches the specified value and consumes it or reports an error
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
expectValue(value) {
|
||||||
|
if (this.isValue(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const addproblem = require("./body-parser3").addproblem;
|
||||||
|
addproblem(this, ParseProblem.Error(this.current, `${value} expected`));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get previous() {
|
||||||
|
for (let idx = this.idx - 1; idx >= 0; idx--) {
|
||||||
|
if (idx === 0 || this.tokens[idx].kind !== 'wsc') {
|
||||||
|
return this.tokens[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} start
|
||||||
|
* @param {number} delete_count
|
||||||
|
* @param {...Token} insert
|
||||||
|
*/
|
||||||
|
splice(start, delete_count, ...insert) {
|
||||||
|
this.tokens.splice(start, delete_count, ...insert);
|
||||||
|
this.current = this.tokens[this.idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.TokenList = TokenList;
|
||||||
@@ -9,6 +9,10 @@ const { SourceMethod, SourceConstructor, SourceInitialiser } = require('./source
|
|||||||
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 { getOperatorType, Token } = require('./tokenizer');
|
const { getOperatorType, Token } = require('./tokenizer');
|
||||||
|
const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resolver');
|
||||||
|
const { typeIdentList } = require('./typeident');
|
||||||
|
const { TokenList } = require("./TokenList");
|
||||||
|
const { AnyMethod, AnyType, AnyValue, ArrayElement, ArrayLiteral, ConstructorCall, LiteralNumber, LiteralValue, Local, MethodCall, ResolvedIdent, TernaryValue, Value } = require("./body-types");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {SourceMethod|SourceConstructor|SourceInitialiser} SourceMC
|
* @typedef {SourceMethod|SourceConstructor|SourceInitialiser} SourceMC
|
||||||
@@ -77,67 +81,6 @@ function addLocals(tokens, locals, new_locals) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TokenList {
|
|
||||||
/**
|
|
||||||
* @param {Token[]} tokens
|
|
||||||
*/
|
|
||||||
constructor(tokens) {
|
|
||||||
this.tokens = tokens;
|
|
||||||
this.idx = -1;
|
|
||||||
/** @type {Token} */
|
|
||||||
this.current = null;
|
|
||||||
this.inc();
|
|
||||||
/** @type {ParseProblem[]} */
|
|
||||||
this.problems = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
inc() {
|
|
||||||
for (;;) {
|
|
||||||
this.current = this.tokens[this.idx += 1];
|
|
||||||
if (!this.current || this.current.kind !== 'wsc') {
|
|
||||||
return this.current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current token matches the specified value and consumes it
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
isValue(value) {
|
|
||||||
if (this.current.value === value) {
|
|
||||||
this.inc();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current token matches the specified value and consumes it or reports an error
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
expectValue(value) {
|
|
||||||
if (this.isValue(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
addproblem(this, ParseProblem.Error(this.current, `${value} expected`));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get previous() {
|
|
||||||
for (let idx = this.idx-1; idx >= 0 ; idx--) {
|
|
||||||
if (idx === 0 || this.tokens[idx].kind !== 'wsc') {
|
|
||||||
return this.tokens[idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
splice(start, delete_count, ...add) {
|
|
||||||
this.tokens.splice(start, delete_count, ...add);
|
|
||||||
this.current = this.tokens[this.idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {TokenList} tokens
|
* @param {TokenList} tokens
|
||||||
* @param {Local[]} locals
|
* @param {Local[]} locals
|
||||||
@@ -1774,82 +1717,6 @@ function expressionList(tokens, locals, method, imports, typemap) {
|
|||||||
return expressions;
|
return expressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {TokenList} tokens
|
|
||||||
* @param {SourceMC} method
|
|
||||||
* @param {ResolvedImport[]} imports
|
|
||||||
* @param {Map<string,JavaType>} typemap
|
|
||||||
*/
|
|
||||||
function typeIdentList(tokens, method, imports, typemap) {
|
|
||||||
let type = typeIdent(tokens, method, imports, typemap);
|
|
||||||
const types = [type];
|
|
||||||
while (tokens.current.value === ',') {
|
|
||||||
tokens.inc();
|
|
||||||
type = typeIdent(tokens, method, imports, typemap);
|
|
||||||
types.push(type);
|
|
||||||
}
|
|
||||||
return types;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {TokenList} tokens
|
|
||||||
* @param {SourceMC} method
|
|
||||||
* @param {ResolvedImport[]} imports
|
|
||||||
* @param {Map<string,JavaType>} typemap
|
|
||||||
*/
|
|
||||||
function typeIdent(tokens, method, imports, typemap) {
|
|
||||||
if (tokens.current.kind !== 'ident') {
|
|
||||||
if (tokens.current.value === '?') {
|
|
||||||
return wildcardTypeArgument(tokens, method, imports, typemap);
|
|
||||||
}
|
|
||||||
return AnyType.Instance;
|
|
||||||
}
|
|
||||||
const { types, package_name } = resolveTypeOrPackage(tokens.current.value, method._owner, imports, typemap);
|
|
||||||
let matches = new ResolvedIdent(tokens.current.value, [], [], types, package_name);
|
|
||||||
tokens.inc();
|
|
||||||
for (;;) {
|
|
||||||
if (tokens.isValue('.')) {
|
|
||||||
matches = parseDottedIdent(matches, tokens, typemap);
|
|
||||||
} else if (tokens.isValue('<')) {
|
|
||||||
if (!tokens.isValue('>')) {
|
|
||||||
typeIdentList(tokens, method, imports, typemap);
|
|
||||||
if (/>>>?/.test(tokens.current.value)) {
|
|
||||||
// we need to split >> and >>> into separate > tokens to handle things like List<Class<?>>
|
|
||||||
const new_tokens = tokens.current.value.split('').map((gt,i) => new Token(tokens.current.range.source, tokens.current.range.start + i, 1, 'comparison-operator'));
|
|
||||||
tokens.splice(tokens.idx, 1, ...new_tokens);
|
|
||||||
}
|
|
||||||
tokens.expectValue('>');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches.types[0] || new UnresolvedType(matches.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {TokenList} tokens
|
|
||||||
* @param {SourceMC} method
|
|
||||||
* @param {ResolvedImport[]} imports
|
|
||||||
* @param {Map<string,JavaType>} typemap
|
|
||||||
*/
|
|
||||||
function wildcardTypeArgument(tokens, method, imports, typemap) {
|
|
||||||
tokens.expectValue('?');
|
|
||||||
let bound = null;
|
|
||||||
switch (tokens.current.value) {
|
|
||||||
case 'extends':
|
|
||||||
case 'super':
|
|
||||||
const kind = tokens.current.value;
|
|
||||||
tokens.inc();
|
|
||||||
bound = {
|
|
||||||
kind,
|
|
||||||
type: typeIdent(tokens, method, imports, typemap),
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return new WildcardType(bound);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {TokenList} tokens
|
* @param {TokenList} tokens
|
||||||
* @param {Local[]} locals
|
* @param {Local[]} locals
|
||||||
@@ -2136,41 +2003,22 @@ function parseDottedIdent(matches, tokens, typemap) {
|
|||||||
matches.types.forEach(t => {
|
matches.types.forEach(t => {
|
||||||
// if there is an AnyType, then add a type, variable and method
|
// if there is an AnyType, then add a type, variable and method
|
||||||
// - this prevents multiple errors in dotted values/
|
// - this prevents multiple errors in dotted values/
|
||||||
// e.g R.layout.name wiil only error once (on R), not on all 3 idents
|
// e.g R.layout.name will only error once (on R), not on all 3 idents
|
||||||
if (t instanceof AnyType) {
|
if (t instanceof AnyType) {
|
||||||
types.push(new AnyType(qualified_ident));
|
types.push(new AnyType(qualified_ident));
|
||||||
variables.push(new AnyValue(qualified_ident));
|
variables.push(new AnyValue(qualified_ident));
|
||||||
methods.push(new AnyMethod(tokens.current.value));
|
methods.push(new AnyMethod(tokens.current.value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t instanceof CEIType) {
|
|
||||||
const enclosed_type_signature = `${t.shortSignature}$${tokens.current.value}`;
|
|
||||||
const enc_type = typemap.get(enclosed_type_signature);
|
|
||||||
if (enc_type) {
|
|
||||||
types.push(enc_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// search static fields and methods
|
// search static fields and methods
|
||||||
const decls = t.findDeclsByName(tokens.current.value);
|
const decls = t.findDeclsByName(tokens.current.value);
|
||||||
variables.push(...decls.fields);
|
variables.push(...decls.fields);
|
||||||
methods.push(...decls.methods);
|
methods.push(...decls.methods);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matches.package_name) {
|
const members = resolveNextTypeOrPackage(tokens.current.value, matches.types, matches.package_name, typemap);
|
||||||
// if there is a package name, the ident could represent a sub-package or a top-leve type name
|
|
||||||
const type_match = `${matches.package_name}/${tokens.current.value}`;
|
|
||||||
if (typemap.has(type_match)) {
|
|
||||||
// it matches a type
|
|
||||||
types.push(typemap.get(type_match));
|
|
||||||
} else {
|
|
||||||
const package_match = `${matches.package_name}/${tokens.current.value}/`;
|
|
||||||
if ([...typemap.keys()].find(fqn => fqn.startsWith(package_match))) {
|
|
||||||
package_name = type_match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = new ResolvedIdent(qualified_ident, variables, methods, types, package_name);
|
const match = new ResolvedIdent(qualified_ident, variables, methods, [...types, ...members.types ], members.package_name);
|
||||||
checkIdentifierFound(tokens, tokens.current.value, match);
|
checkIdentifierFound(tokens, tokens.current.value, match);
|
||||||
tokens.inc();
|
tokens.inc();
|
||||||
return match;
|
return match;
|
||||||
@@ -2271,403 +2119,6 @@ function findIdentifier(ident, locals, method, imports, typemap) {
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} ident
|
|
||||||
* @param {CEIType} scoped_type
|
|
||||||
* @param {ResolvedImport[]} imports
|
|
||||||
* @param {Map<string,JavaType>} typemap
|
|
||||||
*/
|
|
||||||
function resolveTypeOrPackage(ident, scoped_type, imports, typemap) {
|
|
||||||
const types = [];
|
|
||||||
let package_name = '';
|
|
||||||
|
|
||||||
// is it an enclosed type of the currently scoped type or any outer type
|
|
||||||
if (scoped_type) {
|
|
||||||
const scopes = scoped_type.shortSignature.split('$');
|
|
||||||
while (scopes.length) {
|
|
||||||
const enc_type = typemap.get(`${scopes.join('$')}$${ident}`);
|
|
||||||
if (enc_type) {
|
|
||||||
types.push(enc_type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
scopes.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!types[0]) {
|
|
||||||
// is it a top-level type from the imports
|
|
||||||
const top_level_type = '/' + ident;
|
|
||||||
for (let i of imports) {
|
|
||||||
const fqn = i.fullyQualifiedNames.find(fqn => fqn.endsWith(top_level_type));
|
|
||||||
if (fqn) {
|
|
||||||
types.push(i.types.get(fqn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!types[0]) {
|
|
||||||
// is it a default-package type
|
|
||||||
const default_type = typemap.get(ident);
|
|
||||||
if (default_type) {
|
|
||||||
types.push(default_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the final option is the start of a package name
|
|
||||||
const package_root = ident + '/';
|
|
||||||
const typelist = [...typemap.keys()];
|
|
||||||
if (typelist.find(fqn => fqn.startsWith(package_root))) {
|
|
||||||
package_name = ident;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
types,
|
|
||||||
package_name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AnyType is a special type that's used to fill in types that are missing.
|
|
||||||
* To prevent cascading errors, AnyType should be fully assign/cast/type-compatible
|
|
||||||
* with any other type
|
|
||||||
*/
|
|
||||||
class AnyType extends JavaType {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String} label
|
|
||||||
*/
|
|
||||||
constructor(label) {
|
|
||||||
super("class", [], '');
|
|
||||||
super.simpleTypeName = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Instance = new AnyType('');
|
|
||||||
|
|
||||||
get rawTypeSignature() {
|
|
||||||
return 'U';
|
|
||||||
}
|
|
||||||
|
|
||||||
get typeSignature() {
|
|
||||||
return 'U';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AnyMethod extends Method {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
constructor(name) {
|
|
||||||
super(name, [], '');
|
|
||||||
}
|
|
||||||
|
|
||||||
get returnType() {
|
|
||||||
return AnyType.Instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Local {
|
|
||||||
/**
|
|
||||||
* @param {Token[]} modifiers
|
|
||||||
* @param {string} name
|
|
||||||
* @param {Token} decltoken
|
|
||||||
* @param {JavaType} type
|
|
||||||
* @param {JavaType} type
|
|
||||||
* @param {number} postnamearrdims
|
|
||||||
*/
|
|
||||||
constructor(modifiers, name, decltoken, type, postnamearrdims) {
|
|
||||||
this.finalToken = modifiers.find(m => m.source === 'final') || null;
|
|
||||||
this.name = name;
|
|
||||||
this.decltoken = decltoken;
|
|
||||||
if (postnamearrdims > 0) {
|
|
||||||
this.type = (type instanceof ArrayType)
|
|
||||||
? new ArrayType(type.base, type.arrdims + postnamearrdims)
|
|
||||||
: new ArrayType(type, postnamearrdims);
|
|
||||||
} else {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
this.init = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArrayElement {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Local|Parameter|Field|ArrayElement|Value} array_variable
|
|
||||||
* @param {ResolvedIdent} index
|
|
||||||
*/
|
|
||||||
constructor(array_variable, index) {
|
|
||||||
this.array_variable = array_variable;
|
|
||||||
this.index = index;
|
|
||||||
if (!(this.array_variable.type instanceof ArrayType)) {
|
|
||||||
throw new Error('Array element cannot be created from non-array type');
|
|
||||||
}
|
|
||||||
this.name = `${array_variable.name}[${index.source}]`;
|
|
||||||
/** @type {JavaType} */
|
|
||||||
this.type = this.array_variable.type.elementType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Value {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {JavaType} type
|
|
||||||
*/
|
|
||||||
constructor(name, type) {
|
|
||||||
this.name = name;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} ident
|
|
||||||
* @param {ResolvedIdent} lhs
|
|
||||||
* @param {ResolvedIdent} rhs
|
|
||||||
* @param {JavaType} type
|
|
||||||
*/
|
|
||||||
static build(ident, lhs, rhs, type) {
|
|
||||||
if (!lhs.variables[0] || !rhs.variables[0]) {
|
|
||||||
return new Value(ident, type);
|
|
||||||
}
|
|
||||||
if (lhs.variables[0] instanceof LiteralValue && rhs.variables && rhs.variables[0] instanceof LiteralValue) {
|
|
||||||
new LiteralValue(ident, type);
|
|
||||||
}
|
|
||||||
return new Value(ident, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AnyValue extends Value {
|
|
||||||
constructor(name) {
|
|
||||||
super(name, AnyType.Instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LiteralValue extends Value { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LiteralNumberType is a value representing literal numbers (like 0, 5.3, -0.1e+12, etc).
|
|
||||||
*
|
|
||||||
* It's used to allow literal numbers to be type-assignable to variables with different primitive types.
|
|
||||||
* For example, 200 is type-assignable to short, int, long, float and double, but not byte.
|
|
||||||
*/
|
|
||||||
class LiteralNumber extends LiteralValue {
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
* @param {string} kind
|
|
||||||
* @param {PrimitiveType} default_type
|
|
||||||
*/
|
|
||||||
constructor(value, kind, default_type) {
|
|
||||||
super(value, default_type);
|
|
||||||
this.numberValue = value;
|
|
||||||
this.numberKind = kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
static shift(a, b, op) {
|
|
||||||
const ai = a.toInt(), bi = b.toInt();
|
|
||||||
if (ai === null || bi === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const val = op(ai, bi);
|
|
||||||
const type = a.type.typeSignature === 'J' ? PrimitiveType.map.J : PrimitiveType.map.I;
|
|
||||||
return new LiteralNumber(val.toString(), 'int-number-literal', type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bitwise(a, b, op) {
|
|
||||||
const ai = a.toInt(), bi = b.toInt();
|
|
||||||
if (ai === null || bi === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const val = op(ai, bi);
|
|
||||||
const typekey = a.type.typeSignature+ b.type.typeSignature;
|
|
||||||
let type = /J/.test(typekey) ? PrimitiveType.map.J : PrimitiveType.map.I;
|
|
||||||
return new LiteralNumber(val.toString(), 'int-number-literal', type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static math(a, b, op, divmod) {
|
|
||||||
const ai = a.toNumber(), bi = b.toNumber();
|
|
||||||
if (bi === 0 && divmod) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let val = op(ai, bi);
|
|
||||||
const typekey = a.type.typeSignature+ b.type.typeSignature;
|
|
||||||
if (!/[FD]/.test(typekey) && divmod) {
|
|
||||||
val = Math.trunc(val);
|
|
||||||
}
|
|
||||||
let type;
|
|
||||||
if (/^(D|F[^D]|J[^FD])/.test(typekey)) {
|
|
||||||
type = a.type;
|
|
||||||
} else {
|
|
||||||
type = b.type;
|
|
||||||
}
|
|
||||||
return new LiteralNumber(val.toString(), 'int-number-literal', type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static '+'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a + b) }
|
|
||||||
static '-'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a - b) }
|
|
||||||
static '*'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a * b) }
|
|
||||||
static '/'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a / b, true) }
|
|
||||||
static '%'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a % b, true) }
|
|
||||||
static '&'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a & b) }
|
|
||||||
static '|'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a | b) }
|
|
||||||
static '^'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a ^ b) }
|
|
||||||
static '>>'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => a >> b) }
|
|
||||||
static '>>>'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => {
|
|
||||||
// unsigned shift (>>>) is not supported by bigints
|
|
||||||
// @ts-ignore
|
|
||||||
return (a >> b) & ~(-1n << (64n - b));
|
|
||||||
}) }
|
|
||||||
static '<<'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => a << b) }
|
|
||||||
|
|
||||||
toInt() {
|
|
||||||
switch (this.numberKind) {
|
|
||||||
case 'hex-number-literal':
|
|
||||||
case 'int-number-literal':
|
|
||||||
// unlike parseInt, BigInt doesn't like invalid characters, so
|
|
||||||
// ensure we strip any trailing long specifier
|
|
||||||
return BigInt(this.name.match(/(.+?)[lL]?$/)[1]);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
toNumber() {
|
|
||||||
return parseFloat(this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {JavaType} type
|
|
||||||
*/
|
|
||||||
isCompatibleWith(type) {
|
|
||||||
if (this.type === type) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
switch(this.type.simpleTypeName) {
|
|
||||||
case 'double':
|
|
||||||
return /^([D]|Ljava\/lang\/(Double);)$/.test(type.typeSignature);
|
|
||||||
case 'float':
|
|
||||||
return /^([FD]|Ljava\/lang\/(Float|Double);)$/.test(type.typeSignature);
|
|
||||||
}
|
|
||||||
// all integral types are all compatible with long, float and double variables
|
|
||||||
if (/^([JFD]|Ljava\/lang\/(Long|Float|Double);)$/.test(type.typeSignature)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// the desintation type must be a number primitive or one of the corresponding boxed classes
|
|
||||||
if (!/^([BSIJFDC]|Ljava\/lang\/(Byte|Short|Integer|Long|Float|Double|Character);)$/.test(type.typeSignature)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let number = 0;
|
|
||||||
if (this.numberKind === 'hex-number-literal') {
|
|
||||||
if (this.numberValue !== '0x') {
|
|
||||||
const non_leading_zero_digits = this.numberValue.match(/0x0*(.+)/)[1];
|
|
||||||
number = non_leading_zero_digits.length > 8 ? Number.MAX_SAFE_INTEGER : parseInt(non_leading_zero_digits, 16);
|
|
||||||
}
|
|
||||||
} else if (this.numberKind === 'int-number-literal') {
|
|
||||||
const non_leading_zero_digits = this.numberValue.match(/0*(.+)/)[1];
|
|
||||||
number = non_leading_zero_digits.length > 10 ? Number.MAX_SAFE_INTEGER : parseInt(non_leading_zero_digits, 10);
|
|
||||||
}
|
|
||||||
if (number >= -128 && number <= 127) {
|
|
||||||
return true; // byte values are compatible with all other numbers
|
|
||||||
}
|
|
||||||
if (number >= -32768 && number <= 32767) {
|
|
||||||
return !/^([B]|Ljava\/lang\/(Byte);)$/.test(type.typeSignature); // anything except byte
|
|
||||||
}
|
|
||||||
return !/^([BSC]|Ljava\/lang\/(Byte|Short|Character);)$/.test(type.typeSignature); // anything except byte, short and character
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Token} token
|
|
||||||
*/
|
|
||||||
static from(token) {
|
|
||||||
function suffix(which) {
|
|
||||||
switch(which.indexOf(token.value.slice(-1))) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
return PrimitiveType.map.F;
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
return PrimitiveType.map.D;
|
|
||||||
case 4:
|
|
||||||
case 5:
|
|
||||||
return PrimitiveType.map.J;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch(token.kind) {
|
|
||||||
case 'dec-exp-number-literal':
|
|
||||||
case 'dec-number-literal':
|
|
||||||
return new LiteralNumber(token.value, token.kind, suffix('FfDdLl') || PrimitiveType.map.D);
|
|
||||||
case 'hex-number-literal':
|
|
||||||
return new LiteralNumber(token.value, token.kind, suffix(' Ll') || PrimitiveType.map.I);
|
|
||||||
case 'int-number-literal':
|
|
||||||
default:
|
|
||||||
return new LiteralNumber(token.value, token.kind, suffix('FfDdLl') || PrimitiveType.map.I);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MethodCall extends Value {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {ResolvedIdent} instance
|
|
||||||
* @param {Method} method
|
|
||||||
*/
|
|
||||||
constructor(name, instance, method) {
|
|
||||||
super(name, method.returnType);
|
|
||||||
this.instance = instance;
|
|
||||||
this.method = method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConstructorCall extends Value {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {JavaType} type
|
|
||||||
*/
|
|
||||||
constructor(name, type) {
|
|
||||||
super(name, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArrayLiteral extends LiteralValue {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {ResolvedIdent[]} elements
|
|
||||||
*/
|
|
||||||
constructor(name, elements) {
|
|
||||||
super(name, null);
|
|
||||||
this.elements = elements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TernaryValue extends Value {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {JavaType} true_type
|
|
||||||
* @param {Token} colon
|
|
||||||
* @param {Value} false_value
|
|
||||||
*/
|
|
||||||
constructor(name, true_type, colon, false_value) {
|
|
||||||
super(name, true_type);
|
|
||||||
this.colon = colon;
|
|
||||||
this.falseValue = false_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResolvedIdent {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} ident
|
|
||||||
* @param {(Local|Parameter|Field|ArrayElement|Value)[]} variables
|
|
||||||
* @param {Method[]} methods
|
|
||||||
* @param {JavaType[]} types
|
|
||||||
* @param {string} package_name
|
|
||||||
*/
|
|
||||||
constructor(ident, variables = [], methods = [], types = [], package_name = '') {
|
|
||||||
this.source = ident;
|
|
||||||
this.variables = variables;
|
|
||||||
this.methods = methods;
|
|
||||||
this.types = types;
|
|
||||||
this.package_name = package_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
exports.addproblem = addproblem;
|
||||||
exports.parseBody = parseBody;
|
exports.parseBody = parseBody;
|
||||||
|
|||||||
356
langserver/java/body-types.js
Normal file
356
langserver/java/body-types.js
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
const { JavaType, ArrayType, PrimitiveType, Method, Parameter, Field } = require('java-mti');
|
||||||
|
const { Token } = require('./tokenizer');
|
||||||
|
|
||||||
|
class ResolvedIdent {
|
||||||
|
/**
|
||||||
|
* @param {string} ident
|
||||||
|
* @param {(Local|Parameter|Field|ArrayElement|Value)[]} variables
|
||||||
|
* @param {Method[]} methods
|
||||||
|
* @param {JavaType[]} types
|
||||||
|
* @param {string} package_name
|
||||||
|
*/
|
||||||
|
constructor(ident, variables = [], methods = [], types = [], package_name = '') {
|
||||||
|
this.source = ident;
|
||||||
|
this.variables = variables;
|
||||||
|
this.methods = methods;
|
||||||
|
this.types = types;
|
||||||
|
this.package_name = package_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AnyType is a special type that's used to fill in types that are missing.
|
||||||
|
* To prevent cascading errors, AnyType should be fully assign/cast/type-compatible
|
||||||
|
* with any other type
|
||||||
|
*/
|
||||||
|
class AnyType extends JavaType {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} label
|
||||||
|
*/
|
||||||
|
constructor(label) {
|
||||||
|
super("class", [], '');
|
||||||
|
super.simpleTypeName = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Instance = new AnyType('<unknown type>');
|
||||||
|
|
||||||
|
get rawTypeSignature() {
|
||||||
|
return 'U';
|
||||||
|
}
|
||||||
|
|
||||||
|
get typeSignature() {
|
||||||
|
return 'U';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnyMethod extends Method {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
constructor(name) {
|
||||||
|
super(name, [], '');
|
||||||
|
}
|
||||||
|
|
||||||
|
get returnType() {
|
||||||
|
return AnyType.Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Local {
|
||||||
|
/**
|
||||||
|
* @param {Token[]} modifiers
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Token} decltoken
|
||||||
|
* @param {JavaType} type
|
||||||
|
* @param {JavaType} type
|
||||||
|
* @param {number} postnamearrdims
|
||||||
|
*/
|
||||||
|
constructor(modifiers, name, decltoken, type, postnamearrdims) {
|
||||||
|
this.finalToken = modifiers.find(m => m.source === 'final') || null;
|
||||||
|
this.name = name;
|
||||||
|
this.decltoken = decltoken;
|
||||||
|
if (postnamearrdims > 0) {
|
||||||
|
this.type = (type instanceof ArrayType)
|
||||||
|
? new ArrayType(type.base, type.arrdims + postnamearrdims)
|
||||||
|
: new ArrayType(type, postnamearrdims);
|
||||||
|
} else {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
this.init = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayElement {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Local|Parameter|Field|ArrayElement|Value} array_variable
|
||||||
|
* @param {ResolvedIdent} index
|
||||||
|
*/
|
||||||
|
constructor(array_variable, index) {
|
||||||
|
this.array_variable = array_variable;
|
||||||
|
this.index = index;
|
||||||
|
if (!(this.array_variable.type instanceof ArrayType)) {
|
||||||
|
throw new Error('Array element cannot be created from non-array type');
|
||||||
|
}
|
||||||
|
this.name = `${array_variable.name}[${index.source}]`;
|
||||||
|
/** @type {JavaType} */
|
||||||
|
this.type = this.array_variable.type.elementType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Value {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {JavaType} type
|
||||||
|
*/
|
||||||
|
constructor(name, type) {
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} ident
|
||||||
|
* @param {ResolvedIdent} lhs
|
||||||
|
* @param {ResolvedIdent} rhs
|
||||||
|
* @param {JavaType} type
|
||||||
|
*/
|
||||||
|
static build(ident, lhs, rhs, type) {
|
||||||
|
if (!lhs.variables[0] || !rhs.variables[0]) {
|
||||||
|
return new Value(ident, type);
|
||||||
|
}
|
||||||
|
if (lhs.variables[0] instanceof LiteralValue && rhs.variables && rhs.variables[0] instanceof LiteralValue) {
|
||||||
|
new LiteralValue(ident, type);
|
||||||
|
}
|
||||||
|
return new Value(ident, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnyValue extends Value {
|
||||||
|
constructor(name) {
|
||||||
|
super(name, AnyType.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LiteralValue extends Value { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiteralNumberType is a value representing literal numbers (like 0, 5.3, -0.1e+12, etc).
|
||||||
|
*
|
||||||
|
* It's used to allow literal numbers to be type-assignable to variables with different primitive types.
|
||||||
|
* For example, 200 is type-assignable to short, int, long, float and double, but not byte.
|
||||||
|
*/
|
||||||
|
class LiteralNumber extends LiteralValue {
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @param {string} kind
|
||||||
|
* @param {PrimitiveType} default_type
|
||||||
|
*/
|
||||||
|
constructor(value, kind, default_type) {
|
||||||
|
super(value, default_type);
|
||||||
|
this.numberValue = value;
|
||||||
|
this.numberKind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
static shift(a, b, op) {
|
||||||
|
const ai = a.toInt(), bi = b.toInt();
|
||||||
|
if (ai === null || bi === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const val = op(ai, bi);
|
||||||
|
const type = a.type.typeSignature === 'J' ? PrimitiveType.map.J : PrimitiveType.map.I;
|
||||||
|
return new LiteralNumber(val.toString(), 'int-number-literal', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitwise(a, b, op) {
|
||||||
|
const ai = a.toInt(), bi = b.toInt();
|
||||||
|
if (ai === null || bi === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const val = op(ai, bi);
|
||||||
|
const typekey = a.type.typeSignature+ b.type.typeSignature;
|
||||||
|
let type = /J/.test(typekey) ? PrimitiveType.map.J : PrimitiveType.map.I;
|
||||||
|
return new LiteralNumber(val.toString(), 'int-number-literal', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static math(a, b, op, divmod) {
|
||||||
|
const ai = a.toNumber(), bi = b.toNumber();
|
||||||
|
if (bi === 0 && divmod) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let val = op(ai, bi);
|
||||||
|
const typekey = a.type.typeSignature+ b.type.typeSignature;
|
||||||
|
if (!/[FD]/.test(typekey) && divmod) {
|
||||||
|
val = Math.trunc(val);
|
||||||
|
}
|
||||||
|
let type;
|
||||||
|
if (/^(D|F[^D]|J[^FD])/.test(typekey)) {
|
||||||
|
type = a.type;
|
||||||
|
} else {
|
||||||
|
type = b.type;
|
||||||
|
}
|
||||||
|
return new LiteralNumber(val.toString(), 'int-number-literal', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static '+'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a + b) }
|
||||||
|
static '-'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a - b) }
|
||||||
|
static '*'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a * b) }
|
||||||
|
static '/'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a / b, true) }
|
||||||
|
static '%'(lhs, rhs) { return LiteralNumber.math(lhs, rhs, (a,b) => a % b, true) }
|
||||||
|
static '&'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a & b) }
|
||||||
|
static '|'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a | b) }
|
||||||
|
static '^'(lhs, rhs) { return LiteralNumber.bitwise(lhs, rhs, (a,b) => a ^ b) }
|
||||||
|
static '>>'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => a >> b) }
|
||||||
|
static '>>>'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => {
|
||||||
|
// unsigned shift (>>>) is not supported by bigints
|
||||||
|
// @ts-ignore
|
||||||
|
return (a >> b) & ~(-1n << (64n - b));
|
||||||
|
}) }
|
||||||
|
static '<<'(lhs, rhs) { return LiteralNumber.shift(lhs, rhs, (a,b) => a << b) }
|
||||||
|
|
||||||
|
toInt() {
|
||||||
|
switch (this.numberKind) {
|
||||||
|
case 'hex-number-literal':
|
||||||
|
case 'int-number-literal':
|
||||||
|
// unlike parseInt, BigInt doesn't like invalid characters, so
|
||||||
|
// ensure we strip any trailing long specifier
|
||||||
|
return BigInt(this.name.match(/(.+?)[lL]?$/)[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toNumber() {
|
||||||
|
return parseFloat(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {JavaType} type
|
||||||
|
*/
|
||||||
|
isCompatibleWith(type) {
|
||||||
|
if (this.type === type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch(this.type.simpleTypeName) {
|
||||||
|
case 'double':
|
||||||
|
return /^([D]|Ljava\/lang\/(Double);)$/.test(type.typeSignature);
|
||||||
|
case 'float':
|
||||||
|
return /^([FD]|Ljava\/lang\/(Float|Double);)$/.test(type.typeSignature);
|
||||||
|
}
|
||||||
|
// all integral types are all compatible with long, float and double variables
|
||||||
|
if (/^([JFD]|Ljava\/lang\/(Long|Float|Double);)$/.test(type.typeSignature)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// the desintation type must be a number primitive or one of the corresponding boxed classes
|
||||||
|
if (!/^([BSIJFDC]|Ljava\/lang\/(Byte|Short|Integer|Long|Float|Double|Character);)$/.test(type.typeSignature)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let number = 0;
|
||||||
|
if (this.numberKind === 'hex-number-literal') {
|
||||||
|
if (this.numberValue !== '0x') {
|
||||||
|
const non_leading_zero_digits = this.numberValue.match(/0x0*(.+)/)[1];
|
||||||
|
number = non_leading_zero_digits.length > 8 ? Number.MAX_SAFE_INTEGER : parseInt(non_leading_zero_digits, 16);
|
||||||
|
}
|
||||||
|
} else if (this.numberKind === 'int-number-literal') {
|
||||||
|
const non_leading_zero_digits = this.numberValue.match(/0*(.+)/)[1];
|
||||||
|
number = non_leading_zero_digits.length > 10 ? Number.MAX_SAFE_INTEGER : parseInt(non_leading_zero_digits, 10);
|
||||||
|
}
|
||||||
|
if (number >= -128 && number <= 127) {
|
||||||
|
return true; // byte values are compatible with all other numbers
|
||||||
|
}
|
||||||
|
if (number >= -32768 && number <= 32767) {
|
||||||
|
return !/^([B]|Ljava\/lang\/(Byte);)$/.test(type.typeSignature); // anything except byte
|
||||||
|
}
|
||||||
|
return !/^([BSC]|Ljava\/lang\/(Byte|Short|Character);)$/.test(type.typeSignature); // anything except byte, short and character
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Token} token
|
||||||
|
*/
|
||||||
|
static from(token) {
|
||||||
|
function suffix(which) {
|
||||||
|
switch(which.indexOf(token.value.slice(-1))) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
return PrimitiveType.map.F;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
return PrimitiveType.map.D;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
return PrimitiveType.map.J;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch(token.kind) {
|
||||||
|
case 'dec-exp-number-literal':
|
||||||
|
case 'dec-number-literal':
|
||||||
|
return new LiteralNumber(token.value, token.kind, suffix('FfDdLl') || PrimitiveType.map.D);
|
||||||
|
case 'hex-number-literal':
|
||||||
|
return new LiteralNumber(token.value, token.kind, suffix(' Ll') || PrimitiveType.map.I);
|
||||||
|
case 'int-number-literal':
|
||||||
|
default:
|
||||||
|
return new LiteralNumber(token.value, token.kind, suffix('FfDdLl') || PrimitiveType.map.I);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MethodCall extends Value {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {ResolvedIdent} instance
|
||||||
|
* @param {Method} method
|
||||||
|
*/
|
||||||
|
constructor(name, instance, method) {
|
||||||
|
super(name, method.returnType);
|
||||||
|
this.instance = instance;
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConstructorCall extends Value {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {JavaType} type
|
||||||
|
*/
|
||||||
|
constructor(name, type) {
|
||||||
|
super(name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayLiteral extends LiteralValue {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {ResolvedIdent[]} elements
|
||||||
|
*/
|
||||||
|
constructor(name, elements) {
|
||||||
|
super(name, null);
|
||||||
|
this.elements = elements;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TernaryValue extends Value {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {JavaType} true_type
|
||||||
|
* @param {Token} colon
|
||||||
|
* @param {Value} false_value
|
||||||
|
*/
|
||||||
|
constructor(name, true_type, colon, false_value) {
|
||||||
|
super(name, true_type);
|
||||||
|
this.colon = colon;
|
||||||
|
this.falseValue = false_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.ResolvedIdent = ResolvedIdent;
|
||||||
|
exports.AnyType = AnyType;
|
||||||
|
exports.AnyMethod = AnyMethod;
|
||||||
|
exports.Local = Local;
|
||||||
|
exports.ArrayElement = ArrayElement;
|
||||||
|
exports.Value = Value;
|
||||||
|
exports.AnyValue = AnyValue;
|
||||||
|
exports.LiteralValue = LiteralValue;
|
||||||
|
exports.LiteralNumber = LiteralNumber;
|
||||||
|
exports.MethodCall = MethodCall;
|
||||||
|
exports.ConstructorCall = ConstructorCall;
|
||||||
|
exports.ArrayLiteral = ArrayLiteral;
|
||||||
|
exports.TernaryValue = TernaryValue;
|
||||||
@@ -257,10 +257,109 @@ function resolveTypeIdents(types, fully_qualified_scope, resolved_imports, typem
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} ident
|
||||||
|
* @param {CEIType} scoped_type
|
||||||
|
* @param {ResolvedImport[]} imports
|
||||||
|
* @param {Map<string,JavaType>} typemap
|
||||||
|
*/
|
||||||
|
function resolveTypeOrPackage(ident, scoped_type, imports, typemap) {
|
||||||
|
const types = [];
|
||||||
|
let package_name = '';
|
||||||
|
|
||||||
|
// is it an enclosed type of the currently scoped type or any outer type
|
||||||
|
if (scoped_type) {
|
||||||
|
const scopes = scoped_type.shortSignature.split('$');
|
||||||
|
while (scopes.length) {
|
||||||
|
const enc_type = typemap.get(`${scopes.join('$')}$${ident}`);
|
||||||
|
if (enc_type) {
|
||||||
|
types.push(enc_type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scopes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!types[0]) {
|
||||||
|
// is it a top-level type from the imports
|
||||||
|
const top_level_type = '/' + ident;
|
||||||
|
for (let i of imports) {
|
||||||
|
const fqn = i.fullyQualifiedNames.find(fqn => fqn.endsWith(top_level_type));
|
||||||
|
if (fqn) {
|
||||||
|
types.push(i.types.get(fqn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!types[0]) {
|
||||||
|
// is it a default-package type
|
||||||
|
const default_type = typemap.get(ident);
|
||||||
|
if (default_type) {
|
||||||
|
types.push(default_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the final option is the start of a package name
|
||||||
|
const package_root = ident + '/';
|
||||||
|
const typelist = [...typemap.keys()];
|
||||||
|
if (typelist.find(fqn => fqn.startsWith(package_root))) {
|
||||||
|
package_name = ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
types,
|
||||||
|
package_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} ident
|
||||||
|
* @param {JavaType[]} outer_types
|
||||||
|
* @param {string} outer_package_name
|
||||||
|
* @param {Map<string,JavaType>} typemap
|
||||||
|
*/
|
||||||
|
function resolveNextTypeOrPackage(ident, outer_types, outer_package_name, typemap) {
|
||||||
|
const types = [];
|
||||||
|
let package_name = '';
|
||||||
|
|
||||||
|
outer_types.forEach(type => {
|
||||||
|
if (type instanceof CEIType) {
|
||||||
|
const enclosed_type_signature = `${type.shortSignature}$${ident}`;
|
||||||
|
const enclosed_type = typemap.get(enclosed_type_signature);
|
||||||
|
if (enclosed_type) {
|
||||||
|
// it matches an inner/enclosed type
|
||||||
|
types.push(enclosed_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (outer_package_name) {
|
||||||
|
const type_match = `${outer_package_name}/${ident}`;
|
||||||
|
if (typemap.has(type_match)) {
|
||||||
|
// it matches a type
|
||||||
|
types.push(typemap.get(type_match));
|
||||||
|
}
|
||||||
|
const package_match = type_match + '/';
|
||||||
|
if ([...typemap.keys()].find(fqn => fqn.startsWith(package_match))) {
|
||||||
|
// it matches a sub-package
|
||||||
|
package_name = type_match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
types,
|
||||||
|
package_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parse_type,
|
parse_type,
|
||||||
resolveType,
|
resolveType,
|
||||||
resolveTypes,
|
resolveTypes,
|
||||||
resolveTypeIdents,
|
resolveTypeIdents,
|
||||||
ResolvedType,
|
ResolvedType,
|
||||||
|
resolveTypeOrPackage,
|
||||||
|
resolveNextTypeOrPackage,
|
||||||
}
|
}
|
||||||
|
|||||||
94
langserver/java/typeident.js
Normal file
94
langserver/java/typeident.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
const { JavaType, WildcardType } = require('java-mti');
|
||||||
|
const { SourceMethod, SourceConstructor, SourceInitialiser } = require('./source-type');
|
||||||
|
const ResolvedImport = require('./parsetypes/resolved-import');
|
||||||
|
const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resolver');
|
||||||
|
const { Token } = require('./tokenizer');
|
||||||
|
const { AnyType, ResolvedIdent } = require("./body-types");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {SourceMethod|SourceConstructor|SourceInitialiser} SourceMC
|
||||||
|
* @typedef {import('./TokenList').TokenList} TokenList
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TokenList} tokens
|
||||||
|
* @param {SourceMC} method
|
||||||
|
* @param {ResolvedImport[]} imports
|
||||||
|
* @param {Map<string,JavaType>} typemap
|
||||||
|
*/
|
||||||
|
function typeIdentList(tokens, method, imports, typemap) {
|
||||||
|
let type = typeIdent(tokens, method, imports, typemap);
|
||||||
|
const types = [type];
|
||||||
|
while (tokens.current.value === ',') {
|
||||||
|
tokens.inc();
|
||||||
|
type = typeIdent(tokens, method, imports, typemap);
|
||||||
|
types.push(type);
|
||||||
|
}
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TokenList} tokens
|
||||||
|
* @param {SourceMC} method
|
||||||
|
* @param {ResolvedImport[]} imports
|
||||||
|
* @param {Map<string,JavaType>} typemap
|
||||||
|
*/
|
||||||
|
function typeIdent(tokens, method, imports, typemap) {
|
||||||
|
if (tokens.current.kind !== 'ident') {
|
||||||
|
if (tokens.current.value === '?') {
|
||||||
|
return wildcardTypeArgument(tokens, method, imports, typemap);
|
||||||
|
}
|
||||||
|
return AnyType.Instance;
|
||||||
|
}
|
||||||
|
const { types, package_name } = resolveTypeOrPackage(tokens.current.value, method._owner, imports, typemap);
|
||||||
|
tokens.inc();
|
||||||
|
for (;;) {
|
||||||
|
if (tokens.isValue('.')) {
|
||||||
|
if (tokens.current.kind !== 'ident') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
resolveNextTypeOrPackage(tokens.current.value, types, package_name, typemap);
|
||||||
|
} else if (tokens.isValue('<')) {
|
||||||
|
if (!tokens.isValue('>')) {
|
||||||
|
typeIdentList(tokens, method, imports, typemap);
|
||||||
|
if (/>>>?/.test(tokens.current.value)) {
|
||||||
|
// we need to split >> and >>> into separate > tokens to handle things like List<Class<?>>
|
||||||
|
const new_tokens = tokens.current.value.split('').map((gt,i) => new Token(tokens.current.range.source, tokens.current.range.start + i, 1, 'comparison-operator'));
|
||||||
|
tokens.splice(tokens.idx, 1, ...new_tokens);
|
||||||
|
}
|
||||||
|
tokens.expectValue('>');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return types[0] || AnyType.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TokenList} tokens
|
||||||
|
* @param {SourceMC} method
|
||||||
|
* @param {ResolvedImport[]} imports
|
||||||
|
* @param {Map<string,JavaType>} typemap
|
||||||
|
* @returns {WildcardType}
|
||||||
|
*/
|
||||||
|
function wildcardTypeArgument(tokens, method, imports, typemap) {
|
||||||
|
tokens.expectValue('?');
|
||||||
|
let bound = null;
|
||||||
|
switch (tokens.current.value) {
|
||||||
|
case 'extends':
|
||||||
|
case 'super':
|
||||||
|
const kind = tokens.current.value;
|
||||||
|
tokens.inc();
|
||||||
|
bound = {
|
||||||
|
kind,
|
||||||
|
type: typeIdent(tokens, method, imports, typemap),
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new WildcardType(bound);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.typeIdent = typeIdent;
|
||||||
|
exports.typeIdentList = typeIdentList;
|
||||||
Reference in New Issue
Block a user