From 508cd5303799249857595b2c90f897e75e4e8dc8 Mon Sep 17 00:00:00 2001 From: Shane Liesegang Date: Fri, 13 Dec 2024 14:35:06 +0800 Subject: [PATCH] TypeScript -> AssemblyScript; seems to work but not tested --- README.md | 3 +- beschi/protocol.py | 4 + beschi/writers/boilerplate/AssemblyScript.ts | 323 +++++++++++-------- beschi/writers/boilerplate/TypeScript.ts | 34 +- beschi/writers/typescript.py | 194 ++++++----- docs/languages/typescript.md | 2 +- 6 files changed, 331 insertions(+), 229 deletions(-) diff --git a/README.md b/README.md index 29b5d69..c654fc8 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ [![PyPI](https://img.shields.io/pypi/v/beschi)](https://pypi.org/project/beschi/) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/sjml/beschi/ci.yml)](https://github.com/sjml/beschi/actions/workflows/ci.yml) -This is a custom bit-packing and unpacking code generator for C, C#, Go, Rust, Swift, TypeScript, and Zig. You feed it a data description and it generates source files for writing/reading buffers of that data, along the lines of [FlatBuffers](https://google.github.io/flatbuffers/) or [Cap'n Proto](https://capnproto.org), but with much less functionality for much simpler use cases. It was initially written for a larger project that was passing data back and forth between a Unity game, a Go server, and a web client, but I extracted it into its own thing. If all you need is a simple way to pack a data structure into a compact, portable binary form, this might be useful for you. +This is a custom bit-packing and unpacking code generator for C, C#, Go, Rust, Swift, TypeScript, and Zig.[^1] You feed it a data description and it generates source files for writing/reading buffers of that data, along the lines of [FlatBuffers](https://google.github.io/flatbuffers/) or [Cap'n Proto](https://capnproto.org), but with much less functionality for much simpler use cases. It was initially written for a larger project that was passing data back and forth between a Unity game, a Go server, and a web client, but I extracted it into its own thing. If all you need is a simple way to pack a data structure into a compact, portable binary form, this might be useful for you. I go into more explanation for why this exists [in the documentation](https://github.com/sjml/beschi/tree/main/docs/), but I'll be honest, too: it **was** kind of fun to write a code generator. 😝 +[^1]: There is also experimental support for AssemblyScript, but it's not run through the test suite so THERE BE DRAGONS. 🐉 ## Documentation diff --git a/beschi/protocol.py b/beschi/protocol.py index 57dab6d..0f13d62 100644 --- a/beschi/protocol.py +++ b/beschi/protocol.py @@ -102,6 +102,10 @@ def __init__(self, name: str, value_spec: list[str | dict[str, int|str] ]): def get_default_pair(self) -> tuple[str,int]: return next(iter(self.values.items())) + def get_minimum_pair(self) -> tuple[str,int]: + k = min(self.values, key=self.values.get) + return (k, self.values[k]) + class Struct(): def __init__(self, name: str): self.name = name diff --git a/beschi/writers/boilerplate/AssemblyScript.ts b/beschi/writers/boilerplate/AssemblyScript.ts index 91f9f12..5bc5e03 100644 --- a/beschi/writers/boilerplate/AssemblyScript.ts +++ b/beschi/writers/boilerplate/AssemblyScript.ts @@ -1,145 +1,194 @@ -const _textDec = new TextDecoder('utf-8'); -const _textEnc = new TextEncoder(); - export class DataAccess { - buffer: DataView; - currentOffset: usize; - - constructor(buffer: ArrayBuffer|DataView) { - this.currentOffset = 0; - if (buffer instanceof ArrayBuffer) { - this.buffer = new DataView(buffer); - } - else { - this.buffer = buffer; - } - } - - isFinished(): boolean { - return this.currentOffset >= this.buffer.byteLength; - } - - getByte(): u8 { - const ret = this.buffer.getUint8(this.currentOffset); - this.currentOffset += 1; - return ret; - } - - getBool(): boolean { - return this.GetByte() > 0; - } - - getInt16(): i16 { - const ret = this.buffer.getInt16(this.currentOffset, true); - this.currentOffset += 2; - return ret; - } - - getUint16(): u16 { - const ret = this.buffer.getUint16(this.currentOffset, true); - this.currentOffset += 2; - return ret; - } - - getInt32(): i32 { - const ret = this.buffer.getInt32(this.currentOffset, true); - this.currentOffset += 4; - return ret; - } - - getUint32(): u32 { - const ret = this.buffer.getUint32(this.currentOffset, true); - this.currentOffset += 4; - return ret; - } - - getInt64(): i64 { - const ret = this.buffer.getBigInt64(this.currentOffset, true); - this.currentOffset += 8; - return ret; - } - - getUint64(): u64 { - const ret = this.buffer.getBigUint64(this.currentOffset, true); - this.currentOffset += 8; - return ret; - } - - getFloat32(): f32 { - const ret = this.buffer.getFloat32(this.currentOffset, true); - this.currentOffset += 4; - return Math.fround(ret); - } - - getFloat64(): f64 { - const ret = this.buffer.getFloat64(this.currentOffset, true); - this.currentOffset += 8; - return ret; - } - - getString(): string { - const len = this.Get{# STRING_SIZE_TYPE #}(); - const strBuffer = new Uint8Array(this.buffer.buffer, this.currentOffset, len); - this.currentOffset += len; - return _textDec.decode(strBuffer); - } - - - setByte(val: u8) { - this.buffer.setUint8(this.currentOffset, val); - this.currentOffset += 1; - } - - setBool(val: boolean) { - this.setByte(val ? 1 : 0); - } - - setInt16(val: i16) { - this.buffer.setInt16(this.currentOffset, val, true); - this.currentOffset += 2; - } - - setUint16(val: u16) { - this.buffer.setUint16(this.currentOffset, val, true); - this.currentOffset += 2; - } - - setInt32(val: i32) { - this.buffer.setInt32(this.currentOffset, val, true); - this.currentOffset += 4; - } - - setUint32(val: u32) { - this.buffer.setUint32(this.currentOffset, val, true); - this.currentOffset += 4; - } + data: DataView; + currentOffset: u32; + + constructor(buffer: DataView) { + this.currentOffset = 0; + this.data = buffer; + } + + isFinished(): boolean { + return this.currentOffset >= this.data.byteLength; + } + + getByte(): u8 { + const ret = this.data.getUint8(this.currentOffset); + this.currentOffset += 1; + return ret; + } + + getBool(): boolean { + return this.getByte() > 0; + } + + getInt16(): i16 { + const ret = this.data.getInt16(this.currentOffset, true); + this.currentOffset += 2; + return ret; + } + + getUint16(): u16 { + const ret = this.data.getUint16(this.currentOffset, true); + this.currentOffset += 2; + return ret; + } + + getInt32(): i32 { + const ret = this.data.getInt32(this.currentOffset, true); + this.currentOffset += 4; + return ret; + } + + getUint32(): u32 { + const ret = this.data.getUint32(this.currentOffset, true); + this.currentOffset += 4; + return ret; + } + + getInt64(): i64 { + const ret = this.data.getBigInt64(this.currentOffset, true); + this.currentOffset += 8; + return ret; + } + + getUint64(): u64 { + const ret = this.data.getBigUint64(this.currentOffset, true); + this.currentOffset += 8; + return ret; + } + + getFloat32(): f32 { + const ret = this.data.getFloat32(this.currentOffset, true); + this.currentOffset += 4; + return Math.fround(ret); + } + + getFloat64(): f64 { + const ret = this.data.getFloat64(this.currentOffset, true); + this.currentOffset += 8; + return ret; + } + + getString(): string { + const len = this.get{# STRING_SIZE_TYPE #}(); + const strBuffer = new Uint8Array(this.data.buffer, this.currentOffset, len); + this.currentOffset += len; + return String.UTF8.decode(strBuffer, false); + } + + + setByte(val: u8): void { + this.data.setUint8(this.currentOffset, val); + this.currentOffset += 1; + } + + setBool(val: boolean): void { + this.setByte(val ? 1 : 0); + } + + setInt16(val: i16): void { + this.data.setInt16(this.currentOffset, val, true); + this.currentOffset += 2; + } + + setUint16(val: u16): void { + this.data.setUint16(this.currentOffset, val, true); + this.currentOffset += 2; + } + + setInt32(val: i32): void { + this.data.setInt32(this.currentOffset, val, true); + this.currentOffset += 4; + } + + setUint32(val: u32): void { + this.data.setUint32(this.currentOffset, val, true); + this.currentOffset += 4; + } + + setInt64(val: i64): void { + this.data.setBigInt64(this.currentOffset, val, true); + this.currentOffset += 8; + } + + setUint64(val: u64): void { + this.data.setBigUint64(this.currentOffset, val, true); + this.currentOffset += 8; + } + + setFloat32(val: f32): void { + this.data.setFloat32(this.currentOffset, val, true); + this.currentOffset += 4; + } + + setFloat64(val: f64): void { + this.data.setFloat64(this.currentOffset, val, true); + this.currentOffset += 8; + } + + setString(val: string): void { + const strBuffer = String.UTF8.encode(val, false); + const bufferArray = Uint8Array.wrap(strBuffer); + this.setByte(strBuffer.byteLength as u8); + for (let i = 0; i < bufferArray.byteLength; i++) { + this.setByte(bufferArray[i] as u8); + } + } +} - setInt64(val: i64) { - this.buffer.setBigInt64(this.currentOffset, val, true); - this.currentOffset += 8; - } +export abstract class Message { + abstract getMessageType(): MessageType; + abstract writeBytes(dv: DataView, tag: boolean): void; + abstract getSizeInBytes(): number; - setUint64(val: u64) { - this.buffer.setBigUint64(this.currentOffset, val, true); - this.currentOffset += 8; - } + static fromBytes(data: DataView): Message | null { + throw new Error("Cannot read abstract Message from bytes."); + }; +} - setFloat32(val: f32) { - this.buffer.setFloat32(this.currentOffset, val, true); - this.currentOffset += 4; - } +export function GetPackedSize(msgList: Message[]): usize { + let size = 0; + for (const msg of msgList) { + size += msg.getSizeInBytes(); + } + size += msgList.length; + size += 9; + return size; +} - setFloat64(val: f64) { - this.buffer.setFloat64(this.currentOffset, val, true); - this.currentOffset += 8; - } +export function PackMessages(msgList: Message[], data: DataView): void { + const da = new DataAccess(data); + const headerBytes = _textEnc.encode("BSCI"); + const arr = new Uint8Array(da.data.buffer); + arr.set(headerBytes, da.currentOffset); + da.currentOffset += headerBytes.byteLength; + da.setUint32(msgList.length); + for (const msg of msgList) { + msg.writeBytes(da, true); + } + da.setByte(0); +} - setString(val: string) { - const strBuffer = _textEnc.encode(val); - this.Set{# STRING_SIZE_TYPE #}(strBuffer.byteLength); - const arr = new Uint8Array(this.buffer.buffer); - arr.set(strBuffer, this.currentOffset); - this.currentOffset += strBuffer.byteLength; - } +export function UnpackMessages(data: DataView): Message[] { + const da = new DataAccess(data); + const headerBuffer = new Uint8Array(da.data.buffer, da.currentOffset, 4); + da.currentOffset += 4; + const headerLabel = _textDec.decode(headerBuffer); + if (headerLabel !== "BSCI") { + throw new Error("Packed message buffer has invalid header."); + } + const msgCount = da.getUint32(); + if (msgCount == 0) { + return []; + } + const msgList = ProcessRawBytes(da, msgCount); + if (msgList.length == 0) { + throw new Error("No messages in buffer."); + } + if (msgList.length != msgCount) { + throw new Error("Unexpected number of messages in buffer."); + } + return msgList; } + diff --git a/beschi/writers/boilerplate/TypeScript.ts b/beschi/writers/boilerplate/TypeScript.ts index efeedaf..110b0fc 100644 --- a/beschi/writers/boilerplate/TypeScript.ts +++ b/beschi/writers/boilerplate/TypeScript.ts @@ -91,51 +91,51 @@ export class DataAccess { this.currentOffset += 1; } - setBool(val: boolean) { + setBool(val: boolean): void { this.setByte(val ? 1 : 0); } - setInt16(val: number) { + setInt16(val: number): void { this.data.setInt16(this.currentOffset, val, true); this.currentOffset += 2; } - setUint16(val: number) { + setUint16(val: number): void { this.data.setUint16(this.currentOffset, val, true); this.currentOffset += 2; } - setInt32(val: number) { + setInt32(val: number): void { this.data.setInt32(this.currentOffset, val, true); this.currentOffset += 4; } - setUint32(val: number) { + setUint32(val: number): void { this.data.setUint32(this.currentOffset, val, true); this.currentOffset += 4; } - setInt64(val: bigint) { + setInt64(val: bigint): void { this.data.setBigInt64(this.currentOffset, val, true); this.currentOffset += 8; } - setUint64(val: bigint) { + setUint64(val: bigint): void { this.data.setBigUint64(this.currentOffset, val, true); this.currentOffset += 8; } - setFloat32(val: number) { + setFloat32(val: number): void { this.data.setFloat32(this.currentOffset, val, true); this.currentOffset += 4; } - setFloat64(val: number) { + setFloat64(val: number): void { this.data.setFloat64(this.currentOffset, val, true); this.currentOffset += 8; } - setString(val: string) { + setString(val: string): void { const strBuffer = _textEnc.encode(val); this.set{# STRING_SIZE_TYPE #}(strBuffer.byteLength); const arr = new Uint8Array(this.data.buffer); @@ -155,13 +155,13 @@ export abstract class Message { } export function GetPackedSize(msgList: Message[]): number { - let size = 0; - for (const msg of msgList) { - size += msg.getSizeInBytes(); - } - size += msgList.length; - size += 9; - return size; + let size = 0; + for (const msg of msgList) { + size += msg.getSizeInBytes(); + } + size += msgList.length; + size += 9; + return size; } export function PackMessages(msgList: Message[], data: DataView|DataAccess): void { diff --git a/beschi/writers/typescript.py b/beschi/writers/typescript.py index 938a80f..1d99879 100644 --- a/beschi/writers/typescript.py +++ b/beschi/writers/typescript.py @@ -50,18 +50,25 @@ def deserializer(self, var: Variable, accessor: str): var_clean = TextUtil.replace(var.name, [("[", "_"), ("]", "_")]) if var.is_list: self.write_line(f"const {var_clean}_Length = da.get{self.base_serializers[self.protocol.list_size_type]}();") - self.write_line(f"{accessor}{var.name} = Array<{self.type_mapping[var.vartype]}>({var_clean}_Length);") + self.write_line(f"{accessor}{var.name} = new Array<{self.type_mapping[var.vartype]}>({var_clean}_Length);") idx = self.indent_level - self.write_line(f"for (let i{idx} = 0; i{idx} < {var_clean}_Length; i{idx}++) {{") + self.write_line(f"for (let i{idx}: {self.type_mapping[self.protocol.list_size_type]} = 0; i{idx} < {var_clean}_Length; i{idx}++) {{") self.indent_level += 1 if var.vartype in self.protocol.enums: e = self.protocol.enums[var.vartype] self.write_line(f"const _{var.name} = da.get{self.base_serializers[e.encoding]}();") - self.write_line(f"if ({var.vartype}[_{var.name}] === undefined) {{") - self.indent_level += 1 - self.write_line(f"throw new Error(`Enum (${{_{var.name}}}) out of range for {var.vartype}`);") - self.indent_level -= 1 - self.write_line("}") + if self.language_name != "AssemblyScript": + self.write_line(f"if ({var.vartype}[_{var.name}] === undefined) {{") + self.indent_level += 1 + self.write_line(f"throw new Error(`Enum (${{_{var.name}}}) out of range for {var.vartype}`);") + self.indent_level -= 1 + self.write_line("}") + else: + self.write_line(f"if (_{var.name} < {e.get_minimum_pair()[1]} || _{var.name} >= ({var.vartype}._Unknown as {self.type_mapping[e.encoding]})) {{") + self.indent_level += 1 + self.write_line(f"throw new Error(`Enum (${{_{var.name}}}) out of range for {var.vartype}`);") + self.indent_level -= 1 + self.write_line("}") self.write_line(f"{accessor}{var.name}[i{idx}] = _{var.name};") else: inner = Variable(self.protocol, f"{var.name}[i{idx}]", var.vartype) @@ -73,11 +80,18 @@ def deserializer(self, var: Variable, accessor: str): elif var.vartype in self.protocol.enums: e = self.protocol.enums[var.vartype] self.write_line(f"const _{var.name} = da.get{self.base_serializers[e.encoding]}();") - self.write_line(f"if ({var.vartype}[_{var.name}] === undefined) {{") - self.indent_level += 1 - self.write_line(f"throw new Error(`Enum (${{_{var.name}}}) out of range for {var.vartype}`);") - self.indent_level -= 1 - self.write_line("}") + if self.language_name != "AssemblyScript": + self.write_line(f"if ({var.vartype}[_{var.name}] === undefined) {{") + self.indent_level += 1 + self.write_line(f"throw new Error(`Enum (${{_{var.name}}}) out of range for {var.vartype}`);") + self.indent_level -= 1 + self.write_line("}") + else: + self.write_line(f"if (_{var.name} < {e.get_minimum_pair()[1]} || _{var.name} >= ({var.vartype}._Unknown as {self.type_mapping[e.encoding]})) {{") + self.indent_level += 1 + self.write_line(f"throw new Error(`Enum (${{_{var.name}}}) out of range for {var.vartype}`);") + self.indent_level -= 1 + self.write_line("}") self.write_line(f"{accessor}{var.name} = _{var.name};") elif var.vartype in NUMERIC_TYPE_SIZES: self.write_line(f"{accessor}{var.name} = da.get{self.base_serializers[var.vartype]}();") @@ -86,7 +100,11 @@ def deserializer(self, var: Variable, accessor: str): def serializer(self, var: Variable, accessor: str): if var.is_list: - self.write_line(f"da.set{self.base_serializers[self.protocol.list_size_type]}({accessor}{var.name}.length);") + if self.language_name != "AssemblyScript": + self.write_line(f"da.set{self.base_serializers[self.protocol.list_size_type]}({accessor}{var.name}.length);") + else: + e = self.protocol.enums[var.vartype] + self.write_line(f"da.set{self.base_serializers[self.protocol.list_size_type]}({accessor}{var.name}.length as {self.type_mapping[e.encoding]});") self.write_line(f"for (let i = 0; i < {accessor}{var.name}.length; i++) {{") self.indent_level += 1 self.write_line(f"let el = {accessor}{var.name}[i];") @@ -98,7 +116,10 @@ def serializer(self, var: Variable, accessor: str): self.write_line(f"da.setString({accessor}{var.name});") elif var.vartype in self.protocol.enums: e = self.protocol.enums[var.vartype] - self.write_line(f"da.set{self.base_serializers[e.encoding]}({accessor}{var.name});") + if self.language_name != "AssemblyScript": + self.write_line(f"da.set{self.base_serializers[e.encoding]}({accessor}{var.name});") + else: + self.write_line(f"da.set{self.base_serializers[e.encoding]}({accessor}{var.name} as {self.type_mapping[e.encoding]});") elif var.vartype in NUMERIC_TYPE_SIZES: self.write_line(f"da.set{self.base_serializers[var.vartype]}({accessor}{var.name});") else: @@ -151,6 +172,8 @@ def gen_enum(self, ename: str, edata: Enum): self.indent_level += 1 for v, vi in edata.values.items(): self.write_line(f"{v} = {vi},") + if self.language_name == "AssemblyScript": + self.write_line("_Unknown,") self.indent_level -= 1 self.write_line("}") self.write_line() @@ -198,34 +221,39 @@ def gen_struct(self, sname: str, sdata: Struct): self.write_line() if sdata.is_message: - self.write_line(f"static override fromBytes(data: DataView|DataAccess|ArrayBuffer): {sname} {{") - self.indent_level += 1 - self.write_line("let da: DataAccess;") - self.write_line("if (data instanceof DataView) {") - self.indent_level += 1 - self.write_line("da = new DataAccess(data);") - self.indent_level -= 1 - self.write_line("}") - self.write_line("else if (data instanceof ArrayBuffer) {") - self.indent_level += 1 - self.write_line("da = new DataAccess(new DataView(data));") - self.indent_level -= 1 - self.write_line("}") - self.write_line("else {") - self.indent_level += 1 - self.write_line("da = data;") - self.indent_level -= 1 - self.write_line("}") + if self.language_name != "AssemblyScript": + self.write_line(f"static override fromBytes(data: DataView|DataAccess|ArrayBuffer): {sname} {{") + self.indent_level += 1 + self.write_line("let da: DataAccess;") + self.write_line("if (data instanceof DataView) {") + self.indent_level += 1 + self.write_line("da = new DataAccess(data);") + self.indent_level -= 1 + self.write_line("}") + self.write_line("else if (data instanceof ArrayBuffer) {") + self.indent_level += 1 + self.write_line("da = new DataAccess(new DataView(data));") + self.indent_level -= 1 + self.write_line("}") + self.write_line("else {") + self.indent_level += 1 + self.write_line("da = data;") + self.indent_level -= 1 + self.write_line("}") + else: + self.write_line(f"static override fromBytes(data: DataView): {sname} {{") + self.indent_level += 1 + self.write_line("const da = new DataAccess(data);") else: self.write_line(f"static fromBytes(da: DataAccess): {sname} {{") self.indent_level += 1 - if sdata.is_message: + if sdata.is_message and self.language_name != "AssemblyScript": self.write_line("try {") self.indent_level += 1 self.write_line(f"const n{sname} = new {self.type_mapping[sname]}();") [self.deserializer(mem, f"n{sname}.") for mem in sdata.members] self.write_line(f"return n{sname};") - if sdata.is_message: + if sdata.is_message and self.language_name != "AssemblyScript": self.indent_level -= 1 self.write_line("}") self.write_line("catch (err) {") @@ -244,26 +272,36 @@ def gen_struct(self, sname: str, sdata: Struct): self.write_line() if sdata.is_message: - self.write_line("writeBytes(data: DataView|DataAccess, tag: boolean): void {") - self.indent_level += 1 - self.write_line("let da: DataAccess;") - self.write_line("if (data instanceof DataView) {") - self.indent_level += 1 - self.write_line("da = new DataAccess(data);") - self.indent_level -= 1 - self.write_line("}") - self.write_line("else {") - self.indent_level += 1 - self.write_line("da = data;") - self.indent_level -= 1 - self.write_line("}") - self.write_line("if (tag) {") - self.indent_level += 1 - self.write_line(f"da.setByte(MessageType.{sname}Type);") - self.indent_level -= 1 - self.write_line("}") + if self.language_name != "AssemblyScript": + self.write_line("writeBytes(data: DataView|DataAccess, tag: boolean): void {") + self.indent_level += 1 + self.write_line("let da: DataAccess;") + self.write_line("if (data instanceof DataView) {") + self.indent_level += 1 + self.write_line("da = new DataAccess(data);") + self.indent_level -= 1 + self.write_line("}") + self.write_line("else {") + self.indent_level += 1 + self.write_line("da = data;") + self.indent_level -= 1 + self.write_line("}") + self.write_line("if (tag) {") + self.indent_level += 1 + self.write_line(f"da.setByte(MessageType.{sname}Type);") + self.indent_level -= 1 + self.write_line("}") + else: + self.write_line("writeBytes(data: DataView, tag: boolean): void {") + self.indent_level += 1 + self.write_line("const da = new DataAccess(data);") + self.write_line("if (tag) {") + self.indent_level += 1 + self.write_line(f"da.setByte(MessageType.{sname}Type as u8);") + self.indent_level -= 1 + self.write_line("}") else: - self.write_line("writeBytes(da: DataAccess) {") + self.write_line("writeBytes(da: DataAccess): void {") self.indent_level += 1 [self.serializer(mem, "this.") for mem in sdata.members] self.indent_level -= 1 @@ -298,34 +336,43 @@ def generate(self) -> str: self.indent_level += 1 self.add_boilerplate(substitutions=[ - ("{# STRING_SIZE_TYPE #}", self.base_serializers[self.protocol.string_size_type]) + ("{# STRING_SIZE_TYPE #}", self.base_serializers[self.protocol.string_size_type]), + ("{# NATIVE_STRING_SIZE_TYPE #}", self.type_mapping[self.protocol.string_size_type]), ]) self.write_line("export enum MessageType {") self.indent_level += 1 [self.write_line(f"{k}Type = {i+1},") for i, k in enumerate(self.protocol.messages)] + if self.language_name == "AssemblyScript": + self.write_line("_Unknown,") self.indent_level -= 1 self.write_line("}") self.write_line() - self.write_line("export function ProcessRawBytes(data: DataView|DataAccess, max: number): Message[] {") + if self.language_name != "AssemblyScript": + self.write_line("export function ProcessRawBytes(data: DataView|DataAccess, max: number): Message[] {") + else: + self.write_line("export function ProcessRawBytes(data: DataView, max: number): Message[] {") self.indent_level += 1 self.write_line("if (max === undefined) {") self.indent_level += 1 self.write_line("max = -1;") self.indent_level -= 1 self.write_line("}") - self.write_line("let da: DataAccess;") - self.write_line("if (data instanceof DataView) {") - self.indent_level += 1 - self.write_line("da = new DataAccess(data);") - self.indent_level -= 1 - self.write_line("}") - self.write_line("else {") - self.indent_level += 1 - self.write_line("da = data;") - self.indent_level -= 1 - self.write_line("}") + if self.language_name != "AssemblyScript": + self.write_line("let da: DataAccess;") + self.write_line("if (data instanceof DataView) {") + self.indent_level += 1 + self.write_line("da = new DataAccess(data);") + self.indent_level -= 1 + self.write_line("}") + self.write_line("else {") + self.indent_level += 1 + self.write_line("da = data;") + self.indent_level -= 1 + self.write_line("}") + else: + self.write_line("const da = new DataAccess(data);") self.write_line("const msgList: Message[] = [];") self.write_line("if (max == 0) {") self.indent_level += 1 @@ -369,13 +416,14 @@ def generate(self) -> str: for mname, mdata in self.protocol.messages.items(): self.gen_struct(mname, mdata) - self.write_line("export const MessageTypeMap = new Map(["); - self.indent_level += 1 - for mname in self.protocol.messages: - self.write_line(f"[MessageType.{mname}Type, {mname}],") - self.indent_level -= 1 - self.write_line("]);") - self.write_line() + if self.language_name != "AssemblyScript": + self.write_line("export const MessageTypeMap = new Map(["); + self.indent_level += 1 + for mname in self.protocol.messages: + self.write_line(f"[MessageType.{mname}Type, {mname}],") + self.indent_level -= 1 + self.write_line("]);") + self.write_line() if self.use_namespace: self.indent_level -= 1 diff --git a/docs/languages/typescript.md b/docs/languages/typescript.md index 26bc49b..ca6f019 100644 --- a/docs/languages/typescript.md +++ b/docs/languages/typescript.md @@ -29,4 +29,4 @@ The base data types map to TypeScript accordingly: * 64-bit integers (both signed and unsigned) are implemented with [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), which has [pretty broad support at this point](https://caniuse.com/?search=bigint). Your client code may need to handle them differently though -- you can't seamlessly do math with a regular `number` and a `bigint`. Users of other languages are used to these kinds of folds, but JavaScript/TypeScript users may find them new and annoying. :) * Reading and writing messages is wrapped in a custom `DataAccess` class that tracks position in a `DataView`. You can either construct it yourself if you want to, for instance, do multiple passes of writing to the same buffer. If you pass a `DataView` to a function that expects a `DataAccess`, the latter will be used internally but not returned to you. * Be careful of long-lasting `DataView` (and thus `DataAccess`) objects, though, since they become invalid if their underlying `ArrayBuffer` gets detached. Usually you're aware of that happening, but if you're working with a WebAssembly module's memory, it happens whenever the memory grows (which you might **not** be aware of). You can always just make a new `DataView` object from the memory when that happens, though. (Documenting here because I was bitten by it myself!) - +* There is an experimental generator for AssemblyScript which leverages the TypeScript support. It's not run through the test suite (because testing WebAssembly is a pain) but seems to work provisionally.