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; return expr;
} }
static createRegexpLiteralExpression(value: string, range: Range): RegexpLiteralExpression { static createRegexpLiteralExpression(pattern: string, modifiers: string, range: Range): RegexpLiteralExpression {
var expr = new RegexpLiteralExpression(); var expr = new RegexpLiteralExpression();
expr.range = range; expr.range = range;
expr.value = value; expr.pattern = pattern;
expr.modifiers = modifiers;
return expr; return expr;
} }
@ -1045,11 +1046,16 @@ export class RegexpLiteralExpression extends LiteralExpression {
// kind = NodeKind.LITERAL // kind = NodeKind.LITERAL
literalKind = LiteralKind.REGEXP; literalKind = LiteralKind.REGEXP;
/** Value of the expression. */ /** Regular expression pattern. */
value: string; pattern: string;
/** Regular expression modifiers. */
modifiers: string;
serialize(sb: string[]): void { 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 case "isNaN": // isNaN<T>(value: T) -> bool
compiler.currentType = Type.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)) if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable(); return module.createUnreachable();
if ((<Type[]>typeArguments)[0].isAnyInteger) if ((<Type[]>typeArguments)[0].isAnyInteger)

View File

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

View File

@ -63,6 +63,7 @@ export enum DiagnosticCode {
Generic_type_0_requires_1_type_argument_s = 2314, Generic_type_0_requires_1_type_argument_s = 2314,
Type_0_is_not_generic = 2315, Type_0_is_not_generic = 2315,
Type_0_is_not_assignable_to_type_1 = 2322, 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, _this_cannot_be_referenced_in_current_location = 2332,
_super_can_only_be_referenced_in_a_derived_class = 2335, _super_can_only_be_referenced_in_a_derived_class = 2335,
Property_0_does_not_exist_on_type_1 = 2339, 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 2314: return "Generic type '{0}' requires {1} type argument(s).";
case 2315: return "Type '{0}' is not generic."; case 2315: return "Type '{0}' is not generic.";
case 2322: return "Type '{0}' is not assignable to type '{1}'."; 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 2332: return "'this' cannot be referenced in current location.";
case 2335: return "'super' can only be referenced in a derived class."; case 2335: return "'super' can only be referenced in a derived class.";
case 2339: return "Property '{0}' does not exist on type '{1}'."; 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, "Generic type '{0}' requires {1} type argument(s).": 2314,
"Type '{0}' is not generic.": 2315, "Type '{0}' is not generic.": 2315,
"Type '{0}' is not assignable to type '{1}'.": 2322, "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, "'this' cannot be referenced in current location.": 2332,
"'super' can only be referenced in a derived class.": 2335, "'super' can only be referenced in a derived class.": 2335,
"Property '{0}' does not exist on type '{1}'.": 2339, "Property '{0}' does not exist on type '{1}'.": 2339,

View File

@ -31,13 +31,14 @@ import {
NodeKind, NodeKind,
Source, Source,
TypeNode, TypeNode,
// expressions
Expression,
AssertionKind, AssertionKind,
CallExpression, CallExpression,
Expression,
IdentifierExpression, IdentifierExpression,
StringLiteralExpression, StringLiteralExpression,
// statements
Statement,
BlockStatement, BlockStatement,
BreakStatement, BreakStatement,
ClassDeclaration, ClassDeclaration,
@ -63,7 +64,6 @@ import {
NamespaceDeclaration, NamespaceDeclaration,
Parameter, Parameter,
ReturnStatement, ReturnStatement,
Statement,
SwitchCase, SwitchCase,
SwitchStatement, SwitchStatement,
ThrowStatement, ThrowStatement,
@ -73,7 +73,7 @@ import {
VariableStatement, VariableStatement,
VariableDeclaration, VariableDeclaration,
WhileStatement, WhileStatement,
// utility
addModifier, addModifier,
getModifier, getModifier,
hasModifier, hasModifier,
@ -108,6 +108,7 @@ export class Parser extends DiagnosticEmitter {
this.program.sources.push(source); this.program.sources.push(source);
var tn = new Tokenizer(source, this.program.diagnostics); var tn = new Tokenizer(source, this.program.diagnostics);
tn.silentDiagnostics = this.silentDiagnostics;
source.tokenizer = tn; source.tokenizer = tn;
while (!tn.skip(Token.ENDOFFILE)) { while (!tn.skip(Token.ENDOFFILE)) {
@ -1512,18 +1513,15 @@ export class Parser extends DiagnosticEmitter {
return Node.createFloatLiteralExpression(tn.readFloat(), tn.range(startPos, tn.pos)); return Node.createFloatLiteralExpression(tn.readFloat(), tn.range(startPos, tn.pos));
// RegexpLiteralExpression // RegexpLiteralExpression
/*
case Token.SLASH: 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)) { if (!tn.skip(Token.SLASH)) {
this.error(DiagnosticCode._0_expected, tn.range(), "/"); this.error(DiagnosticCode._0_expected, tn.range(), "/");
return null; return null;
} }
// TODO: modifiers, may be move to tokenizer return Node.createRegexpLiteralExpression(regexpPattern, tn.readRegexpModifiers(), tn.range(startPos, tn.pos));
return regexpLit;
*/
case Token.REGEXPLITERAL: // not yet supported
return Node.createRegexpLiteralExpression(tn.readRegexp(), tn.range(startPos, tn.pos));
default: default:
this.error(DiagnosticCode.Expression_expected, tn.range()); 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 queuedExports = new Map<string,QueuedExport>();
var queuedImports = new Array<QueuedImport>(); var queuedImports = new Array<QueuedImport>();
var queuedDerivedClasses = new Array<ClassPrototype>();
// build initial lookup maps of internal names to declarations // build initial lookup maps of internal names to declarations
for (var i = 0, k = this.sources.length; i < k; ++i) { for (var i = 0, k = this.sources.length; i < k; ++i) {
@ -156,7 +157,7 @@ export class Program extends DiagnosticEmitter {
switch (statement.kind) { switch (statement.kind) {
case NodeKind.CLASS: case NodeKind.CLASS:
this.initializeClass(<ClassDeclaration>statement); this.initializeClass(<ClassDeclaration>statement, queuedDerivedClasses);
break; break;
case NodeKind.ENUM: case NodeKind.ENUM:
@ -180,7 +181,7 @@ export class Program extends DiagnosticEmitter {
break; break;
case NodeKind.NAMESPACE: case NodeKind.NAMESPACE:
this.initializeNamespace(<NamespaceDeclaration>statement); this.initializeNamespace(<NamespaceDeclaration>statement, queuedDerivedClasses, null);
break; break;
case NodeKind.TYPEDECLARATION: case NodeKind.TYPEDECLARATION:
@ -232,6 +233,22 @@ export class Program extends DiagnosticEmitter {
} }
} while (currentExport); } 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. */ /** Tries to resolve an import by traversing exports and queued exports. */
@ -252,7 +269,7 @@ export class Program extends DiagnosticEmitter {
} while (true); } 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; var internalName = declaration.internalName;
if (this.elements.has(internalName)) { if (this.elements.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, 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) } else if (declaration.implementsTypes.length)
throw new Error("not implemented"); throw new Error("not implemented");
// remember classes that extend another one
if (declaration.extendsType)
queuedDerivedClasses.push(prototype);
// add as namespace member if applicable // add as namespace member if applicable
if (namespace) { if (namespace) {
if (namespace.members) { 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 internalName = declaration.internalName;
var namespace = this.elements.get(internalName); var namespace = this.elements.get(internalName);
@ -794,7 +815,7 @@ export class Program extends DiagnosticEmitter {
switch (members[i].kind) { switch (members[i].kind) {
case NodeKind.CLASS: case NodeKind.CLASS:
this.initializeClass(<ClassDeclaration>members[i], namespace); this.initializeClass(<ClassDeclaration>members[i], queuedExtendingClasses, namespace);
break; break;
case NodeKind.ENUM: case NodeKind.ENUM:
@ -810,7 +831,7 @@ export class Program extends DiagnosticEmitter {
break; break;
case NodeKind.NAMESPACE: case NodeKind.NAMESPACE:
this.initializeNamespace(<NamespaceDeclaration>members[i], namespace); this.initializeNamespace(<NamespaceDeclaration>members[i], queuedExtendingClasses, namespace);
break; break;
case NodeKind.TYPEDECLARATION: case NodeKind.TYPEDECLARATION:
@ -957,23 +978,27 @@ export class Program extends DiagnosticEmitter {
} }
/** Resolves an identifier to the element it refers to. */ /** 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 name = identifier.name;
var local = contextualFunction.locals.get(name);
if (local)
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
var element: Element | null; var element: Element | null;
var namespace: 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 // search contextual parent namespaces if applicable
if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) { if (namespace = contextualFunction.prototype.namespace) {
do { do {
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)) 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))) // if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)))
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element); return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
} while (namespace = namespace.namespace); } while (namespace = namespace.namespace);
} }
}
// search current file // search current file
if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name)) 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 // at this point we know exactly what the target is, so look up the element within
var propertyName = propertyAccess.property.name; var propertyName = propertyAccess.property.name;
var targetType: Type; var targetType: Type;
var member: Element | null;
switch (target.kind) { switch (target.kind) {
case ElementKind.GLOBAL: case ElementKind.GLOBAL:
@ -1008,12 +1034,31 @@ export class Program extends DiagnosticEmitter {
target = <Class>targetType.classType; target = <Class>targetType.classType;
// fall-through // fall-through
default: case ElementKind.CLASS_PROTOTYPE:
if (target.members) { case ElementKind.CLASS:
var member = target.members.get(propertyName); do {
if (member) 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); return resolvedElement.set(member).withTarget(target, targetExpression);
}
break; break;
} }
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, target.internalName); 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))) if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
return null; return null;
var target = resolvedElement.element; var target = resolvedElement.element;
switch (target.kind) { switch (target.kind) {
// TBD: should indexed access on static classes, like `Heap`, be a supported as well?
case ElementKind.CLASS: case ElementKind.CLASS:
var type = (<Class>target).type; var type = (<Class>target).type;
if (type.classType) { 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; 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; return null;
} }
@ -1820,15 +1868,17 @@ export class ClassPrototype extends Element {
instances: Map<string,Class> = new Map(); instances: Map<string,Class> = new Map();
/** Instance member prototypes. */ /** Instance member prototypes. */
instanceMembers: Map<string,Element> | null = null; 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. */ /** 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. */ /** Overloaded indexed set method, if any. */
opIndexedSet: FunctionPrototype | null; opIndexedSet: FunctionPrototype | null = null;
/** Overloaded concatenation method, if any. */ /** Overloaded concatenation method, if any. */
opConcat: FunctionPrototype | null; opConcat: FunctionPrototype | null = null;
/** Overloaded equality comparison method, if any. */ /** Overloaded equality comparison method, if any. */
opEquals: FunctionPrototype | null; opEquals: FunctionPrototype | null = null;
constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) { constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) {
super(program, simpleName, internalName); 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); this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
return null; 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) { 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)); this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
return null; return null;

View File

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