Regexp literal support; Properly resolve statically inherited members

This commit is contained in:
dcodeIO 2018-01-07 15:07:46 +01:00
parent d8fa04f910
commit 7c8670ac35
10 changed files with 169 additions and 88 deletions

View File

@ -240,10 +240,11 @@ export abstract class Node {
return expr;
}
static createRegexpLiteralExpression(value: string, range: Range): RegexpLiteralExpression {
static createRegexpLiteralExpression(pattern: string, modifiers: string, range: Range): RegexpLiteralExpression {
var expr = new RegexpLiteralExpression();
expr.range = range;
expr.value = value;
expr.pattern = pattern;
expr.modifiers = modifiers;
return expr;
}
@ -1045,11 +1046,16 @@ export class RegexpLiteralExpression extends LiteralExpression {
// kind = NodeKind.LITERAL
literalKind = LiteralKind.REGEXP;
/** Value of the expression. */
value: string;
/** Regular expression pattern. */
pattern: string;
/** Regular expression modifiers. */
modifiers: string;
serialize(sb: string[]): void {
sb.push(this.value);
sb.push("/");
sb.push(this.pattern);
sb.push("/");
sb.push(this.modifiers);
}
}

View File

@ -212,6 +212,11 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
case "isNaN": // isNaN<T>(value: T) -> bool
compiler.currentType = Type.bool;
// if (operands.length != 1) {
// compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "1", operands.length.toString(10));
// return module.createUnreachable();
// }
// TODO: infer type argument if omitted
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if ((<Type[]>typeArguments)[0].isAnyInteger)

View File

@ -30,8 +30,7 @@ export class Decompiler {
private tempI64: I64 = new I64();
constructor() {
}
constructor() { }
/** Decompiles a module to an AST that can then be serialized. */
decompile(module: Module) {

View File

@ -63,6 +63,7 @@ export enum DiagnosticCode {
Generic_type_0_requires_1_type_argument_s = 2314,
Type_0_is_not_generic = 2315,
Type_0_is_not_assignable_to_type_1 = 2322,
Index_signature_is_missing_in_type_0 = 2329,
_this_cannot_be_referenced_in_current_location = 2332,
_super_can_only_be_referenced_in_a_derived_class = 2335,
Property_0_does_not_exist_on_type_1 = 2339,
@ -147,6 +148,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2314: return "Generic type '{0}' requires {1} type argument(s).";
case 2315: return "Type '{0}' is not generic.";
case 2322: return "Type '{0}' is not assignable to type '{1}'.";
case 2329: return "Index signature is missing in type '{0}'.";
case 2332: return "'this' cannot be referenced in current location.";
case 2335: return "'super' can only be referenced in a derived class.";
case 2339: return "Property '{0}' does not exist on type '{1}'.";

View File

@ -63,6 +63,7 @@
"Generic type '{0}' requires {1} type argument(s).": 2314,
"Type '{0}' is not generic.": 2315,
"Type '{0}' is not assignable to type '{1}'.": 2322,
"Index signature is missing in type '{0}'.": 2329,
"'this' cannot be referenced in current location.": 2332,
"'super' can only be referenced in a derived class.": 2335,
"Property '{0}' does not exist on type '{1}'.": 2339,

View File

@ -31,13 +31,14 @@ import {
NodeKind,
Source,
TypeNode,
// expressions
Expression,
AssertionKind,
CallExpression,
Expression,
IdentifierExpression,
StringLiteralExpression,
// statements
Statement,
BlockStatement,
BreakStatement,
ClassDeclaration,
@ -63,7 +64,6 @@ import {
NamespaceDeclaration,
Parameter,
ReturnStatement,
Statement,
SwitchCase,
SwitchStatement,
ThrowStatement,
@ -73,7 +73,7 @@ import {
VariableStatement,
VariableDeclaration,
WhileStatement,
// utility
addModifier,
getModifier,
hasModifier,
@ -108,6 +108,7 @@ export class Parser extends DiagnosticEmitter {
this.program.sources.push(source);
var tn = new Tokenizer(source, this.program.diagnostics);
tn.silentDiagnostics = this.silentDiagnostics;
source.tokenizer = tn;
while (!tn.skip(Token.ENDOFFILE)) {
@ -1512,18 +1513,15 @@ export class Parser extends DiagnosticEmitter {
return Node.createFloatLiteralExpression(tn.readFloat(), tn.range(startPos, tn.pos));
// RegexpLiteralExpression
/*
case Token.SLASH:
var regexpLit = Node.createRegexpLiteral(tn.readRegexp(), tn.range(startPos, tn.pos));
var regexpPattern = tn.readRegexpPattern();
if (regexpPattern == null)
return null;
if (!tn.skip(Token.SLASH)) {
this.error(DiagnosticCode._0_expected, tn.range(), "/");
return null;
}
// TODO: modifiers, may be move to tokenizer
return regexpLit;
*/
case Token.REGEXPLITERAL: // not yet supported
return Node.createRegexpLiteralExpression(tn.readRegexp(), tn.range(startPos, tn.pos));
return Node.createRegexpLiteralExpression(regexpPattern, tn.readRegexpModifiers(), tn.range(startPos, tn.pos));
default:
this.error(DiagnosticCode.Expression_expected, tn.range());

View File

@ -146,6 +146,7 @@ export class Program extends DiagnosticEmitter {
var queuedExports = new Map<string,QueuedExport>();
var queuedImports = new Array<QueuedImport>();
var queuedDerivedClasses = new Array<ClassPrototype>();
// build initial lookup maps of internal names to declarations
for (var i = 0, k = this.sources.length; i < k; ++i) {
@ -156,7 +157,7 @@ export class Program extends DiagnosticEmitter {
switch (statement.kind) {
case NodeKind.CLASS:
this.initializeClass(<ClassDeclaration>statement);
this.initializeClass(<ClassDeclaration>statement, queuedDerivedClasses);
break;
case NodeKind.ENUM:
@ -180,7 +181,7 @@ export class Program extends DiagnosticEmitter {
break;
case NodeKind.NAMESPACE:
this.initializeNamespace(<NamespaceDeclaration>statement);
this.initializeNamespace(<NamespaceDeclaration>statement, queuedDerivedClasses, null);
break;
case NodeKind.TYPEDECLARATION:
@ -232,6 +233,22 @@ export class Program extends DiagnosticEmitter {
}
} while (currentExport);
}
// resolve base prototypes of derived classes
for (i = 0, k = queuedDerivedClasses.length; i < k; ++i) {
var derivedDeclaration = queuedDerivedClasses[i].declaration;
assert(derivedDeclaration != null);
var derivedType = (<ClassDeclaration>derivedDeclaration).extendsType;
assert(derivedType != null);
var resolved = this.resolveIdentifier((<TypeNode>derivedType).identifier, null); // reports
if (resolved) {
if (resolved.element.kind != ElementKind.CLASS_PROTOTYPE) {
this.error(DiagnosticCode.A_class_may_only_extend_another_class, (<TypeNode>derivedType).range);
continue;
}
queuedDerivedClasses[i].basePrototype = <ClassPrototype>resolved.element;
}
}
}
/** Tries to resolve an import by traversing exports and queued exports. */
@ -252,7 +269,7 @@ export class Program extends DiagnosticEmitter {
} while (true);
}
private initializeClass(declaration: ClassDeclaration, namespace: Element | null = null): void {
private initializeClass(declaration: ClassDeclaration, queuedDerivedClasses: ClassPrototype[], namespace: Element | null = null): void {
var internalName = declaration.internalName;
if (this.elements.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
@ -276,6 +293,10 @@ export class Program extends DiagnosticEmitter {
} else if (declaration.implementsTypes.length)
throw new Error("not implemented");
// remember classes that extend another one
if (declaration.extendsType)
queuedDerivedClasses.push(prototype);
// add as namespace member if applicable
if (namespace) {
if (namespace.members) {
@ -762,7 +783,7 @@ export class Program extends DiagnosticEmitter {
}
}
private initializeNamespace(declaration: NamespaceDeclaration, parentNamespace: Element | null = null): void {
private initializeNamespace(declaration: NamespaceDeclaration, queuedExtendingClasses: ClassPrototype[], parentNamespace: Element | null = null): void {
var internalName = declaration.internalName;
var namespace = this.elements.get(internalName);
@ -794,7 +815,7 @@ export class Program extends DiagnosticEmitter {
switch (members[i].kind) {
case NodeKind.CLASS:
this.initializeClass(<ClassDeclaration>members[i], namespace);
this.initializeClass(<ClassDeclaration>members[i], queuedExtendingClasses, namespace);
break;
case NodeKind.ENUM:
@ -810,7 +831,7 @@ export class Program extends DiagnosticEmitter {
break;
case NodeKind.NAMESPACE:
this.initializeNamespace(<NamespaceDeclaration>members[i], namespace);
this.initializeNamespace(<NamespaceDeclaration>members[i], queuedExtendingClasses, namespace);
break;
case NodeKind.TYPEDECLARATION:
@ -957,23 +978,27 @@ export class Program extends DiagnosticEmitter {
}
/** Resolves an identifier to the element it refers to. */
resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function): ResolvedElement | null {
resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function | null): ResolvedElement | null {
var name = identifier.name;
var local = contextualFunction.locals.get(name);
if (local)
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
var element: Element | null;
var namespace: Element | null;
if (contextualFunction) {
// check locals
var local = contextualFunction.locals.get(name);
if (local)
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
// search contextual parent namespaces if applicable
if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) {
if (namespace = contextualFunction.prototype.namespace) {
do {
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))
// if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)))
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
} while (namespace = namespace.namespace);
}
}
// search current file
if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name))
@ -998,6 +1023,7 @@ export class Program extends DiagnosticEmitter {
// at this point we know exactly what the target is, so look up the element within
var propertyName = propertyAccess.property.name;
var targetType: Type;
var member: Element | null;
switch (target.kind) {
case ElementKind.GLOBAL:
@ -1008,12 +1034,31 @@ export class Program extends DiagnosticEmitter {
target = <Class>targetType.classType;
// fall-through
default:
if (target.members) {
var member = target.members.get(propertyName);
if (member)
case ElementKind.CLASS_PROTOTYPE:
case ElementKind.CLASS:
do {
if (target.members && (member = target.members.get(propertyName)))
return resolvedElement.set(member).withTarget(target, targetExpression);
// check inherited static members on the base prototype while target is a class prototype
if (target.kind == ElementKind.CLASS_PROTOTYPE) {
if ((<ClassPrototype>target).basePrototype)
target = <ClassPrototype>(<ClassPrototype>target).basePrototype;
else
break;
// or inherited instance members on the cbase class while target is a class instance
} else if (target.kind == ElementKind.CLASS) {
if ((<Class>target).base)
target = <Class>(<Class>target).base;
else
break;
} else
break;
} while (true);
break;
default: // enums or other namespace-like elements
if (target.members && (member = target.members.get(propertyName)))
return resolvedElement.set(member).withTarget(target, targetExpression);
}
break;
}
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, target.internalName);
@ -1026,16 +1071,19 @@ export class Program extends DiagnosticEmitter {
if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
return null;
var target = resolvedElement.element;
switch (target.kind) {
// TBD: should indexed access on static classes, like `Heap`, be a supported as well?
case ElementKind.CLASS:
var type = (<Class>target).type;
if (type.classType) {
// TODO: check if array etc.
var indexedGet: FunctionPrototype | null;
if (indexedGet = (target = type.classType).prototype.opIndexedGet)
return resolvedElement.set(indexedGet).withTarget(target, targetExpression);
}
break;
}
this.error(DiagnosticCode.Operation_not_supported, elementAccess.range);
this.error(DiagnosticCode.Index_signature_is_missing_in_type_0, targetExpression.range, target.internalName);
return null;
}
@ -1820,15 +1868,17 @@ export class ClassPrototype extends Element {
instances: Map<string,Class> = new Map();
/** Instance member prototypes. */
instanceMembers: Map<string,Element> | null = null;
/** Base class prototype, if applicable. */
basePrototype: ClassPrototype | null = null; // set in Program#initialize
/** Overloaded indexed get method, if any. */
opIndexedGet: FunctionPrototype | null;
opIndexedGet: FunctionPrototype | null = null; // TODO: indexedGet and indexedSet as an accessor?
/** Overloaded indexed set method, if any. */
opIndexedSet: FunctionPrototype | null;
opIndexedSet: FunctionPrototype | null = null;
/** Overloaded concatenation method, if any. */
opConcat: FunctionPrototype | null;
opConcat: FunctionPrototype | null = null;
/** Overloaded equality comparison method, if any. */
opEquals: FunctionPrototype | null;
opEquals: FunctionPrototype | null = null;
constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) {
super(program, simpleName, internalName);
@ -1878,16 +1928,6 @@ export class ClassPrototype extends Element {
this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
return null;
}
if ((this.flags & ElementFlags.HAS_STATIC_BASE_MEMBERS) == 0) { // inherit static base members once
this.flags |= ElementFlags.HAS_STATIC_BASE_MEMBERS;
if (baseClass.prototype.members) {
if (!this.members)
this.members = new Map();
for (var baseMember of baseClass.prototype.members.values())
if (!baseMember.isInstance)
this.members.set(baseMember.simpleName, baseMember);
}
}
if (baseClass.prototype.isStruct != this.isStruct) {
this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
return null;

View File

@ -14,7 +14,6 @@
readFloat() on FLOATLITERAL
readIdentifier() on IDENTIFIER
readInteger() on INTEGERLITERAL
readRegexp() on REGEXPLITERAL // TODO
readString() on STRINGLITERAL
*/
@ -174,7 +173,6 @@ export enum Token {
STRINGLITERAL,
INTEGERLITERAL,
FLOATLITERAL,
REGEXPLITERAL,
// meta
@ -562,8 +560,6 @@ export class Tokenizer extends DiagnosticEmitter {
return Token.SLASH_EQUALS;
}
}
if (this.testRegexp())
return Token.REGEXPLITERAL; // expects a call to readRegexp
return Token.SLASH;
case CharCode._0:
@ -905,21 +901,14 @@ export class Tokenizer extends DiagnosticEmitter {
}
}
testRegexp(): bool {
// TODO: this'll require more context
return false;
}
readRegexp(): string {
readRegexpPattern(): string | null {
var text = this.source.text;
var start = this.pos;
var result = "";
var escaped = false;
while (true) {
if (this.pos >= this.end) {
result += text.substring(start, this.pos);
this.error(DiagnosticCode.Unterminated_regular_expression_literal, this.range(start, this.end));
break;
return null;
}
if (text.charCodeAt(this.pos) == CharCode.BACKSLASH) {
++this.pos;
@ -927,19 +916,36 @@ export class Tokenizer extends DiagnosticEmitter {
continue;
}
var c = text.charCodeAt(this.pos);
if (c == CharCode.SLASH) {
result += text.substring(start, this.pos);
++this.pos;
if (c == CharCode.SLASH && !escaped)
break;
}
if (isLineBreak(c)) {
result += text.substring(start, this.pos);
this.error(DiagnosticCode.Unterminated_regular_expression_literal, this.range(start, this.pos));
break;
return null;
}
++this.pos;
escaped = false;
}
return result;
return text.substring(start, this.pos);
}
readRegexpModifiers(): string {
var text = this.source.text;
var start = this.pos;
/a/
while (this.pos < this.end) {
switch (text.charCodeAt(this.pos)) {
case CharCode.g:
case CharCode.i:
case CharCode.m:
++this.pos;
break;
default:
return text.substring(start, this.pos);
}
}
return text.substring(start, this.pos);
}
testInteger(): bool {

18
tests/parser/regexp.ts Normal file
View File

@ -0,0 +1,18 @@
// with modifiers
/(abc)\//ig;
// without modifiers
/(abc)\//;
// can be assigned
var re = /(abc)\//ig;
// generally behaves like an expression
var noRe = !/(abc)\//i;
// inner line break is unterminated
/a
b/ig;
// just a comment
//ig;

View File

@ -0,0 +1,6 @@
/(abc)\//ig;
/(abc)\//;
let re = /(abc)\//ig;
let noRe = !/(abc)\//i;
b / ig;
// ERROR 1161: "Unterminated regular expression literal." in regexp.ts @ 75,76