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,22 +978,26 @@ 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;
// search contextual parent namespaces if applicable if (contextualFunction) {
if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) { // check locals
do { var local = contextualFunction.locals.get(name);
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)) if (local)
// if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))) return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
} while (namespace = namespace.namespace); // search contextual parent namespaces if applicable
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 // search current file
@ -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); 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; 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

@ -4,18 +4,17 @@
as much bookkeeping, simply skips over trivia and provides a more general as much bookkeeping, simply skips over trivia and provides a more general
mark/reset mechanism for the parser to utilize on ambiguous tokens. mark/reset mechanism for the parser to utilize on ambiguous tokens.
next() advances the token next() advances the token
peek() peeks for the next token peek() peeks for the next token
skip(token) skips over a token if possible skip(token) skips over a token if possible
mark() marks at current token mark() marks at current token
reset() resets to marked state reset() resets to marked state
range() gets the range of the current token range() gets the range of the current token
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