From 3265671c01dc6eb35b56101699e73aff19525e58 Mon Sep 17 00:00:00 2001 From: Jairus Date: Fri, 14 Jun 2024 18:19:49 -0700 Subject: [PATCH] release: v0.9.0 --- CHANGELOG | 5 +- README.md | 10 ++- assembly/deserialize/object.ts | 18 ++-- assembly/index.ts | 6 +- assembly/test.ts | 47 +++++++--- package.json | 2 +- transform/lib/index.js | 143 +++++++++++++++--------------- transform/package.json | 2 +- transform/src/index.ts | 153 +++++++++++++++------------------ 9 files changed, 195 insertions(+), 191 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1d29bb4..1b58721 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,4 +2,7 @@ v0.8.2 - Properties starting with `static` or `private` would be ignored v0.8.3 - Dirty fix to issue #68. Add __JSON_Stringify callable to global scope. v0.8.4 - Fix #71. Classes with the extending class overriding a property cause the property to be serialized twice. v0.8.5 - Fix #73. Support for nullable primatives with Box from as-container -v0.8.6 - Fix. Forgot to stash before publishing. Stash and push what should have been v0.8.5 \ No newline at end of file +v0.8.6 - Fix. Forgot to stash before publishing. Stash and push what should have been v0.8.5 + +v0.9.0 - Large update. Refactor all the code, nullable primitives, rewrite the transform, allow extensibility with @omit keywords, and fix a plethora of bugs +[UNRELEASED] v0.9.1 - Port JSON.Value from the `develop` branch to allow for union types, parsing of arbitrary data, and whatever the hell you want. \ No newline at end of file diff --git a/README.md b/README.md index 7dd9dda..b7e308f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ██║ ██║███████║ ╚█████╔╝███████║╚██████╔╝██║ ╚████║ ╚═╝ ╚═╝╚══════╝ ╚════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ -v0.8.6 +v0.9.0 @@ -34,6 +34,8 @@ Alternatively, add it to your `asconfig.json` } ``` +If you'd like to see the code that the transform generates, run with `JSON_DEBUG=true` + ## Usage ```js @@ -53,6 +55,8 @@ class Player { firstName!: string; lastName!: string; lastActive!: i32[]; + // Drop in a code block, function, or expression that evaluates to a boolean + @omitif("this.age < 18") age!: i32; @omitnull() pos!: Vec3 | null; @@ -79,6 +83,10 @@ const parsed = JSON.parse(stringified); If you use this project in your codebase, consider dropping a [star](https://github.com/JairusSW/as-json). I would really appreciate it! +## Notes + +If you want a feature, drop an issue (and again, maybe a star). I'll likely add it in less than 7 days. + ## Performance Run or view the benchmarks [here](https://github.com/JairusSW/as-json/tree/master/bench) diff --git a/assembly/deserialize/object.ts b/assembly/deserialize/object.ts index 8e91d9b..e6f2132 100644 --- a/assembly/deserialize/object.ts +++ b/assembly/deserialize/object.ts @@ -3,13 +3,13 @@ import { aCode, backSlashCode, commaCode, eCode, fCode, lCode, leftBraceCode, le import { isSpace } from "util/string"; // @ts-ignore: Decorator -@inline export function deserializeObject(data: string, initializeDefaultValues: boolean): T { +@inline export function deserializeObject(data: string): T { const schema: nonnull = changetype>( __new(offsetof>(), idof>()) ); // @ts-ignore - if (initializeDefaultValues) schema.__INITIALIZE(); + schema.__INITIALIZE(); let key_start: i32 = 0; let key_end: i32 = 0; @@ -73,20 +73,12 @@ import { isSpace } from "util/string"; } else { if (char === quoteCode && !escaping) { if (isKey === false) { - // perf: we can avoid creating a new string here if the key doesn't contain any escape sequences - if (containsCodePoint(data, backSlashCode, outerLoopIndex, stringValueIndex)) { - key_start = outerLoopIndex - 1; - key_end = stringValueIndex; - //console.log(`[KEY-00]: ${data.slice(outerLoopIndex - 1, stringValueIndex)}`) - } else { - key_start = outerLoopIndex; - key_end = stringValueIndex; - //console.log(`[KEY-01]: ${data.slice(outerLoopIndex, stringValueIndex)}`) - } + key_start = outerLoopIndex; + key_end = stringValueIndex; isKey = true; } else { // @ts-ignore - schema.__DESERIALIZE(data, key_start, key_end, outerLoopIndex - 1, stringValueIndex); + schema.__DESERIALIZE(data, key_start, key_end, outerLoopIndex - 1, stringValueIndex + 1); isKey = false; } outerLoopIndex = ++stringValueIndex; diff --git a/assembly/index.ts b/assembly/index.ts index b1094b0..4074831 100644 --- a/assembly/index.ts +++ b/assembly/index.ts @@ -77,7 +77,7 @@ export namespace JSON { */ // @ts-ignore: Decorator - @inline export function parse(data: string, initializeDefaultValues: boolean = false): T { + @inline export function parse(data: string): T { if (isString()) { // @ts-ignore return deserializeString(data); @@ -91,7 +91,7 @@ export namespace JSON { // @ts-ignore return deserializeArray(data); } - let type: nonnull = changetype>(0); + let type: nonnull = changetype>(__new(offsetof>(), idof>())); if (type instanceof Box) { return deserializeBox(data); } else if (isNullable() && data == nullWord) { @@ -99,7 +99,7 @@ export namespace JSON { return null; // @ts-ignore } else if (isDefined(type.__DESERIALIZE)) { - return deserializeObject(data.trimStart(), initializeDefaultValues); + return deserializeObject(data.trimStart()); } else if (type instanceof Map) { return deserializeMap(data.trimStart()); } else if (type instanceof Date) { diff --git a/assembly/test.ts b/assembly/test.ts index db1c512..e59816e 100644 --- a/assembly/test.ts +++ b/assembly/test.ts @@ -1,16 +1,41 @@ +import { JSON } from "."; + @json -class BaseObject { - a: string; - constructor(a: string) { - this.a = a; - } +class Vec3 { + x: f32 = 0.0; + y: f32 = 0.0; + z: f32 = 0.0; } @json -class DerivedObject extends BaseObject { - b: string; - constructor(a: string, b: string) { - super(a); - this.b = b; - } +class Player { + @alias("first name") + firstName!: string; + lastName!: string; + lastActive!: i32[]; + @omitif("this.age < 18") + age!: i32; + @omitnull() + pos!: Vec3 | null; + isVerified!: boolean; } + +const player: Player = { + firstName: "Emmet", + lastName: "West", + lastActive: [8, 27, 2022], + age: 13, + pos: { + x: 3.4, + y: 1.2, + z: 8.3 + }, + isVerified: true +}; + +const stringified = JSON.stringify(player); + +const parsed = JSON.parse(stringified); + +console.log("Stringified: " + stringified); +console.log("Parsed: " + JSON.stringify(parsed)); \ No newline at end of file diff --git a/package.json b/package.json index 2f4f051..6d1478d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-as", - "version": "0.8.6", + "version": "0.8.7", "description": "JSON encoder/decoder for AssemblyScript", "types": "assembly/index.ts", "author": "Jairus Tanaka", diff --git a/transform/lib/index.js b/transform/lib/index.js index 0c94efb..8641f71 100644 --- a/transform/lib/index.js +++ b/transform/lib/index.js @@ -1,4 +1,4 @@ -import { FieldDeclaration, Parser, Source, Tokenizer, } from "assemblyscript/dist/assemblyscript.js"; +import { FieldDeclaration, } from "assemblyscript/dist/assemblyscript.js"; import { toString, isStdlib } from "visitor-as/dist/utils.js"; import { BaseVisitor, SimpleParser } from "visitor-as/dist/index.js"; import { Transform } from "assemblyscript/dist/transform.js"; @@ -84,24 +84,29 @@ class JSONTransform extends BaseVisitor { mem.name = mem.alias; } else if (mem.flag === PropertyFlags.None) { - mem.serialize = encodeKey(mem.name) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; + mem.serialize = escapeString(JSON.stringify(mem.name)) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));"; } if (mem.flag == PropertyFlags.OmitNull) { - mem.serialize = "${changetype(this." + mem.name + ") == 0" + " ? \"\" : '" + encodeKey(mem.name) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ") + \",\"}"; + mem.serialize = "${changetype(this." + mem.name + ") == 0" + " ? \"\" : '" + escapeString(JSON.stringify(mem.name)) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ") + \",\"}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));"; } else if (mem.flag == PropertyFlags.OmitIf) { - mem.serialize = "${" + mem.args[0] + " ? \"\" : '" + encodeKey(mem.name) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ")}"; + mem.serialize = "${" + mem.args[0] + " ? \"\" : '" + escapeString(JSON.stringify(mem.name)) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ")}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));"; } else if (mem.flag == PropertyFlags.Alias) { - mem.serialize = encodeKey(mem.name) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; + mem.serialize = escapeString(JSON.stringify(mem.name)) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));"; mem.name = name.text; } - if (mem.value) + const t = mem.node.type.name.identifier.text; + if (this.schemasList.find(v => v.name == t)) { + mem.initialize = "this." + name.text + " = changetype>(__new(offsetof>(), idof>()));\n changetype>(this." + name.text + ").__INITIALIZE()"; + } + else if (mem.value) { mem.initialize = "this." + name.text + " = " + mem.value; + } schema.members.push(mem); } let SERIALIZE_RAW = "@inline __SERIALIZE(): string {\n let out = `{"; @@ -156,8 +161,9 @@ class JSONTransform extends BaseVisitor { len = _sorted[0]?.name.length; for (let i = 1; i < _sorted.length; i++) { const member = _sorted[i]; - if (len < member.name.length) { + if (member.alias?.length || member.name.length > len) { sortedMembers.push([member]); + len = member.alias?.length || member.name.length; offset++; } else { @@ -167,12 +173,8 @@ class JSONTransform extends BaseVisitor { let first = true; for (const memberSet of sortedMembers) { const firstMember = memberSet[0]; - if (firstMember.flag == PropertyFlags.Alias) { - let alias = firstMember.alias; - firstMember.alias = firstMember.name; - firstMember.name = alias; - } - if (firstMember.name.length === 1) { + const name = encodeKey(firstMember.alias || firstMember.name); + if (name.length === 1) { if (first) { DESERIALIZE += " if (1 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; first = false; @@ -181,7 +183,7 @@ class JSONTransform extends BaseVisitor { DESERIALIZE += "else if (1 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; } } - else if (firstMember.name.length === 2) { + else if (name.length === 2) { if (first) { DESERIALIZE += " if (2 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; first = false; @@ -190,7 +192,7 @@ class JSONTransform extends BaseVisitor { DESERIALIZE += "else if (2 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; } } - else if (firstMember.name.length === 4) { + else if (name.length === 4) { if (first) { DESERIALIZE += " if (4 === len) {\n const code = load(changetype(data) + (key_start << 1));\n"; first = false; @@ -201,75 +203,71 @@ class JSONTransform extends BaseVisitor { } else { if (first) { - DESERIALIZE += " if (" + firstMember.name.length + " === len) {\n"; + DESERIALIZE += " if (" + name.length + " === len) {\n"; first = false; } else { - DESERIALIZE += "else if (" + firstMember.name.length + " === len) {\n"; + DESERIALIZE += "else if (" + name.length + " === len) {\n"; } } - if (firstMember.flag == PropertyFlags.Alias) { - let name = firstMember.alias; - firstMember.alias = firstMember.name; - firstMember.name = name; - } + let f = true; for (let i = 0; i < memberSet.length; i++) { const member = memberSet[i]; - if (member.flag == PropertyFlags.Alias) { - let alias = member.alias; - member.alias = member.name; - member.name = alias; + const name = encodeKey(member.alias || member.name); + if (name.length === 1) { + DESERIALIZE += ` case ${name.charCodeAt(0)}: {\n ${member.deserialize}\n return true;\n }\n`; } - if (member.name.length === 1) { - DESERIALIZE += ` case ${member.name.charCodeAt(0)}: {\n ${member.deserialize}\n return true;\n }\n`; + else if (name.length === 2) { + DESERIALIZE += ` case ${charCodeAt32(name, 0)}: {\n ${member.deserialize}\n return true;\n }\n`; } - else if (member.name.length === 2) { - DESERIALIZE += ` case ${charCodeAt32(member.name, 0)}: {\n ${member.deserialize}\n return true;\n }\n`; - } - else if (member.name.length === 4) { - DESERIALIZE += ` if (${charCodeAt64(member.name, 0)} === code) {\n ${member.deserialize}\n return true;\n }\n`; + else if (name.length === 4) { + if (f) { + f = false; + DESERIALIZE += ` if (${charCodeAt64(name, 0)} === code) {\n ${member.deserialize}\n return true;\n }\n`; + } + else { + DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + `else if (${charCodeAt64(name, 0)} === code) {\n ${member.deserialize}\n return true;\n }\n`; + } } else { - DESERIALIZE += ` if (memory.compare(changetype("${escapeQuotes(member.name)}"), changetype(data) + (key_start << 1), ${member.name.length << 1})) {\n ${member.deserialize}\n return true;\n }\n`; - } - if (member.flag == PropertyFlags.Alias) { - let name = member.alias; - member.alias = member.name; - member.name = name; + if (f) { + f = false; + DESERIALIZE += ` if (0 == memory.compare(changetype("${escapeQuote(escapeSlash(name))}"), changetype(data) + (key_start << 1), ${name.length << 1})) {\n ${member.deserialize}\n return true;\n }\n`; + } + else { + DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + ` else if (0 == memory.compare(changetype("${escapeQuote(escapeSlash(name))}"), changetype(data) + (key_start << 1), ${name.length << 1})) {\n ${member.deserialize}\n return true;\n }\n`; + } } } - if (firstMember.flag == PropertyFlags.Alias) { - let alias = firstMember.alias; - firstMember.alias = firstMember.name; - firstMember.name = alias; - } - if (memberSet[0].name.length < 3) { + if (name.length < 3) { DESERIALIZE += ` default: {\n return false;\n }\n }\n`; } - else if (memberSet[0].name.length == 4) { + else if (name.length == 4) { DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + ` else {\n return false;\n }\n`; } else { DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + ` else {\n return false;\n }\n`; } DESERIALIZE += " } "; - if (firstMember.flag == PropertyFlags.Alias) { - let name = firstMember.alias; - firstMember.alias = firstMember.name; - firstMember.name = name; - } } DESERIALIZE += "\n return false;\n}"; //console.log(sortedMembers); - //console.log(SERIALIZE_RAW); - //console.log(SERIALIZE_PRETTY); - //console.log(INITIALIZE); - console.log(DESERIALIZE); + if (process.env["JSON_DEBUG"]) { + console.log(SERIALIZE_RAW); + //console.log(SERIALIZE_PRETTY); + console.log(INITIALIZE); + console.log(DESERIALIZE); + } const SERIALIZE_RAW_METHOD = SimpleParser.parseClassMember(SERIALIZE_RAW, node); //const SERIALIZE_PRETTY_METHOD = SimpleParser.parseClassMember(SERIALIZE_PRETTY, node); const INITIALIZE_METHOD = SimpleParser.parseClassMember(INITIALIZE, node); const DESERIALIZE_METHOD = SimpleParser.parseClassMember(DESERIALIZE, node); - node.members.push(SERIALIZE_RAW_METHOD, INITIALIZE_METHOD, DESERIALIZE_METHOD); + if (!node.members.find(v => v.name.text == "__SERIALIZE")) + node.members.push(SERIALIZE_RAW_METHOD); + if (!node.members.find(v => v.name.text == "__INITIALIZE")) + node.members.push(INITIALIZE_METHOD); + if (!node.members.find(v => v.name.text == "__DESERIALIZE")) + node.members.push(DESERIALIZE_METHOD); this.schemasList.push(schema); } visitSource(node) { @@ -278,25 +276,8 @@ class JSONTransform extends BaseVisitor { if (!this.sources.has(node)) { return; } - // Note, the following one liner would be easier, but it fails with an assertion error - // because as-virtual's SimpleParser doesn't set the parser.currentSource correctly. - // - // const stmt = SimpleParser.parseTopLevelStatement('import { Virtual as __Virtual } from "as-virtual/assembly";'); - // ... So we have to do it the long way: - const txt = 'import { Virtual as __Virtual } from "as-virtual/assembly";'; - const tokenizer = new Tokenizer(new Source(0 /* SourceKind.User */, node.normalizedPath, txt)); - const parser = new Parser(); - parser.currentSource = tokenizer.source; - const stmt = parser.parseTopLevelStatement(tokenizer); - // Add the import statement to the top of the source. - node.statements.unshift(stmt); } } -function encodeKey(aliasName) { - return JSON.stringify(aliasName) - .replace(/\\/g, "\\\\") - .replace(/\`/g, '\\`'); -} export default class Transformer extends Transform { // Trigger the transform after parse. afterParse(parser) { @@ -378,6 +359,18 @@ function charCodeAt64(data, offset) { const u64Value = (fourthCharCode << 48n) | (thirdCharCode << 32n) | (secondCharCode << 16n) | firstCharCode; return u64Value; } -function escapeQuotes(data) { - return data.replace(/"/g, '\\"'); +function encodeKey(key) { + const data = JSON.stringify(key); + return data.slice(1, data.length - 1); +} +function escapeString(data) { + return data.replace(/\\/g, "\\\\") + .replace(/\`/g, '\\`'); +} +function escapeSlash(data) { + return data.replace(/\\/g, "\\\\") + .replace(/\`/g, '\\`'); +} +function escapeQuote(data) { + return data.replace(/\"/g, "\\\""); } diff --git a/transform/package.json b/transform/package.json index d94497c..4966535 100644 --- a/transform/package.json +++ b/transform/package.json @@ -1,6 +1,6 @@ { "name": "@json-as/transform", - "version": "0.8.6", + "version": "0.8.7", "description": "JSON encoder/decoder for AssemblyScript", "main": "./lib/index.js", "author": "Jairus Tanaka", diff --git a/transform/src/index.ts b/transform/src/index.ts index 2f03828..e1a663c 100644 --- a/transform/src/index.ts +++ b/transform/src/index.ts @@ -2,6 +2,8 @@ import { ClassDeclaration, FieldDeclaration, IdentifierExpression, + NamedTypeNode, + StringLiteralExpression, Parser, Source, SourceKind, @@ -12,7 +14,6 @@ import { toString, isStdlib } from "visitor-as/dist/utils.js"; import { BaseVisitor, SimpleParser } from "visitor-as/dist/index.js"; import { Transform } from "assemblyscript/dist/transform.js"; import { CommonFlags } from "types:assemblyscript/src/common"; -import { StringLiteralExpression } from "types:assemblyscript/src/ast"; class JSONTransform extends BaseVisitor { public schemasList: SchemaData[] = []; @@ -95,23 +96,28 @@ class JSONTransform extends BaseVisitor { if (mem.flag === PropertyFlags.Alias) { mem.name = mem.alias!; } else if (mem.flag === PropertyFlags.None) { - mem.serialize = encodeKey(mem.name) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; + mem.serialize = escapeString(JSON.stringify(mem.name)) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));" } if (mem.flag == PropertyFlags.OmitNull) { - mem.serialize = "${changetype(this." + mem.name + ") == 0" + " ? \"\" : '" + encodeKey(mem.name) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ") + \",\"}"; + mem.serialize = "${changetype(this." + mem.name + ") == 0" + " ? \"\" : '" + escapeString(JSON.stringify(mem.name)) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ") + \",\"}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));" } else if (mem.flag == PropertyFlags.OmitIf) { - mem.serialize = "${" + mem.args![0]! + " ? \"\" : '" + encodeKey(mem.name) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ")}"; + mem.serialize = "${" + mem.args![0]! + " ? \"\" : '" + escapeString(JSON.stringify(mem.name)) + ":' + __SERIALIZE<" + type + ">(this." + name.text + ")}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));" } else if (mem.flag == PropertyFlags.Alias) { - mem.serialize = encodeKey(mem.name) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; + mem.serialize = escapeString(JSON.stringify(mem.name)) + ":${__SERIALIZE<" + type + ">(this." + name.text + ")}"; mem.deserialize = "this." + name.text + " = " + "__DESERIALIZE<" + type + ">(data.substring(value_start, value_end));" mem.name = name.text; } - if (mem.value) mem.initialize = "this." + name.text + " = " + mem.value; + const t = (mem.node.type as NamedTypeNode).name.identifier.text; + if (this.schemasList.find(v => v.name == t)) { + mem.initialize = "this." + name.text + " = changetype>(__new(offsetof>(), idof>()));\n changetype>(this." + name.text + ").__INITIALIZE()" + } else if (mem.value) { + mem.initialize = "this." + name.text + " = " + mem.value; + } schema.members.push(mem); } @@ -176,38 +182,34 @@ class JSONTransform extends BaseVisitor { len = _sorted[0]?.name.length!; for (let i = 1; i < _sorted.length; i++) { const member = _sorted[i]!; - if (len < member.name.length) { + if (member.alias?.length || member.name.length > len) { sortedMembers.push([member]); + len = member.alias?.length || member.name.length offset++; } else { - sortedMembers[offset]!.push(member) + sortedMembers[offset]!.push(member); } } let first = true; - for (const memberSet of sortedMembers) { const firstMember = memberSet[0]!; - if (firstMember.flag == PropertyFlags.Alias) { - let alias = firstMember.alias! - firstMember.alias = firstMember.name; - firstMember.name = alias; - } - if (firstMember.name.length === 1) { + const name = encodeKey(firstMember.alias || firstMember.name); + if (name.length === 1) { if (first) { DESERIALIZE += " if (1 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; first = false; } else { DESERIALIZE += "else if (1 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; } - } else if (firstMember.name.length === 2) { + } else if (name.length === 2) { if (first) { DESERIALIZE += " if (2 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; first = false; } else { DESERIALIZE += "else if (2 === len) {\n switch (load(changetype(data) + (key_start << 1))) {\n"; } - } else if (firstMember.name.length === 4) { + } else if (name.length === 4) { if (first) { DESERIALIZE += " if (4 === len) {\n const code = load(changetype(data) + (key_start << 1));\n"; first = false; @@ -216,78 +218,65 @@ class JSONTransform extends BaseVisitor { } } else { if (first) { - DESERIALIZE += " if (" + firstMember.name.length + " === len) {\n"; + DESERIALIZE += " if (" + name.length + " === len) {\n"; first = false; } else { - DESERIALIZE += "else if (" + firstMember.name.length + " === len) {\n"; + DESERIALIZE += "else if (" + name.length + " === len) {\n"; } } - if (firstMember.flag == PropertyFlags.Alias) { - let name = firstMember.alias! - firstMember.alias = firstMember.name; - firstMember.name = name; - } + let f = true; for (let i = 0; i < memberSet.length; i++) { const member = memberSet[i]!; - if (member.flag == PropertyFlags.Alias) { - let alias = member.alias! - member.alias = member.name; - member.name = alias; - } - if (member.name.length === 1) { - DESERIALIZE += ` case ${member.name.charCodeAt(0)}: {\n ${member.deserialize}\n return true;\n }\n`; - } else if (member.name.length === 2) { - DESERIALIZE += ` case ${charCodeAt32(member.name, 0)}: {\n ${member.deserialize}\n return true;\n }\n`; - } else if (member.name.length === 4) { - DESERIALIZE += ` if (${charCodeAt64(member.name, 0)} === code) {\n ${member.deserialize}\n return true;\n }\n`; + const name = encodeKey(member.alias || member.name); + if (name.length === 1) { + DESERIALIZE += ` case ${name.charCodeAt(0)}: {\n ${member.deserialize}\n return true;\n }\n`; + } else if (name.length === 2) { + DESERIALIZE += ` case ${charCodeAt32(name, 0)}: {\n ${member.deserialize}\n return true;\n }\n`; + } else if (name.length === 4) { + if (f) { + f = false; + DESERIALIZE += ` if (${charCodeAt64(name, 0)} === code) {\n ${member.deserialize}\n return true;\n }\n`; + } else { + DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + `else if (${charCodeAt64(name, 0)} === code) {\n ${member.deserialize}\n return true;\n }\n`; + } } else { - DESERIALIZE += ` if (memory.compare(changetype("${escapeQuotes(member.name)}"), changetype(data) + (key_start << 1), ${member.name.length << 1})) {\n ${member.deserialize}\n return true;\n }\n` - } - if (member.flag == PropertyFlags.Alias) { - let name = member.alias! - member.alias = member.name; - member.name = name; + if (f) { + f = false; + DESERIALIZE += ` if (0 == memory.compare(changetype("${escapeQuote(escapeSlash(name))}"), changetype(data) + (key_start << 1), ${name.length << 1})) {\n ${member.deserialize}\n return true;\n }\n` + } else { + DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + ` else if (0 == memory.compare(changetype("${escapeQuote(escapeSlash(name))}"), changetype(data) + (key_start << 1), ${name.length << 1})) {\n ${member.deserialize}\n return true;\n }\n` + } } } - if (firstMember.flag == PropertyFlags.Alias) { - let alias = firstMember.alias! - firstMember.alias = firstMember.name; - firstMember.name = alias; - } - if (memberSet[0]!.name.length < 3) { + if (name.length < 3) { DESERIALIZE += ` default: {\n return false;\n }\n }\n` - } else if (memberSet[0]!.name.length == 4) { + } else if (name.length == 4) { DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + ` else {\n return false;\n }\n` } else { DESERIALIZE = DESERIALIZE.slice(0, DESERIALIZE.length - 1) + ` else {\n return false;\n }\n` } DESERIALIZE += " } "; - if (firstMember.flag == PropertyFlags.Alias) { - let name = firstMember.alias! - firstMember.alias = firstMember.name; - firstMember.name = name; - } } DESERIALIZE += "\n return false;\n}" //console.log(sortedMembers); - //console.log(SERIALIZE_RAW); - //console.log(SERIALIZE_PRETTY); - //console.log(INITIALIZE); - //console.log(DESERIALIZE) + if (process.env["JSON_DEBUG"]) { + console.log(SERIALIZE_RAW); + //console.log(SERIALIZE_PRETTY); + console.log(INITIALIZE); + console.log(DESERIALIZE); + } const SERIALIZE_RAW_METHOD = SimpleParser.parseClassMember(SERIALIZE_RAW, node); //const SERIALIZE_PRETTY_METHOD = SimpleParser.parseClassMember(SERIALIZE_PRETTY, node); const INITIALIZE_METHOD = SimpleParser.parseClassMember(INITIALIZE, node); const DESERIALIZE_METHOD = SimpleParser.parseClassMember(DESERIALIZE, node); - node.members.push( - SERIALIZE_RAW_METHOD, - INITIALIZE_METHOD, - DESERIALIZE_METHOD - ); + if (!node.members.find(v => v.name.text == "__SERIALIZE")) node.members.push(SERIALIZE_RAW_METHOD); + if (!node.members.find(v => v.name.text == "__INITIALIZE")) node.members.push(INITIALIZE_METHOD); + if (!node.members.find(v => v.name.text == "__DESERIALIZE")) node.members.push(DESERIALIZE_METHOD); this.schemasList.push(schema); } @@ -298,30 +287,9 @@ class JSONTransform extends BaseVisitor { if (!this.sources.has(node)) { return; } - - // Note, the following one liner would be easier, but it fails with an assertion error - // because as-virtual's SimpleParser doesn't set the parser.currentSource correctly. - // - // const stmt = SimpleParser.parseTopLevelStatement('import { Virtual as __Virtual } from "as-virtual/assembly";'); - - // ... So we have to do it the long way: - const txt = 'import { Virtual as __Virtual } from "as-virtual/assembly";' - const tokenizer = new Tokenizer(new Source(SourceKind.User, node.normalizedPath, txt)); - const parser = new Parser(); - parser.currentSource = tokenizer.source; - const stmt = parser.parseTopLevelStatement(tokenizer)!; - - // Add the import statement to the top of the source. - node.statements.unshift(stmt); } } -function encodeKey(aliasName: string): string { - return JSON.stringify(aliasName) - .replace(/\\/g, "\\\\") - .replace(/\`/g, '\\`'); -} - export default class Transformer extends Transform { // Trigger the transform after parse. afterParse(parser: Parser): void { @@ -410,6 +378,21 @@ function charCodeAt64(data: string, offset: number): bigint { return u64Value; } -function escapeQuotes(data: string): string { - return data.replace(/"/g, '\\"'); +function encodeKey(key: string): string { + const data = JSON.stringify(key); + return data.slice(1, data.length - 1); +} + +function escapeString(data: string): string { + return data.replace(/\\/g, "\\\\") + .replace(/\`/g, '\\`'); +} + +function escapeSlash(data: string): string { + return data.replace(/\\/g, "\\\\") + .replace(/\`/g, '\\`'); +} + +function escapeQuote(data: string): string { + return data.replace(/\"/g, "\\\""); } \ No newline at end of file