@typesol/cli 0.22.0 → 0.24.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/CHANGELOG.md +82 -0
- package/README.md +13 -12
- package/SECURITY.md +1 -1
- package/dist/clientgen.js +190 -41
- package/dist/compiler/analyze.js +195 -25
- package/dist/compiler/emit/base.d.ts +28 -5
- package/dist/compiler/emit/base.js +81 -15
- package/dist/compiler/emit/calls.d.ts +18 -1
- package/dist/compiler/emit/calls.js +159 -7
- package/dist/compiler/emit/cpi.d.ts +23 -0
- package/dist/compiler/emit/cpi.js +274 -5
- package/dist/compiler/emit/expressions.d.ts +9 -1
- package/dist/compiler/emit/expressions.js +25 -12
- package/dist/compiler/emit/function.js +8 -4
- package/dist/compiler/emit/program.js +9 -2
- package/dist/compiler/emit/shared.d.ts +43 -3
- package/dist/compiler/emit/shared.js +43 -0
- package/dist/compiler/emit/statements.d.ts +39 -2
- package/dist/compiler/emit/statements.js +346 -87
- package/dist/compiler/emit/storage.d.ts +6 -2
- package/dist/compiler/emit/storage.js +75 -45
- package/dist/compiler/model.d.ts +26 -1
- package/dist/idlpublish.d.ts +6 -1
- package/dist/idlpublish.js +47 -5
- package/dist/portgen.js +1 -1
- package/dist/svmrpc.d.ts +7 -1
- package/dist/svmrpc.js +133 -19
- package/dist/testing.js +23 -3
- package/docs/cli.md +2 -2
- package/docs/client.md +13 -10
- package/docs/errors.md +9 -3
- package/docs/language.md +21 -20
- package/docs/mainnet.md +1 -1
- package/docs/migrate-from-anchor.md +8 -9
- package/docs/test-your-program.md +6 -2
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +3 -2
- package/lib/typesol.d.ts +127 -20
- package/package.json +1 -1
- package/runtime/typesol_rt.h +73 -18
|
@@ -128,7 +128,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
128
128
|
if (ts.isNonNullExpression(node)) {
|
|
129
129
|
const v = this.expr(node.expression, hint && hint.kind !== "option" ? { kind: "option", inner: hint } : hint);
|
|
130
130
|
if (v.type.kind !== "option")
|
|
131
|
-
this.fail(node, "! works on Option values
|
|
131
|
+
this.fail(node, "! works on Option values and optional accounts (acct!.field); check other values with `x !== null`");
|
|
132
132
|
return this.unwrap(v, node);
|
|
133
133
|
}
|
|
134
134
|
if (ts.isPropertyAccessExpression(node) && node.questionDotToken)
|
|
@@ -154,6 +154,8 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
154
154
|
if (ts.isTemplateExpression(node)) {
|
|
155
155
|
// `a${x}b${y}c`: one join on the heap (or a literal when every piece is known at compile time).
|
|
156
156
|
const pieces = this.templatePieces(node);
|
|
157
|
+
if (pieces.only !== undefined)
|
|
158
|
+
return { c: pieces.only, type: STRING };
|
|
157
159
|
if (pieces.text !== undefined)
|
|
158
160
|
return { c: `ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})`, type: STRING, stringLiteral: pieces.text };
|
|
159
161
|
const t = this.tmp("tpl");
|
|
@@ -171,9 +173,11 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
171
173
|
const l = this.lookup(node.text);
|
|
172
174
|
if (l) {
|
|
173
175
|
if (l.closure)
|
|
174
|
-
this.fail(node, `"${node.text}" is a function:
|
|
176
|
+
this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
|
|
175
177
|
if (l.type.kind === "array")
|
|
176
178
|
this.fail(node, `"${node.text}" is an array; use .get(i)`);
|
|
179
|
+
if (l.type.kind === "vec")
|
|
180
|
+
this.fail(node, `"${node.text}" is a list; use .get(i) and .length`);
|
|
177
181
|
if (l.type.kind === "remaining")
|
|
178
182
|
return { c: "", type: l.type };
|
|
179
183
|
return l.type.kind === "pubkey" && !l.ptr ? { c: `(&${l.c})`, type: l.type } : { c: l.c, type: l.type };
|
|
@@ -202,7 +206,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
202
206
|
if (this.accounts.has(node.text))
|
|
203
207
|
this.fail(node, `"${node.text}" is an account; use ${node.text}.key for its address`);
|
|
204
208
|
if (calleeName(node, this.model.helperDecls, this.checker) || this.model.helpers.has(node.text))
|
|
205
|
-
this.fail(node, `"${node.text}" is a function:
|
|
209
|
+
this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
|
|
206
210
|
this.fail(node, `unknown name "${node.text}"`);
|
|
207
211
|
}
|
|
208
212
|
if (ts.isPropertyAccessExpression(node))
|
|
@@ -261,6 +265,13 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
261
265
|
if (field === "length") {
|
|
262
266
|
if (this.isRemaining(node.expression))
|
|
263
267
|
return { c: `((uint64_t)(params->ka_num - ${this.ix.accounts.length}))`, type: U64 };
|
|
268
|
+
// The number of elements a map / filter chain gives: counted, nothing stored.
|
|
269
|
+
if (this.isPipeline(node.expression)) {
|
|
270
|
+
const n = this.tmp("count");
|
|
271
|
+
this.line(`uint64_t ${n} = 0;`);
|
|
272
|
+
this.eachElement(node.expression, node, { index: `__ln${this.temp++}`, ci: this.tmp("ln_i") }, () => this.line(`${n}++;`));
|
|
273
|
+
return { c: n, type: U64 };
|
|
274
|
+
}
|
|
264
275
|
const arr = this.ref(node.expression);
|
|
265
276
|
if (arr?.type.kind === "array")
|
|
266
277
|
return { c: `${arr.type.len}ULL`, type: U64 };
|
|
@@ -446,16 +457,20 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
446
457
|
lit(known);
|
|
447
458
|
else {
|
|
448
459
|
const mark = this.lines.length;
|
|
449
|
-
const
|
|
460
|
+
const converted = { yes: false };
|
|
461
|
+
const c = this.toText(span.expression, converted);
|
|
450
462
|
for (const p of parts)
|
|
451
463
|
if (p.c !== undefined)
|
|
452
464
|
p.c = this.settle({ c: p.c, type: STRING }, mark).c;
|
|
453
|
-
parts.push({ c });
|
|
465
|
+
parts.push({ c, converted: converted.yes });
|
|
454
466
|
}
|
|
455
467
|
lit(span.literal.text);
|
|
456
468
|
}
|
|
457
469
|
if (parts.every((p) => p.text !== undefined))
|
|
458
470
|
return { text: parts.map((p) => p.text).join("") };
|
|
471
|
+
// `${n}` alone: the text a conversion just made (or a constant one) is already a string of its own; no join.
|
|
472
|
+
if (parts.length === 1 && parts[0].converted)
|
|
473
|
+
return { only: parts[0].c };
|
|
459
474
|
const strOf = (text) => `ts_str((const uint8_t *)${cString(text)}, ${Buffer.byteLength(text, "utf8")})`;
|
|
460
475
|
const arr = this.tmp("parts");
|
|
461
476
|
this.line(`TS_Str ${arr}[${parts.length}] = {${parts.map((p) => (p.text !== undefined ? strOf(p.text) : p.c)).join(", ")}};`);
|
|
@@ -485,10 +500,13 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
485
500
|
}
|
|
486
501
|
return undefined;
|
|
487
502
|
}
|
|
488
|
-
|
|
503
|
+
/** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
|
|
504
|
+
toText(node, converted) {
|
|
489
505
|
const v = this.expr(node);
|
|
490
506
|
if (v.type.kind === "string")
|
|
491
507
|
return v.c;
|
|
508
|
+
if (converted)
|
|
509
|
+
converted.yes = true;
|
|
492
510
|
if (v.type.kind === "option" && (v.type.inner.kind === "int" || v.type.inner.kind === "bool")) {
|
|
493
511
|
// `${opt}`: the value, or "null" like JS.
|
|
494
512
|
const inner = v.type.inner;
|
|
@@ -548,12 +566,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
548
566
|
(ts.isBigIntLiteral(node.whenFalse) || ts.isPrefixUnaryExpression(node.whenFalse));
|
|
549
567
|
if (!type && !bothLiteral && (ts.isBigIntLiteral(node.whenTrue) || node.whenTrue.kind === ts.SyntaxKind.NullKeyword)) {
|
|
550
568
|
// Type the non-literal branch first, without emitting it, to give the literal its type.
|
|
551
|
-
const
|
|
552
|
-
const probe = this.reporter.guard(() => this.expr(node.whenFalse));
|
|
553
|
-
this.lines.length = saved.lines;
|
|
554
|
-
this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
|
|
555
|
-
this.temp = saved.temp;
|
|
556
|
-
this.reporter.diagnostics.length = saved.diagnostics;
|
|
569
|
+
const probe = this.probe(() => this.expr(node.whenFalse));
|
|
557
570
|
if (probe && probe.literal === undefined && !probe.isNull)
|
|
558
571
|
type = probe.type;
|
|
559
572
|
}
|
|
@@ -3,7 +3,7 @@ import { snakeCase } from "../analyze.js";
|
|
|
3
3
|
import { decodeAddress } from "../../keys.js";
|
|
4
4
|
import { INT_INFO, isScalar, isWritable, scalarSize, typeName, } from "../model.js";
|
|
5
5
|
import { borshIdentical, borshMinSize } from "./borsh.js";
|
|
6
|
-
import { MAX_ACCOUNTS, ATA_PROGRAM_BYTES, cBytes, cString, optC, fieldC, storageDecl, helperSignature } from "./shared.js";
|
|
6
|
+
import { MAX_ACCOUNTS, ATA_PROGRAM_BYTES, cBytes, cString, optC, fieldC, storageDecl, helperSignature, emptySizeC, vecLenCheck } from "./shared.js";
|
|
7
7
|
import { StatementEmitter } from "./statements.js";
|
|
8
8
|
/**
|
|
9
9
|
* One instruction or helper as a C function: the instruction prologue (account checks, constraints and
|
|
@@ -42,7 +42,7 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
42
42
|
head.push(` TS_TRY(ts_check_writable(${v}));`);
|
|
43
43
|
// A @layout borsh account only has to hold its smallest Borsh form (unpacking checks the rest).
|
|
44
44
|
if (k.kind === "account")
|
|
45
|
-
head.push(` TS_TRY(ts_check_program_account(${v}, params->program_id, TS_DISC_${k.struct.name}, ${k.struct.layout === "borsh" ? borshMinSize(k.struct, this.model) :
|
|
45
|
+
head.push(` TS_TRY(ts_check_program_account(${v}, params->program_id, TS_DISC_${k.struct.name}, ${k.struct.layout === "borsh" ? borshMinSize(k.struct, this.model) : emptySizeC(k.struct)}));`);
|
|
46
46
|
// A token account created by this instruction is checked where it is created (create/createIfNeeded).
|
|
47
47
|
if (k.kind === "token" && !k.init)
|
|
48
48
|
head.push(` TS_TRY(ts_check_token_account(${v}));`);
|
|
@@ -106,7 +106,7 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
106
106
|
if (k.struct.vec) {
|
|
107
107
|
head.push(` uint64_t entry_${a.name} = ${v}->data_len;`, ` (void)entry_${a.name};`);
|
|
108
108
|
if (k.kind === "account")
|
|
109
|
-
head.push(
|
|
109
|
+
head.push(...vecLenCheck(k.struct, v, `d_${a.name}`));
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
// realloc(x, …) needs the size the account had when the instruction started (the 10 KiB growth limit).
|
|
@@ -150,14 +150,16 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
150
150
|
// The loader passes each account once and marks repeats as duplicates, which share its record (ts_deserialize),
|
|
151
151
|
// so two parameters name the same account exactly when their pointers are equal.
|
|
152
152
|
const writable = ix.accounts.filter((a) => isWritable(a.kind));
|
|
153
|
+
const pairs = [];
|
|
153
154
|
for (let i = 0; i < writable.length; i++)
|
|
154
155
|
for (let j = i + 1; j < writable.length; j++) {
|
|
155
156
|
const present = [writable[i], writable[j]]
|
|
156
157
|
.filter((a) => a.optional)
|
|
157
158
|
.map((a) => `a_${a.name} && `)
|
|
158
159
|
.join("");
|
|
159
|
-
|
|
160
|
+
pairs.push(`if (${present}a_${writable[i].name} == a_${writable[j].name}) TS_FAIL(TS_ERR_DUPLICATE_MUT, "TypeSOL: ${writable[i].name} and ${writable[j].name} must be different accounts");`);
|
|
160
161
|
}
|
|
162
|
+
head.push(...pairs.map((p) => ` ${p}`));
|
|
161
163
|
// Arguments: Borsh, read sequentially (strings make later offsets dynamic).
|
|
162
164
|
if (ix.args.length)
|
|
163
165
|
head.push(` uint64_t off = ${ix.discriminator.length};`);
|
|
@@ -219,6 +221,8 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
219
221
|
const size = this.fieldBytes(elem);
|
|
220
222
|
need("4");
|
|
221
223
|
head.push(` TS_VecArg arg_${a.name};`, ` arg_${a.name}.len = (uint32_t)ts_read_le(${at}, 4);`, ` off += 4;`);
|
|
224
|
+
if (a.type.cap !== undefined)
|
|
225
|
+
head.push(` if (arg_${a.name}.len > ${a.type.cap}) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: more elements than the FixedVec argument holds");`);
|
|
222
226
|
need(`(uint64_t)arg_${a.name}.len * ${size}`);
|
|
223
227
|
head.push(` arg_${a.name}.ptr = ${at};`, ` off += (uint64_t)arg_${a.name}.len * ${size};`);
|
|
224
228
|
// Struct elements are read in place, so check them all now; scalars are checked when read.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_ACCOUNTS, cBytes, scalarStorageC, optC, storageDecl, helperSignature, fixedVecC } from "./shared.js";
|
|
1
|
+
import { MAX_ACCOUNTS, cBytes, scalarStorageC, optC, storageDecl, helperSignature, fixedVecC, laterVecC } from "./shared.js";
|
|
2
2
|
import { FunctionEmitter } from "./function.js";
|
|
3
3
|
import { emitBorshCodecs } from "./borsh.js";
|
|
4
4
|
/**
|
|
@@ -96,9 +96,14 @@ export function emitProgram(analysis, opts = {}) {
|
|
|
96
96
|
return;
|
|
97
97
|
emitted.add(s.name);
|
|
98
98
|
s.fields.forEach((f) => deps(f.type));
|
|
99
|
+
// Growable Vecs after the first live past the C struct (see StructDef.vecs): a header type each.
|
|
100
|
+
const later = new Set((s.vecs ?? []).slice(1).map((v) => v.name));
|
|
101
|
+
for (const v of (s.vecs ?? []).slice(1))
|
|
102
|
+
out.push(`typedef struct __attribute__((packed)) {`, ` uint32_t len;`, ` ${storageDecl(v.elem, "items[]")};`, `} ${laterVecC(s.name, v.name)};`);
|
|
99
103
|
out.push(`typedef struct __attribute__((packed)) {`);
|
|
100
104
|
for (const f of s.fields)
|
|
101
|
-
|
|
105
|
+
if (!later.has(f.name))
|
|
106
|
+
out.push(` ${storageDecl(f.type, f.name)};`);
|
|
102
107
|
if (!s.fields.length)
|
|
103
108
|
out.push(` uint8_t __empty[0];`); // a marker account (Anchor `pub struct NftAuthority {}`): discriminator only
|
|
104
109
|
out.push(`} TSS_${s.name};`);
|
|
@@ -113,6 +118,8 @@ export function emitProgram(analysis, opts = {}) {
|
|
|
113
118
|
emitStruct(s);
|
|
114
119
|
for (const e of model.dataEnums.values())
|
|
115
120
|
deps({ kind: "dataEnum", name: e.name });
|
|
121
|
+
for (const t of model.localVecs.values())
|
|
122
|
+
deps(t);
|
|
116
123
|
for (const k of model.constants.values())
|
|
117
124
|
if (typeof k.value === "object")
|
|
118
125
|
out.push(`static const SolPubkey TSK_${k.name} = {${cBytes(k.value.pubkey)}};`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { type AccountParam, type FieldType, type Helper, type IntName, type ParamKind, type ScalarType, type ValueType } from "../model.js";
|
|
2
|
+
import { type AccountParam, type FieldType, type Helper, type IntName, type ParamKind, type ScalarType, type StructDef, type ValueType } from "../model.js";
|
|
3
3
|
/** Maximum accounts the generated entrypoint deserializes. */
|
|
4
4
|
export declare const MAX_ACCOUNTS = 32;
|
|
5
5
|
export declare const MAX_SEEDS = 15;
|
|
@@ -46,16 +46,27 @@ export interface Ref {
|
|
|
46
46
|
vec?: {
|
|
47
47
|
len: string;
|
|
48
48
|
items: string;
|
|
49
|
+
/** The account's size without this Vec's elements (so with `n` of them: 8 + fixed + n × elemSize). */
|
|
49
50
|
fixed: string;
|
|
50
51
|
elemSize: number;
|
|
51
52
|
resizable: boolean;
|
|
52
53
|
cap?: number;
|
|
54
|
+
/** Growable Vecs stored after this one move when it grows or shrinks: where its elements end, and their paths. */
|
|
55
|
+
tail?: {
|
|
56
|
+
end: string;
|
|
57
|
+
dataEnd: string;
|
|
58
|
+
paths: string[];
|
|
59
|
+
};
|
|
53
60
|
};
|
|
54
61
|
}
|
|
55
62
|
export interface Local {
|
|
56
63
|
c: string;
|
|
64
|
+
/** A value, or a local FixedArray / FixedVec (`fixedArray<T, N>(…)`, `const ys = xs.map(…)`) held in place. */
|
|
57
65
|
type: ValueType | (FieldType & {
|
|
58
66
|
kind: "array";
|
|
67
|
+
}) | (FieldType & {
|
|
68
|
+
kind: "vec";
|
|
69
|
+
cap: number;
|
|
59
70
|
});
|
|
60
71
|
mutable: boolean;
|
|
61
72
|
/** Pubkey held as a `const SolPubkey *` (helper parameters) rather than a value. */
|
|
@@ -64,15 +75,26 @@ export interface Local {
|
|
|
64
75
|
readonly?: boolean;
|
|
65
76
|
/** For struct / data-enum references: the storage paths they alias (e.g. "book.asks[]"), and the declaration. */
|
|
66
77
|
paths?: string[];
|
|
67
|
-
decl?: ts.VariableDeclaration;
|
|
78
|
+
decl?: ts.VariableDeclaration | ts.ParameterDeclaration;
|
|
68
79
|
/** A local closure (`const f = (…) => …` reading the enclosing function's variables), expanded where it is called. */
|
|
69
80
|
closure?: Closure;
|
|
70
81
|
}
|
|
71
82
|
/** A closure and the scopes it was declared in (its free names resolve there, wherever it is called). */
|
|
72
83
|
export interface Closure {
|
|
73
|
-
|
|
84
|
+
/** The function expanded: a closure, or a helper expanded where it is called because it was given one. */
|
|
85
|
+
node: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration;
|
|
74
86
|
name: string;
|
|
75
87
|
scopes: Map<string, Local>[];
|
|
88
|
+
/**
|
|
89
|
+
* The instruction arguments and accounts its body may name: those where it was written for a closure, none for a
|
|
90
|
+
* helper or a function a helper made (they see only their parameters and the module).
|
|
91
|
+
*/
|
|
92
|
+
env: {
|
|
93
|
+
args: Map<string, Local>;
|
|
94
|
+
accounts: Map<string, AccountParam>;
|
|
95
|
+
};
|
|
96
|
+
/** The `typesol` imports of the file it is written in: its type names mean what they mean there. */
|
|
97
|
+
imports: Map<string, string>;
|
|
76
98
|
}
|
|
77
99
|
export declare const cBytes: (bytes: number[]) => string;
|
|
78
100
|
/** C expressions that nothing in a TypeSOL expression can change: temporaries, locals, arguments, account keys, constants. */
|
|
@@ -102,6 +124,24 @@ export interface EmitOptions {
|
|
|
102
124
|
/** Log a marker and the remaining compute units before every statement (`typesol build --profile`). */
|
|
103
125
|
profile?: boolean;
|
|
104
126
|
}
|
|
127
|
+
/** C type of a growable Vec stored after the first one of struct `s`: `{ uint32_t len; T items[]; }`. */
|
|
128
|
+
export declare function laterVecC(s: string, field: string): string;
|
|
129
|
+
/** The account size of struct `s` with every growable Vec empty, as C (8-byte discriminator not included). */
|
|
130
|
+
export declare function emptySizeC(s: StructDef): string;
|
|
131
|
+
/**
|
|
132
|
+
* Where growable Vec `k` of a struct at `base` (a C lvalue of type TSS_<S>) starts, as C: its length and elements.
|
|
133
|
+
* The first is a member of the struct; each further one starts right after the previous one's elements.
|
|
134
|
+
*/
|
|
135
|
+
export declare function vecPlace(s: StructDef, base: string, k: number): {
|
|
136
|
+
len: string;
|
|
137
|
+
items: string;
|
|
138
|
+
end: string;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* C lines checking that an account's size matches its growable Vecs' lengths (`d`: its TSS_<S> pointer, `a`: its
|
|
142
|
+
* TsAccount). One Vec: ts_check_vec_len; more: each length read only once the bytes before it are known to exist.
|
|
143
|
+
*/
|
|
144
|
+
export declare function vecLenCheck(s: StructDef, a: string, d: string, pad?: string): string[];
|
|
105
145
|
/** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
|
|
106
146
|
export declare function fixedVecC(t: FieldType & {
|
|
107
147
|
kind: "vec";
|
|
@@ -202,6 +202,49 @@ export function helperSignature(h) {
|
|
|
202
202
|
params.push(h.returns.kind === "struct" ? `TSS_${h.returns.name} *ret` : h.returns.kind === "string" ? `TS_Str *ret` : `${scalarC(h.returns)} *ret`);
|
|
203
203
|
return `static uint64_t fn_${h.name}(${params.join(", ")})`;
|
|
204
204
|
}
|
|
205
|
+
/** C type of a growable Vec stored after the first one of struct `s`: `{ uint32_t len; T items[]; }`. */
|
|
206
|
+
export function laterVecC(s, field) {
|
|
207
|
+
return `TSW_${s}__${field}`;
|
|
208
|
+
}
|
|
209
|
+
/** The account size of struct `s` with every growable Vec empty, as C (8-byte discriminator not included). */
|
|
210
|
+
export function emptySizeC(s) {
|
|
211
|
+
const n = s.vecs?.length ?? 0;
|
|
212
|
+
return n > 1 ? `(sizeof(TSS_${s.name}) + ${4 * (n - 1)})` : `sizeof(TSS_${s.name})`;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Where growable Vec `k` of a struct at `base` (a C lvalue of type TSS_<S>) starts, as C: its length and elements.
|
|
216
|
+
* The first is a member of the struct; each further one starts right after the previous one's elements.
|
|
217
|
+
*/
|
|
218
|
+
export function vecPlace(s, base, k) {
|
|
219
|
+
const vs = s.vecs;
|
|
220
|
+
const first = vs[0];
|
|
221
|
+
let end = `((uint8_t *)${base}.${first.name} + (uint64_t)${base}.${first.name}__len * ${first.elemSize})`;
|
|
222
|
+
let place = { len: `${base}.${first.name}__len`, items: `${base}.${first.name}`, end };
|
|
223
|
+
for (let j = 1; j <= k; j++) {
|
|
224
|
+
const v = vs[j];
|
|
225
|
+
const h = `((${laterVecC(s.name, v.name)} *)${end})`;
|
|
226
|
+
end = `((uint8_t *)${h}->items + (uint64_t)${h}->len * ${v.elemSize})`;
|
|
227
|
+
place = { len: `${h}->len`, items: `${h}->items`, end };
|
|
228
|
+
}
|
|
229
|
+
return place;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* C lines checking that an account's size matches its growable Vecs' lengths (`d`: its TSS_<S> pointer, `a`: its
|
|
233
|
+
* TsAccount). One Vec: ts_check_vec_len; more: each length read only once the bytes before it are known to exist.
|
|
234
|
+
*/
|
|
235
|
+
export function vecLenCheck(s, a, d, pad = " ") {
|
|
236
|
+
const vs = s.vecs ?? [];
|
|
237
|
+
if (vs.length === 1)
|
|
238
|
+
return [`${pad}TS_TRY(ts_check_vec_len(${a}, sizeof(TSS_${s.name}), ${d}->${vs[0].name}__len, TS_ELEM_${s.name}));`];
|
|
239
|
+
const o = `${d.replace(/\W/g, "")}__vo`;
|
|
240
|
+
const lines = [`${pad}{`, `${pad} uint64_t ${o} = sizeof(TSS_${s.name}) + (uint64_t)${d}->${vs[0].name}__len * ${vs[0].elemSize};`];
|
|
241
|
+
for (const v of vs.slice(1)) {
|
|
242
|
+
lines.push(`${pad} if (${a}->data_len < 8 + ${o} + 4) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account size does not match its Vec lengths");`);
|
|
243
|
+
lines.push(`${pad} ${o} += 4 + (uint64_t)((${laterVecC(s.name, v.name)} *)(${a}->data + 8 + ${o}))->len * ${v.elemSize};`);
|
|
244
|
+
}
|
|
245
|
+
lines.push(`${pad} if (${a}->data_len != 8 + ${o}) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account size does not match its Vec lengths");`, `${pad}}`);
|
|
246
|
+
return lines;
|
|
247
|
+
}
|
|
205
248
|
/** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
|
|
206
249
|
export function fixedVecC(t) {
|
|
207
250
|
return `TSF_${fixedVecName(t)}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { type
|
|
2
|
+
import { type FieldType } from "../model.js";
|
|
3
|
+
import { type Value, type Closure, type Ref } from "./shared.js";
|
|
3
4
|
import { CpiEmitter } from "./cpi.js";
|
|
4
5
|
/**
|
|
5
6
|
* Statements: variables, assignment, control flow and return.
|
|
@@ -17,6 +18,42 @@ export declare abstract class StatementEmitter extends CpiEmitter {
|
|
|
17
18
|
* As in JS, the length is read again before every step, so a body that pushes or pops sees the new length.
|
|
18
19
|
*/
|
|
19
20
|
protected forOf(stmt: ts.ForOfStatement): void;
|
|
21
|
+
/** The function an identifier names (a closure, a local function or a helper), as something to expand; else undefined. */
|
|
22
|
+
protected functionNamed(id: ts.Identifier): Closure | undefined;
|
|
23
|
+
/** `xs.map(…)` / `xs.filter(…)` (or a chain of them): a list built element by element, not stored anywhere yet. */
|
|
24
|
+
protected isPipeline(e: ts.Expression): e is ts.CallExpression;
|
|
25
|
+
/** The list under a chain of map / filter calls, and the calls in the order they run. */
|
|
26
|
+
protected pipelineStages(e: ts.Expression): {
|
|
27
|
+
base: ts.Expression;
|
|
28
|
+
stages: {
|
|
29
|
+
kind: "map" | "filter";
|
|
30
|
+
fn: ts.ArrowFunction | ts.FunctionExpression;
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Run `body` for each element of a list, or of a chain of `map` / `filter` calls on one (`xs.filter(f).map(g)`):
|
|
35
|
+
* one index loop over the list (its length read every step, as `for … of`), each callback expanded in it. A
|
|
36
|
+
* filter skips to the next element, a map binds its result, so a chain builds no list. `body` gets the element (as
|
|
37
|
+
* `list.get(i)` gives it, structs by reference, or the mapped value) and its index in the chain's output (after a
|
|
38
|
+
* filter, the count of elements it let through, as JS gives callbacks).
|
|
39
|
+
*/
|
|
40
|
+
protected eachElement(list: ts.Expression, at: ts.Node, names: {
|
|
41
|
+
index: string;
|
|
42
|
+
ci: string;
|
|
43
|
+
next?: string;
|
|
44
|
+
alwaysNext?: boolean;
|
|
45
|
+
}, body: (element: ts.Expression, index: ts.Expression) => void): void;
|
|
46
|
+
/**
|
|
47
|
+
* `const ys = xs.map(…)` / `xs.filter(…)`: the chain's elements in a local FixedVec, as big as the list it starts
|
|
48
|
+
* from (a FixedArray's length, a FixedVec's capacity) or as annotated (`const ys: FixedVec<u64, 16> = …`, needed
|
|
49
|
+
* when it starts from a Vec). It lives on the stack, so it holds at most 2 KiB; one more element than it holds aborts.
|
|
50
|
+
*/
|
|
51
|
+
protected localList(d: ts.VariableDeclaration, init: ts.CallExpression, c: string): FieldType & {
|
|
52
|
+
kind: "vec";
|
|
53
|
+
cap: number;
|
|
54
|
+
};
|
|
55
|
+
/** The element type of a Vec, FixedArray, FixedVec or Vec argument. */
|
|
56
|
+
protected listElem(list: ts.Expression): FieldType;
|
|
20
57
|
/** Whether `e` is the remaining accounts (or a const bound to them). */
|
|
21
58
|
protected isRemaining(e: ts.Expression): boolean;
|
|
22
59
|
/**
|
|
@@ -30,7 +67,7 @@ export declare abstract class StatementEmitter extends CpiEmitter {
|
|
|
30
67
|
* was declared with, so a block around the call that declares the same name does not change what it reads, and
|
|
31
68
|
* writes to captured `let`s are seen by the enclosing function. Returns the result (undefined for no value).
|
|
32
69
|
*/
|
|
33
|
-
protected expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node): Value | undefined;
|
|
70
|
+
protected expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node, into?: Ref): Value | undefined;
|
|
34
71
|
/**
|
|
35
72
|
* `xs.forEach / some / every / find / findIndex / reduce / includes / indexOf` on a Vec, FixedArray, FixedVec or Vec
|
|
36
73
|
* argument: an index loop (the length read each step, as `for … of`), the callback (an arrow written at the call)
|