gen-typescript-from-tolk-dev 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/bin/generator.js +15 -0
- package/package.json +32 -0
- package/src/abi-types.ts +157 -0
- package/src/abi.ts +132 -0
- package/src/cli-generate-from-abi-json.ts +21 -0
- package/src/codegen-ctx.ts +115 -0
- package/src/dynamic-ctx.ts +55 -0
- package/src/dynamic-debug-print.ts +191 -0
- package/src/dynamic-get-methods.ts +454 -0
- package/src/dynamic-serialization.ts +430 -0
- package/src/dynamic-validation.ts +55 -0
- package/src/emit-field-defs.ts +60 -0
- package/src/emit-pack-unpack.ts +280 -0
- package/src/emit-stack-rw.ts +239 -0
- package/src/emit-ts-types.ts +66 -0
- package/src/formatting.ts +98 -0
- package/src/generate-from-abi-json.ts +22 -0
- package/src/generate-ts-wrappers.ts +477 -0
- package/src/out-template.ts +514 -0
- package/src/tolk-to-abi.ts +5 -0
- package/src/types-kernel.ts +215 -0
- package/src/unsupported-errors.ts +82 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { Ty, UnionVariant } from './abi-types';
|
|
2
|
+
import { SymTable } from './codegen-ctx';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Contains basic functions working with ABI types,
|
|
6
|
+
which are used both for generating wrappers and dynamic serialization.
|
|
7
|
+
They do not emit TypeScript expressions and do not depend on CodegenCtx and DynamicCtx.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Render intermediate representation back to a valid Tolk type, exactly as it was in original sources.
|
|
11
|
+
export function renderTy(ty: Ty): string {
|
|
12
|
+
switch (ty.kind) {
|
|
13
|
+
case 'int': return `int`;
|
|
14
|
+
case 'intN': return `int${ty.n}`;
|
|
15
|
+
case 'uintN': return `uint${ty.n}`;
|
|
16
|
+
case 'varintN': return `varint${ty.n}`;
|
|
17
|
+
case 'varuintN': return `varuint${ty.n}`;
|
|
18
|
+
case 'coins': return `coins`;
|
|
19
|
+
case 'bool': return `bool`;
|
|
20
|
+
case 'cell': return `cell`;
|
|
21
|
+
case 'builder': return `builder`;
|
|
22
|
+
case 'slice': return `slice`;
|
|
23
|
+
case 'string': return `string`;
|
|
24
|
+
case 'remaining': return `RemainingBitsAndRefs`;
|
|
25
|
+
case 'address': return `address`;
|
|
26
|
+
case 'addressOpt': return `address?`;
|
|
27
|
+
case 'addressExt': return `ext_address`;
|
|
28
|
+
case 'addressAny': return `any_address`;
|
|
29
|
+
case 'bitsN': return `bits${ty.n}`;
|
|
30
|
+
case 'nullLiteral': return `null`;
|
|
31
|
+
case 'callable': return `continuation`;
|
|
32
|
+
case 'void': return `void`;
|
|
33
|
+
case 'unknown': return `unknown`;
|
|
34
|
+
case 'nullable': return `${renderTy(ty.inner)}?`;
|
|
35
|
+
case 'cellOf': return `Cell<${renderTy(ty.inner)}>`;
|
|
36
|
+
case 'arrayOf': return `array<${renderTy(ty.inner)}>`;
|
|
37
|
+
case 'lispListOf': return `lisp_list<${renderTy(ty.inner)}>`;
|
|
38
|
+
case 'tensor': return `(${ty.items.map(renderTy).join(', ')})`;
|
|
39
|
+
case 'shapedTuple': return `[${ty.items.map(renderTy).join(', ')}]`;
|
|
40
|
+
case 'mapKV': return `map<${renderTy(ty.k)}, ${renderTy(ty.v)}>`;
|
|
41
|
+
case 'EnumRef': return ty.enumName;
|
|
42
|
+
case 'StructRef': return ty.structName + (ty.typeArgs ? `<${ty.typeArgs.map(renderTy).join(', ')}>` : '');
|
|
43
|
+
case 'AliasRef': return ty.aliasName + (ty.typeArgs ? `<${ty.typeArgs.map(renderTy).join(', ')}>` : '');
|
|
44
|
+
case 'genericT': return ty.nameT;
|
|
45
|
+
case 'union': return ty.variants.map(v => renderTy(v.variantTy)).join(' | ');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Union types from Tolk are represented as union types in TypeScript, with some nuances.
|
|
50
|
+
// For structures, like `A | B`, every struct (TS interface) has its own `$` field,
|
|
51
|
+
// that's why it's emitted directly `type U = A | B`.
|
|
52
|
+
// For primitives, like `int32 | int64`, every type is prefixed with a `$` field (label):
|
|
53
|
+
// `type U = { $: 'int32', value: bigint } | { $: 'int64', value: bigint }`
|
|
54
|
+
// Hence, for every union variant, we calculate whether we need to expose `$ + value`.
|
|
55
|
+
export interface UnionVariantLabeled extends UnionVariant {
|
|
56
|
+
labelStr: string // value for implicit or explicit `$`: 'A' / 'B' / 'int32' / etc.
|
|
57
|
+
hasValueField: boolean // whether we need `$ + value` (a type has no `$` on its own)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// A "label" is the `$` field in TypeScript output, used to differentiate structs and union variants.
|
|
61
|
+
// Tolk `int32 | int64` -> TS `{ $: 'int32', value: bigint } | { $: 'int64', value: bigint }`.
|
|
62
|
+
// This function creates a string for `$`.
|
|
63
|
+
function createLabel(symbols: SymTable, ty: Ty): string {
|
|
64
|
+
switch (ty.kind) {
|
|
65
|
+
case 'int': return `int`;
|
|
66
|
+
case 'intN': return `int${ty.n}`;
|
|
67
|
+
case 'uintN': return `uint${ty.n}`;
|
|
68
|
+
case 'varintN': return `varint${ty.n}`;
|
|
69
|
+
case 'varuintN': return `varuint${ty.n}`;
|
|
70
|
+
case 'coins': return `coins`;
|
|
71
|
+
case 'bool': return `bool`;
|
|
72
|
+
case 'cell': return `cell`;
|
|
73
|
+
case 'builder': return `builder`;
|
|
74
|
+
case 'slice': return `slice`;
|
|
75
|
+
case 'string': return `string`;
|
|
76
|
+
case 'remaining': return `RemainingBitsAndRefs`;
|
|
77
|
+
case 'address': return `address`;
|
|
78
|
+
case 'addressOpt': return `address?`;
|
|
79
|
+
case 'addressExt': return `ext_address`;
|
|
80
|
+
case 'addressAny': return `any_address`;
|
|
81
|
+
case 'bitsN': return `bits${ty.n}`;
|
|
82
|
+
case 'nullLiteral': return `null`;
|
|
83
|
+
case 'callable': return `callable`;
|
|
84
|
+
case 'void': return `void`;
|
|
85
|
+
case 'unknown': return `unknown`;
|
|
86
|
+
case 'nullable': return `${createLabel(symbols, ty.inner)}?`;
|
|
87
|
+
case 'cellOf': return `Cell`;
|
|
88
|
+
case 'arrayOf': return `array`;
|
|
89
|
+
case 'lispListOf': return `lisp_list`;
|
|
90
|
+
case 'tensor': return `tensor`;
|
|
91
|
+
case 'shapedTuple': return `shaped`;
|
|
92
|
+
case 'mapKV': return `map`;
|
|
93
|
+
case 'EnumRef': return ty.enumName;
|
|
94
|
+
case 'StructRef': return ty.structName;
|
|
95
|
+
case 'AliasRef': return createLabel(symbols, symbols.getAliasTarget(ty.aliasName));
|
|
96
|
+
case 'genericT': return ty.nameT;
|
|
97
|
+
case 'union': return ty.variants.map(v => createLabel(symbols, v.variantTy)).join('|');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Every Tolk struct is generated as TS `interface` with `$` field.
|
|
102
|
+
// Tolk `struct A { ... }` -> TS `interface A { $: 'A', ... }`.
|
|
103
|
+
// So, Tolk `A | B` is represented as TS `A | B` (unlike primitives),
|
|
104
|
+
// because every struct has its own label.
|
|
105
|
+
function isStructWithItsOwnLabel(symbols: SymTable, ty: Ty): boolean {
|
|
106
|
+
if (ty.kind === 'StructRef') return true;
|
|
107
|
+
if (ty.kind === 'AliasRef') return isStructWithItsOwnLabel(symbols, symbols.getAliasTarget(ty.aliasName));
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Given a union `T1 | T2 | ...`, calculate a TypeScript representation:
|
|
112
|
+
// whether each variant should be `$ + value` or not, and what string does `$` hold.
|
|
113
|
+
export function createLabelsForUnion(symbols: SymTable, variants: UnionVariant[]): UnionVariantLabeled[] {
|
|
114
|
+
// in all practical cases, `T_i` is a structure or a primitive;
|
|
115
|
+
// we try to shorten a label (`createLabel()` is a "simple string of a type"):
|
|
116
|
+
// - for a generic struct, use `Wrapper`, not `Wrapper<xxx>`
|
|
117
|
+
// - for `Cell<...>`, use a shorthand `Cell`
|
|
118
|
+
// - etc.
|
|
119
|
+
// but if it results in a duplicate (e.g. `Wrapper<int32> | Wrapper<int64>` => ['Wrapper','Wrapper']),
|
|
120
|
+
// then full-string Tolk types are used, and `value` is always required:
|
|
121
|
+
// `type U = { $: 'Wrapper<int32>', value: Wrapper<int32> } | ...`
|
|
122
|
+
let unique: Set<string> = new Set();
|
|
123
|
+
let hasDuplicates = false;
|
|
124
|
+
variants.forEach(variant => {
|
|
125
|
+
let labelStr = createLabel(symbols, variant.variantTy);
|
|
126
|
+
hasDuplicates ||= unique.has(labelStr);
|
|
127
|
+
unique.add(labelStr);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return variants.map(variant => {
|
|
131
|
+
let variantTy = variant.variantTy;
|
|
132
|
+
if (variantTy.kind === 'nullLiteral') {
|
|
133
|
+
return { ...variant, labelStr: '', hasValueField: false };
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
...variant,
|
|
137
|
+
labelStr: hasDuplicates ? renderTy(variantTy) : createLabel(symbols, variantTy),
|
|
138
|
+
hasValueField: hasDuplicates ? true : !isStructWithItsOwnLabel(symbols, variantTy)
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Replace all generic Ts (typeParams) with instantiation (typeArgs) recursively.
|
|
144
|
+
// Example: `(int, T, Wrapper<T?>)` and T=coins => `(int, coins, Wrapper<coins?>)`
|
|
145
|
+
export function instantiateGenerics(ty: Ty, typeParams: string[] | undefined, typeArgs: Ty[]): Ty {
|
|
146
|
+
if (ty.kind === 'nullable') return { ...ty, inner: instantiateGenerics(ty.inner, typeParams, typeArgs) };
|
|
147
|
+
if (ty.kind === 'cellOf') return { ...ty, inner: instantiateGenerics(ty.inner, typeParams, typeArgs) };
|
|
148
|
+
if (ty.kind === 'arrayOf') return { ...ty, inner: instantiateGenerics(ty.inner, typeParams, typeArgs) };
|
|
149
|
+
if (ty.kind === 'lispListOf') return { ...ty, inner: instantiateGenerics(ty.inner, typeParams, typeArgs) };
|
|
150
|
+
if (ty.kind === 'tensor') return { ...ty, items: ty.items.map(item => instantiateGenerics(item, typeParams, typeArgs)) };
|
|
151
|
+
if (ty.kind === 'shapedTuple') return { ...ty, items: ty.items.map(item => instantiateGenerics(item, typeParams, typeArgs)) };
|
|
152
|
+
if (ty.kind === 'mapKV') return { ...ty, k: instantiateGenerics(ty.k, typeParams, typeArgs), v: instantiateGenerics(ty.v, typeParams, typeArgs) };
|
|
153
|
+
if (ty.kind === 'StructRef') return { ...ty, typeArgs: ty.typeArgs?.map(ta => instantiateGenerics(ta, typeParams, typeArgs)) };
|
|
154
|
+
if (ty.kind === 'AliasRef') return { ...ty, typeArgs: ty.typeArgs?.map(ta => instantiateGenerics(ta, typeParams, typeArgs)) };
|
|
155
|
+
if (ty.kind === 'union') return { ...ty, variants: ty.variants.map(v => ({ ...v, variantTy: instantiateGenerics(v.variantTy, typeParams, typeArgs) })) };
|
|
156
|
+
if (ty.kind === 'genericT') {
|
|
157
|
+
const idx = typeParams?.indexOf(ty.nameT);
|
|
158
|
+
if (idx === undefined || idx === -1 || idx >= typeArgs.length)
|
|
159
|
+
throw new Error(`inconsistent generics: could not find type argument for ${ty.nameT}`)
|
|
160
|
+
return typeArgs[idx]
|
|
161
|
+
}
|
|
162
|
+
return ty;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Calculate, how many stack slots a type occupies.
|
|
166
|
+
export function calcWidthOnStack(symbols: SymTable, ty: Ty): number {
|
|
167
|
+
switch (ty.kind) {
|
|
168
|
+
case 'void': {
|
|
169
|
+
// void is like "unit", equal to an empty tensor
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
case 'tensor': {
|
|
173
|
+
// a tensor is a sum of its elements
|
|
174
|
+
return ty.items.map(item => calcWidthOnStack(symbols, item)).reduce((p, c) => p + c, 0);
|
|
175
|
+
}
|
|
176
|
+
case 'StructRef': {
|
|
177
|
+
// a struct is a named tensor: fields one by one;
|
|
178
|
+
// if a struct is generic `Wrapper<T>`, we have typeArgs T=xxx, and replace T in each field
|
|
179
|
+
// (it works unless `T` is used in unions)
|
|
180
|
+
const structRef = symbols.getStruct(ty.structName);
|
|
181
|
+
return structRef.fields.map(f => {
|
|
182
|
+
const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
|
|
183
|
+
return calcWidthOnStack(symbols, fTy);
|
|
184
|
+
}).reduce((p, c) => p + c, 0);
|
|
185
|
+
}
|
|
186
|
+
case 'AliasRef': {
|
|
187
|
+
// an alias is the same as its underlying (target) type;
|
|
188
|
+
// if an alias is generic `Maybe<T>`, we have typeArgs T=xxx, and replace T in its target
|
|
189
|
+
const aliasRef = symbols.getAlias(ty.aliasName);
|
|
190
|
+
const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
|
|
191
|
+
return calcWidthOnStack(symbols, targetTy);
|
|
192
|
+
}
|
|
193
|
+
case 'nullable': {
|
|
194
|
+
// for primitive nullables (common case), like `int?` and `address?`, it's 1 (TVM value or NULL);
|
|
195
|
+
// for non-primitive nullables, the compiler inserts stackWidth and stackTypeId
|
|
196
|
+
return ty.stackWidth ?? 1;
|
|
197
|
+
}
|
|
198
|
+
case 'union': {
|
|
199
|
+
// for union types, the compiler always inserts stackWidth for simplicity (and stackTypeId for each variant)
|
|
200
|
+
return ty.stackWidth;
|
|
201
|
+
}
|
|
202
|
+
case 'genericT': {
|
|
203
|
+
// all generics must have been instantiated up to this point
|
|
204
|
+
throw new Error(`unexpected genericT=${ty.nameT} in calcWidthOnStack`);
|
|
205
|
+
}
|
|
206
|
+
default: {
|
|
207
|
+
// almost all types are TVM primitives that occupy 1 stack slot:
|
|
208
|
+
// - intN is TVM INT
|
|
209
|
+
// - array<T> is TVM TUPLE
|
|
210
|
+
// - map<K, V> is TVM DICT or NULL
|
|
211
|
+
// etc.
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Ty } from './abi-types';
|
|
2
|
+
import { renderTy } from './types-kernel'
|
|
3
|
+
|
|
4
|
+
// Fired when LSP (temporary used for parsing Tolk files into ABI)
|
|
5
|
+
// can not be initialized, or has unresolved types, or other errors while parsing Tolk sources.
|
|
6
|
+
export class CantParseInputTolkFile extends Error {
|
|
7
|
+
constructor(message: string) {
|
|
8
|
+
super(message);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Fired when TyRef contains unresolved symbols,
|
|
13
|
+
// e.g. { kind: 'StructRef', structName: 'non-existing' }
|
|
14
|
+
export class SymbolNotFound extends Error {
|
|
15
|
+
constructor(symbolKind: string, notFoundName: string) {
|
|
16
|
+
super(`${symbolKind} ${notFoundName} not found`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Fired if serialization to/from cells can not be applied to a specific type/struct.
|
|
21
|
+
// E.g., a struct contains `int` (not int32/uint64): it's used only in contract getters.
|
|
22
|
+
// For such structs, `fromSlice`/`store` is just `throw` with description
|
|
23
|
+
// (the overall process of generating wrappers continues, only a specific struct can't be serialized).
|
|
24
|
+
export class CantGeneratePackUnpack extends Error {
|
|
25
|
+
constructor(message: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// `Dictionary<K, V>` from @ton/core allows only predefined KeyT.
|
|
31
|
+
// For others, we can not generate wrappers at all (not only to/from cell, but even declarations).
|
|
32
|
+
export class NonStandardDictKey extends Error {
|
|
33
|
+
constructor(message: string) {
|
|
34
|
+
super(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Some types, like callables/lambdas, can not be used in get methods, we can't generate wrappers then.
|
|
39
|
+
export class NotSupportedTypeOnStack extends Error {
|
|
40
|
+
constructor(ty: Ty, fieldPath: string) {
|
|
41
|
+
super(`'${fieldPath}' can not be used in get methods, because it contains '${renderTy(ty)}'`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// This error is fired "to the outer world".
|
|
46
|
+
export class CantGenerateWrappersAtAll extends Error {
|
|
47
|
+
constructor(where: string, prevEx: any) {
|
|
48
|
+
super(`${where}: [${prevEx.constructor.name}] ${prevEx.message ?? prevEx.toString()}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// This error is fired when invalid input is passed into dynamic serialization.
|
|
53
|
+
// For example, passed an object instead of a number, a field is missing, etc.
|
|
54
|
+
export class InvalidDynamicInput extends Error {
|
|
55
|
+
constructor(msg: string) {
|
|
56
|
+
super(msg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// This error is fired when dynamic serialization (from a JS object to a cell) failed.
|
|
61
|
+
// For example, passed an object instead of a number, a field is missing, etc.
|
|
62
|
+
export class CantPackDynamic extends Error {
|
|
63
|
+
constructor(fieldPath: string, msg: string) {
|
|
64
|
+
super(`cannot serialize '${fieldPath}' dynamically: ${msg}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// This error is fired when dynamic deserialization (from a cell into a JS object) failed.
|
|
69
|
+
// For example, none of union prefixes match.
|
|
70
|
+
export class CantUnpackDynamic extends Error {
|
|
71
|
+
constructor(fieldPath: string, msg: string) {
|
|
72
|
+
super(`cannot deserialize '${fieldPath}' dynamically: ${msg}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// This error is fired when dynamic calling get method (from a JS object to a TVM tuple) failed.
|
|
77
|
+
// For example, passed an object instead of a number, a field is missing, etc.
|
|
78
|
+
export class CantCallGetMethodDynamic extends Error {
|
|
79
|
+
constructor(getMethodName: string, msg: string) {
|
|
80
|
+
super(`cannot call get method '${getMethodName}' dynamically: ${msg}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"baseUrl": "."
|
|
12
|
+
}
|
|
13
|
+
}
|