@typesol/cli 0.17.0 → 0.18.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 +75 -0
- package/README.md +4 -4
- package/dist/clientgen.js +2 -0
- package/dist/compiler/analyze.d.ts +10 -1
- package/dist/compiler/analyze.js +129 -45
- package/dist/compiler/emit/base.d.ts +29 -1
- package/dist/compiler/emit/base.js +79 -8
- package/dist/compiler/emit/borsh.js +71 -5
- package/dist/compiler/emit/calls.d.ts +2 -3
- package/dist/compiler/emit/calls.js +93 -15
- package/dist/compiler/emit/cpi.d.ts +30 -0
- package/dist/compiler/emit/cpi.js +955 -39
- package/dist/compiler/emit/expressions.d.ts +5 -0
- package/dist/compiler/emit/expressions.js +66 -3
- package/dist/compiler/emit/function.js +51 -6
- package/dist/compiler/emit/shared.d.ts +8 -0
- package/dist/compiler/emit/shared.js +4 -1
- package/dist/compiler/emit/statements.d.ts +21 -0
- package/dist/compiler/emit/statements.js +369 -16
- package/dist/compiler/emit/storage.d.ts +2 -0
- package/dist/compiler/emit/storage.js +28 -4
- package/dist/compiler/idl.js +4 -1
- package/dist/compiler/model.d.ts +29 -2
- package/dist/portgen.js +32 -2
- package/docs/errors.md +3 -2
- package/docs/language.md +30 -22
- package/docs/migrate-from-anchor.md +10 -2
- package/lib/typesol.d.ts +396 -21
- package/package.json +1 -1
- package/runtime/typesol_rt.h +198 -0
|
@@ -12,6 +12,11 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
|
|
|
12
12
|
* check is a backstop: an empty option aborts with 5108.
|
|
13
13
|
*/
|
|
14
14
|
protected unwrap(v: Value, node: ts.Node): Value;
|
|
15
|
+
/**
|
|
16
|
+
* `acct?.field` on an optional account, `x.home?.field` on an Option<struct> place: the field as an Option, empty
|
|
17
|
+
* when the account was left out / the option is empty. (A method call through `?.` is not supported.)
|
|
18
|
+
*/
|
|
19
|
+
protected optionalChain(node: ts.PropertyAccessExpression): Value;
|
|
15
20
|
protected literal(value: bigint, type: ValueType, node: ts.Node): Value;
|
|
16
21
|
protected expr(node: ts.Expression, hint?: ValueType): Value;
|
|
17
22
|
protected property(node: ts.PropertyAccessExpression): Value;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { calleeName, resolveValueType } from "../analyze.js";
|
|
3
|
-
import { INT_INFO, unsignedC, sameType, typeName } from "../model.js";
|
|
3
|
+
import { INT_INFO, unsignedC, optionKey, sameType, typeName } from "../model.js";
|
|
4
4
|
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cString, intLiteral, scalarStorageC, optC, valueC, vecCount } from "./shared.js";
|
|
5
5
|
import { StorageEmitter } from "./storage.js";
|
|
6
6
|
/**
|
|
@@ -52,6 +52,52 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
52
52
|
return { c: `(${t}.v != 0)`, type: inner };
|
|
53
53
|
return { c: `${t}.v`, type: inner };
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* `acct?.field` on an optional account, `x.home?.field` on an Option<struct> place: the field as an Option, empty
|
|
57
|
+
* when the account was left out / the option is empty. (A method call through `?.` is not supported.)
|
|
58
|
+
*/
|
|
59
|
+
optionalChain(node) {
|
|
60
|
+
const base = node.expression;
|
|
61
|
+
const optAccount = ts.isIdentifier(base) ? this.accounts.get(base.text) : undefined;
|
|
62
|
+
let present;
|
|
63
|
+
if (optAccount?.optional)
|
|
64
|
+
present = `a_${optAccount.name} != NULL`;
|
|
65
|
+
else {
|
|
66
|
+
const r = this.ref(base);
|
|
67
|
+
if (!r || r.type.kind !== "option" || r.type.inner.kind !== "struct")
|
|
68
|
+
this.fail(node, "?. works on optional accounts and Option<struct> values (other values are never null)");
|
|
69
|
+
present = `${r.lv}.some`;
|
|
70
|
+
}
|
|
71
|
+
// The same access without `?.`, lowered where the base is known to be there.
|
|
72
|
+
const f = ts.factory;
|
|
73
|
+
const plain = f.createPropertyAccessExpression(base, node.name);
|
|
74
|
+
ts.setTextRange(plain, node);
|
|
75
|
+
plain.parent = node.parent;
|
|
76
|
+
const t = this.tmp("oc");
|
|
77
|
+
const declAt = this.lines.length;
|
|
78
|
+
this.line(`/* ${t} */`);
|
|
79
|
+
let inner;
|
|
80
|
+
this.line(`if (${present}) {`);
|
|
81
|
+
this.block(() => {
|
|
82
|
+
const v = this.expr(plain);
|
|
83
|
+
const typed = v.literal !== undefined ? this.literal(v.literal, U64, node) : v;
|
|
84
|
+
if (typed.type.kind === "option") {
|
|
85
|
+
inner = typed.type.inner;
|
|
86
|
+
this.line(`ts_memcpy(&${t}, &${typed.c}, sizeof(${t}));`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (typed.type.kind !== "int" && typed.type.kind !== "bool" && typed.type.kind !== "pubkey" && typed.type.kind !== "enum")
|
|
90
|
+
this.fail(node, `?. gives an Option, so the field must be an integer, boolean, Pubkey or enum, not ${typeName(typed.type)}`);
|
|
91
|
+
inner = typed.type;
|
|
92
|
+
this.line(`${t}.some = 1;`);
|
|
93
|
+
this.line(inner.kind === "pubkey" ? `${t}.v = *(${typed.c});` : `${t}.v = ${typed.c};`);
|
|
94
|
+
});
|
|
95
|
+
this.line(`}`);
|
|
96
|
+
const scalar = inner;
|
|
97
|
+
this.model.options.set(optionKey(scalar), scalar);
|
|
98
|
+
this.lines[declAt] = `${" ".repeat(this.indent)}${optC(scalar)} ${t};\n${" ".repeat(this.indent)}ts_memzero(&${t}, sizeof(${t}));`;
|
|
99
|
+
return { c: t, type: { kind: "option", inner: scalar } };
|
|
100
|
+
}
|
|
55
101
|
literal(value, type, node) {
|
|
56
102
|
if (type.kind !== "int")
|
|
57
103
|
this.fail(node, `expected ${typeName(type)}, got an integer literal`);
|
|
@@ -84,6 +130,11 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
84
130
|
this.fail(node, "! works on Option values; check optional accounts with `x !== null`");
|
|
85
131
|
return this.unwrap(v, node);
|
|
86
132
|
}
|
|
133
|
+
if (ts.isPropertyAccessExpression(node) && node.questionDotToken)
|
|
134
|
+
return this.optionalChain(node);
|
|
135
|
+
const unbanged = this.withoutBang(node);
|
|
136
|
+
if (unbanged)
|
|
137
|
+
return this.expr(unbanged, hint);
|
|
87
138
|
this.noOptionalChain(node);
|
|
88
139
|
if (ts.isBigIntLiteral(node)) {
|
|
89
140
|
const value = BigInt(node.text.slice(0, -1).replace(/_/g, ""));
|
|
@@ -126,6 +177,8 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
126
177
|
if (ts.isIdentifier(node)) {
|
|
127
178
|
const l = this.lookup(node.text);
|
|
128
179
|
if (l) {
|
|
180
|
+
if (l.closure)
|
|
181
|
+
this.fail(node, `"${node.text}" is a function: TypeSOL functions are called (${node.text}(…)), not passed around or stored`);
|
|
129
182
|
if (l.type.kind === "array")
|
|
130
183
|
this.fail(node, `"${node.text}" is an array; use .get(i)`);
|
|
131
184
|
if (l.type.kind === "remaining")
|
|
@@ -248,8 +301,11 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
248
301
|
if (ts.isIdentifier(node.expression)) {
|
|
249
302
|
const a = this.accounts.get(node.expression.text);
|
|
250
303
|
if (a) {
|
|
251
|
-
this.assertUsable(a, node);
|
|
252
304
|
const v = `a_${a.name}`;
|
|
305
|
+
// The address is known before an Init account is created (Anchor seeds use `mint.key()` that way).
|
|
306
|
+
if (field === "key" && !a.optional)
|
|
307
|
+
return { c: `&${v}->key`, type: PUBKEY };
|
|
308
|
+
this.assertUsable(a, node);
|
|
253
309
|
if (field === "key")
|
|
254
310
|
return { c: `&${v}->key`, type: PUBKEY };
|
|
255
311
|
if (field === "lamports")
|
|
@@ -404,7 +460,14 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
404
460
|
this.line(this.textOfInt(typed.type.name, typed.c, t));
|
|
405
461
|
return t;
|
|
406
462
|
}
|
|
407
|
-
|
|
463
|
+
if (typed.type.kind === "pubkey") {
|
|
464
|
+
// A Pubkey (or Hash) as base58, as Rust's `{}` prints it.
|
|
465
|
+
const t = this.tmp("b58");
|
|
466
|
+
this.line(`TS_Str ${t};`);
|
|
467
|
+
this.line(`TS_TRY(ts_base58_32((const uint8_t *)(${typed.c}), &${t}));`);
|
|
468
|
+
return t;
|
|
469
|
+
}
|
|
470
|
+
this.fail(node, `only strings, integers, booleans and Pubkeys can be joined into text, not ${typeName(typed.type)}`);
|
|
408
471
|
}
|
|
409
472
|
/** C statement writing the decimal text of an integer into the TS_Str `out`. */
|
|
410
473
|
textOfInt(name, c, out) {
|
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
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
|
-
import { borshMinSize } from "./borsh.js";
|
|
5
|
+
import { borshIdentical, borshMinSize } from "./borsh.js";
|
|
6
6
|
import { MAX_ACCOUNTS, ATA_PROGRAM_BYTES, cBytes, cString, optC, fieldC, storageDecl, helperSignature } from "./shared.js";
|
|
7
7
|
import { StatementEmitter } from "./statements.js";
|
|
8
8
|
/**
|
|
@@ -46,8 +46,11 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
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}));`);
|
|
49
|
-
|
|
49
|
+
// A mint created by this instruction is checked where it is created.
|
|
50
|
+
if (k.kind === "mint" && !k.init)
|
|
50
51
|
head.push(` TS_TRY(ts_check_mint(${v}));`);
|
|
52
|
+
if (k.kind === "info" && k.systemOwned)
|
|
53
|
+
head.push(` if (!ts_key_eq(&${v}->owner, &TS_SYSTEM_PROGRAM_ID)) TS_FAIL(TS_ERR_NOT_SYSTEM_OWNED, "TypeSOL: ${a.name} must be owned by the System Program");`);
|
|
51
54
|
if (k.kind === "system")
|
|
52
55
|
head.push(` TS_TRY(ts_check_address(${v}, &TS_SYSTEM_PROGRAM_ID));`);
|
|
53
56
|
if (k.kind === "instructionsSysvar")
|
|
@@ -96,7 +99,7 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
96
99
|
const S = k.struct.name;
|
|
97
100
|
head.push(` TSS_${S} *d_${a.name} = NULL;`, ` TS_TRY(ts_alloc(sizeof(TSS_${S}), (uint8_t **)&d_${a.name}));`);
|
|
98
101
|
if (k.kind === "account")
|
|
99
|
-
head.push(` TS_TRY(ts_unpack_${S}(${v}->data + 8, ${v}->data_len - 8, d_${a.name}));`);
|
|
102
|
+
head.push(` TS_TRY(ts_unpack_${S}(${v}->data + 8, ${v}->data_len - 8, d_${a.name}, NULL));`);
|
|
100
103
|
}
|
|
101
104
|
else if (k.kind === "account" || k.kind === "init" || k.kind === "zeroed" || k.kind === "initIfNeeded") {
|
|
102
105
|
head.push(` TSS_${k.struct.name} *d_${a.name} = (TSS_${k.struct.name} *)(${v}->data + 8);`);
|
|
@@ -200,6 +203,17 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
200
203
|
}
|
|
201
204
|
head.push(` }`);
|
|
202
205
|
}
|
|
206
|
+
else if (a.type.kind === "vecArg" && a.type.elem.kind === "string") {
|
|
207
|
+
// Borsh Vec<String>: u32 count, then each string's u32 length and UTF-8 bytes. Kept as views of the
|
|
208
|
+
// instruction data in a heap array of TS_Str (each string at least 4 bytes, so the count is bounded first).
|
|
209
|
+
const v = `arg_${a.name}`;
|
|
210
|
+
const items = `vs_${a.name}`;
|
|
211
|
+
const i = `vi_${a.name}`;
|
|
212
|
+
need("4");
|
|
213
|
+
head.push(` TS_VecArg ${v};`, ` ${v}.len = (uint32_t)ts_read_le(${at}, 4);`, ` off += 4;`);
|
|
214
|
+
need(`(uint64_t)${v}.len * 4`);
|
|
215
|
+
head.push(` TS_Str *${items};`, ` TS_TRY(ts_alloc((uint64_t)${v}.len * sizeof(TS_Str), (uint8_t **)&${items}));`, ` for (uint64_t ${i} = 0; ${i} < ${v}.len; ${i}++) {`, ` if (params->data_len < off + 4) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: instruction data too short");`, ` uint32_t n = (uint32_t)ts_read_le(params->data + off, 4);`, ` off += 4;`, ` if (params->data_len - off < n) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: instruction data too short");`, ` if (!ts_utf8_valid(params->data + off, n)) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: invalid UTF-8 in a string argument");`, ` ${items}[${i}] = ts_str(params->data + off, n);`, ` off += n;`, ` }`, ` ${v}.ptr = (const uint8_t *)${items};`);
|
|
216
|
+
}
|
|
203
217
|
else if (a.type.kind === "vecArg") {
|
|
204
218
|
const elem = a.type.elem;
|
|
205
219
|
const size = this.fieldBytes(elem);
|
|
@@ -215,6 +229,21 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
215
229
|
head.push(` for (uint64_t ${i} = 0; ${i} < arg_${a.name}.len; ${i}++) {`, ...checks, ` }`);
|
|
216
230
|
}
|
|
217
231
|
}
|
|
232
|
+
else if (a.type.kind === "structRef" && this.model.structs.get(a.type.struct).layout === "borsh") {
|
|
233
|
+
// A @layout borsh struct: Borsh-decoded into its fixed-layout struct, as its accounts are.
|
|
234
|
+
const S = a.type.struct;
|
|
235
|
+
const v = `arg_${a.name}`;
|
|
236
|
+
const used = `used_${a.name}`;
|
|
237
|
+
head.push(` TSS_${S} *${v};`, ` TS_TRY(ts_alloc(sizeof(TSS_${S}), (uint8_t **)&${v}));`, ` uint64_t ${used} = 0;`, ` if (ts_unpack_${S}(${at}, params->data_len - off, ${v}, &${used}) != SUCCESS) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: invalid ${S} argument");`, ` off += ${used};`);
|
|
238
|
+
// Strings from outside are checked as UTF-8 (account data was checked when it was written).
|
|
239
|
+
for (const f of this.model.structs.get(S).fields) {
|
|
240
|
+
const bad = `TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: invalid UTF-8 in a string argument")`;
|
|
241
|
+
if (f.type.kind === "fstring")
|
|
242
|
+
head.push(` if (!ts_utf8_valid(${v}->${f.name}.bytes, ${v}->${f.name}.len)) ${bad};`);
|
|
243
|
+
else if (f.type.kind === "vec" && f.type.elem.kind === "fstring")
|
|
244
|
+
head.push(` for (uint64_t i = 0; i < ${v}->${f.name}.len; i++)`, ` if (!ts_utf8_valid(${v}->${f.name}.items[i].bytes, ${v}->${f.name}.items[i].len)) ${bad};`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
218
247
|
else if (a.type.kind === "structRef" || a.type.kind === "arrayArg") {
|
|
219
248
|
// Fixed layouts are Borsh-identical: copy to the heap, then check bools, enums, options and strings.
|
|
220
249
|
const v = `arg_${a.name}`;
|
|
@@ -245,8 +274,17 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
245
274
|
head.push(` if (!ts_utf8_valid(${lv}.ptr, ${lv}.len)) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: invalid UTF-8 in a string argument");`);
|
|
246
275
|
continue;
|
|
247
276
|
}
|
|
277
|
+
if (!isScalar(f.type) && borshIdentical(f.type, this.model)) {
|
|
278
|
+
// A struct or array whose Borsh bytes are its fixed layout: copied, then checked (bools, enums).
|
|
279
|
+
const lv = `${v}.v.${variant.name}.${f.name}`;
|
|
280
|
+
const size = this.fieldBytes(f.type);
|
|
281
|
+
head.push(` if (params->data_len < off + ${size}) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: instruction data too short");`);
|
|
282
|
+
head.push(` ts_memcpy(&${lv}, params->data + off, ${size});`, ` off += ${size};`);
|
|
283
|
+
head.push(...this.validation(lv, f.type, " "));
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
248
286
|
if (!isScalar(f.type))
|
|
249
|
-
this.reporter.fail(a.node, `${e.name}.${variant.name}.${f.name}: enum arguments carry integers, booleans, Pubkeys, enums and
|
|
287
|
+
this.reporter.fail(a.node, `${e.name}.${variant.name}.${f.name}: enum arguments carry integers, booleans, Pubkeys, enums, strings and fixed-size structs and arrays of those`);
|
|
250
288
|
const size = scalarSize(f.type);
|
|
251
289
|
head.push(` if (params->data_len < off + ${size}) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: instruction data too short");`);
|
|
252
290
|
head.push(` ts_memcpy(&${v}.v.${variant.name}.${f.name}, params->data + off, ${size});`, ` off += ${size};`);
|
|
@@ -284,7 +322,14 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
284
322
|
this.reporter.guard(() => this.statement(stmt, true));
|
|
285
323
|
for (const name of this.pendingInit) {
|
|
286
324
|
const k = this.accounts.get(name).kind;
|
|
287
|
-
const
|
|
325
|
+
const a = this.accounts.get(name);
|
|
326
|
+
const how = k.kind === "token" && a.constraints.ata
|
|
327
|
+
? `${name}.${k.init === "create" ? "create" : "createIfNeeded"}(payer)`
|
|
328
|
+
: k.kind === "token"
|
|
329
|
+
? `${name}.${k.init === "create" ? "create" : "createIfNeeded"}(payer, seeds | null, mint, authority)`
|
|
330
|
+
: k.kind === "mint"
|
|
331
|
+
? `${name}.${k.init === "create" ? "create" : "createIfNeeded"}(payer, seeds | null, decimals, authority, freezeAuthority | null)`
|
|
332
|
+
: `${name}.create(payer, seeds)`;
|
|
288
333
|
this.reporter.error(this.accounts.get(name).node, `Init account "${name}" is never created; call ${how}`);
|
|
289
334
|
}
|
|
290
335
|
const end = ix.returns
|
|
@@ -303,7 +348,7 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
303
348
|
.map((a) => {
|
|
304
349
|
const v = `a_${a.name}`;
|
|
305
350
|
const S = a.kind.struct.name;
|
|
306
|
-
return `if (${a.optional ? `${v} != NULL && ` : ""}${v}->data_len >= 8 && ts_key_eq(&${v}->owner, params->program_id)) TS_TRY(ts_pack_${S}(d_${a.name}, ${v}->data + 8, ${v}->data_len - 8));`;
|
|
351
|
+
return `if (${a.optional ? `${v} != NULL && ` : ""}${v}->data_len >= 8 && ts_key_eq(&${v}->owner, params->program_id)) TS_TRY(ts_pack_${S}(d_${a.name}, ${v}->data + 8, ${v}->data_len - 8, NULL));`;
|
|
307
352
|
});
|
|
308
353
|
return [
|
|
309
354
|
...packs,
|
|
@@ -65,6 +65,14 @@ export interface Local {
|
|
|
65
65
|
/** For struct / data-enum references: the storage paths they alias (e.g. "book.asks[]"), and the declaration. */
|
|
66
66
|
paths?: string[];
|
|
67
67
|
decl?: ts.VariableDeclaration;
|
|
68
|
+
/** A local closure (`const f = (…) => …` reading the enclosing function's variables), expanded where it is called. */
|
|
69
|
+
closure?: Closure;
|
|
70
|
+
}
|
|
71
|
+
/** A closure and the scopes it was declared in (its free names resolve there, wherever it is called). */
|
|
72
|
+
export interface Closure {
|
|
73
|
+
node: ts.ArrowFunction | ts.FunctionExpression;
|
|
74
|
+
name: string;
|
|
75
|
+
scopes: Map<string, Local>[];
|
|
68
76
|
}
|
|
69
77
|
export declare const cBytes: (bytes: number[]) => string;
|
|
70
78
|
/** C expressions that nothing in a TypeSOL expression can change: temporaries, locals, arguments, account keys, constants. */
|
|
@@ -185,6 +185,9 @@ export function helperSignature(h) {
|
|
|
185
185
|
params.push(`TsAccount *a_${p.name}`);
|
|
186
186
|
if (p.account.kind === "account" && p.account.struct.layout === "borsh")
|
|
187
187
|
params.push(`TSS_${p.account.struct.name} *d_${p.name}`);
|
|
188
|
+
// A writable account with a growable Vec: its length at instruction entry, for push / pop / clear.
|
|
189
|
+
if (p.account.kind === "account" && p.account.mut && p.account.struct.vec)
|
|
190
|
+
params.push(`uint64_t entry_${p.name}`);
|
|
188
191
|
}
|
|
189
192
|
else if (p.type.kind === "signerSeeds")
|
|
190
193
|
params.push(`const SolSignerSeed *p_${p.name}`, `int p_${p.name}__n`);
|
|
@@ -196,7 +199,7 @@ export function helperSignature(h) {
|
|
|
196
199
|
params.push(`${valueC(p.type)} p_${p.name}`);
|
|
197
200
|
}
|
|
198
201
|
if (h.returns)
|
|
199
|
-
params.push(h.returns.kind === "struct" ? `TSS_${h.returns.name} *ret` : `${scalarC(h.returns)} *ret`);
|
|
202
|
+
params.push(h.returns.kind === "struct" ? `TSS_${h.returns.name} *ret` : h.returns.kind === "string" ? `TS_Str *ret` : `${scalarC(h.returns)} *ret`);
|
|
200
203
|
return `static uint64_t fn_${h.name}(${params.join(", ")})`;
|
|
201
204
|
}
|
|
202
205
|
/** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { type Value, type Closure } from "./shared.js";
|
|
2
3
|
import { CpiEmitter } from "./cpi.js";
|
|
3
4
|
/**
|
|
4
5
|
* Statements: variables, assignment, control flow and return.
|
|
@@ -23,6 +24,26 @@ export declare abstract class StatementEmitter extends CpiEmitter {
|
|
|
23
24
|
* inside the loop so its checked-arithmetic temporaries are recomputed every iteration.
|
|
24
25
|
*/
|
|
25
26
|
protected loop(cond: ts.Expression | undefined, body: ts.Statement, step: ts.Expression | undefined): void;
|
|
27
|
+
/**
|
|
28
|
+
* Expand a closure where it is called: the arguments are evaluated here, in order, into the parameters (scalars,
|
|
29
|
+
* Pubkeys and strings by value, structs by reference, as JS passes objects); the body then runs with the names it
|
|
30
|
+
* was declared with, so a block around the call that declares the same name does not change what it reads, and
|
|
31
|
+
* writes to captured `let`s are seen by the enclosing function. Returns the result (undefined for no value).
|
|
32
|
+
*/
|
|
33
|
+
protected expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node): Value | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* `xs.forEach / some / every / find / findIndex / reduce / includes / indexOf` on a Vec, FixedArray, FixedVec or Vec
|
|
36
|
+
* argument: an index loop (the length read each step, as `for … of`), the callback (an arrow written at the call)
|
|
37
|
+
* expanded with each element as `xs.get(i)` gives it (structs by reference) and, if it takes one, the index.
|
|
38
|
+
* `find` gives an Option of the element (scalar elements), `findIndex` / `indexOf` an i64 (-1 when absent).
|
|
39
|
+
*/
|
|
40
|
+
protected listMethod(node: ts.CallExpression, method: string): Value | undefined;
|
|
41
|
+
/** `return` inside an expanded closure: the value goes to the closure's result, then past its body. */
|
|
42
|
+
protected closureReturn(stmt: ts.ReturnStatement, frame: (typeof this.inlineFrames)[number]): void;
|
|
43
|
+
/** Store a closure's result (fixing its type on the first one) and jump past the rest of its body. */
|
|
44
|
+
protected closureResult(value: Value, at: ts.Expression, frame: (typeof this.inlineFrames)[number]): void;
|
|
45
|
+
/** `do { … } while (cond)`: the body once, then the test; `continue` goes to the test. */
|
|
46
|
+
protected doLoop(stmt: ts.DoStatement): void;
|
|
26
47
|
protected body(stmt: ts.Statement): void;
|
|
27
48
|
protected variable(list: ts.VariableDeclarationList, topLevel?: boolean): void;
|
|
28
49
|
/**
|