diff --git a/lib/utils/README.md b/lib/utils/README.md index 8e62cda1..c1741b3a 100644 --- a/lib/utils/README.md +++ b/lib/utils/README.md @@ -13,5 +13,5 @@ var myModule = ...; var helpers = utils(myModule); -var str = helpers.getString(myModule.exportReturningAString()); +var str = helpers.string(myModule.exportReturningAString()); ``` diff --git a/lib/utils/index.js b/lib/utils/index.js index d29de790..d5aaec69 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,21 +1,103 @@ -module.exports = function(module) { - return { - getI32: function(ptr) { - return new Int32Array(module.memory.buffer, ptr, 4)[0]; - }, - getU32: function(ptr) { - return new Uint32Array(module.memory.buffer, ptr, 4)[0]; - }, - getF32: function(ptr) { - return new Float32Array(module.memory.buffer, ptr, 4)[0]; - }, - getF64: function(ptr) { - return new Float64Array(module.memory.buffer, ptr, 8)[0]; - }, - getString: function(ptr) { - var len = new Uint32Array(module.memory.buffer, ptr, 4)[0]; - var str = new Uint16Array(module.memory.buffer, ptr + 4, len << 1); - return String.fromCharCode.apply(String, str); - } +function utils(module) { + + var i8, + u8 = new Uint8Array(0), + i16, + u16, + i32, + u32, + f32, + f64; + + function maybeUpdate() { + var mem = module.memory.buffer; + if (mem.byteLength === u8.length) + return; + i8 = Int8Array(mem); + u8 = Uint8Array(mem); + i16 = Int16Array(mem); + u16 = Uint16Array(mem); + i32 = Int32Array(mem); + u32 = Uint32Array(mem); + f32 = Float32Array(mem); + f64 = Float64Array(mem); } + + var helpers = { + + i8: function(ptr) { + maybeUpdate(); + return i8[ptr]; + }, + + u8: function(ptr) { + maybeUpdate(); + return u8[ptr]; + }, + + i16: function(ptr) { + maybeUpdate(); + return i16[ptr >>> 1]; + }, + + u16: function(ptr) { + maybeUpdate(); + return u16[ptr >>> 1]; + }, + + i32: function(ptr) { + maybeUpdate(); + return i32[ptr >>> 2]; + }, + + u32: function(ptr) { + maybeUpdate(); + return u32[ptr >>> 2]; + }, + + i64: function(ptr) { + maybeUpdate(); + return { + low: i32[ptr >>> 2], + high: i32[(ptr >>> 2) + 1] + }; + }, + + u64: function(ptr) { + maybeUpdate(); + return { + low: u32[ptr >>> 2], + high: u32[(ptr >>> 2) + 1] + }; + }, + + bool: function(ptr) { + maybeUpdate(); + return u8[ptr] === 1; + }, + + f32: function(ptr) { + maybeUpdate(); + return f32[ptr >>> 2]; + }, + + f64: function(ptr) { + maybeUpdate(); + return f64[ptr >>> 3]; + }, + + string: function(ptr) { + maybeUpdate(); + var len = u32[ptr >>> 2]; + var off = (ptr >>> 1) + 2; + return String.fromCharCode.apply(String, u16.subarray(off, off + len)); + } + }; + + return helpers; }; + +Object.defineProperties(module.exports = utils, { + __esModule: { value: true }, + default: { value: utils } +}); diff --git a/lib/webpack/README.md b/lib/webpack/README.md index 8c411d9b..fb2b2737 100644 --- a/lib/webpack/README.md +++ b/lib/webpack/README.md @@ -11,3 +11,5 @@ import Module from "@assemblyscript/webpack!module.wasm"; var myModule = Module({ imports: { ... }}); ``` + +TODO: Wire .ts files to the compiler API, accepting options, but also keep raw .wasm support. diff --git a/lib/webpack/decode.js b/lib/webpack/decode.js index b167b9c8..6352c0dd 100644 --- a/lib/webpack/decode.js +++ b/lib/webpack/decode.js @@ -7,23 +7,23 @@ module.exports = function(string) { // determine buffer length var length = string.length; if (length) { - var n = 0; - while (--length % 4 > 1 && string.charCodeAt(p) === 61) ++n; - length = Math.ceil(string.length * 3) / 4 - n; + var n = 0, p = length; + while (--p % 4 > 1 && string.charCodeAt(p) === 61) ++n; + length = Math.ceil(length * 3) / 4 - n; } // decode to buffer var buffer = new Uint8Array(length); - var j = 0, t; + var j = 0, o = 0, t; for (var i = 0, k = string.length; i < k;) { var c = string.charCodeAt(i++); if (c === 61 && j > 1) break; if ((c = s64[c]) === undefined) throw Error(); switch (j) { case 0: t = c; j = 1; break; - case 1: buffer[offset++] = t << 2 | (c & 48) >> 4; t = c; j = 2; break; - case 2: buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2; t = c; j = 3; break; - case 3: buffer[offset++] = (t & 3) << 6 | c; j = 0; break; + case 1: buffer[o++] = t << 2 | (c & 48) >> 4; t = c; j = 2; break; + case 2: buffer[o++] = (t & 15) << 4 | (c & 60) >> 2; t = c; j = 3; break; + case 3: buffer[o++] = (t & 3) << 6 | c; j = 0; break; } } if (j === 1) throw Error(); diff --git a/lib/webpack/index.js b/lib/webpack/index.js index 4b30af44..f17898ba 100644 --- a/lib/webpack/index.js +++ b/lib/webpack/index.js @@ -1,6 +1,6 @@ var base64 = require("@protobufjs/base64"); -(module.exports = function(buffer) { +function loader(buffer) { var data = base64.encode(buffer, 0, buffer.length); var code = [ 'var data = "' + data + '", wasm;', @@ -9,5 +9,12 @@ var base64 = require("@protobufjs/base64"); ' return new WebAssembly.Instance(new WebAssembly.Module(wasm), options && options.imports || {}).exports;', '};' ]; - return code.join("\n"); -}).raw = true; + return code.join("\n") + "\n"; +} + +loader.raw = true; + +Object.defineProperties(module.exports = loader, { + __esModule: { value: true }, + default: { value: loader } +}); diff --git a/src/parser.ts b/src/parser.ts index c99f9fa1..83d90609 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -36,6 +36,7 @@ import { Expression, AssertionKind, CallExpression, + ElementAccessExpression, IdentifierExpression, StringLiteralExpression, @@ -1467,7 +1468,7 @@ export class Parser extends DiagnosticEmitter { var elementExpressions = new Array(); if (!tn.skip(Token.CLOSEBRACKET)) { do { - if (tn.peek() == Token.COMMA || tn.peek() == Token.CLOSEBRACKET) + if (tn.peek() == Token.COMMA) expr = null; // omitted else { expr = this.parseExpression(tn, Precedence.COMMA + 1); @@ -1475,6 +1476,8 @@ export class Parser extends DiagnosticEmitter { return null; } elementExpressions.push(expr); + if (tn.peek() == Token.CLOSEBRACKET) + break; } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.CLOSEBRACKET)) { this.error(DiagnosticCode._0_expected, tn.range(), "]"); @@ -1588,19 +1591,6 @@ export class Parser extends DiagnosticEmitter { var startPos = expr.range.start; - // ElementAccessExpression - if (tn.skip(Token.OPENBRACKET)) { - next = this.parseExpression(tn); // resets precedence - if (!next) - return null; - if (tn.skip(Token.CLOSEBRACKET)) - expr = Node.createElementAccessExpression(expr, next, tn.range(startPos, tn.pos)); - else { - this.error(DiagnosticCode._0_expected, tn.range(), "]"); - return null; - } - } - // CallExpression var typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn); // skips '(' on success // there might be better ways to distinguish a LESSTHAN from a CALL with type arguments @@ -1617,70 +1607,93 @@ export class Parser extends DiagnosticEmitter { while ((nextPrecedence = determinePrecedence(token = tn.peek())) >= precedence) { // precedence climbing tn.next(); - // AssertionExpression - if (token == Token.AS) { - var toType = this.parseType(tn); - if (!toType) - return null; - expr = Node.createAssertionExpression(AssertionKind.AS, expr, toType, tn.range(startPos, tn.pos)); + switch (token) { - // UnaryPostfixExpression - } else if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) { - if (expr.kind != NodeKind.IDENTIFIER && expr.kind != NodeKind.ELEMENTACCESS && expr.kind != NodeKind.PROPERTYACCESS) - this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, expr.range); - expr = Node.createUnaryPostfixExpression(token, expr, tn.range(startPos, tn.pos)); - - // TernaryExpression - } else if (token == Token.QUESTION) { - var ifThen = this.parseExpression(tn); - if (!ifThen) - return null; - if (tn.skip(Token.COLON)) { - var ifElse = this.parseExpression(tn); - if (!ifElse) + // AssertionExpression + case Token.AS: + var toType = this.parseType(tn); + if (!toType) return null; - expr = Node.createTernaryExpression(expr, ifThen, ifElse, tn.range(startPos, tn.pos)); - } else { - this.error(DiagnosticCode._0_expected, tn.range(), ":"); - return null; - } + expr = Node.createAssertionExpression(AssertionKind.AS, expr, toType, tn.range(startPos, tn.pos)); + break; - // CommaExpression - } else if (token == Token.COMMA) { - var commaExprs = new Array(1); - commaExprs[0] = expr; - do { - expr = this.parseExpression(tn, Precedence.COMMA + 1); - if (!expr) + // ElementAccessExpression + case Token.OPENBRACKET: + next = this.parseExpression(tn); + if (!next) return null; - commaExprs.push(expr); - } while (tn.skip(Token.COMMA)); - expr = Node.createCommaExpression(commaExprs, tn.range(startPos, tn.pos)); - - } else { - next = this.parseExpression(tn, isRightAssociative(token) ? nextPrecedence : 1 + nextPrecedence); - if (!next) - return null; - - // PropertyAccessExpression - if (token == Token.DOT) { - if (next.kind == NodeKind.IDENTIFIER) { - expr = Node.createPropertyAccessExpression(expr, next, tn.range(startPos, tn.pos)); - } else if (next.kind == NodeKind.CALL) { // amend - var propertyCall = next; - if (propertyCall.expression.kind == NodeKind.IDENTIFIER) { - propertyCall.expression = Node.createPropertyAccessExpression(expr, propertyCall.expression, tn.range(startPos, tn.pos)); - } else - throw new Error("unexpected expression kind"); - expr = propertyCall; - } else { - this.error(DiagnosticCode.Identifier_expected, next.range); + if (!tn.skip(Token.CLOSEBRACKET)) { + this.error(DiagnosticCode._0_expected, tn.range(), "]"); return null; } + expr = Node.createElementAccessExpression(expr, next, tn.range(startPos, tn.pos)); + break; - // BinaryExpression - } else - expr = Node.createBinaryExpression(token, expr, next, tn.range(startPos, tn.pos)); + // UnaryPostfixExpression + case Token.PLUS_PLUS: + case Token.MINUS_MINUS: + if (expr.kind != NodeKind.IDENTIFIER && expr.kind != NodeKind.ELEMENTACCESS && expr.kind != NodeKind.PROPERTYACCESS) + this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, expr.range); + expr = Node.createUnaryPostfixExpression(token, expr, tn.range(startPos, tn.pos)); + break; + + // TernaryExpression + case Token.QUESTION: + var ifThen = this.parseExpression(tn); + if (!ifThen) + return null; + if (tn.skip(Token.COLON)) { + var ifElse = this.parseExpression(tn); + if (!ifElse) + return null; + expr = Node.createTernaryExpression(expr, ifThen, ifElse, tn.range(startPos, tn.pos)); + } else { + this.error(DiagnosticCode._0_expected, tn.range(), ":"); + return null; + } + break; + + // CommaExpression + case Token.COMMA: + var commaExprs = new Array(1); + commaExprs[0] = expr; + do { + expr = this.parseExpression(tn, Precedence.COMMA + 1); + if (!expr) + return null; + commaExprs.push(expr); + } while (tn.skip(Token.COMMA)); + expr = Node.createCommaExpression(commaExprs, tn.range(startPos, tn.pos)); + break; + + default: + next = this.parseExpression(tn, isRightAssociative(token) ? nextPrecedence : 1 + nextPrecedence); + if (!next) + return null; + + // PropertyAccessExpression + if (token == Token.DOT) { + if (next.kind == NodeKind.IDENTIFIER) + expr = Node.createPropertyAccessExpression(expr, next, tn.range(startPos, tn.pos)); + else if (next.kind == NodeKind.CALL) { // join + var propertyCall = next; + if (propertyCall.expression.kind == NodeKind.IDENTIFIER) { + propertyCall.expression = Node.createPropertyAccessExpression(expr, propertyCall.expression, tn.range(startPos, tn.pos)); + } else { + this.error(DiagnosticCode.Identifier_expected, propertyCall.expression.range); + return null; + } + expr = propertyCall; + } else { + this.error(DiagnosticCode.Identifier_expected, next.range); + return null; + } + + // BinaryExpression + } else + expr = Node.createBinaryExpression(token, expr, next, tn.range(startPos, tn.pos)); + + break; } } return expr; @@ -1820,6 +1833,7 @@ function determinePrecedence(kind: Token): i32 { case Token.DOT: case Token.NEW: + case Token.OPENBRACKET: return Precedence.MEMBERACCESS; default: diff --git a/src/program.ts b/src/program.ts index b58e643e..d87502a1 100644 --- a/src/program.ts +++ b/src/program.ts @@ -33,10 +33,12 @@ import { DecoratorKind, Expression, + AssertionExpression, ElementAccessExpression, IdentifierExpression, LiteralExpression, LiteralKind, + ParenthesizedExpression, PropertyAccessExpression, StringLiteralExpression, SuperExpression, @@ -66,7 +68,8 @@ import { hasDecorator, hasModifier, mangleInternalName, - getFirstDecorator + getFirstDecorator, + BinaryExpression } from "./ast"; import { @@ -1156,8 +1159,22 @@ export class Program extends DiagnosticEmitter { resolveExpression(expression: Expression, contextualFunction: Function): ResolvedElement | null { var classType: Class | null; + + while (expression.kind == NodeKind.PARENTHESIZED) + expression = (expression).expression; + switch (expression.kind) { + case NodeKind.ASSERTION: + var type = this.resolveType((expression).toType); // reports + if (type && (classType = type.classType)) + return (resolvedElement || (resolvedElement = new ResolvedElement())).set(classType); + return null; + + case NodeKind.BINARY: + // TODO: string concatenation, mostly + throw new Error("not implemented"); + case NodeKind.THIS: // -> Class if (classType = contextualFunction.instanceMethodOf) return (resolvedElement || (resolvedElement = new ResolvedElement())).set(classType); diff --git a/std/assembly/iterator.ts b/std/assembly/iterator.ts new file mode 100644 index 00000000..fd98ead4 --- /dev/null +++ b/std/assembly/iterator.ts @@ -0,0 +1,4 @@ +// export abstract class Iterator { +// abstract get done(): bool; +// abstract next(): T; +// } diff --git a/std/assembly/map.ts b/std/assembly/map.ts index 06983725..1506b00a 100644 --- a/std/assembly/map.ts +++ b/std/assembly/map.ts @@ -1,3 +1,37 @@ export class Map { - // TODO + + private __keys: K[] = []; + private __values: V[] = []; + + // FIXME: not a proper map implementation, just a filler + + get size(): i32 { + return this.__keys.length; + } + + get(key: K): V | null { + var keys = this.__keys; + for (var i = 0, k = keys.length; i < k; ++i) + if (keys[i] == key) + return this.__values[i]; + return null; + } + + has(key: K): bool { + var keys = this.__keys; + for (var i = 0, k = keys.length; i < k; ++i) + if (keys[i] == key) + return true; + return false; + } + + set(key: K, value: V): void { + this.__keys.push(key); + this.__values.push(value); + } + + clear(): void { + this.__keys.length = 0; + this.__values.length = 0; + } } diff --git a/std/assembly/set.ts b/std/assembly/set.ts index 35fc199e..464c0208 100644 --- a/std/assembly/set.ts +++ b/std/assembly/set.ts @@ -66,3 +66,14 @@ export class Set { // TODO: think about iterators } + +// class SetIterator extends Iterator { + +// get done(): bool { +// throw new Error("not implemented"); +// } + +// next(): T { +// throw new Error("not implemented"); +// } +// } diff --git a/tests/compiler/std/array.wast b/tests/compiler/std/array.wast index 26026953..4a5cec37 100644 --- a/tests/compiler/std/array.wast +++ b/tests/compiler/std/array.wast @@ -4052,6 +4052,7 @@ CLASS_PROTOTYPE: std:error/RangeError CLASS_PROTOTYPE: RangeError CLASS_PROTOTYPE: std:map/Map + PROPERTY: std:map/Map#size CLASS_PROTOTYPE: Map FUNCTION_PROTOTYPE: std:memory/copy_memory FUNCTION_PROTOTYPE: std:memory/move_memory diff --git a/tests/compiler/std/carray.wast b/tests/compiler/std/carray.wast index 4fbcc01d..64f5559d 100644 --- a/tests/compiler/std/carray.wast +++ b/tests/compiler/std/carray.wast @@ -259,6 +259,7 @@ CLASS_PROTOTYPE: std:error/RangeError CLASS_PROTOTYPE: RangeError CLASS_PROTOTYPE: std:map/Map + PROPERTY: std:map/Map#size CLASS_PROTOTYPE: Map FUNCTION_PROTOTYPE: std:memory/copy_memory FUNCTION_PROTOTYPE: std:memory/move_memory diff --git a/tests/compiler/std/heap.wast b/tests/compiler/std/heap.wast index db5572d6..36a0a1ab 100644 --- a/tests/compiler/std/heap.wast +++ b/tests/compiler/std/heap.wast @@ -2868,6 +2868,7 @@ CLASS_PROTOTYPE: std:error/RangeError CLASS_PROTOTYPE: RangeError CLASS_PROTOTYPE: std:map/Map + PROPERTY: std:map/Map#size CLASS_PROTOTYPE: Map FUNCTION_PROTOTYPE: std:memory/copy_memory FUNCTION_PROTOTYPE: std:memory/move_memory diff --git a/tests/compiler/std/new.wast b/tests/compiler/std/new.wast index 534e4d74..67c76302 100644 --- a/tests/compiler/std/new.wast +++ b/tests/compiler/std/new.wast @@ -209,6 +209,7 @@ CLASS_PROTOTYPE: std:error/RangeError CLASS_PROTOTYPE: RangeError CLASS_PROTOTYPE: std:map/Map + PROPERTY: std:map/Map#size CLASS_PROTOTYPE: Map FUNCTION_PROTOTYPE: std:memory/copy_memory FUNCTION_PROTOTYPE: std:memory/move_memory diff --git a/tests/compiler/std/set.wast b/tests/compiler/std/set.wast index b6ef7a8f..6d7a2b6c 100644 --- a/tests/compiler/std/set.wast +++ b/tests/compiler/std/set.wast @@ -2777,6 +2777,7 @@ CLASS_PROTOTYPE: std:error/RangeError CLASS_PROTOTYPE: RangeError CLASS_PROTOTYPE: std:map/Map + PROPERTY: std:map/Map#size CLASS_PROTOTYPE: Map FUNCTION_PROTOTYPE: std:memory/copy_memory FUNCTION_PROTOTYPE: std:memory/move_memory diff --git a/tests/compiler/std/string.wast b/tests/compiler/std/string.wast index 4fca1051..abfc3add 100644 --- a/tests/compiler/std/string.wast +++ b/tests/compiler/std/string.wast @@ -1504,6 +1504,7 @@ CLASS_PROTOTYPE: std:error/RangeError CLASS_PROTOTYPE: RangeError CLASS_PROTOTYPE: std:map/Map + PROPERTY: std:map/Map#size CLASS_PROTOTYPE: Map FUNCTION_PROTOTYPE: std:memory/copy_memory FUNCTION_PROTOTYPE: std:memory/move_memory diff --git a/tests/parser/propertyelementaccess.ts b/tests/parser/propertyelementaccess.ts new file mode 100644 index 00000000..d3972d02 --- /dev/null +++ b/tests/parser/propertyelementaccess.ts @@ -0,0 +1 @@ +(((this).values[0].prop1.prop2.another[1]).prop)[2]; diff --git a/tests/parser/propertyelementaccess.ts.fixture.ts b/tests/parser/propertyelementaccess.ts.fixture.ts new file mode 100644 index 00000000..d3972d02 --- /dev/null +++ b/tests/parser/propertyelementaccess.ts.fixture.ts @@ -0,0 +1 @@ +(((this).values[0].prop1.prop2.another[1]).prop)[2];