diff --git a/bin/asc.js b/bin/asc.js index a2979dda..a93be3fd 100644 --- a/bin/asc.js +++ b/bin/asc.js @@ -66,7 +66,7 @@ if (args.help || args._.length < 1) { "Syntax: asc [options] [entryFile ...]", "", "Examples: asc hello.ts", - " asc hello.ts -b hello.wasm -t hello.wast -a hello.js", + " asc hello.ts -b hello.wasm -t hello.wast", " asc hello1.ts hello2.ts -b -O > hello.wasm", "", "Options:" diff --git a/src/ast.ts b/src/ast.ts index 8c65beea..f4c08497 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -391,6 +391,18 @@ export abstract class Node { const stmt: ImportStatement = new ImportStatement(); stmt.range = range; for (let i: i32 = 0, k: i32 = (stmt.declarations = declarations).length; i < k; ++i) declarations[i].parent = stmt; + stmt.namespaceName = null; + stmt.path = path; + stmt.normalizedPath = resolvePath(normalizePath(path.value), range.source.normalizedPath); + stmt.internalPath = mangleInternalPath(stmt.normalizedPath); + return stmt; + } + + static createImportAll(identifier: IdentifierExpression, path: StringLiteralExpression, range: Range): ImportStatement { + const stmt: ImportStatement = new ImportStatement(); + stmt.range = range; + stmt.declarations = null; + stmt.namespaceName = identifier; stmt.path = path; stmt.normalizedPath = resolvePath(normalizePath(path.value), range.source.normalizedPath); stmt.internalPath = mangleInternalPath(stmt.normalizedPath); @@ -1569,8 +1581,10 @@ export class ImportStatement extends Statement { kind = NodeKind.IMPORT; - /** Array of member declarations. */ - declarations: ImportDeclaration[]; + /** Array of member declarations or `null` if an asterisk import. */ + declarations: ImportDeclaration[] | null; + /** Name of the local namespace, if an asterisk import. */ + namespaceName: IdentifierExpression | null; /** Path being imported from. */ path: StringLiteralExpression; /** Normalized path. */ @@ -1579,13 +1593,22 @@ export class ImportStatement extends Statement { internalPath: string; serialize(sb: string[]): void { - sb.push("import {\n"); - for (let i: i32 = 0, k: i32 = this.declarations.length; i < k; ++i) { - if (i > 0) - sb.push(",\n"); - this.declarations[i].serialize(sb); + if (this.declarations) { + sb.push("import {\n"); + for (let i: i32 = 0, k: i32 = this.declarations.length; i < k; ++i) { + if (i > 0) + sb.push(",\n"); + this.declarations[i].serialize(sb); + } + sb.push("\n}"); + } else { + sb.push("import * as "); + if (this.namespaceName) + this.namespaceName.serialize(sb); + else + throw new Error("missing asterisk import identifier"); } - sb.push("\n} from "); + sb.push(" from "); this.path.serialize(sb); } } diff --git a/src/compiler.ts b/src/compiler.ts index 74c723d3..b3ff1901 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -2093,7 +2093,7 @@ export class Compiler extends DiagnosticEmitter { return this.module.createBinary(BinaryOp.EqF64, operand, this.module.createF64(0)); } op = this.currentType.isLongInteger - ? UnaryOp.EqzI64 // TODO: does this yield i64 0/1? + ? UnaryOp.EqzI64 : UnaryOp.EqzI32; this.currentType = Type.bool; break; diff --git a/src/parser.ts b/src/parser.ts index 3859c4a0..7a6fdac7 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -93,7 +93,7 @@ export class Parser extends DiagnosticEmitter { const normalizedPath: string = normalizePath(path); for (let i: i32 = 0, k: i32 = this.program.sources.length; i < k; ++i) if (this.program.sources[i].normalizedPath == normalizedPath) - throw new Error("duplicate source"); + return; // already parsed this.seenlog.add(normalizedPath); const source: Source = new Source(path, text, isEntry); @@ -104,10 +104,10 @@ export class Parser extends DiagnosticEmitter { while (!tn.skip(Token.ENDOFFILE)) { const statement: Statement | null = this.parseTopLevelStatement(tn); - if (!statement) - return; - statement.parent = source; - source.statements.push(statement); + if (statement) { + statement.parent = source; + source.statements.push(statement); + } } } @@ -201,7 +201,7 @@ export class Parser extends DiagnosticEmitter { default: if (hasModifier(ModifierKind.EXPORT, modifiers)) { tn.reset(); - statement = this.parseExport(tn, modifiers); // TODO: why exactly does this have modifiers again? + statement = this.parseExport(tn, modifiers); // TODO: why exactly does this have modifiers again? 'declare'? } else { if (modifiers) { if (modifier = getModifier(ModifierKind.DECLARE, modifiers)) @@ -713,13 +713,10 @@ export class Parser extends DiagnosticEmitter { let isGetter: bool = false; let isSetter: bool = false; - if (tn.skip(Token.GET)) { + if (isGetter = tn.skip(Token.GET)) modifiers = addModifier(Node.createModifier(ModifierKind.GET, tn.range()), modifiers); - isGetter = true; - } else if (tn.skip(Token.SET)) { // can't be both + else if (isSetter = tn.skip(Token.SET)) // can't be both modifiers = addModifier(Node.createModifier(ModifierKind.SET, tn.range()), modifiers); - isSetter = true; - } if (tn.skip(Token.CONSTRUCTOR) || tn.skip(Token.IDENTIFIER)) { // order is important const identifier: IdentifierExpression = tn.token == Token.CONSTRUCTOR @@ -899,10 +896,12 @@ export class Parser extends DiagnosticEmitter { } parseImport(tn: Tokenizer): ImportStatement | null { - // at 'import': '{' (ImportMember (',' ImportMember)*)? '}' 'from' StringLiteral ';'? - const startRange: Range = tn.range(); + // at 'import': ('{' (ImportMember (',' ImportMember)*)? '}' | '*' 'as' Identifier) 'from' StringLiteral ';'? + const startPos: i32 = tn.tokenPos; + let members: ImportDeclaration[] | null = null; + let namespaceName: IdentifierExpression | null = null; if (tn.skip(Token.OPENBRACE)) { - const members: ImportDeclaration[] = new Array(); + members = new Array(); if (!tn.skip(Token.CLOSEBRACE)) { do { const member: ImportDeclaration | null = this.parseImportDeclaration(tn); @@ -915,22 +914,49 @@ export class Parser extends DiagnosticEmitter { return null; } } - if (tn.skip(Token.FROM)) { - if (tn.skip(Token.STRINGLITERAL)) { - const path: StringLiteralExpression = Node.createStringLiteral(tn.readString(), tn.range()); - const ret: ImportStatement = Node.createImport(members, path, Range.join(startRange, tn.range())); - if (!this.seenlog.has(ret.normalizedPath)) { - this.backlog.push(ret.normalizedPath); - this.seenlog.add(ret.normalizedPath); - } - tn.skip(Token.SEMICOLON); - return ret; - } else - this.error(DiagnosticCode.String_literal_expected, tn.range()); - } else - this.error(DiagnosticCode._0_expected, tn.range(), "from"); - } else + } else if (tn.skip(Token.ASTERISK)) { + if (tn.skip(Token.AS)) { + if (tn.skip(Token.IDENTIFIER)) { + namespaceName = Node.createIdentifier(tn.readIdentifier(), tn.range()); + } else { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + } else { + this.error(DiagnosticCode._0_expected, tn.range(), "as"); + return null; + } + } else { this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + if (tn.skip(Token.FROM)) { + if (tn.skip(Token.STRINGLITERAL)) { + const path: StringLiteralExpression = Node.createStringLiteral(tn.readString(), tn.range()); + let ret: ImportStatement; + if (members) { + if (!namespaceName) + ret = Node.createImport(members, path, tn.range(startPos, tn.pos)); + else { + assert(false); + return null; + } + } else if (namespaceName) { + ret = Node.createImportAll(namespaceName, path, tn.range(startPos, tn.pos)); + } else { + assert(false); + return null; + } + if (!this.seenlog.has(ret.normalizedPath)) { + this.backlog.push(ret.normalizedPath); + this.seenlog.add(ret.normalizedPath); + } + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode.String_literal_expected, tn.range()); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "from"); return null; } @@ -1363,7 +1389,7 @@ export class Parser extends DiagnosticEmitter { // see: http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing parseExpressionStart(tn: Tokenizer): Expression | null { - const token: Token = tn.next(); + const token: Token = tn.next(true); const startPos: i32 = tn.tokenPos; let expr: Expression | null = null; diff --git a/src/program.ts b/src/program.ts index f6cc9a39..54e384f5 100644 --- a/src/program.ts +++ b/src/program.ts @@ -501,11 +501,21 @@ export class Program extends DiagnosticEmitter { } private initializeImports(statement: ImportStatement, queuedExports: Map, queuedImports: QueuedImport[]): void { - const members: ImportDeclaration[] = statement.declarations; - for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { - const declaration: ImportDeclaration = members[i]; - this.initializeImport(declaration, statement.internalPath, queuedExports, queuedImports); - } + const declarations: ImportDeclaration[] | null = statement.declarations; + if (declarations) { + for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { + const declaration: ImportDeclaration = declarations[i]; + this.initializeImport(declaration, statement.internalPath, queuedExports, queuedImports); + } + } else if (statement.namespaceName) { + const internalName: string = statement.range.source.internalPath + "/" + statement.namespaceName.name; + if (this.elements.has(internalName)) { + this.error(DiagnosticCode.Duplicate_identifier_0, statement.namespaceName.range, internalName); + return; + } + this.error(DiagnosticCode.Operation_not_supported, statement.range); // TODO + } else + assert(false); } private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map, queuedImports: QueuedImport[]): void { diff --git a/src/tokenizer.ts b/src/tokenizer.ts index 8aabaef0..671f5264 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -731,6 +731,17 @@ export class Tokenizer extends DiagnosticEmitter { } } + // skipUntil(token1: Token, token2: Token = -1): bool { + // let next: Token; + // do { + // if ((next = this.peek()) == Token.ENDOFFILE) + // return false; + // if (next == token1 || next == token2) + // return true; + // this.next(); + // } while (true); + // } + mark(): void { this.markedPos = this.pos; this.markedToken = this.token; diff --git a/src/util/charcode.ts b/src/util/charcode.ts index 0a0185ed..e63b80d5 100644 --- a/src/util/charcode.ts +++ b/src/util/charcode.ts @@ -22,7 +22,7 @@ export const enum CharCode { THINSPACE = 0x2009, HAIRSPACE = 0x200A, ZEROWIDTHSPACE = 0x200B, - NARRINOBREAKSPACE = 0x202F, + NARROWNOBREAKSPACE = 0x202F, IDEOGRAPHICSPACE = 0x3000, MATHEMATICALSPACE = 0x205F, OGHAM = 0x1680, @@ -156,7 +156,7 @@ export function isWhiteSpace(c: i32): bool { case CharCode.NONBREAKINGSPACE: case CharCode.NEXTLINE: case CharCode.OGHAM: - case CharCode.NARRINOBREAKSPACE: + case CharCode.NARROWNOBREAKSPACE: case CharCode.MATHEMATICALSPACE: case CharCode.IDEOGRAPHICSPACE: case CharCode.BYTEORDERMARK: diff --git a/std/assembly.d.ts b/std/assembly.d.ts index 94856253..01f618b0 100644 --- a/std/assembly.d.ts +++ b/std/assembly.d.ts @@ -178,6 +178,7 @@ declare function parseFloat(str: string): f64; /** Class representing a sequence of values of type `T`. */ declare class Array { + [key: number]: T; /** Current maximum capacity of the array. */ readonly capacity: i32; @@ -275,3 +276,6 @@ declare function global(): any; /** Annotates a method being an operator overload. */ declare function operator(token: string): any; + +declare function struct(): any; +declare function size(size: usize): any; diff --git a/std/assembly/array.ts b/std/assembly/array.ts index e57a7a64..394ec3a0 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -1,9 +1,10 @@ @global() export class Array { + private ptr: usize; + readonly capacity: i32; length: i32; - ptr: usize; constructor(capacity: i32 = 0) { if (capacity < 0) diff --git a/std/assembly/string.ts b/std/assembly/string.ts index 7db5e5d5..734220c8 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -1,98 +1,197 @@ +const EMPTY: String = changetype(""); + @global() export class String { + // [key: number]: string; private ptr: usize; - readonly length: u32; - constructor(ptr: usize, len: u32) { + readonly length: i32; + + constructor(ptr: usize, lenght: i32) { this.ptr = ptr; - this.length = len; + this.length = lenght; } - charAt(index: u32): String { - assert(this != null && index < this.length); - return new String(this.ptr + (index << 1), 1); + charAt(pos: i32): String { + assert(this != null); + return pos < 0 || pos >= this.length ? EMPTY + : new String(this.ptr + (pos << 1), 1); } - charCodeAt(index: u32): u16 { - assert(this != null && index < this.length); - return load(this.ptr + (index << 1)); + charCodeAt(pos: i32): i32 { + assert(this != null); + return pos < 0 || pos >= this.length ? -1 // NaN + : load(this.ptr + (pos << 1)); + } + + codePointAt(pos: i32): i32 { + assert(this != null); + if (pos < 0 || pos >= this.length) + return -1; // undefined + let first: i32 = load(this.ptr + (pos << 1)); + if (first < 0xD800 || first > 0xDBFF || pos + 1 == this.length) + return first; + let second: i32 = load(this.ptr + ((pos + 1) << 1)); + if (second < 0xDC00 || second > 0xDFFF) + return first; + return ((first - 0xD800) << 10) + (second - 0xDC00) + 0x10000; } @operator("+") concat(other: String): String { - assert(this != null && other != null); - const len: u32 = this.length + other.length; - const ptr: usize = Heap.allocate(len << 1); - Heap.copy(ptr, this.ptr, this.length << 1); - Heap.copy(ptr, this.ptr + (len << 1), other.length << 1); - return new String(ptr, len); + assert(this != null); + assert(other != null); + const thisLen: isize = this.length; + const otherLen: isize = other.length; + const len: usize = thisLen + otherLen; + return new String( + Heap.copy( + Heap.copy( + Heap.allocate(len << 1), + this.ptr, + thisLen << 1 + ) + (thisLen << 1), + other.ptr, + otherLen << 1 + ), + len + ); } - endsWith(other: String): bool { - assert(this != null && other != null); - if (other.length > this.length) + endsWith(searchString: String, endPosition: i32 = 0x7fffffff): bool { + assert(this != null); + assert(searchString != null); + let end: isize = min(max(endPosition, 0), this.length); + let searchLength: isize = searchString.length; + let start: isize = end - searchLength; + if (start < 0) return false; - for (let i: u32 = this.length - other.length, j: u32 = 0, k: u32 = this.length; i < k;) - if (this.charCodeAt(i++) != other.charCodeAt(j++)) - return false; - return true; + return !Heap.compare(this.ptr + (start << 1), searchString.ptr, searchLength << 1); } @operator("==") equals(other: String): bool { - assert(this != null && other != null); - if (this.length != other.length) + assert(this != null); + assert(other != null); + return this.length != other.length ? false + : !Heap.compare(this.ptr, other.ptr, this.length); + } + + includes(searchString: String, position: i32 = 0): bool { + return this.indexOf(searchString, position) != -1; + } + + indexOf(searchString: String, position: i32 = 0): i32 { + assert(this != null); + assert(searchString != null); + let pos: isize = position; + let len: isize = this.length; + let start: isize = min(max(pos, 0), len); + let searchLen: isize = searchString.length; + for (let k: usize = start; k + searchLen <= len; ++k) + if (!Heap.compare(this.ptr + (k << 1), searchString.ptr, searchLen << 1)) + return k; + return -1; + } + + startsWith(searchString: String, position: i32 = 0): bool { + assert(this != null); + assert(searchString != null); + let pos: isize = position; + let len: isize = this.length; + let start: isize = min(max(position, 0), len); + let searchLength: isize = searchString.length; + if (searchLength + start > len) return false; - for (let i: u32 = 0, k: u32 = this.length; i < k; ++i) - if (this.charCodeAt(i) != other.charCodeAt(i)) - return false; - return true; + return !Heap.compare(this.ptr + (start << 1), searchString.ptr, searchLength << 1); } - indexOf(other: String): u32 { - assert(this != null && other != null); - throw new Error("not implemented"); - } - - startsWith(other: String): bool { - assert(this != null && other != null); - if (other.length > this.length) - return false; - for (let i: u32 = 0, k: u32 = other.length; i < k; ++i) - if (this.charCodeAt(i) != other.charCodeAt(i)) - return false; - return true; - } - - substr(start: u32, length: u32 = -1): String { + substr(start: i32, length: i32 = i32.MAX_VALUE): String { assert(this != null); - if (start >= this.length) - return changetype(""); - const len: u32 = min(length, this.length - start); - return new String(this.ptr + (start << 1), len); + let intStart: isize = start; + let end: isize = length; + let size: isize = this.length; + if (intStart < 0) + intStart = max(size + intStart, 0); + let resultLength: isize = min(max(end, 0), size - intStart); + if (resultLength < 0) + return EMPTY; + return new String(this.ptr + (intStart << 1), resultLength); } - substring(start: u32, end: u32 = -1): String { + substring(start: i32, end: i32 = i32.MAX_VALUE): String { assert(this != null); - if (start >= this.length || end <= start) - return changetype(""); - const len: u32 = min(end - start, this.length - start); - return new String(this.ptr + (start << 1), len); + let len: i32 = this.length; + let finalStart: i32 = min(max(start, 0), len); + let finalEnd: i32 = min(max(end, 0), len); + let from: i32 = min(finalStart, finalEnd); + let to: i32 = max(finalStart, finalEnd); + len = to - from; + if (!len) + return EMPTY; + if (!from && to == this.length) + return this; + return new String(this.ptr + (from << 1), len); } - trim(): string { + trim(): String { assert(this != null); - throw new Error("not implemented"); + let length: usize = this.length; + while (length && isWhiteSpaceOrLineTerminator(load(this.ptr + (length << 1)))) + --length; + let start: usize = 0; + while (start < length && isWhiteSpaceOrLineTerminator(load(this.ptr + (start << 1)))) { + ++start; --length; + } + if (!length) + return EMPTY; + if (!start && length == this.length) + return this; + return new String(this.ptr + (start << 1), length); } - trimLeft(): string { + trimLeft(): String { assert(this != null); - throw new Error("not implemented"); + let start: isize = 0; + let len: isize = this.length; + while (start < len && isWhiteSpaceOrLineTerminator(load(this.ptr + (start << 1)))) + ++start; + if (!start) + return this; + return new String(this.ptr + (start << 1), (len - start)); } - trimRight(): string { + trimRight(): String { assert(this != null); - throw new Error("not implemented"); + let len: isize = this.length; + while (len > 0 && isWhiteSpaceOrLineTerminator(load(this.ptr + (len << 1)))) + --len; + if (len <= 0) + return EMPTY; + if (len == this.length) + return this; + return new String(this.ptr, len); + } +} + +function isWhiteSpaceOrLineTerminator(c: u16): bool { + switch (c) { + + case 10: // + case 13: // + case 8232: // + case 8233: // + + case 9: // + case 11: // + case 12: // + case 32: // + case 160: // + case 65279: // + + return true; + default: + return false; } } diff --git a/std/portable/heap.js b/std/portable/heap.js index 75dfdd5b..6021d80a 100644 --- a/std/portable/heap.js +++ b/std/portable/heap.js @@ -27,5 +27,5 @@ Object.defineProperties(globalScope["Heap"] = { size: { get: function get_size() { return HEAP.length; } } }); -globalScope["store"] = function store(ptr, val) { binaryen.HEAPU8[ptr] = val; }; -globalScope["load"] = function load(ptr) { return binaryen.HEAPU8[ptr]; }; +globalScope["store"] = function store(ptr, val) { HEAP[ptr] = val; }; +globalScope["load"] = function load(ptr) { return HEAP[ptr]; }; diff --git a/tests/compiler/std/array.wast b/tests/compiler/std/array.wast index dcb229a2..800e7ccc 100644 --- a/tests/compiler/std/array.wast +++ b/tests/compiler/std/array.wast @@ -73,8 +73,10 @@ Map std:set/Set Set + std:string/EMPTY std:string/String String + std:string/isWhiteSpaceOrLineTerminator [program.exports] std:array/Array std:error/Error diff --git a/tests/compiler/std/heap.wast b/tests/compiler/std/heap.wast index 69e8d181..8f4069b2 100644 --- a/tests/compiler/std/heap.wast +++ b/tests/compiler/std/heap.wast @@ -2626,8 +2626,10 @@ Map std:set/Set Set + std:string/EMPTY std:string/String String + std:string/isWhiteSpaceOrLineTerminator std/heap/size std/heap/ptr1 std/heap/ptr2 diff --git a/tests/parser/import.ts b/tests/parser/import.ts new file mode 100644 index 00000000..3b3248d0 --- /dev/null +++ b/tests/parser/import.ts @@ -0,0 +1,7 @@ +import { A } from "./other"; + +import { A, B, C } from "./other"; + +import { A as B, C, D as E, F } from "./other"; + +import * as A from "./other"; diff --git a/tests/parser/import.ts.fixture.ts b/tests/parser/import.ts.fixture.ts new file mode 100644 index 00000000..fd389fb9 --- /dev/null +++ b/tests/parser/import.ts.fixture.ts @@ -0,0 +1,15 @@ +import { +A +} from "./other"; +import { +A, +B, +C +} from "./other"; +import { +A as B, +C, +D as E, +F +} from "./other"; +import * as A from "./other";