From 944ff8ae9a62ecb87bef90477f9b645312808fc7 Mon Sep 17 00:00:00 2001 From: blitz-1306 Date: Tue, 28 Sep 2021 16:49:23 +0500 Subject: [PATCH 1/5] Introduced pretty-printing logic and related tests. Tweaked assert() to respect new logic. --- src/misc/index.ts | 5 +- src/misc/node.ts | 3 +- src/misc/pretty_printing.ts | 71 ++++++++++++++ src/misc/utils.ts | 12 +-- test/unit/misc/pretty_printing.spec.ts | 130 +++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 src/misc/pretty_printing.ts create mode 100644 test/unit/misc/pretty_printing.spec.ts diff --git a/src/misc/index.ts b/src/misc/index.ts index 4346ab0a..c20074e7 100644 --- a/src/misc/index.ts +++ b/src/misc/index.ts @@ -1,4 +1,5 @@ export * from "./location"; -export * from "./utils"; -export * from "./struct_equality"; export * from "./node"; +export * from "./pretty_printing"; +export * from "./struct_equality"; +export * from "./utils"; diff --git a/src/misc/node.ts b/src/misc/node.ts index fe62522c..e2c2d3a0 100644 --- a/src/misc/node.ts +++ b/src/misc/node.ts @@ -1,3 +1,4 @@ +import { PPAble } from "./pretty_printing"; import { StructEqualityComparable } from "./struct_equality"; import { assert } from "./utils"; @@ -10,7 +11,7 @@ let nNodes = 0; * Generic tree node with pretty-printer, optional source tripple information * and structural equality comparison. Useful for building ASTs. */ -export abstract class Node implements StructEqualityComparable { +export abstract class Node implements StructEqualityComparable, PPAble { readonly id: number; readonly src?: Range; diff --git a/src/misc/pretty_printing.ts b/src/misc/pretty_printing.ts new file mode 100644 index 00000000..db212680 --- /dev/null +++ b/src/misc/pretty_printing.ts @@ -0,0 +1,71 @@ +import { ASTNode } from "../ast/ast_node"; + +export interface PPAble { + pp(): string; +} + +export type PPIsh = PPAble | ASTNode | string | number | boolean | bigint | null | undefined; + +export function isPPAble(value: any): value is PPAble { + return value ? typeof value.pp === "function" : false; +} + +export function pp(value: PPIsh): string { + if (value instanceof ASTNode) { + return value.type + "#" + value.id; + } + + if (value === undefined) { + return ""; + } + + if ( + value === null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" || + typeof value === "bigint" + ) { + return String(value); + } + + if (isPPAble(value)) { + return value.pp(); + } + + throw new Error("Unhandled value in pp(): " + String(value)); +} + +export function ppArr(array: PPIsh[], separator = ",", start = "[", end = "]"): string { + return start + array.map(pp).join(separator) + end; +} + +export function ppIter(iter: Iterable, separator = ",", start = "[", end = "]"): string { + const parts: string[] = []; + + for (const part of iter) { + parts.push(pp(part)); + } + + return start + parts.join(separator) + end; +} + +export function ppSet(set: Set, separator = ",", start = "{", end = "}"): string { + return ppIter(set, separator, start, end); +} + +export function ppMap( + map: Map, + separator = ",", + keyValueSeparator = ":", + start = "{", + end = "}" +): string { + const parts: string[] = []; + + for (const [name, val] of map.entries()) { + parts.push(pp(name) + keyValueSeparator + pp(val)); + } + + return start + parts.join(separator) + end; +} diff --git a/src/misc/utils.ts b/src/misc/utils.ts index a24babd8..6777984c 100644 --- a/src/misc/utils.ts +++ b/src/misc/utils.ts @@ -1,4 +1,4 @@ -import { TypeNode } from ".."; +import { pp, ppArr, PPIsh } from ".."; import { ASTNode } from "../ast/ast_node"; export function forAll(iterable: Iterable, cb: (v: T) => boolean): boolean { @@ -14,7 +14,7 @@ export function forAll(iterable: Iterable, cb: (v: T) => boolean): boolean export function assert( condition: boolean, message: string, - ...details: Array + ...details: PPIsh[] ): asserts condition { if (condition) { return; @@ -29,13 +29,13 @@ export function assert( let part: string; if (detail instanceof ASTNode) { - part = detail.type + " #" + detail.id; + part = pp(detail); nodes.push(detail); - } else if (detail instanceof TypeNode) { - part = detail.pp(); + } else if (detail instanceof Array) { + part = ppArr(detail); } else { - part = detail; + part = pp(detail); } message = message.replace(new RegExp("\\{" + i + "\\}", "g"), part); diff --git a/test/unit/misc/pretty_printing.spec.ts b/test/unit/misc/pretty_printing.spec.ts new file mode 100644 index 00000000..11d11147 --- /dev/null +++ b/test/unit/misc/pretty_printing.spec.ts @@ -0,0 +1,130 @@ +import expect from "expect"; +import { isPPAble, pp, ppArr, ppIter, ppMap, ppSet } from "../../../src"; + +describe("Utility formatting routines", () => { + describe("isPPAble()", () => { + const cases: Array<[any, boolean]> = [ + [{}, false], + [[], false], + [ + { + name: "test", + pp: () => "PPAble object" + }, + true + ] + ]; + + for (const [value, result] of cases) { + it(`${JSON.stringify(value)} results ${result}`, () => { + expect(isPPAble(value)).toEqual(result); + }); + } + }); + + describe("pp()", () => { + const cases: Array<[any, string]> = [ + [1, "1"], + [BigInt(1), "1"], + ["abc", "abc"], + [true, "true"], + [false, "false"], + [null, "null"], + [undefined, ""], + [ + { + name: "test", + pp: () => "PPAble object" + }, + "PPAble object" + ] + ]; + + for (const [value, result] of cases) { + it(`${ + typeof value === "bigint" ? value.toString() + "n" : JSON.stringify(value) + } results ${result}`, () => { + expect(pp(value)).toEqual(result); + }); + } + }); + + describe("ppArr()", () => { + const cases: Array<[any[], Array, string]> = [ + [[1, 2, 3], [], "[1,2,3]"], + [["x", "y", "z"], ["/", "<", ">"], ""] + ]; + + for (const [value, options, result] of cases) { + it(`${JSON.stringify(value)} with options ${JSON.stringify( + options + )} results ${result}`, () => { + expect(ppArr(value, ...options)).toEqual(result); + }); + } + }); + + describe("ppIter()", () => { + function makeIterable(...array: T[]): Iterable { + return { + *[Symbol.iterator]() { + for (const element of array) { + yield element; + } + } + }; + } + + const cases: Array<[Iterable, Array, string]> = [ + [[1, 2, 3], [], "[1,2,3]"], + [new Set(["x", "y", "z"]), ["/", "<", ">"], ""], + [makeIterable("1x", 2, "y3"), [], "[1x,2,y3]"] + ]; + + for (const [value, options, result] of cases) { + const array = Array.from(value); + + it(`Iterable ${JSON.stringify(array)} with options ${JSON.stringify( + options + )} results ${result}`, () => { + expect(ppIter(value, ...options)).toEqual(result); + }); + } + }); + + describe("ppSet()", () => { + const cases: Array<[Set, Array, string]> = [ + [new Set([1, 2, 3]), [], "{1,2,3}"], + [new Set(["x", "y", "z"]), ["/", "<", ">"], ""] + ]; + + for (const [value, options, result] of cases) { + it(`${JSON.stringify(value)} with options ${JSON.stringify( + options + )} results ${result}`, () => { + expect(ppSet(value, ...options)).toEqual(result); + }); + } + }); + + describe("ppMap()", () => { + const map = new Map([ + ["a", 0], + ["b", 3], + ["c", 1] + ]); + + const cases: Array<[Map, Array, string]> = [ + [map, [], "{a:0,b:3,c:1}"], + [map, ["/", " => ", "<", ">"], " 0/b => 3/c => 1>"] + ]; + + for (const [value, options, result] of cases) { + it(`${JSON.stringify(value)} with options ${JSON.stringify( + options + )} results ${result}`, () => { + expect(ppMap(value, ...options)).toEqual(result); + }); + } + }); +}); From 29af0bbbe1cb7b102fe96ec1bbeddcfabfafe6de Mon Sep 17 00:00:00 2001 From: blitz-1306 Date: Tue, 28 Sep 2021 16:53:01 +0500 Subject: [PATCH 2/5] Added test case with custom ASTNode to pp() test suite --- test/unit/misc/pretty_printing.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/misc/pretty_printing.spec.ts b/test/unit/misc/pretty_printing.spec.ts index 11d11147..2a979e8a 100644 --- a/test/unit/misc/pretty_printing.spec.ts +++ b/test/unit/misc/pretty_printing.spec.ts @@ -1,5 +1,5 @@ import expect from "expect"; -import { isPPAble, pp, ppArr, ppIter, ppMap, ppSet } from "../../../src"; +import { ASTNode, isPPAble, pp, ppArr, ppIter, ppMap, ppSet } from "../../../src"; describe("Utility formatting routines", () => { describe("isPPAble()", () => { @@ -37,7 +37,8 @@ describe("Utility formatting routines", () => { pp: () => "PPAble object" }, "PPAble object" - ] + ], + [new ASTNode(1, "0:0:0", "CustomNode"), "CustomNode#1"] ]; for (const [value, result] of cases) { From 7bcf93f2320e14f3f41cc1f106ca3aa902b3375d Mon Sep 17 00:00:00 2001 From: blitz-1306 Date: Tue, 28 Sep 2021 20:56:09 +0500 Subject: [PATCH 3/5] Fix failing test cases with AST nodes in pp() --- test/unit/misc/pretty_printing.spec.ts | 7 +++++-- test/unit/misc/utils/assert.spec.ts | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/unit/misc/pretty_printing.spec.ts b/test/unit/misc/pretty_printing.spec.ts index 2a979e8a..dabbf8ac 100644 --- a/test/unit/misc/pretty_printing.spec.ts +++ b/test/unit/misc/pretty_printing.spec.ts @@ -1,5 +1,5 @@ import expect from "expect"; -import { ASTNode, isPPAble, pp, ppArr, ppIter, ppMap, ppSet } from "../../../src"; +import { ElementaryTypeName, isPPAble, pp, ppArr, ppIter, ppMap, ppSet } from "../../../src"; describe("Utility formatting routines", () => { describe("isPPAble()", () => { @@ -38,7 +38,10 @@ describe("Utility formatting routines", () => { }, "PPAble object" ], - [new ASTNode(1, "0:0:0", "CustomNode"), "CustomNode#1"] + [ + new ElementaryTypeName(1, "0:0:0", "ElementaryTypeName", "uint8", "uint8"), + "ElementaryTypeName#1" + ] ]; for (const [value, result] of cases) { diff --git a/test/unit/misc/utils/assert.spec.ts b/test/unit/misc/utils/assert.spec.ts index 56364a27..76254a3b 100644 --- a/test/unit/misc/utils/assert.spec.ts +++ b/test/unit/misc/utils/assert.spec.ts @@ -17,19 +17,19 @@ ctx.register(astNode); const cases: Array<[boolean, string, Array, string | undefined]> = [ [true, "Test none", [], undefined], [false, "Test string {0}, {1}, {2}", ["x", "y", "z"], "Test string x, y, z"], - [false, "Test {0} AST node", [astNode], "Test ElementaryTypeName #1 AST node"], + [false, "Test {0} AST node", [astNode], "Test ElementaryTypeName#1 AST node"], [false, "Test {0} type node", [typeNode], "Test uint256 type node"], [ false, "Test {0} combined {1} parts {2}", [astNode, typeNode, "xyz"], - "Test ElementaryTypeName #1 combined uint256 parts xyz" + "Test ElementaryTypeName#1 combined uint256 parts xyz" ], [ false, "Test not mentioned AST node", [astNode], - "Test not mentioned AST node.\n\nElementaryTypeName #1" + "Test not mentioned AST node.\n\nElementaryTypeName#1" ] ]; From 507fd557a7eeca57257a4c37443b411957a00fde Mon Sep 17 00:00:00 2001 From: blitz-1306 Date: Tue, 28 Sep 2021 21:38:53 +0500 Subject: [PATCH 4/5] Fix failing test case in assert() --- test/unit/misc/utils/assert.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/misc/utils/assert.spec.ts b/test/unit/misc/utils/assert.spec.ts index 76254a3b..3b6e37ff 100644 --- a/test/unit/misc/utils/assert.spec.ts +++ b/test/unit/misc/utils/assert.spec.ts @@ -29,7 +29,7 @@ const cases: Array<[boolean, string, Array, string false, "Test not mentioned AST node", [astNode], - "Test not mentioned AST node.\n\nElementaryTypeName#1" + "Test not mentioned AST node.\n\nElementaryTypeName #1" ] ]; From 6e612113efada422eb324ed77841541855af8c10 Mon Sep 17 00:00:00 2001 From: blitz-1306 Date: Wed, 29 Sep 2021 10:05:30 +0500 Subject: [PATCH 5/5] Address review feedback --- src/misc/pretty_printing.ts | 34 +++++++++++++++++++++--- src/misc/utils.ts | 11 ++------ test/unit/misc/pretty_printing.spec.ts | 36 ++++++++++++++++---------- test/unit/misc/utils/assert.spec.ts | 4 +-- 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/misc/pretty_printing.ts b/src/misc/pretty_printing.ts index db212680..538ccb0a 100644 --- a/src/misc/pretty_printing.ts +++ b/src/misc/pretty_printing.ts @@ -4,7 +4,19 @@ export interface PPAble { pp(): string; } -export type PPIsh = PPAble | ASTNode | string | number | boolean | bigint | null | undefined; +export type PPIsh = + | PPAble + | ASTNode + | string + | number + | boolean + | bigint + | null + | undefined + | PPIsh[] + | Set + | Map + | Iterable; export function isPPAble(value: any): value is PPAble { return value ? typeof value.pp === "function" : false; @@ -12,7 +24,7 @@ export function isPPAble(value: any): value is PPAble { export function pp(value: PPIsh): string { if (value instanceof ASTNode) { - return value.type + "#" + value.id; + return value.type + " #" + value.id; } if (value === undefined) { @@ -33,6 +45,22 @@ export function pp(value: PPIsh): string { return value.pp(); } + if (value instanceof Array) { + return ppArr(value); + } + + if (value instanceof Set) { + return ppSet(value); + } + + if (value instanceof Map) { + return ppMap(value); + } + + if (typeof value[Symbol.iterator] === "function") { + return ppIter(value); + } + throw new Error("Unhandled value in pp(): " + String(value)); } @@ -55,7 +83,7 @@ export function ppSet(set: Set, separator = ",", start = "{", end = "}"): } export function ppMap( - map: Map, + map: Map, separator = ",", keyValueSeparator = ":", start = "{", diff --git a/src/misc/utils.ts b/src/misc/utils.ts index 6777984c..395f84cb 100644 --- a/src/misc/utils.ts +++ b/src/misc/utils.ts @@ -1,4 +1,4 @@ -import { pp, ppArr, PPIsh } from ".."; +import { pp, PPIsh } from ".."; import { ASTNode } from "../ast/ast_node"; export function forAll(iterable: Iterable, cb: (v: T) => boolean): boolean { @@ -25,17 +25,10 @@ export function assert( for (let i = 0; i < details.length; i++) { const detail = details[i]; - - let part: string; + const part = pp(detail); if (detail instanceof ASTNode) { - part = pp(detail); - nodes.push(detail); - } else if (detail instanceof Array) { - part = ppArr(detail); - } else { - part = pp(detail); } message = message.replace(new RegExp("\\{" + i + "\\}", "g"), part); diff --git a/test/unit/misc/pretty_printing.spec.ts b/test/unit/misc/pretty_printing.spec.ts index dabbf8ac..d30051d6 100644 --- a/test/unit/misc/pretty_printing.spec.ts +++ b/test/unit/misc/pretty_printing.spec.ts @@ -40,14 +40,30 @@ describe("Utility formatting routines", () => { ], [ new ElementaryTypeName(1, "0:0:0", "ElementaryTypeName", "uint8", "uint8"), - "ElementaryTypeName#1" + "ElementaryTypeName #1" + ], + [["x", 1, true, null], "[x,1,true,null]"], + [new Set(["a", 2, false, null]), "{a,2,false,null}"], + [ + new Map([ + ["x", true], + ["y", 100], + ["z", false] + ]), + "{x:true,y:100,z:false}" + ], + [ + (function* gen() { + yield 10; + yield 20; + yield 30; + })(), + "[10,20,30]" ] ]; for (const [value, result] of cases) { - it(`${ - typeof value === "bigint" ? value.toString() + "n" : JSON.stringify(value) - } results ${result}`, () => { + it(`${value} results ${result}`, () => { expect(pp(value)).toEqual(result); }); } @@ -60,9 +76,7 @@ describe("Utility formatting routines", () => { ]; for (const [value, options, result] of cases) { - it(`${JSON.stringify(value)} with options ${JSON.stringify( - options - )} results ${result}`, () => { + it(`${value} with options ${JSON.stringify(options)} results ${result}`, () => { expect(ppArr(value, ...options)).toEqual(result); }); } @@ -103,9 +117,7 @@ describe("Utility formatting routines", () => { ]; for (const [value, options, result] of cases) { - it(`${JSON.stringify(value)} with options ${JSON.stringify( - options - )} results ${result}`, () => { + it(`${value} with options ${JSON.stringify(options)} results ${result}`, () => { expect(ppSet(value, ...options)).toEqual(result); }); } @@ -124,9 +136,7 @@ describe("Utility formatting routines", () => { ]; for (const [value, options, result] of cases) { - it(`${JSON.stringify(value)} with options ${JSON.stringify( - options - )} results ${result}`, () => { + it(`${value} with options ${JSON.stringify(options)} results ${result}`, () => { expect(ppMap(value, ...options)).toEqual(result); }); } diff --git a/test/unit/misc/utils/assert.spec.ts b/test/unit/misc/utils/assert.spec.ts index 3b6e37ff..56364a27 100644 --- a/test/unit/misc/utils/assert.spec.ts +++ b/test/unit/misc/utils/assert.spec.ts @@ -17,13 +17,13 @@ ctx.register(astNode); const cases: Array<[boolean, string, Array, string | undefined]> = [ [true, "Test none", [], undefined], [false, "Test string {0}, {1}, {2}", ["x", "y", "z"], "Test string x, y, z"], - [false, "Test {0} AST node", [astNode], "Test ElementaryTypeName#1 AST node"], + [false, "Test {0} AST node", [astNode], "Test ElementaryTypeName #1 AST node"], [false, "Test {0} type node", [typeNode], "Test uint256 type node"], [ false, "Test {0} combined {1} parts {2}", [astNode, typeNode, "xyz"], - "Test ElementaryTypeName#1 combined uint256 parts xyz" + "Test ElementaryTypeName #1 combined uint256 parts xyz" ], [ false,