From 73f989e20caf47f5d51a64dc4f41444a1cac5b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 14:25:08 +0200 Subject: [PATCH 1/9] Re-write type expression parser. --- package.json | 3 +- src/abi/typeFormula.ts | 18 +++ src/abi/typeFormulaParser.spec.ts | 25 +++ src/abi/typeFormulaParser.ts | 109 +++++++++++++ .../typesystem/typeExpressionParser.spec.ts | 146 ++++++++++++------ .../typesystem/typeExpressionParser.ts | 98 ++---------- 6 files changed, 273 insertions(+), 126 deletions(-) create mode 100644 src/abi/typeFormula.ts create mode 100644 src/abi/typeFormulaParser.spec.ts create mode 100644 src/abi/typeFormulaParser.ts diff --git a/package.json b/package.json index e4cc2286..a8e6852b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.0.0-beta.4", + "version": "13.0.0-beta.5", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", @@ -31,7 +31,6 @@ "bech32": "1.1.4", "blake2b": "2.1.3", "buffer": "6.0.3", - "json-duplicate-key-handle": "1.0.0", "keccak": "3.0.2", "protobufjs": "7.2.4" }, diff --git a/src/abi/typeFormula.ts b/src/abi/typeFormula.ts new file mode 100644 index 00000000..ca24926b --- /dev/null +++ b/src/abi/typeFormula.ts @@ -0,0 +1,18 @@ +export class TypeFormula { + name: string; + typeParameters: TypeFormula[]; + + constructor(name: string, typeParameters: TypeFormula[]) { + this.name = name; + this.typeParameters = typeParameters; + } + + toString(): string { + if (this.typeParameters.length > 0) { + const typeParameters = this.typeParameters.map((typeParameter) => typeParameter.toString()).join(", "); + return `${this.name}<${typeParameters}>`; + } else { + return this.name; + } + } +} diff --git a/src/abi/typeFormulaParser.spec.ts b/src/abi/typeFormulaParser.spec.ts new file mode 100644 index 00000000..7271b86b --- /dev/null +++ b/src/abi/typeFormulaParser.spec.ts @@ -0,0 +1,25 @@ +import { assert } from "chai"; +import { TypeFormulaParser } from "./typeFormulaParser"; + +describe("test type formula parser", () => { + it("should parse expression", async () => { + const parser = new TypeFormulaParser(); + + const testVectors = [ + ["i64", "i64"], + [" i64 ", "i64"], + ["utf-8 string", "utf-8 string"], + ["MultiResultVec>", "MultiResultVec>"], + ["tuple3>", "tuple3>"], + ["tuple2", "tuple2"], + ["tuple2 ", "tuple2"], + ["tuple, List>", "tuple, List>"], + ]; + + for (const [inputExpression, expectedExpression] of testVectors) { + const typeFormula = parser.parseExpression(inputExpression); + const outputExpression = typeFormula.toString(); + assert.equal(outputExpression, expectedExpression); + } + }); +}); diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts new file mode 100644 index 00000000..0399ab5f --- /dev/null +++ b/src/abi/typeFormulaParser.ts @@ -0,0 +1,109 @@ +import { TypeFormula } from "./typeFormula"; + +export class TypeFormulaParser { + static BEGIN_TYPE_PARAMETERS = "<"; + static END_TYPE_PARAMETERS = ">"; + static COMMA = ","; + static PUNCTUATION = [ + TypeFormulaParser.COMMA, + TypeFormulaParser.BEGIN_TYPE_PARAMETERS, + TypeFormulaParser.END_TYPE_PARAMETERS, + ]; + + parseExpression(expression: string): TypeFormula { + expression = expression.trim(); + + const tokens = this.tokenizeExpression(expression).filter((token) => token !== TypeFormulaParser.COMMA); + + const stack: any[] = []; + + for (const token of tokens) { + if (TypeFormulaParser.PUNCTUATION.includes(token)) { + if (token === TypeFormulaParser.END_TYPE_PARAMETERS) { + const type_parameters: TypeFormula[] = []; + + while (true) { + if (stack.length === 0) { + throw new Error("Badly specified type parameters."); + } + + if (stack[stack.length - 1] === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { + break; + } + + let item = stack.pop(); + let type_formula: TypeFormula; + + if (item instanceof TypeFormula) { + type_formula = item; + } else { + type_formula = new TypeFormula(item, []); + } + + type_parameters.push(type_formula); + } + + stack.pop(); // pop "<" symbol + const type_name = stack.pop(); + const type_formula = new TypeFormula(type_name, type_parameters.reverse()); + stack.push(type_formula); + } else if (token === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { + // The symbol is pushed as a simple string, + // as it will never be interpreted, anyway. + stack.push(token); + } else { + throw new Error(`Unexpected token (punctuation): ${token}`); + } + } else { + // It's a type name. We push it as a simple string. + stack.push(token); + } + } + + if (stack.length !== 1) { + throw new Error(`Unexpected stack length at end of parsing: ${stack.length}`); + } + if (TypeFormulaParser.PUNCTUATION.includes(stack[0])) { + throw new Error("Unexpected root element."); + } + + const item = stack[0]; + + if (typeof item === "string") { + // Expression contained a simple, non-generic type + return new TypeFormula(item, []); + } else if (item instanceof TypeFormula) { + return item; + } else { + throw new Error(`Unexpected item on stack: ${item}`); + } + } + + private tokenizeExpression(expression: string): string[] { + const tokens: string[] = []; + let currentToken = ""; + + for (let i = 0; i < expression.length; i++) { + const character = expression[i]; + + if (!TypeFormulaParser.PUNCTUATION.includes(character)) { + // Non-punctuation character + currentToken += character; + } else { + if (currentToken) { + tokens.push(currentToken.trim()); + currentToken = ""; + } + + // Punctuation character + tokens.push(character); + } + } + + if (currentToken) { + tokens.push(currentToken.trim()); + } + + return tokens; + } +} diff --git a/src/smartcontracts/typesystem/typeExpressionParser.spec.ts b/src/smartcontracts/typesystem/typeExpressionParser.spec.ts index 0465a1b5..0aa09ae2 100644 --- a/src/smartcontracts/typesystem/typeExpressionParser.spec.ts +++ b/src/smartcontracts/typesystem/typeExpressionParser.spec.ts @@ -1,7 +1,7 @@ -import * as errors from "../../errors"; import { assert } from "chai"; -import { Type } from "./types"; +import { ErrTypingSystem } from "../../errors"; import { TypeExpressionParser } from "./typeExpressionParser"; +import { Type } from "./types"; describe("test parser", () => { let parser = new TypeExpressionParser(); @@ -102,54 +102,53 @@ describe("test parser", () => { ], }); - type = parser.parse("MultiArg, List>"); assert.deepEqual(type.toJSON(), { - "name": "MultiArg", - "typeParameters": [ + name: "MultiArg", + typeParameters: [ { - "name": "Option", - "typeParameters": [ + name: "Option", + typeParameters: [ { - "name": "u8", - "typeParameters": [] - } - ] + name: "u8", + typeParameters: [], + }, + ], }, { - "name": "List", - "typeParameters": [ + name: "List", + typeParameters: [ { - "name": "u16", - "typeParameters": [] - } - ] - } - ] + name: "u16", + typeParameters: [], + }, + ], + }, + ], }); type = parser.parse("variadic>"); assert.deepEqual(type.toJSON(), { - "name": "variadic", - "typeParameters": [ + name: "variadic", + typeParameters: [ { - "name": "multi", - "typeParameters": [ + name: "multi", + typeParameters: [ { - "name": "array32", - "typeParameters": [] + name: "array32", + typeParameters: [], }, { - "name": "u32", - "typeParameters": [] + name: "u32", + typeParameters: [], }, { - "name": "array64", - "typeParameters": [] - } - ] - } - ] + name: "array64", + typeParameters: [], + }, + ], + }, + ], }); }); @@ -195,8 +194,6 @@ describe("test parser", () => { ], }); - // TODO: In a future PR, replace the JSON-based parsing logic with a better one and enable this test. - // This test currently fails because JSON key de-duplication takes place: i32 is incorrectly de-duplicated by the parser. type = parser.parse("tuple2"); assert.deepEqual(type.toJSON(), { name: "tuple2", @@ -222,7 +219,7 @@ describe("test parser", () => { { name: "u64", typeParameters: [], - } + }, ], }, { @@ -231,13 +228,78 @@ describe("test parser", () => { { name: "u64", typeParameters: [], - } + }, + ], + }, + ], + }); + }); + + it("should parse ", () => { + let type: Type; + type = parser.parse("variadic>"); + assert.deepEqual(type.toJSON(), { + name: "variadic", + typeParameters: [ + { + name: "multi", + typeParameters: [ + { + name: "BigUint", + typeParameters: [], + }, + { + name: "BigUint", + typeParameters: [], + }, + { + name: "u64", + typeParameters: [], + }, + { + name: "BigUint", + typeParameters: [], + }, ], }, ], }); }); + it("should parse multi", () => { + const type = parser.parse("multi"); + + assert.deepEqual(type.toJSON(), { + name: "multi", + typeParameters: [ + { + name: "u8", + typeParameters: [], + }, + { + name: "utf-8 string", + typeParameters: [], + }, + { + name: "u8", + typeParameters: [], + }, + { + name: "utf-8 string", + typeParameters: [], + }, + { + name: "u8", + typeParameters: [], + }, + { + name: "utf-8 string", + typeParameters: [], + }, + ], + }); + }); + it("should handle utf-8 string types which contain spaces", () => { let type: Type; @@ -264,14 +326,12 @@ describe("test parser", () => { }, ], }); - }); it("should not parse expression", () => { - assert.throw(() => parser.parse("<>"), errors.ErrTypingSystem); - assert.throw(() => parser.parse("<"), errors.ErrTypingSystem); - // TODO: In a future PR replace Json Parsing logic with a better one and enable this test - //assert.throw(() => parser.parse("MultiResultVec"), errors.ErrTypingSystem); - assert.throw(() => parser.parse("a, b"), errors.ErrTypingSystem); + assert.throw(() => parser.parse("<>"), ErrTypingSystem); + assert.throw(() => parser.parse("<"), ErrTypingSystem); + assert.throw(() => parser.parse("MultiResultVec"), ErrTypingSystem); + assert.throw(() => parser.parse("a, b"), ErrTypingSystem); }); }); diff --git a/src/smartcontracts/typesystem/typeExpressionParser.ts b/src/smartcontracts/typesystem/typeExpressionParser.ts index 564bf8be..62d2216c 100644 --- a/src/smartcontracts/typesystem/typeExpressionParser.ts +++ b/src/smartcontracts/typesystem/typeExpressionParser.ts @@ -1,95 +1,31 @@ -import * as errors from "../../errors"; +import { TypeFormula } from "../../abi/typeFormula"; +import { TypeFormulaParser } from "../../abi/typeFormulaParser"; +import { ErrTypingSystem } from "../../errors"; import { Type } from "./types"; -const jsonHandler = require("json-duplicate-key-handle"); export class TypeExpressionParser { - parse(expression: string): Type { - let root = this.doParse(expression); - let rootKeys = Object.keys(root); - - if (rootKeys.length != 1) { - throw new errors.ErrTypingSystem(`bad type expression: ${expression}`); - } + private readonly backingTypeFormulaParser: TypeFormulaParser; - let name = rootKeys[0]; - let type = this.nodeToType(name, root[name]); - return type; + constructor() { + this.backingTypeFormulaParser = new TypeFormulaParser(); } - private doParse(expression: string): any { - let jsoned = this.getJsonedString(expression); - + parse(expression: string): Type { try { - return jsonHandler.parse(jsoned); - } catch (error) { - throw new errors.ErrTypingSystem(`cannot parse type expression: ${expression}. internal json: ${jsoned}.`); + return this.doParse(expression); + } catch (e) { + throw new ErrTypingSystem(`Failed to parse type expression: ${expression}. Error: ${e}`); } } - /** - * Converts a raw type expression to a JSON, parsing-friendly format. - * This is a workaround, so that the parser implementation is simpler (thus we actually rely on the JSON parser). - * - * @param expression a string such as: - * - * ``` - * - Option> - * - VarArgs> - * - MultiResultVec - * ``` - */ - private getJsonedString(expression: string) { - let jsoned = ""; - - for (let i = 0; i < expression.length; i++) { - let char = expression.charAt(i); - let previousChar = expression.charAt(i - 1); - let nextChar = expression.charAt(i + 1); - - if (char == "<") { - jsoned += ": {"; - } else if (char == ">") { - if (previousChar != ">") { - jsoned += ": {} }"; - } else { - jsoned += "}"; - } - } else if (char == ",") { - if (nextChar == ">") { - // Skip superfluous comma - } else if (previousChar == ">") { - jsoned += ","; - } else { - jsoned += ": {},"; - } - } else { - jsoned += char; - } - } - - // Split by the delimiters, but exclude the spaces that are found in the middle of "utf-8 string" - let symbolsRegex = /(:|\{|\}|,|\s)/; - let tokens = jsoned - // Hack for Safari compatibility, where we can't use negative lookbehind - .replace(/utf\-8\sstring/ig, "utf-8-string") - .split(symbolsRegex) - .filter((token) => token); - - jsoned = tokens.map((token) => (symbolsRegex.test(token) ? token : `"${token}"`)) - .map((token) => token.replace(/utf\-8\-string/ig, "utf-8 string")) - .join(""); - - if (tokens.length == 1) { - // Workaround for simple, non-generic types. - return `{${jsoned}: {}}`; - } - - return `{${jsoned}}`; + private doParse(expression: string): Type { + const typeFormula = this.backingTypeFormulaParser.parseExpression(expression); + const type = this.typeFormulaToType(typeFormula); + return type; } - private nodeToType(name: string, node: any): Type { - if (name.charAt(name.length - 1) === "1") { name = name.slice(0, -1); } - let typeParameters = Object.keys(node).map((key) => this.nodeToType(key, node[key])); - return new Type(name, typeParameters); + private typeFormulaToType(typeFormula: TypeFormula): Type { + const typeParameters = typeFormula.typeParameters.map((typeFormula) => this.typeFormulaToType(typeFormula)); + return new Type(typeFormula.name, typeParameters); } } From 7cabc3a2baad318509b59eeafbfa451d72f4bd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 14:25:29 +0200 Subject: [PATCH 2/9] Adjust package lock. --- package-lock.json | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37071db9..f4fa1062 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.0.0-beta.4", + "version": "13.0.0-beta.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.0.0-beta.4", + "version": "13.0.0-beta.5", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", @@ -14,7 +14,6 @@ "blake2b": "2.1.3", "buffer": "6.0.3", "json-bigint": "1.0.0", - "json-duplicate-key-handle": "1.0.0", "keccak": "3.0.2", "protobufjs": "7.2.4" }, @@ -1489,11 +1488,6 @@ "babylon": "bin/babylon.js" } }, - "node_modules/backslash": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/backslash/-/backslash-0.2.0.tgz", - "integrity": "sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3700,14 +3694,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-duplicate-key-handle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-duplicate-key-handle/-/json-duplicate-key-handle-1.0.0.tgz", - "integrity": "sha512-OLIxL+UpfwUsqcLX3i6Z51ChTou/Vje+6bSeGUSubj96dF/SfjObDprLy++ZXYH07KITuEzsXS7PX7e/BGf4jw==", - "dependencies": { - "backslash": "^0.2.0" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -6765,11 +6751,6 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, - "backslash": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/backslash/-/backslash-0.2.0.tgz", - "integrity": "sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A==" - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8488,14 +8469,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "json-duplicate-key-handle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-duplicate-key-handle/-/json-duplicate-key-handle-1.0.0.tgz", - "integrity": "sha512-OLIxL+UpfwUsqcLX3i6Z51ChTou/Vje+6bSeGUSubj96dF/SfjObDprLy++ZXYH07KITuEzsXS7PX7e/BGf4jw==", - "requires": { - "backslash": "^0.2.0" - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", From df7c920aa3b73ac581c16806f4c0712a4f509b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 14:34:14 +0200 Subject: [PATCH 3/9] Fix after self-review. --- src/abi/typeFormulaParser.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index 0399ab5f..4dbb1bcf 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -83,9 +83,7 @@ export class TypeFormulaParser { const tokens: string[] = []; let currentToken = ""; - for (let i = 0; i < expression.length; i++) { - const character = expression[i]; - + for (const character of expression) { if (!TypeFormulaParser.PUNCTUATION.includes(character)) { // Non-punctuation character currentToken += character; From a5275439a9c2a35e822eb734c261004d58b7606b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 14:39:47 +0200 Subject: [PATCH 4/9] Add extra comments. --- src/abi/typeFormulaParser.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index 4dbb1bcf..caeb7fd2 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -22,16 +22,18 @@ export class TypeFormulaParser { if (token === TypeFormulaParser.END_TYPE_PARAMETERS) { const type_parameters: TypeFormula[] = []; + // Parse type parameters while (true) { if (stack.length === 0) { throw new Error("Badly specified type parameters."); } + // If top of stack is "<", we're done with type parameters. if (stack[stack.length - 1] === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { break; } - let item = stack.pop(); + const item = stack.pop(); let type_formula: TypeFormula; if (item instanceof TypeFormula) { @@ -89,7 +91,9 @@ export class TypeFormulaParser { currentToken += character; } else { if (currentToken) { + // Retain current token tokens.push(currentToken.trim()); + // Reset current token currentToken = ""; } @@ -99,6 +103,7 @@ export class TypeFormulaParser { } if (currentToken) { + // Retain the last token (if any) tokens.push(currentToken.trim()); } From 32c3d599f2843722c6a5242c920d079ee786c10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 18:21:46 +0200 Subject: [PATCH 5/9] Refactor (extract method). --- src/abi/typeFormulaParser.ts | 53 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index caeb7fd2..19b6290c 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -20,30 +20,7 @@ export class TypeFormulaParser { for (const token of tokens) { if (TypeFormulaParser.PUNCTUATION.includes(token)) { if (token === TypeFormulaParser.END_TYPE_PARAMETERS) { - const type_parameters: TypeFormula[] = []; - - // Parse type parameters - while (true) { - if (stack.length === 0) { - throw new Error("Badly specified type parameters."); - } - - // If top of stack is "<", we're done with type parameters. - if (stack[stack.length - 1] === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { - break; - } - - const item = stack.pop(); - let type_formula: TypeFormula; - - if (item instanceof TypeFormula) { - type_formula = item; - } else { - type_formula = new TypeFormula(item, []); - } - - type_parameters.push(type_formula); - } + const type_parameters = this.acquireTypeParameters(stack); stack.pop(); // pop "<" symbol const type_name = stack.pop(); @@ -109,4 +86,32 @@ export class TypeFormulaParser { return tokens; } + + private acquireTypeParameters(stack: any[]): TypeFormula[] { + const type_parameters: TypeFormula[] = []; + + while (true) { + if (stack.length === 0) { + throw new Error("Badly specified type parameters."); + } + + // If top of stack is "<", we're done with type parameters. + if (stack[stack.length - 1] === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { + break; + } + + const item = stack.pop(); + let type_formula: TypeFormula; + + if (item instanceof TypeFormula) { + type_formula = item; + } else { + type_formula = new TypeFormula(item, []); + } + + type_parameters.push(type_formula); + } + + return type_parameters; + } } From 372135a3d0797ca273242ba68153eed06b7e8d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 18:37:37 +0200 Subject: [PATCH 6/9] Refactoring. --- src/abi/typeFormulaParser.ts | 48 +++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index 19b6290c..96938c57 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -14,19 +14,14 @@ export class TypeFormulaParser { expression = expression.trim(); const tokens = this.tokenizeExpression(expression).filter((token) => token !== TypeFormulaParser.COMMA); - const stack: any[] = []; for (const token of tokens) { - if (TypeFormulaParser.PUNCTUATION.includes(token)) { - if (token === TypeFormulaParser.END_TYPE_PARAMETERS) { - const type_parameters = this.acquireTypeParameters(stack); - - stack.pop(); // pop "<" symbol - const type_name = stack.pop(); - const type_formula = new TypeFormula(type_name, type_parameters.reverse()); + if (this.isPunctuation(token)) { + if (this.isEndOfTypeParameters(token)) { + const type_formula = this.acquireTypeWithParameters(stack); stack.push(type_formula); - } else if (token === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { + } else if (this.isBeginningOfTypeParameters(token)) { // The symbol is pushed as a simple string, // as it will never be interpreted, anyway. stack.push(token); @@ -63,7 +58,7 @@ export class TypeFormulaParser { let currentToken = ""; for (const character of expression) { - if (!TypeFormulaParser.PUNCTUATION.includes(character)) { + if (!this.isPunctuation(character)) { // Non-punctuation character currentToken += character; } else { @@ -87,6 +82,13 @@ export class TypeFormulaParser { return tokens; } + private acquireTypeWithParameters(stack: any[]): TypeFormula { + const type_parameters = this.acquireTypeParameters(stack); + const type_name = stack.pop(); + const type_formula = new TypeFormula(type_name, type_parameters.reverse()); + return type_formula; + } + private acquireTypeParameters(stack: any[]): TypeFormula[] { const type_parameters: TypeFormula[] = []; @@ -95,23 +97,35 @@ export class TypeFormulaParser { throw new Error("Badly specified type parameters."); } - // If top of stack is "<", we're done with type parameters. - if (stack[stack.length - 1] === TypeFormulaParser.BEGIN_TYPE_PARAMETERS) { + const topOfStack = stack[stack.length - 1]; + if (this.isBeginningOfTypeParameters(topOfStack)) { + stack.pop(); break; } const item = stack.pop(); - let type_formula: TypeFormula; if (item instanceof TypeFormula) { - type_formula = item; + type_parameters.push(item); + } else if (typeof item === "string") { + type_parameters.push(new TypeFormula(item, [])); } else { - type_formula = new TypeFormula(item, []); + throw new Error(`Unexpected type parameter object in stack: ${item}`); } - - type_parameters.push(type_formula); } return type_parameters; } + + private isPunctuation(token: string): boolean { + return TypeFormulaParser.PUNCTUATION.includes(token); + } + + private isEndOfTypeParameters(token: string): boolean { + return token === TypeFormulaParser.END_TYPE_PARAMETERS; + } + + private isBeginningOfTypeParameters(token: string): boolean { + return token === TypeFormulaParser.BEGIN_TYPE_PARAMETERS; + } } From adf72efacbfd8b2f81d6b2535491ffab656e8f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 18:44:43 +0200 Subject: [PATCH 7/9] Refactoring. --- src/abi/typeFormulaParser.ts | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index 96938c57..560743bb 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -19,8 +19,8 @@ export class TypeFormulaParser { for (const token of tokens) { if (this.isPunctuation(token)) { if (this.isEndOfTypeParameters(token)) { - const type_formula = this.acquireTypeWithParameters(stack); - stack.push(type_formula); + const typeFormula = this.acquireTypeWithParameters(stack); + stack.push(typeFormula); } else if (this.isBeginningOfTypeParameters(token)) { // The symbol is pushed as a simple string, // as it will never be interpreted, anyway. @@ -83,38 +83,39 @@ export class TypeFormulaParser { } private acquireTypeWithParameters(stack: any[]): TypeFormula { - const type_parameters = this.acquireTypeParameters(stack); - const type_name = stack.pop(); - const type_formula = new TypeFormula(type_name, type_parameters.reverse()); - return type_formula; + const typeParameters = this.acquireTypeParameters(stack); + const typeName = stack.pop(); + const typeFormula = new TypeFormula(typeName, typeParameters.reverse()); + return typeFormula; } private acquireTypeParameters(stack: any[]): TypeFormula[] { - const type_parameters: TypeFormula[] = []; + const typeParameters: TypeFormula[] = []; while (true) { - if (stack.length === 0) { - throw new Error("Badly specified type parameters."); + const item = stack.pop(); + + if (item === undefined) { + throw new Error("Badly specified type parameters"); } - const topOfStack = stack[stack.length - 1]; - if (this.isBeginningOfTypeParameters(topOfStack)) { - stack.pop(); + if (this.isBeginningOfTypeParameters(item)) { + // We've acquired all type parameters break; } - const item = stack.pop(); - if (item instanceof TypeFormula) { - type_parameters.push(item); + // Type parameter is a previously-acquired type + typeParameters.push(item); } else if (typeof item === "string") { - type_parameters.push(new TypeFormula(item, [])); + // Type parameter is a simple, non-generic type + typeParameters.push(new TypeFormula(item, [])); } else { throw new Error(`Unexpected type parameter object in stack: ${item}`); } } - return type_parameters; + return typeParameters; } private isPunctuation(token: string): boolean { From 531281f2a535c79474882b418cda4377e4644a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 12 Mar 2024 19:13:34 +0200 Subject: [PATCH 8/9] Refactoring. --- src/abi/typeFormulaParser.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index 560743bb..8d720f05 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -22,8 +22,7 @@ export class TypeFormulaParser { const typeFormula = this.acquireTypeWithParameters(stack); stack.push(typeFormula); } else if (this.isBeginningOfTypeParameters(token)) { - // The symbol is pushed as a simple string, - // as it will never be interpreted, anyway. + // This symbol is pushed as a simple string. stack.push(token); } else { throw new Error(`Unexpected token (punctuation): ${token}`); @@ -43,11 +42,11 @@ export class TypeFormulaParser { const item = stack[0]; - if (typeof item === "string") { - // Expression contained a simple, non-generic type - return new TypeFormula(item, []); - } else if (item instanceof TypeFormula) { + if (item instanceof TypeFormula) { return item; + } else if (typeof item === "string") { + // Expression contained a simple, non-generic type. + return new TypeFormula(item, []); } else { throw new Error(`Unexpected item on stack: ${item}`); } @@ -75,7 +74,7 @@ export class TypeFormulaParser { } if (currentToken) { - // Retain the last token (if any) + // Retain the last token (if any). tokens.push(currentToken.trim()); } @@ -100,15 +99,15 @@ export class TypeFormulaParser { } if (this.isBeginningOfTypeParameters(item)) { - // We've acquired all type parameters + // We've acquired all type parameters. break; } if (item instanceof TypeFormula) { - // Type parameter is a previously-acquired type + // Type parameter is a previously-acquired type. typeParameters.push(item); } else if (typeof item === "string") { - // Type parameter is a simple, non-generic type + // Type parameter is a simple, non-generic type. typeParameters.push(new TypeFormula(item, [])); } else { throw new Error(`Unexpected type parameter object in stack: ${item}`); From 4039cb44528e184b2621f30bf196f32569072f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 13 Mar 2024 12:37:08 +0200 Subject: [PATCH 9/9] Minor refactoring. --- src/abi/typeFormulaParser.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index 8d720f05..02e7b961 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -57,10 +57,7 @@ export class TypeFormulaParser { let currentToken = ""; for (const character of expression) { - if (!this.isPunctuation(character)) { - // Non-punctuation character - currentToken += character; - } else { + if (this.isPunctuation(character)) { if (currentToken) { // Retain current token tokens.push(currentToken.trim()); @@ -70,6 +67,8 @@ export class TypeFormulaParser { // Punctuation character tokens.push(character); + } else { + currentToken += character; } }