diff --git a/src/ast.ts b/src/ast.ts index 65210b8b..de52e27c 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -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); } } diff --git a/src/builtins.ts b/src/builtins.ts index cf519560..3a24a77c 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -212,6 +212,11 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty case "isNaN": // isNaN(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 ((typeArguments)[0].isAnyInteger) diff --git a/src/decompiler.ts b/src/decompiler.ts index 15d7b4bf..78bd0613 100644 --- a/src/decompiler.ts +++ b/src/decompiler.ts @@ -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) { diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index ab25f974..42853df5 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -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}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index a0563670..6eddbf63 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -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, diff --git a/src/parser.ts b/src/parser.ts index 5f730199..649e6395 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -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()); diff --git a/src/program.ts b/src/program.ts index d9883ae7..d71761fb 100644 --- a/src/program.ts +++ b/src/program.ts @@ -146,6 +146,7 @@ export class Program extends DiagnosticEmitter { var queuedExports = new Map(); var queuedImports = new Array(); + var queuedDerivedClasses = new Array(); // 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(statement); + this.initializeClass(statement, queuedDerivedClasses); break; case NodeKind.ENUM: @@ -180,7 +181,7 @@ export class Program extends DiagnosticEmitter { break; case NodeKind.NAMESPACE: - this.initializeNamespace(statement); + this.initializeNamespace(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 = (derivedDeclaration).extendsType; + assert(derivedType != null); + var resolved = this.resolveIdentifier((derivedType).identifier, null); // reports + if (resolved) { + if (resolved.element.kind != ElementKind.CLASS_PROTOTYPE) { + this.error(DiagnosticCode.A_class_may_only_extend_another_class, (derivedType).range); + continue; + } + queuedDerivedClasses[i].basePrototype = 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(members[i], namespace); + this.initializeClass(members[i], queuedExtendingClasses, namespace); break; case NodeKind.ENUM: @@ -810,7 +831,7 @@ export class Program extends DiagnosticEmitter { break; case NodeKind.NAMESPACE: - this.initializeNamespace(members[i], namespace); + this.initializeNamespace(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 = 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 ((target).basePrototype) + target = (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 ((target).base) + target = (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 = (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 = new Map(); /** Instance member prototypes. */ instanceMembers: Map | 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; diff --git a/src/tokenizer.ts b/src/tokenizer.ts index a7b786aa..c99b314b 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -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 { diff --git a/tests/parser/regexp.ts b/tests/parser/regexp.ts new file mode 100644 index 00000000..b6451957 --- /dev/null +++ b/tests/parser/regexp.ts @@ -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; diff --git a/tests/parser/regexp.ts.fixture.ts b/tests/parser/regexp.ts.fixture.ts new file mode 100644 index 00000000..a49d47de --- /dev/null +++ b/tests/parser/regexp.ts.fixture.ts @@ -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