-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #398 from multiversx/better-type-expression-parser
Re-write type expression parser
- Loading branch information
Showing
7 changed files
with
296 additions
and
155 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.