Skip to content

Commit

Permalink
Merge pull request #71 from ConsenSys/pretty-printing
Browse files Browse the repository at this point in the history
Pretty printing
  • Loading branch information
blitz-1306 authored Sep 29, 2021
2 parents f255c39 + 6e61211 commit 2dcd27b
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 13 deletions.
5 changes: 3 additions & 2 deletions src/misc/index.ts
Original file line number Diff line number Diff line change
@@ -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";
3 changes: 2 additions & 1 deletion src/misc/node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PPAble } from "./pretty_printing";
import { StructEqualityComparable } from "./struct_equality";
import { assert } from "./utils";

Expand All @@ -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;

Expand Down
99 changes: 99 additions & 0 deletions src/misc/pretty_printing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ASTNode } from "../ast/ast_node";

export interface PPAble {
pp(): string;
}

export type PPIsh =
| PPAble
| ASTNode
| string
| number
| boolean
| bigint
| null
| undefined
| PPIsh[]
| Set<PPIsh>
| Map<PPIsh, PPIsh>
| Iterable<PPIsh>;

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 "<undefined>";
}

if (
value === null ||
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean" ||
typeof value === "bigint"
) {
return String(value);
}

if (isPPAble(value)) {
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));
}

export function ppArr(array: PPIsh[], separator = ",", start = "[", end = "]"): string {
return start + array.map(pp).join(separator) + end;
}

export function ppIter(iter: Iterable<PPIsh>, 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<PPIsh>, separator = ",", start = "{", end = "}"): string {
return ppIter(set, separator, start, end);
}

export function ppMap(
map: Map<PPIsh, PPIsh>,
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;
}
13 changes: 3 additions & 10 deletions src/misc/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TypeNode } from "..";
import { pp, PPIsh } from "..";
import { ASTNode } from "../ast/ast_node";

export function forAll<T>(iterable: Iterable<T>, cb: (v: T) => boolean): boolean {
Expand All @@ -14,7 +14,7 @@ export function forAll<T>(iterable: Iterable<T>, cb: (v: T) => boolean): boolean
export function assert(
condition: boolean,
message: string,
...details: Array<ASTNode | TypeNode | string>
...details: PPIsh[]
): asserts condition {
if (condition) {
return;
Expand All @@ -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 = detail.type + " #" + detail.id;

nodes.push(detail);
} else if (detail instanceof TypeNode) {
part = detail.pp();
} else {
part = detail;
}

message = message.replace(new RegExp("\\{" + i + "\\}", "g"), part);
Expand Down
144 changes: 144 additions & 0 deletions test/unit/misc/pretty_printing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import expect from "expect";
import { ElementaryTypeName, 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, "<undefined>"],
[
{
name: "test",
pp: () => "PPAble object"
},
"PPAble object"
],
[
new ElementaryTypeName(1, "0:0:0", "ElementaryTypeName", "uint8", "uint8"),
"ElementaryTypeName #1"
],
[["x", 1, true, null], "[x,1,true,null]"],
[new Set(["a", 2, false, null]), "{a,2,false,null}"],
[
new Map<string, boolean | number>([
["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(`${value} results ${result}`, () => {
expect(pp(value)).toEqual(result);
});
}
});

describe("ppArr()", () => {
const cases: Array<[any[], Array<string | undefined>, string]> = [
[[1, 2, 3], [], "[1,2,3]"],
[["x", "y", "z"], ["/", "<", ">"], "<x/y/z>"]
];

for (const [value, options, result] of cases) {
it(`${value} with options ${JSON.stringify(options)} results ${result}`, () => {
expect(ppArr(value, ...options)).toEqual(result);
});
}
});

describe("ppIter()", () => {
function makeIterable<T>(...array: T[]): Iterable<T> {
return {
*[Symbol.iterator]() {
for (const element of array) {
yield element;
}
}
};
}

const cases: Array<[Iterable<any>, Array<string | undefined>, string]> = [
[[1, 2, 3], [], "[1,2,3]"],
[new Set(["x", "y", "z"]), ["/", "<", ">"], "<x/y/z>"],
[makeIterable<any>("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<any>, Array<string | undefined>, string]> = [
[new Set([1, 2, 3]), [], "{1,2,3}"],
[new Set(["x", "y", "z"]), ["/", "<", ">"], "<x/y/z>"]
];

for (const [value, options, result] of cases) {
it(`${value} with options ${JSON.stringify(options)} results ${result}`, () => {
expect(ppSet(value, ...options)).toEqual(result);
});
}
});

describe("ppMap()", () => {
const map = new Map<string, number>([
["a", 0],
["b", 3],
["c", 1]
]);

const cases: Array<[Map<any, any>, Array<string | undefined>, string]> = [
[map, [], "{a:0,b:3,c:1}"],
[map, ["/", " => ", "<", ">"], "<a => 0/b => 3/c => 1>"]
];

for (const [value, options, result] of cases) {
it(`${value} with options ${JSON.stringify(options)} results ${result}`, () => {
expect(ppMap(value, ...options)).toEqual(result);
});
}
});
});

0 comments on commit 2dcd27b

Please sign in to comment.