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,22 +978,26 @@ 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;
// search contextual parent namespaces if applicable
if (contextualFunction && (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);
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 (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
@ -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

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