@scriptc/compiler 0.0.6 → 0.0.8
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/dist/backend/emission/emit-exprs.js +47 -1
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-island.js +9 -1
- package/dist/backend/emission/emit-island.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +3 -1
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +22 -8
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.js +12 -1
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +4 -3
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/llvm/emitter.js +105 -9
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +49 -1
- package/dist/diagnostics/diagnostic.js +119 -5
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +114 -0
- package/dist/frontend/lib-contract.js +372 -0
- package/dist/frontend/lib-contract.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +16 -1
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +47 -7
- package/dist/frontend/lowering/lower-calls.js +560 -36
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +14 -1
- package/dist/frontend/lowering/lower-classes.js +85 -15
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +43 -1
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +1 -1
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
- package/dist/frontend/lowering/lower-exprs.js +474 -44
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +7 -0
- package/dist/frontend/lowering/lower-island.js +71 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
- package/dist/frontend/lowering/lower-mixins.js +1 -1
- package/dist/frontend/lowering/lower-modules.js +95 -4
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +430 -44
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +19 -3
- package/dist/frontend/lowering/lowerer.js +188 -11
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +300 -8
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +96 -7
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +48 -2
- package/dist/ir/nodes.js +103 -3
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +35 -6
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +53 -3
- package/dist/library/profile.js +143 -12
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar-validate.d.ts +4 -0
- package/dist/library/sidecar-validate.js +506 -0
- package/dist/library/sidecar-validate.js.map +1 -0
- package/dist/library/sidecar.d.ts +191 -0
- package/dist/library/sidecar.js +687 -0
- package/dist/library/sidecar.js.map +1 -0
- package/dist/library/trap-teaching.d.ts +11 -0
- package/dist/library/trap-teaching.js +49 -0
- package/dist/library/trap-teaching.js.map +1 -0
- package/dist/library/wyhash.d.ts +12 -0
- package/dist/library/wyhash.js +116 -0
- package/dist/library/wyhash.js.map +1 -0
- package/package.json +2 -2
- package/surface-manifest.json +37 -2
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
/* The ask-2 contract sidecar emitter: projects the entry module's typed
|
|
2
|
+
* contract into the profile-supplied schema (format 1) — a single JSON
|
|
3
|
+
* document beside the archive carrying the version/identity spine, the
|
|
4
|
+
* type table, the model/msg designations, helper signatures, shape flags,
|
|
5
|
+
* channels, the ABI attestation, and the determinism attestations.
|
|
6
|
+
*
|
|
7
|
+
* The sidecar is the CONTRACT, not the program: no source spans, no
|
|
8
|
+
* bodies, no IR, no environment or machine data, no absolute paths, no
|
|
9
|
+
* timestamps. Emission is deterministic — top-level keys in the schema's
|
|
10
|
+
* §1 order, every array order semantic (declaration order where the
|
|
11
|
+
* schema says declaration, profile canonical order for abi.exports), so
|
|
12
|
+
* re-running the identical invocation reproduces a byte-identical file.
|
|
13
|
+
*
|
|
14
|
+
* Declaration order is read from the SYNTAX TREE (frontend/lib-contract.ts)
|
|
15
|
+
* — never from checker property enumeration, which hands back internal or
|
|
16
|
+
* sorted order (the ratified record-field-order ruling: the IR's sorted
|
|
17
|
+
* canonicalization is storage; AST order is the contract).
|
|
18
|
+
*
|
|
19
|
+
* Order must be DERIVABLE FROM ONE DECLARATION SITE or the build refuses
|
|
20
|
+
* (ask 3's define-or-refuse rule): declaration merging into a tabled type
|
|
21
|
+
* refuses with every site named (SC4010), conditional/mapped types
|
|
22
|
+
* producing a tabled type refuse (SC4011), and a union COMPOSED of other
|
|
23
|
+
* kind-tagged unions (`type Msg = A | B` — the reducer-composition
|
|
24
|
+
* pattern) is allowed with a pinned order: depth-first source order of
|
|
25
|
+
* the constituent declarations (A's arms in A's own declaration order,
|
|
26
|
+
* then B's), an arm name appearing in several constituents keeping its
|
|
27
|
+
* FIRST occurrence. A type alias of a named type is transparent: the
|
|
28
|
+
* table derives from the aliased declaration, the alias adds no entry
|
|
29
|
+
* and no reordering.
|
|
30
|
+
*
|
|
31
|
+
* Not-yet facts emit the schema's stated absent forms, never invented
|
|
32
|
+
* values: `integer_slots` is `[]` with every numeric slot spelled `f64`
|
|
33
|
+
* (the pre-ask-4 sequencing the schema names valid — the empty list is
|
|
34
|
+
* itself an attestation), and `deterministic` is computed from the module
|
|
35
|
+
* graph (ir/nodes.ts's conservative ambient-surface scan), never
|
|
36
|
+
* defaulted. */
|
|
37
|
+
import { Buffer } from "node:buffer";
|
|
38
|
+
import { readFileSync } from "node:fs";
|
|
39
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
40
|
+
import { fileURLToPath } from "node:url";
|
|
41
|
+
import { libSidecarComputedDiag, libSidecarDiag, libSidecarMergedDiag } from "../diagnostics/diagnostic.js";
|
|
42
|
+
import { BUILD_ID_SEED, hex16, lengthPrefixedStream, SOURCE_HASH_SEED, wyhash64 } from "./wyhash.js";
|
|
43
|
+
/** The sidecar schema's format this emitter writes. */
|
|
44
|
+
export const SIDECAR_FORMAT = 1;
|
|
45
|
+
/* ── identity hashing (schema §2 + the "module-graph" source contract) ─── */
|
|
46
|
+
let releaseVersion = null;
|
|
47
|
+
/** The compiler's exact release identifier — the published package
|
|
48
|
+
* version, read once from the compiler package's own package.json (this
|
|
49
|
+
* module lives two levels below the package root in src/ and dist/
|
|
50
|
+
* alike). build_id input 1 and the sidecar's `compiler_version`. */
|
|
51
|
+
export function compilerReleaseVersion() {
|
|
52
|
+
if (releaseVersion === null) {
|
|
53
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
54
|
+
releaseVersion = JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
55
|
+
}
|
|
56
|
+
return releaseVersion;
|
|
57
|
+
}
|
|
58
|
+
/** One path under the sidecar's canonical rules: compilation-root-relative
|
|
59
|
+
* POSIX, or `profile:`-namespaced when the file sits outside the root
|
|
60
|
+
* (never absolute, no `.`/`..` segments). */
|
|
61
|
+
export function canonicalPath(rootDir, file) {
|
|
62
|
+
const rel = relative(rootDir, file);
|
|
63
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
64
|
+
return `profile:${file.split(/[\\/]/).pop()}`;
|
|
65
|
+
}
|
|
66
|
+
return sep === "/" ? rel : rel.split(sep).join("/");
|
|
67
|
+
}
|
|
68
|
+
/** Canonicalize + sort the module graph for hashing: root-relative POSIX
|
|
69
|
+
* paths, ascending by plain bytewise comparison of the path strings. A
|
|
70
|
+
* module outside the compilation root (the profile file's directory)
|
|
71
|
+
* cannot spell a canonical relative path, so it rides the `profile:`
|
|
72
|
+
* namespace under its basename — deterministic and absolute-path-free. */
|
|
73
|
+
export function canonicalModuleGraph(rootDir, sources) {
|
|
74
|
+
const enc = new TextEncoder();
|
|
75
|
+
const entries = [];
|
|
76
|
+
for (const [file, text] of sources) {
|
|
77
|
+
entries.push({ canonical: canonicalPath(rootDir, file), bytes: enc.encode(text) });
|
|
78
|
+
}
|
|
79
|
+
entries.sort((a, b) => {
|
|
80
|
+
const c = Buffer.compare(Buffer.from(a.canonical, "utf8"), Buffer.from(b.canonical, "utf8"));
|
|
81
|
+
return c !== 0 ? c : Buffer.compare(a.bytes, b.bytes);
|
|
82
|
+
});
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
/** build_id (schema §2: compiler version, profile bytes, sorted module
|
|
86
|
+
* graph — every input length-prefixed) and source_hash (the
|
|
87
|
+
* "module-graph" contract: the sorted module graph alone, so the value is
|
|
88
|
+
* stable across compiler releases). Both 16 lowercase hex digits. */
|
|
89
|
+
export function libraryIdentityHashes(compilerVersion, profileBytes, modules) {
|
|
90
|
+
const enc = new TextEncoder();
|
|
91
|
+
const graphChunks = [];
|
|
92
|
+
for (const m of modules) {
|
|
93
|
+
graphChunks.push(enc.encode(m.canonical), m.bytes);
|
|
94
|
+
}
|
|
95
|
+
const buildChunks = [enc.encode(compilerVersion), profileBytes, ...graphChunks];
|
|
96
|
+
return {
|
|
97
|
+
buildId: hex16(wyhash64(lengthPrefixedStream(buildChunks), BUILD_ID_SEED)),
|
|
98
|
+
sourceHash: hex16(wyhash64(lengthPrefixedStream(graphChunks), SOURCE_HASH_SEED)),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** The profile's canonical `abi.exports` suffix order: the identity
|
|
102
|
+
* getters first (abi_version, then build_id), the mode-provided entries
|
|
103
|
+
* (sink registration, init, collect, reset — declared ones only), then
|
|
104
|
+
* the export map in profile order. Suffix = symbol minus prefix. */
|
|
105
|
+
export function abiExportSuffixes(profile) {
|
|
106
|
+
const strip = (sym) => sym.slice(profile.prefix.length);
|
|
107
|
+
const out = [];
|
|
108
|
+
if (profile.sidecar !== null) {
|
|
109
|
+
out.push(strip(profile.sidecar.abiVersionSymbol), strip(profile.sidecar.buildIdSymbol));
|
|
110
|
+
}
|
|
111
|
+
out.push(strip(profile.sinkRegisterSymbol), strip(profile.initSymbol));
|
|
112
|
+
if (profile.collectSymbol !== null)
|
|
113
|
+
out.push(strip(profile.collectSymbol));
|
|
114
|
+
if (profile.resultResetSymbol !== null)
|
|
115
|
+
out.push(strip(profile.resultResetSymbol));
|
|
116
|
+
for (const e of profile.exports)
|
|
117
|
+
out.push(strip(e.symbol));
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
function classify(decl, index) {
|
|
121
|
+
const s = decl.shape;
|
|
122
|
+
if (s.k === "unsupported") {
|
|
123
|
+
return s.computed === undefined
|
|
124
|
+
? { c: "unsupported", why: s.text, decl, index }
|
|
125
|
+
: { c: "unsupported", why: s.text, computed: s.computed, decl, index };
|
|
126
|
+
}
|
|
127
|
+
if (s.k === "object") {
|
|
128
|
+
return { c: "struct", storage: decl.form === "interface" ? "node" : "value", fields: s.fields, decl, index };
|
|
129
|
+
}
|
|
130
|
+
if (decl.form === "alias" && s.k === "ref")
|
|
131
|
+
return { c: "alias", target: s.name, decl, index };
|
|
132
|
+
if (decl.form === "alias" && s.k === "stringLit")
|
|
133
|
+
return { c: "enum", members: [s.text], decl, index };
|
|
134
|
+
if (decl.form === "alias" && s.k === "union") {
|
|
135
|
+
if (s.parts.every((p) => p.k === "stringLit")) {
|
|
136
|
+
return { c: "enum", members: s.parts.map((p) => p.text), decl, index };
|
|
137
|
+
}
|
|
138
|
+
const parts = [];
|
|
139
|
+
for (const p of s.parts) {
|
|
140
|
+
if (p.k === "ref") {
|
|
141
|
+
parts.push({ p: "ref", name: p.name, loc: decl.loc });
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (p.k !== "object") {
|
|
145
|
+
return { c: "unsupported", why: "a union mixing non-object constituents (a tagged union's arms are object literals with a string-literal 'kind', or references to other kind-tagged unions)", decl, index };
|
|
146
|
+
}
|
|
147
|
+
const kindField = p.fields.find((f) => f.name === "kind");
|
|
148
|
+
if (kindField === undefined || kindField.shape.k !== "stringLit" || kindField.optional) {
|
|
149
|
+
return { c: "unsupported", why: "a union constituent without a non-optional string-literal 'kind' discriminant", decl, index };
|
|
150
|
+
}
|
|
151
|
+
parts.push({ p: "arm", name: kindField.shape.text, fields: p.fields.filter((f) => f.name !== "kind"), loc: kindField.loc });
|
|
152
|
+
}
|
|
153
|
+
return { c: "tagged", parts, decl, index };
|
|
154
|
+
}
|
|
155
|
+
return { c: "unsupported", why: `a shape outside the sidecar's vocabulary (${s.k})`, decl, index };
|
|
156
|
+
}
|
|
157
|
+
class SidecarError extends Error {
|
|
158
|
+
detail;
|
|
159
|
+
loc;
|
|
160
|
+
constructor(detail, loc) {
|
|
161
|
+
super(detail);
|
|
162
|
+
this.detail = detail;
|
|
163
|
+
this.loc = loc;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** A refusal that carries its own minted diagnostic (SC4010/SC4011 —
|
|
167
|
+
* the SC4009 default rides SidecarError). */
|
|
168
|
+
class SidecarRefusal extends Error {
|
|
169
|
+
diag;
|
|
170
|
+
constructor(diag) {
|
|
171
|
+
super(diag.message);
|
|
172
|
+
this.diag = diag;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
class Projector {
|
|
176
|
+
facts;
|
|
177
|
+
config;
|
|
178
|
+
entryLoc;
|
|
179
|
+
byName = new Map();
|
|
180
|
+
multiSite = new Map();
|
|
181
|
+
table = new Map();
|
|
182
|
+
inProgress = new Set();
|
|
183
|
+
flatArms = new Map();
|
|
184
|
+
flattening = new Set();
|
|
185
|
+
synthCounter = 0;
|
|
186
|
+
constructor(facts, config, entryLoc) {
|
|
187
|
+
this.facts = facts;
|
|
188
|
+
this.config = config;
|
|
189
|
+
this.entryLoc = entryLoc;
|
|
190
|
+
// facts.types carries one entry per name (a repeated exported name is
|
|
191
|
+
// a multi-site fact, refused below when the name is actually used).
|
|
192
|
+
facts.types.forEach((decl, index) => this.byName.set(decl.name, classify(decl, index)));
|
|
193
|
+
for (const m of facts.multiSiteTypes)
|
|
194
|
+
this.multiSite.set(m.name, m.sites);
|
|
195
|
+
}
|
|
196
|
+
lookup(name, loc) {
|
|
197
|
+
const c = this.byName.get(name);
|
|
198
|
+
if (c === undefined) {
|
|
199
|
+
throw new SidecarError(`'${name}' is not an exported type declaration of the program's modules`, loc);
|
|
200
|
+
}
|
|
201
|
+
// Define-or-refuse: a name whose members gather from several
|
|
202
|
+
// declaration sites has no single-source order — refuse the moment
|
|
203
|
+
// the contract touches it, naming every site (SC4010).
|
|
204
|
+
const sites = this.multiSite.get(name);
|
|
205
|
+
if (sites !== undefined) {
|
|
206
|
+
throw new SidecarRefusal(libSidecarMergedDiag(name, sites, c.decl.loc));
|
|
207
|
+
}
|
|
208
|
+
if (c.c === "unsupported") {
|
|
209
|
+
if (c.computed !== undefined) {
|
|
210
|
+
throw new SidecarRefusal(libSidecarComputedDiag(name, c.computed, c.decl.loc));
|
|
211
|
+
}
|
|
212
|
+
throw new SidecarError(`'${name}' cannot join the type table: it is ${c.why}`, c.decl.loc);
|
|
213
|
+
}
|
|
214
|
+
return c;
|
|
215
|
+
}
|
|
216
|
+
/** Follow `type A = B` alias chains to the aliased declaration: the
|
|
217
|
+
* table derives from the target's declaration site, and the alias
|
|
218
|
+
* introduces no entry and no reordering. */
|
|
219
|
+
resolve(name, loc) {
|
|
220
|
+
const seen = new Set();
|
|
221
|
+
let cur = name;
|
|
222
|
+
for (;;) {
|
|
223
|
+
if (seen.has(cur)) {
|
|
224
|
+
throw new SidecarError(`the type alias chain through '${name}' is cyclic`, loc);
|
|
225
|
+
}
|
|
226
|
+
seen.add(cur);
|
|
227
|
+
const c = this.lookup(cur, loc);
|
|
228
|
+
if (c.c !== "alias")
|
|
229
|
+
return { name: cur, c };
|
|
230
|
+
cur = c.target;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** A kind-tagged union's arms, its reference constituents flattened
|
|
234
|
+
* DEPTH-FIRST IN SOURCE ORDER (the ask-3 composition rule, pinned by
|
|
235
|
+
* conformance): an inline arm lands where it is spelled; a constituent
|
|
236
|
+
* union contributes its own (recursively flattened) arms, in its own
|
|
237
|
+
* declaration order, at the position of the reference. An arm name
|
|
238
|
+
* arriving from several constituents keeps its FIRST occurrence; a
|
|
239
|
+
* repeat among one declaration's own inline arms stays a refusal. */
|
|
240
|
+
unionArms(unionName, loc) {
|
|
241
|
+
const memo = this.flatArms.get(unionName);
|
|
242
|
+
if (memo !== undefined)
|
|
243
|
+
return memo;
|
|
244
|
+
if (this.flattening.has(unionName)) {
|
|
245
|
+
throw new SidecarError(`union composition is cyclic through '${unionName}' — a union cannot spread itself`, loc);
|
|
246
|
+
}
|
|
247
|
+
const c = this.lookup(unionName, loc);
|
|
248
|
+
if (c.c !== "tagged") {
|
|
249
|
+
throw new SidecarError(`'${unionName}' is not a kind-tagged union of object literals`, c.decl.loc);
|
|
250
|
+
}
|
|
251
|
+
this.flattening.add(unionName);
|
|
252
|
+
try {
|
|
253
|
+
const out = [];
|
|
254
|
+
const originOf = new Map();
|
|
255
|
+
for (const part of c.parts) {
|
|
256
|
+
if (part.p === "arm") {
|
|
257
|
+
const origin = originOf.get(part.name);
|
|
258
|
+
if (origin === unionName) {
|
|
259
|
+
throw new SidecarError(`union '${unionName}' repeats arm '${part.name}'`, part.loc);
|
|
260
|
+
}
|
|
261
|
+
if (origin !== undefined)
|
|
262
|
+
continue; // an earlier constituent's arm — first occurrence wins
|
|
263
|
+
originOf.set(part.name, unionName);
|
|
264
|
+
out.push({ name: part.name, fields: part.fields, loc: part.loc });
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const r = this.resolve(part.name, part.loc);
|
|
268
|
+
if (r.c.c !== "tagged") {
|
|
269
|
+
throw new SidecarError(`constituent '${part.name}' of union '${unionName}' is not a kind-tagged union — only kind-tagged unions compose by reference`, part.loc);
|
|
270
|
+
}
|
|
271
|
+
for (const arm of this.unionArms(r.name, part.loc)) {
|
|
272
|
+
if (originOf.has(arm.name))
|
|
273
|
+
continue; // first occurrence wins under depth-first order
|
|
274
|
+
originOf.set(arm.name, r.name);
|
|
275
|
+
out.push(arm);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.flatArms.set(unionName, out);
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
this.flattening.delete(unionName);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** Project a syntactic field to a TypeRef, tabling every named type it
|
|
286
|
+
* references. `container`/`member` seed synthesized names. */
|
|
287
|
+
fieldRef(field, container) {
|
|
288
|
+
const inner = this.shapeRef(field.shape, container, field.name, field.loc);
|
|
289
|
+
if (field.optional) {
|
|
290
|
+
if (inner.kind === "optional")
|
|
291
|
+
return inner;
|
|
292
|
+
return { kind: "optional", inner };
|
|
293
|
+
}
|
|
294
|
+
return inner;
|
|
295
|
+
}
|
|
296
|
+
shapeRef(shape, container, member, loc) {
|
|
297
|
+
switch (shape.k) {
|
|
298
|
+
case "bool":
|
|
299
|
+
return { kind: "bool" };
|
|
300
|
+
case "number":
|
|
301
|
+
// Every numeric slot spells f64 until ask-4 integer inference
|
|
302
|
+
// lands (the schema's stated pre-inference sequencing).
|
|
303
|
+
return { kind: "f64" };
|
|
304
|
+
case "text":
|
|
305
|
+
case "bytes":
|
|
306
|
+
return { kind: "bytes" };
|
|
307
|
+
case "array":
|
|
308
|
+
return { kind: "slice", elem: this.shapeRef(shape.elem, container, member, loc) };
|
|
309
|
+
case "union": {
|
|
310
|
+
const present = shape.parts.filter((p) => p.k !== "absent");
|
|
311
|
+
const absents = shape.parts.length - present.length;
|
|
312
|
+
if (absents > 0 && present.length === 1) {
|
|
313
|
+
const inner = this.shapeRef(present[0], container, member, loc);
|
|
314
|
+
return inner.kind === "optional" ? inner : { kind: "optional", inner };
|
|
315
|
+
}
|
|
316
|
+
throw new SidecarError(`'${container}.${member}' is an inline union — declare it as a named kind-tagged union (or a string-literal-union enum) and reference it by name`, loc);
|
|
317
|
+
}
|
|
318
|
+
case "ref": {
|
|
319
|
+
// Aliases are transparent: the table entry (and its order) is the
|
|
320
|
+
// ALIASED declaration's, under the aliased declaration's name.
|
|
321
|
+
const r = this.resolve(shape.name, loc);
|
|
322
|
+
if (r.c.c === "struct") {
|
|
323
|
+
this.tableNamed(r.name, loc);
|
|
324
|
+
return { kind: r.c.storage, name: r.name };
|
|
325
|
+
}
|
|
326
|
+
if (r.c.c === "enum") {
|
|
327
|
+
this.tableNamed(r.name, loc);
|
|
328
|
+
return { kind: "enum", name: r.name };
|
|
329
|
+
}
|
|
330
|
+
if (r.name === this.config.msg) {
|
|
331
|
+
throw new SidecarError(`'${container}.${member}' references the designated msg union '${r.name}' — the msg union is the dispatch surface, not a table type`, loc);
|
|
332
|
+
}
|
|
333
|
+
this.tableNamed(r.name, loc);
|
|
334
|
+
return { kind: "union", name: r.name };
|
|
335
|
+
}
|
|
336
|
+
case "object":
|
|
337
|
+
return { kind: "value", name: this.tableSynthesized(container, member, shape.fields, loc) };
|
|
338
|
+
case "stringLit":
|
|
339
|
+
throw new SidecarError(`'${container}.${member}' is a bare string-literal type — declare a named string-literal union and reference it as an enum`, loc);
|
|
340
|
+
case "void":
|
|
341
|
+
throw new SidecarError(`'${container}.${member}' is void — void exists only as a bare union arm's payload`, loc);
|
|
342
|
+
case "absent":
|
|
343
|
+
throw new SidecarError(`'${container}.${member}' is null/undefined alone — pair it with a value type for an optional slot`, loc);
|
|
344
|
+
case "tuple":
|
|
345
|
+
throw new SidecarError(`'${container}.${member}' is a tuple — the sidecar vocabulary has slices and named records, not positional tuples`, loc);
|
|
346
|
+
case "unsupported":
|
|
347
|
+
throw new SidecarError(`'${container}.${member}' has no sidecar projection: ${shape.text}`, loc);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Ensure a declared type's table entry exists (recursively projecting
|
|
351
|
+
* what it references). Cycles refuse: recursive contract types cannot
|
|
352
|
+
* encode (schema rule V5). */
|
|
353
|
+
tableNamed(name, loc) {
|
|
354
|
+
if (this.table.has(name))
|
|
355
|
+
return;
|
|
356
|
+
if (name === this.config.msg) {
|
|
357
|
+
throw new SidecarError(`the designated msg union '${name}' cannot join the type table`, loc);
|
|
358
|
+
}
|
|
359
|
+
if (this.inProgress.has(name)) {
|
|
360
|
+
throw new SidecarError(`the contract type graph is cyclic through '${name}' — recursive contract types cannot encode`, loc);
|
|
361
|
+
}
|
|
362
|
+
const c = this.lookup(name, loc);
|
|
363
|
+
if (c.c === "alias") {
|
|
364
|
+
// Callers table resolved names; a designation reaching here through
|
|
365
|
+
// an alias still tables the aliased declaration, nothing else.
|
|
366
|
+
this.tableNamed(this.resolve(name, loc).name, loc);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this.inProgress.add(name);
|
|
370
|
+
try {
|
|
371
|
+
if (c.c === "enum") {
|
|
372
|
+
const seen = new Set();
|
|
373
|
+
for (const m of c.members) {
|
|
374
|
+
if (seen.has(m))
|
|
375
|
+
throw new SidecarError(`enum '${name}' repeats member '${m}'`, c.decl.loc);
|
|
376
|
+
seen.add(m);
|
|
377
|
+
}
|
|
378
|
+
this.table.set(name, { kind: "enum", entry: { name, members: c.members }, anchor: c.index, sub: -1 });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (c.c === "struct") {
|
|
382
|
+
const entry = { name, fields: [] };
|
|
383
|
+
// Insert before projecting fields? No: cycle detection rides
|
|
384
|
+
// inProgress; the entry lands complete.
|
|
385
|
+
const seen = new Set();
|
|
386
|
+
for (const f of c.fields) {
|
|
387
|
+
if (seen.has(f.name))
|
|
388
|
+
throw new SidecarError(`record '${name}' repeats field '${f.name}'`, f.loc);
|
|
389
|
+
seen.add(f.name);
|
|
390
|
+
entry.fields.push({ name: f.name, type: this.fieldRef(f, name) });
|
|
391
|
+
}
|
|
392
|
+
this.table.set(name, { kind: "struct", entry, anchor: c.index, sub: -1 });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
// A named tagged union (never the msg union — fenced above), its
|
|
396
|
+
// arms flattened depth-first (composition + the repeat refusal live
|
|
397
|
+
// in unionArms).
|
|
398
|
+
const entry = { name, arms: [] };
|
|
399
|
+
for (const arm of this.unionArms(name, loc)) {
|
|
400
|
+
entry.arms.push({ name: arm.name, payload: this.armPayloadRef(name, arm) });
|
|
401
|
+
}
|
|
402
|
+
this.table.set(name, { kind: "union", entry, anchor: c.index, sub: -1 });
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
this.inProgress.delete(name);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/** A named union arm's payload TypeRef: void for bare arms, the single
|
|
409
|
+
* payload field's type, or a synthesized by-value record for
|
|
410
|
+
* multi-field inline payloads. */
|
|
411
|
+
armPayloadRef(unionName, arm) {
|
|
412
|
+
if (arm.fields.length === 0)
|
|
413
|
+
return { kind: "void" };
|
|
414
|
+
if (arm.fields.length === 1)
|
|
415
|
+
return this.fieldRef(arm.fields[0], unionName);
|
|
416
|
+
return { kind: "value", name: this.tableSynthesized(unionName, arm.name, arm.fields, arm.loc) };
|
|
417
|
+
}
|
|
418
|
+
/** Table an anonymous inline record under the schema's synthesized-name
|
|
419
|
+
* contract: `<Container>_<member>`, deterministic and stable across
|
|
420
|
+
* identical re-compiles, unique in the one namespace. */
|
|
421
|
+
tableSynthesized(container, member, fields, loc) {
|
|
422
|
+
const name = `${container}_${member}`;
|
|
423
|
+
const existing = this.table.get(name);
|
|
424
|
+
if (existing !== undefined)
|
|
425
|
+
return name; // the same inline decl, revisited
|
|
426
|
+
if (this.byName.has(name)) {
|
|
427
|
+
throw new SidecarError(`the inline record at '${container}.${member}' needs the synthesized name '${name}', which a declared type already uses — rename one`, loc);
|
|
428
|
+
}
|
|
429
|
+
const entry = { name, synthesized: true, fields: [] };
|
|
430
|
+
const anchor = this.byName.get(container)?.index ?? this.facts.types.length;
|
|
431
|
+
const sub = this.synthCounter++;
|
|
432
|
+
this.table.set(name, { kind: "struct", entry, anchor, sub });
|
|
433
|
+
const seen = new Set();
|
|
434
|
+
for (const f of fields) {
|
|
435
|
+
if (seen.has(f.name))
|
|
436
|
+
throw new SidecarError(`the inline record at '${container}.${member}' repeats field '${f.name}'`, f.loc);
|
|
437
|
+
seen.add(f.name);
|
|
438
|
+
entry.fields.push({ name: f.name, type: this.fieldRef(f, name) });
|
|
439
|
+
}
|
|
440
|
+
return name;
|
|
441
|
+
}
|
|
442
|
+
/** A msg arm's payload descriptor (§5's five families). */
|
|
443
|
+
msgDescriptor(msgName, arm) {
|
|
444
|
+
const fields = arm.fields;
|
|
445
|
+
if (fields.length === 0)
|
|
446
|
+
return { kind: "void" };
|
|
447
|
+
if (fields.length === 2) {
|
|
448
|
+
const [first, second] = [fields[0], fields[1]];
|
|
449
|
+
// Family 4 covers exactly the number-first two-field record; a
|
|
450
|
+
// bytes-first spelling takes the record family instead
|
|
451
|
+
// (declaration order is semantic everywhere — the clarified rule).
|
|
452
|
+
if (!first.optional &&
|
|
453
|
+
!second.optional &&
|
|
454
|
+
first.shape.k === "number" &&
|
|
455
|
+
(second.shape.k === "text" || second.shape.k === "bytes")) {
|
|
456
|
+
return { kind: "number_bytes", number_field: first.name, number_class: "f64", bytes_field: second.name };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (fields.length === 1 && !fields[0].optional) {
|
|
460
|
+
const ref = this.fieldRef(fields[0], msgName);
|
|
461
|
+
switch (ref.kind) {
|
|
462
|
+
case "bytes":
|
|
463
|
+
return { kind: "bytes" };
|
|
464
|
+
case "f64":
|
|
465
|
+
case "i64":
|
|
466
|
+
return { kind: "number", class: ref.kind };
|
|
467
|
+
case "node":
|
|
468
|
+
case "value":
|
|
469
|
+
return { kind: "record", name: ref.name };
|
|
470
|
+
case "union":
|
|
471
|
+
return { kind: "union", name: ref.name };
|
|
472
|
+
case "enum":
|
|
473
|
+
return { kind: "enum", name: ref.name };
|
|
474
|
+
case "void":
|
|
475
|
+
return { kind: "void" };
|
|
476
|
+
default:
|
|
477
|
+
return { kind: "scalar", type: ref };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Everything else — bytes-first pairs, three-plus fields, optional
|
|
481
|
+
// payload fields — tables a synthesized record (family 5).
|
|
482
|
+
return { kind: "record", name: this.tableSynthesized(msgName, arm.name, fields, arm.loc) };
|
|
483
|
+
}
|
|
484
|
+
/** The finished type table, each array in declaration order (synthesized
|
|
485
|
+
* entries anchor at their containing declaration). */
|
|
486
|
+
finishedTable() {
|
|
487
|
+
const ordered = [...this.table.values()].sort((a, b) => a.anchor - b.anchor || a.sub - b.sub);
|
|
488
|
+
return {
|
|
489
|
+
structs: ordered.filter((e) => e.kind === "struct").map((e) => e.entry),
|
|
490
|
+
enums: ordered.filter((e) => e.kind === "enum").map((e) => e.entry),
|
|
491
|
+
unions: ordered.filter((e) => e.kind === "union").map((e) => e.entry),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/** Whether a helper's result rides the transient result arena (anything
|
|
496
|
+
* materialized: buffers, slices, records, optionals) rather than a plain
|
|
497
|
+
* scalar return. */
|
|
498
|
+
function helperArena(returns) {
|
|
499
|
+
return returns.kind !== "bool" && returns.kind !== "f64" && returns.kind !== "i64" && returns.kind !== "enum";
|
|
500
|
+
}
|
|
501
|
+
export function buildSidecar(input) {
|
|
502
|
+
const { profile, facts } = input;
|
|
503
|
+
const config = profile.sidecar;
|
|
504
|
+
const entryLoc = { file: profile.entry, start: 0, end: 0 };
|
|
505
|
+
const diagnostics = [];
|
|
506
|
+
for (const m of facts.malformedConsts) {
|
|
507
|
+
diagnostics.push(libSidecarDiag(`exported const '${m.name}' ${m.detail}`, m.loc));
|
|
508
|
+
}
|
|
509
|
+
if (diagnostics.length > 0)
|
|
510
|
+
return { ok: false, diagnostics };
|
|
511
|
+
try {
|
|
512
|
+
const projector = new Projector(facts, config, entryLoc);
|
|
513
|
+
// The model: the designated root state type, a record in the table.
|
|
514
|
+
const modelClass = projector.lookup(config.model, entryLoc);
|
|
515
|
+
if (modelClass.c !== "struct") {
|
|
516
|
+
throw new SidecarError(`the profile designates model '${config.model}', which is not a record type`, modelClass.decl.loc);
|
|
517
|
+
}
|
|
518
|
+
projector.tableNamed(config.model, entryLoc);
|
|
519
|
+
const modelFieldNames = new Set(modelClass.fields.map((f) => f.name));
|
|
520
|
+
// The msg union: declaration-order arms (composed constituents
|
|
521
|
+
// flattened depth-first in source order, first occurrence winning a
|
|
522
|
+
// duplicated arm name — the reducer-composition rule), positional
|
|
523
|
+
// wire tags, at most 256 arms (tags ride a u8).
|
|
524
|
+
const msgClass = projector.lookup(config.msg, entryLoc);
|
|
525
|
+
if (msgClass.c !== "tagged") {
|
|
526
|
+
throw new SidecarError(`the profile designates msg '${config.msg}', which is not a kind-tagged union of object literals`, msgClass.decl.loc);
|
|
527
|
+
}
|
|
528
|
+
const flatArms = projector.unionArms(config.msg, entryLoc);
|
|
529
|
+
if (flatArms.length > 256) {
|
|
530
|
+
throw new SidecarError(`msg union '${config.msg}' declares ${flatArms.length} arms — wire tags ride a u8, so at most 256 are permitted`, msgClass.decl.loc);
|
|
531
|
+
}
|
|
532
|
+
const msgArms = flatArms.map((arm) => ({
|
|
533
|
+
name: arm.name,
|
|
534
|
+
payload: projector.msgDescriptor(config.msg, arm),
|
|
535
|
+
}));
|
|
536
|
+
const armByName = new Map(msgArms.map((a) => [a.name, a.payload]));
|
|
537
|
+
// Helpers: exported functions taking the model first, in declaration
|
|
538
|
+
// order (the array index is the ABI call index), minus the designated
|
|
539
|
+
// init/update/subscriptions entries.
|
|
540
|
+
const designated = new Set([config.initExport, config.updateExport, config.subscriptionsExport]);
|
|
541
|
+
const helpers = [];
|
|
542
|
+
for (const fn of facts.functions) {
|
|
543
|
+
if (designated.has(fn.name))
|
|
544
|
+
continue;
|
|
545
|
+
const first = fn.params[0];
|
|
546
|
+
if (first === undefined || first.shape === null || first.shape.k !== "ref" || first.shape.name !== config.model)
|
|
547
|
+
continue;
|
|
548
|
+
if (fn.generic) {
|
|
549
|
+
throw new SidecarError(`helper '${fn.name}' is generic — a contract helper needs one concrete signature`, fn.loc);
|
|
550
|
+
}
|
|
551
|
+
const params = [];
|
|
552
|
+
fn.params.slice(1).forEach((p, i) => {
|
|
553
|
+
if (p.shape === null) {
|
|
554
|
+
throw new SidecarError(`helper '${fn.name}' parameter ${i + 2} ('${p.name}') has no type annotation`, fn.loc);
|
|
555
|
+
}
|
|
556
|
+
params.push(projector.shapeRef(p.shape, `helpers_${fn.name}`, p.name, fn.loc));
|
|
557
|
+
});
|
|
558
|
+
if (fn.returns === null) {
|
|
559
|
+
throw new SidecarError(`helper '${fn.name}' has no return type annotation`, fn.loc);
|
|
560
|
+
}
|
|
561
|
+
if (fn.returns.k === "void") {
|
|
562
|
+
throw new SidecarError(`helper '${fn.name}' returns void — a contract helper returns a value the host can read`, fn.loc);
|
|
563
|
+
}
|
|
564
|
+
// Helper-return synthesized names are two-part like everything else:
|
|
565
|
+
// container 'helpers', member the helper's name — `helpers_<name>`,
|
|
566
|
+
// never a '_return' suffix (the ratified spelling).
|
|
567
|
+
const returns = projector.shapeRef(fn.returns, "helpers", fn.name, fn.loc);
|
|
568
|
+
if (helpers.some((h) => h.name === fn.name)) {
|
|
569
|
+
throw new SidecarError(`helper '${fn.name}' is declared twice`, fn.loc);
|
|
570
|
+
}
|
|
571
|
+
helpers.push({ name: fn.name, params, returns, arena: helperArena(returns) });
|
|
572
|
+
}
|
|
573
|
+
const helperNames = new Set(helpers.map((h) => h.name));
|
|
574
|
+
// Shape flags from the designated entries' declared signatures.
|
|
575
|
+
const returnsCmd = (which, exportName) => {
|
|
576
|
+
const fn = facts.functions.find((f) => f.name === exportName);
|
|
577
|
+
if (fn === undefined) {
|
|
578
|
+
throw new SidecarError(`the profile designates ${which} export '${exportName}', but the entry module exports no function by that name`, entryLoc);
|
|
579
|
+
}
|
|
580
|
+
const r = fn.returns;
|
|
581
|
+
if (r !== null && r.k === "ref" && r.name === config.model)
|
|
582
|
+
return false;
|
|
583
|
+
if (r !== null && r.k === "tuple" && r.elems.length === 2 && r.elems[0].k === "ref" && r.elems[0].name === config.model) {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
throw new SidecarError(`${which} export '${exportName}' must declare its return as '${config.model}' (bare state) or a two-element tuple '[${config.model}, ...]' (state plus an effect value)`, fn.loc);
|
|
587
|
+
};
|
|
588
|
+
const initReturnsCmd = returnsCmd("init", config.initExport);
|
|
589
|
+
const updateReturnsCmd = returnsCmd("update", config.updateExport);
|
|
590
|
+
const hasSubscriptions = facts.functions.some((f) => f.name === config.subscriptionsExport);
|
|
591
|
+
// Unbound lists: model fields or helper entries (helpers are bindable
|
|
592
|
+
// surface — the clarified rule) on the model side, arm names on the
|
|
593
|
+
// msg side. Absent consts mean "the program declares none".
|
|
594
|
+
const modelUnbound = facts.modelUnbound?.value ?? [];
|
|
595
|
+
for (const name of modelUnbound) {
|
|
596
|
+
if (!modelFieldNames.has(name) && !helperNames.has(name)) {
|
|
597
|
+
throw new SidecarError(`modelUnbound names '${name}', which is neither a field of '${config.model}' nor a helper entry`, facts.modelUnbound.loc);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const msgUnbound = facts.msgUnbound?.value ?? [];
|
|
601
|
+
for (const name of msgUnbound) {
|
|
602
|
+
if (!armByName.has(name)) {
|
|
603
|
+
throw new SidecarError(`msgUnbound names '${name}', which is not an arm of '${config.msg}'`, facts.msgUnbound.loc);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Channels: the four function channels answer export presence by
|
|
607
|
+
// suffix; the two host-constructed channels and the env map ride the
|
|
608
|
+
// exported-const conventions and must target conforming arms.
|
|
609
|
+
const exports = abiExportSuffixes(profile);
|
|
610
|
+
const exportSet = new Set(exports);
|
|
611
|
+
const namedChannel = (constName) => {
|
|
612
|
+
const c = facts[constName];
|
|
613
|
+
if (c === null)
|
|
614
|
+
return null;
|
|
615
|
+
const payload = armByName.get(c.value);
|
|
616
|
+
if (payload === undefined) {
|
|
617
|
+
throw new SidecarError(`${constName} names '${c.value}', which is not an arm of '${config.msg}'`, c.loc);
|
|
618
|
+
}
|
|
619
|
+
if (payload.kind !== "record" && payload.kind !== "union" && payload.kind !== "enum" && payload.kind !== "scalar") {
|
|
620
|
+
throw new SidecarError(`${constName} names arm '${c.value}', whose payload descriptor is '${payload.kind}' — a host-constructed channel arm needs a named-type-family payload`, c.loc);
|
|
621
|
+
}
|
|
622
|
+
return c.value;
|
|
623
|
+
};
|
|
624
|
+
const envMsgs = facts.envMsgs?.value ?? [];
|
|
625
|
+
const seenEnv = new Set();
|
|
626
|
+
for (const e of envMsgs) {
|
|
627
|
+
if (seenEnv.has(e.env)) {
|
|
628
|
+
throw new SidecarError(`envMsgs repeats environment variable '${e.env}'`, facts.envMsgs.loc);
|
|
629
|
+
}
|
|
630
|
+
seenEnv.add(e.env);
|
|
631
|
+
const payload = armByName.get(e.msg);
|
|
632
|
+
if (payload === undefined) {
|
|
633
|
+
throw new SidecarError(`envMsgs targets '${e.msg}', which is not an arm of '${config.msg}'`, facts.envMsgs.loc);
|
|
634
|
+
}
|
|
635
|
+
if (payload.kind !== "bytes") {
|
|
636
|
+
throw new SidecarError(`envMsgs targets arm '${e.msg}', whose payload descriptor is '${payload.kind}' — the host delivers an environment value as bytes`, facts.envMsgs.loc);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const doc = {
|
|
640
|
+
format: SIDECAR_FORMAT,
|
|
641
|
+
wire_version: config.wireVersion,
|
|
642
|
+
abi_version: config.abiVersion,
|
|
643
|
+
compiler_version: input.compilerVersion,
|
|
644
|
+
entry: input.entry,
|
|
645
|
+
source_hash: input.sourceHash,
|
|
646
|
+
build_id: input.buildId,
|
|
647
|
+
types: projector.finishedTable(),
|
|
648
|
+
model: config.model,
|
|
649
|
+
model_helpers: helpers,
|
|
650
|
+
model_unbound: modelUnbound,
|
|
651
|
+
msg: { name: config.msg, arms: msgArms, unbound: msgUnbound },
|
|
652
|
+
init_returns_cmd: initReturnsCmd,
|
|
653
|
+
update_returns_cmd: updateReturnsCmd,
|
|
654
|
+
has_subscriptions: hasSubscriptions,
|
|
655
|
+
channels: {
|
|
656
|
+
command_msg: exportSet.has("command_msg"),
|
|
657
|
+
frame_msg: exportSet.has("frame_msg"),
|
|
658
|
+
key_msg: exportSet.has("key_msg"),
|
|
659
|
+
pinch_msg: exportSet.has("pinch_msg"),
|
|
660
|
+
appearance_msg: namedChannel("appearanceMsg"),
|
|
661
|
+
chrome_msg: namedChannel("chromeMsg"),
|
|
662
|
+
env_msgs: envMsgs,
|
|
663
|
+
},
|
|
664
|
+
abi: { prefix: profile.prefix, exports, snapshot_format: config.snapshotFormat },
|
|
665
|
+
// The pre-ask-4 absent form the schema names valid: every numeric
|
|
666
|
+
// slot above spelled f64, and the empty list is itself the
|
|
667
|
+
// attestation that no slot was integer-classed.
|
|
668
|
+
integer_slots: [],
|
|
669
|
+
deterministic: input.deterministic,
|
|
670
|
+
// Structural in library mode: the SC4005 gate refused any graph
|
|
671
|
+
// reaching async/timer/event-loop surface before emission, so a
|
|
672
|
+
// sidecar exists only for async_free graphs.
|
|
673
|
+
async_free: true,
|
|
674
|
+
};
|
|
675
|
+
return { ok: true, doc, json: JSON.stringify(doc, null, 2) + "\n" };
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
if (e instanceof SidecarRefusal) {
|
|
679
|
+
return { ok: false, diagnostics: [e.diag] };
|
|
680
|
+
}
|
|
681
|
+
if (e instanceof SidecarError) {
|
|
682
|
+
return { ok: false, diagnostics: [libSidecarDiag(e.detail, e.loc)] };
|
|
683
|
+
}
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
//# sourceMappingURL=sidecar.js.map
|