Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Implement parser for mapping key lookup (e.g. m[0][1]) #3943

Draft
wants to merge 16 commits into
base: wrap
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"lerna-update-wizard": "^0.16.0",
"lint-staged": "^10.4.2",
"nyc": "^13.0.1",
"prettier": "^2.0.5",
"prettier": "^2.2.1",
"prs-merged-since": "^1.1.0"
},
"workspaces": {
Expand Down
10 changes: 10 additions & 0 deletions packages/parse-mapping-lookup/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"overrides": [
{
"files": ["**/*.spec.ts"],
"env": {
"jest": true
}
}
]
}
1 change: 1 addition & 0 deletions packages/parse-mapping-lookup/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
13 changes: 13 additions & 0 deletions packages/parse-mapping-lookup/.madgerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"excludeRegExp": [
"\\.\\.",
"test"
],
"fileExtensions": ["ts"],
"detectiveOptions": {
"ts": {
"skipTypeImports": true
}
},
"tsConfig": "./tsconfig.json"
}
9 changes: 9 additions & 0 deletions packages/parse-mapping-lookup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# `@truffle/parse-mapping-lookup`

## Usage

```typescript
import { parse } from "@truffle/parse-mapping-lookup";

parse("ledger.balances[0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e]");
```
84 changes: 84 additions & 0 deletions packages/parse-mapping-lookup/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"name": "@truffle/parse-mapping-lookup",
"version": "0.1.0",
"description": "Parse pointers to mapping/struct innards (e.g. m[0])",
"keywords": [
"smart-contract",
"truffle"
],
"author": "g. nicholas d'andrea <gnidan@trufflesuite.com>",
"homepage": "https://github.com/trufflesuite/truffle#readme",
"license": "MIT",
"main": "dist/src/index.js",
"directories": {
"dist": "dist"
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"repository": {
gnidan marked this conversation as resolved.
Show resolved Hide resolved
"type": "git",
"url": "https://github.com/trufflesuite/truffle.git",
"directory": "packages/parse-mapping-lookup"
},
"scripts": {
"build": "yarn build:dist && yarn build:docs",
"clean": "rm -rf ./dist",
"prepare": "yarn build",
"build:dist": "ttsc",
"build:docs": "typedoc",
"madge": "madge ./src/index.ts",
"test": "jest --verbose --detectOpenHandles --forceExit --passWithNoTests"
},
"devDependencies": {
gnidan marked this conversation as resolved.
Show resolved Hide resolved
"@types/jest": "^26.0.20",
"@types/node": "12.12.21",
"jest": "^26.5.2",
"madge": "^3.11.0",
"ts-jest": "^26.4.1",
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0",
"ttypescript": "^1.5.7",
"typedoc": "^0.20.19",
"typescript": "^4.1.4",
"typescript-transform-paths": "^2.1.0"
},
"bugs": {
"url": "https://github.com/trufflesuite/truffle/issues"
},
"dependencies": {
"parjs": "^0.12.7"
},
"jest": {
"moduleFileExtensions": [
"ts",
"js",
"json",
"node"
],
"testEnvironment": "node",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"globals": {
"ts-jest": {
"tsConfig": "<rootDir>/tsconfig.json",
"diagnostics": true
}
},
"moduleNameMapper": {
"^@truffle/parse-mapping-lookup/(.*)": "<rootDir>/src/$1",
"^@truffle/parse-mapping-lookup$": "<rootDir>/src",
"^test/(.*)": "<rootDir>/test/$1"
},
"testMatch": [
"<rootDir>/src/**/test/*\\.(ts|js)",
"<rootDir>/test/**/test/*\\.(ts|js)",
"<rootDir>/src/**/*\\.(spec|test)\\.(ts|js)",
"<rootDir>/test/**/*\\.(spec|test)\\.(ts|js)"
]
}
}
25 changes: 25 additions & 0 deletions packages/parse-mapping-lookup/src/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import debugModule from "debug";
const debug = debugModule("parse-mapping-lookup:ast");

import { Forms, definitions } from "./grammar";
import { Node, makeConstructors } from "./meta";

export type Identifier = Node<Forms, "identifier">;
export type String = Node<Forms, "string">;
export type Value = Node<Forms, "value">;
export type IndexAccess = Node<Forms, "indexAccess">;
export type MemberLookup = Node<Forms, "memberLookup">;
export type Pointer = Node<Forms, "pointer">;
export type Expression = Node<Forms, "expression">;

const constructors = makeConstructors<Forms>({ definitions });

export const {
identifier,
string,
value,
indexAccess,
memberLookup,
pointer,
expression
} = constructors;
75 changes: 75 additions & 0 deletions packages/parse-mapping-lookup/src/grammar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { string, regexp, noCharOf } from "parjs";
import { between, map, then, qthen, many, or } from "parjs/combinators";

import { Definitions } from "./meta";

import { solidityString } from "./string";

export type Forms = {
identifier: {
name: { type: string };
};
string: {
contents: { type: string };
};
value: {
contents: { type: string };
};
indexAccess: {
index: { kind: "string" | "value" };
};
memberLookup: {
property: { kind: "identifier" };
};
pointer: {
path: Array<{ kind: "memberLookup" | "indexAccess" }>;
};
expression: {
root: { kind: "identifier" };
pointer: { kind: "pointer" };
};
};

export const definitions: Definitions<Forms> = {
identifier: ({ construct }) =>
regexp(/[a-zA-Z_$][a-zA-Z0-9_$]*/).pipe(
map(([name]) => construct({ name }))
),

string: ({ construct }) =>
solidityString.pipe(map(contents => construct({ contents }))),

value: ({ construct }) =>
noCharOf("]").pipe(
then(noCharOf("]").pipe(many())),
map(([first, rest]) => construct({ contents: [first, ...rest].join("") }))
),

indexAccess: ({ construct, tie }) =>
tie("string").pipe(
or(tie("value")),
between(string("["), string("]")),
map(index => construct({ index }))
),

memberLookup: ({ construct, tie }) =>
string(".").pipe(
qthen(tie("identifier")),
map(property => construct({ property }))
),

pointer: ({ construct, tie }) => {
const stepP = tie("memberLookup").pipe(or(tie("indexAccess")));

return stepP.pipe(
then(stepP.pipe(many())),
map(([first, rest]) => construct({ path: [first, ...rest] }))
);
},

expression: ({ construct, tie }) =>
tie("identifier").pipe(
then(tie("pointer")),
map(([root, pointer]) => construct({ root, pointer }))
)
};
7 changes: 7 additions & 0 deletions packages/parse-mapping-lookup/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import debugModule from "debug";
const debug = debugModule("parse-mapping-lookup");

export { parseExpression } from "./parser";

import * as Ast from "./ast";
export { Ast };
36 changes: 36 additions & 0 deletions packages/parse-mapping-lookup/src/meta/constructors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Forms, FormKind, Node } from "./forms";

export type Constructors<F extends Forms> = {
[K in FormKind<F>]: Constructor<F, K>;
};

export type Constructor<F extends Forms, K extends FormKind<F>> = (
fields: Omit<Node<F, K>, "kind">
) => Node<F, K>;

export type MakeConstructorOptions<F extends Forms, K extends FormKind<F>> = {
kind: K;
};

const makeConstructor = <F extends Forms, K extends FormKind<F>>(
options: MakeConstructorOptions<F, K>
): Constructor<F, K> => {
const { kind } = options;
return fields => ({ kind, ...fields } as Node<F, K>);
};

export type Definition<F extends Forms, _K extends FormKind<F>> = any;
export type MakeConstructorsOptions<F extends Forms> = {
definitions: {
[K in FormKind<F>]: Definition<F, K>;
};
};

export const makeConstructors = <F extends Forms>(
options: MakeConstructorsOptions<F>
): Constructors<F> => {
const { definitions } = options;
return Object.keys(definitions)
.map(kind => ({ [kind]: makeConstructor({ kind }) }))
.reduce((a, b) => ({ ...a, ...b }), {}) as Constructors<F>;
};
47 changes: 47 additions & 0 deletions packages/parse-mapping-lookup/src/meta/forms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export type Forms = {
[kind: string]: {
[name: string]: { kind: string } | { kind: string }[] | { type: any };
};
};

export type FormKind<F extends Forms> = string & keyof F;
export type Form<F extends Forms, K extends FormKind<F>> = F[K];

export type FormFields<F extends Forms, K extends FormKind<F>> = Form<F, K>;

export type FormFieldName<F extends Forms, K extends FormKind<F>> = string &
keyof FormFields<F, K>;

export type FormField<
F extends Forms,
K extends FormKind<F>,
N extends FormFieldName<F, K>
> = FormFields<F, K>[N];

export type FormFieldKind<
F extends Forms,
K extends FormKind<F>,
_N extends FormFieldName<F, K>,
T extends any
> = "kind" extends keyof T ? Node<F, T["kind"] & FormKind<F>> : never;

type _FormFieldNode<
F extends Forms,
K extends FormKind<F>,
N extends FormFieldName<F, K>,
T extends any
> = T extends (infer I)[]
? _FormFieldNode<F, K, N, I>[]
: "type" extends keyof T
? T["type"]
: FormFieldKind<F, K, N, T>;

export type FormFieldNode<
F extends Forms,
K extends FormKind<F>,
N extends FormFieldName<F, K>
> = _FormFieldNode<F, K, N, FormField<F, K, N>>;

export type Node<F extends Forms, K extends FormKind<F>> = { kind: K } & {
[N in FormFieldName<F, K>]: FormFieldNode<F, K, N>;
};
19 changes: 19 additions & 0 deletions packages/parse-mapping-lookup/src/meta/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Node, Forms, FormKind } from "./forms";
export { Node };

import type * as Constructors from "./constructors";
import { makeConstructors } from "./constructors";
export { makeConstructors };

import type * as Parsers from "./parsers";
import { makeParsers } from "./parsers";
export { makeParsers };

export type Definition<
F extends Forms,
K extends FormKind<F>
> = Constructors.Definition<F, K> & Parsers.Definition<F, K>;

export type Definitions<F extends Forms> = {
[K in FormKind<F>]: Definition<F, K>;
};