Skip to content

Commit

Permalink
Merge pull request #398 from multiversx/better-type-expression-parser
Browse files Browse the repository at this point in the history
Re-write type expression parser
  • Loading branch information
andreibancioiu authored Mar 13, 2024
2 parents 286fdef + 4039cb4 commit a87efbe
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 155 deletions.
31 changes: 2 additions & 29 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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"
},
Expand Down
18 changes: 18 additions & 0 deletions src/abi/typeFormula.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
25 changes: 25 additions & 0 deletions src/abi/typeFormulaParser.spec.ts
Original file line number Diff line number Diff line change
@@ -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<MultiResult2<Address, u64>>", "MultiResultVec<MultiResult2<Address, u64>>"],
["tuple3<i32, bytes, Option<i64>>", "tuple3<i32, bytes, Option<i64>>"],
["tuple2<i32, i32>", "tuple2<i32, i32>"],
["tuple2<i32,i32> ", "tuple2<i32, i32>"],
["tuple<List<u64>, List<u64>>", "tuple<List<u64>, List<u64>>"],
];

for (const [inputExpression, expectedExpression] of testVectors) {
const typeFormula = parser.parseExpression(inputExpression);
const outputExpression = typeFormula.toString();
assert.equal(outputExpression, expectedExpression);
}
});
});
130 changes: 130 additions & 0 deletions src/abi/typeFormulaParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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 (this.isPunctuation(token)) {
if (this.isEndOfTypeParameters(token)) {
const typeFormula = this.acquireTypeWithParameters(stack);
stack.push(typeFormula);
} else if (this.isBeginningOfTypeParameters(token)) {
// This symbol is pushed as a simple string.
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 (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}`);
}
}

private tokenizeExpression(expression: string): string[] {
const tokens: string[] = [];
let currentToken = "";

for (const character of expression) {
if (this.isPunctuation(character)) {
if (currentToken) {
// Retain current token
tokens.push(currentToken.trim());
// Reset current token
currentToken = "";
}

// Punctuation character
tokens.push(character);
} else {
currentToken += character;
}
}

if (currentToken) {
// Retain the last token (if any).
tokens.push(currentToken.trim());
}

return tokens;
}

private acquireTypeWithParameters(stack: any[]): TypeFormula {
const typeParameters = this.acquireTypeParameters(stack);
const typeName = stack.pop();
const typeFormula = new TypeFormula(typeName, typeParameters.reverse());
return typeFormula;
}

private acquireTypeParameters(stack: any[]): TypeFormula[] {
const typeParameters: TypeFormula[] = [];

while (true) {
const item = stack.pop();

if (item === undefined) {
throw new Error("Badly specified type parameters");
}

if (this.isBeginningOfTypeParameters(item)) {
// We've acquired all type parameters.
break;
}

if (item instanceof TypeFormula) {
// Type parameter is a previously-acquired type.
typeParameters.push(item);
} else if (typeof item === "string") {
// 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 typeParameters;
}

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;
}
}
Loading

0 comments on commit a87efbe

Please sign in to comment.