Skip to content

Commit

Permalink
feat(molecule): make union support the custom id
Browse files Browse the repository at this point in the history
  • Loading branch information
homura committed Dec 13, 2023
1 parent 04f5146 commit be1d723
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-rabbits-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-lumos/molecule": minor
---

feat: supported union with custom id
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const scopeEnumValues = [
"utils",
"runner",
"e2e-test",
"molecule",
];
const Configuration = {
extends: ["@commitlint/config-conventional"],
Expand Down
14 changes: 14 additions & 0 deletions packages/molecule/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# @ckb-lumos/molecule

A molecule parser written in JavaScript that helps developers to parse molecule into a codec map.

```js
const { createParser } = require("@ckb-lumos/molecule");

const parser = createParser();
const codecMap = parser.parse(`
array Uint8 [byte; 1];
`);

codecMap.Uint8.pack(1);
```
53 changes: 37 additions & 16 deletions packages/molecule/src/codec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
FixedBytesCodec,
createBytesCodec,
BytesLike,
BytesCodec,
BytesLike,
createBytesCodec,
FixedBytesCodec,
} from "@ckb-lumos/codec/lib/base";
import {
array,
Expand Down Expand Up @@ -46,7 +46,7 @@ export const toCodec = (
}
const molType: MolType = molTypeMap[key];
nonNull(molType);
let codec = null;
let codec: BytesCodec | null = null;
switch (molType.type) {
case "array": {
if (molType.name.startsWith("Uint")) {
Expand Down Expand Up @@ -104,21 +104,42 @@ export const toCodec = (
break;
}
case "union": {
const unionCodecs: Record<string, BytesCodec> = {};
molType.items.forEach((itemMolTypeName) => {
if (itemMolTypeName === byte) {
unionCodecs[itemMolTypeName] = createFixedHexBytesCodec(1);
// Tuple of [UnionFieldName, UnionFieldId, UnionTypeCodec]
const unionCodecs: [string, number, BytesCodec][] = [];

molType.items.forEach((unionTypeItem, index) => {
if (unionTypeItem === byte) {
unionCodecs.push([unionTypeItem, index, createFixedHexBytesCodec(1)]);
} else {
const itemMolType = toCodec(
itemMolTypeName,
molTypeMap,
result,
refs
);
unionCodecs[itemMolTypeName] = itemMolType;
if (typeof unionTypeItem === "string") {
const itemMolType = toCodec(
unionTypeItem,
molTypeMap,
result,
refs
);
unionCodecs.push([unionTypeItem, index, itemMolType]);
} else if (Array.isArray(unionTypeItem)) {
const [key, fieldId] = unionTypeItem;

const itemMolType = toCodec(key, molTypeMap, result, refs);
unionCodecs.push([key, fieldId, itemMolType]);
}
}
});
codec = union(unionCodecs, Object.keys(unionCodecs));

const unionFieldsCodecs: Record<string, BytesCodec> = unionCodecs.reduce(
(codecMap, [fieldName, _fieldId, fieldCodec]) =>
Object.assign(codecMap, { [fieldName]: fieldCodec }),
{}
);
const unionFieldIds: Record<string, number> = unionCodecs.reduce(
(idMap, [fieldName, fieldId, _fieldCodec]) =>
Object.assign(idMap, { [fieldName]: fieldId }),
{}
);

codec = union(unionFieldsCodecs, unionFieldIds);
break;
}
case "table": {
Expand Down
20 changes: 17 additions & 3 deletions packages/molecule/src/grammar/mol.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,26 @@
};
},
},
{
name: "union_item_decl",
symbols: ["identifier", "_", { literal: ":" }, "_", "number"],
postprocess: function (data) {
return [data[0].value, Number(data[4].value)];
},
},
{
name: "union_item_decl",
symbols: ["identifier"],
postprocess: function (data) {
return data[0].value;
},
},
{
name: "union_definition$ebnf$1$subexpression$1",
symbols: [
"multi_line_ws_char",
"_",
"identifier",
"union_item_decl",
"_",
"comma",
"_",
Expand All @@ -251,7 +265,7 @@
symbols: [
"multi_line_ws_char",
"_",
"identifier",
"union_item_decl",
"_",
"comma",
"_",
Expand Down Expand Up @@ -287,7 +301,7 @@
return {
type: "union",
name: data[2].value,
items: data[6].map((d) => d[2].value),
items: data[6].map((d) => d[2]),
};
},
},
Expand Down
34 changes: 24 additions & 10 deletions packages/molecule/src/grammar/mol.ne
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,20 @@ top_level_statement

array_definition
-> "array" __ identifier _ lbracket _ identifier _ semicolon _ number _ rbracket _ semicolon _ comment_opt
{%
{%
function(data) {
return {
type: "array",
name: data[2].value,
item: data[6].value,
item_count: data[10].value
item_count: data[10].value
};
}
%}

vector_definition
-> "vector" __ identifier _ labracket _ identifier _ rabracket _ semicolon _ comment_opt
{%
{%
function(data) {
return {
type: "vector",
Expand All @@ -142,7 +142,7 @@ vector_definition

option_definition
-> "option" __ identifier _ lparan _ identifier _ rparan _ semicolon _ comment_opt
{%
{%
function(data) {
return {
type: "option",
Expand All @@ -152,21 +152,35 @@ option_definition
}
%}

union_item_decl
-> identifier _ ":" _ number
{%
function (data) {
return [data[0].value, Number(data[4].value)]
}
%}
| identifier
{%
function (data) {
return data[0].value
}
%}

union_definition
-> "union" __ identifier _ lbrace _ (multi_line_ws_char _ identifier _ comma _ comment_opt _ multi_line_ws_char):+ _ rbrace
{%
-> "union" __ identifier _ lbrace _ (multi_line_ws_char _ union_item_decl _ comma _ comment_opt _ multi_line_ws_char):+ _ rbrace
{%
function(data) {
return {
type: "union",
name: data[2].value,
items: data[6].map(d => d[2].value),
items: data[6].map(d => d[2]),
};
}
%}

struct_definition
-> "struct" __ identifier _ block_definition
{%
{%
function(data) {
return {
type: "struct",
Expand All @@ -178,7 +192,7 @@ struct_definition

table_definition
-> "table" __ identifier _ block_definition
{%
{%
function(data) {
return {
type: "table",
Expand Down Expand Up @@ -217,4 +231,4 @@ multi_line_ws_char

__ -> %ws:+

_ -> %ws:*
_ -> %ws:*
11 changes: 9 additions & 2 deletions packages/molecule/src/nearley.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ParseOptions,
} from "./type";
import { nonNull, toMolTypeMap } from "./utils";
import { Uint32 } from "@ckb-lumos/codec/lib/number";

// eslint-disable-next-line @typescript-eslint/no-var-requires
const grammar = require("./grammar/mol.js");
Expand Down Expand Up @@ -92,10 +93,16 @@ export const checkDependencies = (results: MolType[]): void => {
}
case "union": {
const unionDeps = (molItem as Union).items;
unionDeps.forEach((dep: string) => {
if (dep !== byte) {
unionDeps.forEach((dep) => {
if (typeof dep === "string" && dep !== byte) {

Check warning on line 97 in packages/molecule/src/nearley.ts

View check run for this annotation

Codecov / codecov/patch

packages/molecule/src/nearley.ts#L96-L97

Added lines #L96 - L97 were not covered by tests
nonNull(map[dep]);
}
if (Array.isArray(dep)) {
const [key, id] = dep;
// check if the id is a valid uint32
Uint32.pack(id);
nonNull(map[key]);
}

Check warning on line 105 in packages/molecule/src/nearley.ts

View check run for this annotation

Codecov / codecov/patch

packages/molecule/src/nearley.ts#L100-L105

Added lines #L100 - L105 were not covered by tests
});
break;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/molecule/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type Option = {
export type Union = {
type: "union";
name: string;
items: string[];
items: (string | [string, number])[];
};

export type Struct = {
Expand Down
45 changes: 45 additions & 0 deletions packages/molecule/tests/grammar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,51 @@ test("should parse sample with refs", (t) => {
);
});

test("union with custom id", (t) => {
const parser = createParser();
const withCustomId = parser.parse(`
array Uint8 [byte; 1];
array Uint16 [byte; 2];
array Uint32 [byte; 4];
union JSNumber {
Uint8: 8,
Uint16: 16,
Uint32: 32,
}
`);

t.deepEqual(
withCustomId.JSNumber.pack({ type: "Uint8", value: 1 }),
// prettier-ignore
Uint8Array.from([
0x08, 0x00, 0x00, 0x00, // id should be 8
0x01
])
);

const withoutCustomId = parser.parse(`
array Uint8 [byte; 1];
array Uint16 [byte; 2];
array Uint32 [byte; 4];
union JSNumber {
Uint8,
Uint16,
Uint32,
}
`);

t.deepEqual(
withoutCustomId.JSNumber.pack({ type: "Uint8", value: 1 }),
// prettier-ignore
Uint8Array.from([
0x00, 0x00, 0x00, 0x00, // id should be 0
0x01
])
);
});

test("should parse blockchain.mol", (t) => {
const parser = createParser();
// https://github.com/nervosnetwork/ckb/blob/5a7efe7a0b720de79ff3761dc6e8424b8d5b22ea/util/types/schemas/blockchain.mol
Expand Down

0 comments on commit be1d723

Please sign in to comment.