bonsai-js 1.0.0 → 1.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/CHANGELOG.md +11 -0
- package/README.md +24 -4
- package/dist/artifact.d.mts +47 -0
- package/dist/artifact.mjs +139 -0
- package/dist/artifact.mjs.map +1 -0
- package/dist/check/bind.mjs +117 -0
- package/dist/check/bind.mjs.map +1 -0
- package/dist/check/checker.mjs +14 -116
- package/dist/check/checker.mjs.map +1 -1
- package/dist/compile/compiler.mjs +2 -3
- package/dist/compile/compiler.mjs.map +1 -1
- package/dist/declared.mjs +8 -1
- package/dist/declared.mjs.map +1 -1
- package/dist/digest.mjs +109 -0
- package/dist/digest.mjs.map +1 -0
- package/dist/environment.d.mts +1 -1
- package/dist/environment.mjs +52 -309
- package/dist/environment.mjs.map +1 -1
- package/dist/errors.d.mts +21 -2
- package/dist/errors.mjs +17 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/features.mjs +3 -3
- package/dist/features.mjs.map +1 -1
- package/dist/functions/builtins.mjs +2 -2
- package/dist/functions/define.mjs +13 -6
- package/dist/functions/define.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/lite/index.d.mts +3 -2
- package/dist/lite/index.mjs +3 -2
- package/dist/lite/index.mjs.map +1 -1
- package/dist/load.mjs +308 -0
- package/dist/load.mjs.map +1 -0
- package/dist/program.mjs +323 -0
- package/dist/program.mjs.map +1 -0
- package/dist/query/index.mjs +1 -1
- package/dist/runtime/index.d.mts +61 -0
- package/dist/runtime/index.mjs +95 -0
- package/dist/runtime/index.mjs.map +1 -0
- package/dist/service/index.mjs +8 -5
- package/dist/service/index.mjs.map +1 -1
- package/dist/syntax/parser.mjs +1 -1
- package/dist/syntax/printer.mjs +1 -1
- package/dist/types.mjs +103 -23
- package/dist/types.mjs.map +1 -1
- package/dist/version.mjs +7 -0
- package/dist/version.mjs.map +1 -0
- package/package.json +10 -2
package/dist/environment.mjs
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { BonsaiCheckError, BonsaiError, BonsaiLimitError, BonsaiRuntimeError, locate } from "./errors.mjs";
|
|
2
2
|
import { isName } from "./syntax/lexer.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { assertHostSpec, assertType, conforms, describeMismatch, isRecord, overload } from "./functions/define.mjs";
|
|
7
|
-
import { BUILTINS, zoneParameter } from "./functions/builtins.mjs";
|
|
8
|
-
import { isStackOverflow, tooDeep } from "./runtime/overflow.mjs";
|
|
3
|
+
import { isExact, isNullable, typeData } from "./types.mjs";
|
|
4
|
+
import { assertHostSpec, assertType, isRecord, overload } from "./functions/define.mjs";
|
|
5
|
+
import { BUILTINS } from "./functions/builtins.mjs";
|
|
9
6
|
import { DEFAULT_PARSE_LIMITS, parse } from "./syntax/parser.mjs";
|
|
10
7
|
import { analyze } from "./check/checker.mjs";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
8
|
+
import { raised } from "./runtime/attribution.mjs";
|
|
9
|
+
import { DEFAULT_RUNTIME_LIMITS, MAX_VALUE_DEPTH_LIMIT } from "./runtime/state.mjs";
|
|
10
|
+
import { recordCompiled, recordDeclared } from "./declared.mjs";
|
|
11
|
+
import { assertKeys, numberOption, programRunner, timeoutOption, warmZones } from "./program.mjs";
|
|
15
12
|
//#region src/environment.ts
|
|
16
13
|
/**
|
|
17
14
|
* Declares a host function.
|
|
@@ -107,24 +104,6 @@ function mergeVariables(base, options) {
|
|
|
107
104
|
for (const [k, v] of Object.entries(options.variables ?? {})) add(k, v, "the variables option");
|
|
108
105
|
return out === void 0 ? void 0 : Object.freeze(out);
|
|
109
106
|
}
|
|
110
|
-
/** A number option: TypeError when it is not a number, RangeError when out of range. */
|
|
111
|
-
function numberOption(name, value, fallback, minimum) {
|
|
112
|
-
if (value === void 0) return fallback;
|
|
113
|
-
if (typeof value !== "number") throw new TypeError(`${name} must be a number`);
|
|
114
|
-
if (!Number.isInteger(value) || value < minimum) throw new RangeError(minimum === 0 ? `${name} must be a non-negative integer (0 disables it)` : `${name} must be a positive integer`);
|
|
115
|
-
return value;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* A timeout in milliseconds: 0 turns it off; any other non-negative number,
|
|
119
|
-
* fractions included, is a limit, so a budget computed down to its last
|
|
120
|
-
* fraction of a millisecond stays a limit rather than becoming 0.
|
|
121
|
-
*/
|
|
122
|
-
function timeoutOption(name, value, fallback) {
|
|
123
|
-
if (value === void 0) return fallback;
|
|
124
|
-
if (typeof value !== "number") throw new TypeError(`${name} must be a number`);
|
|
125
|
-
if (!Number.isFinite(value) || value < 0) throw new RangeError(`${name} must be a non-negative number of milliseconds (0 disables it)`);
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
107
|
const OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
129
108
|
"variables",
|
|
130
109
|
"strict",
|
|
@@ -145,10 +124,6 @@ const LIBRARY_KEYS = /* @__PURE__ */ new Set([
|
|
|
145
124
|
"functions",
|
|
146
125
|
"variables"
|
|
147
126
|
]);
|
|
148
|
-
function assertKeys(value, allowed, what) {
|
|
149
|
-
if (!isRecord(value)) throw new TypeError(`${what} must be an object`);
|
|
150
|
-
for (const key of Object.keys(value)) if (!allowed.has(key)) throw new TypeError(`Unknown ${what.toLowerCase()} key "${key}" (expected one of: ${[...allowed].join(", ")})`);
|
|
151
|
-
}
|
|
152
127
|
/** Environment options come from configuration: mistakes there are programming errors. */
|
|
153
128
|
function assertOptions(options) {
|
|
154
129
|
assertKeys(options, OPTION_KEYS, "Options");
|
|
@@ -253,7 +228,6 @@ var ProgramCache = class {
|
|
|
253
228
|
}
|
|
254
229
|
}
|
|
255
230
|
};
|
|
256
|
-
const EMPTY_CONTEXT = Object.freeze({});
|
|
257
231
|
const NO_DIAGNOSTICS = Object.freeze([]);
|
|
258
232
|
/**
|
|
259
233
|
* What check() found. `ok`, `type`, and `diagnostics` are the JSON form; the
|
|
@@ -292,66 +266,6 @@ var CheckSuccess = class extends CheckFailure {
|
|
|
292
266
|
return this.#program();
|
|
293
267
|
}
|
|
294
268
|
};
|
|
295
|
-
const EVALUATE_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
296
|
-
"timeout",
|
|
297
|
-
"maxSteps",
|
|
298
|
-
"signal",
|
|
299
|
-
"now",
|
|
300
|
-
"validateContext"
|
|
301
|
-
]);
|
|
302
|
-
/** Whether a value is a valid Date (an object that only inherits from Date.prototype is not). */
|
|
303
|
-
function isValidDate(value) {
|
|
304
|
-
try {
|
|
305
|
-
return value instanceof Date && !Number.isNaN(Date.prototype.getTime.call(value));
|
|
306
|
-
} catch {
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/** Whether a value has an AbortSignal's members; a signal whose getters throw does not. */
|
|
311
|
-
function looksLikeSignal(signal) {
|
|
312
|
-
try {
|
|
313
|
-
const s = signal;
|
|
314
|
-
return typeof s.aborted === "boolean" && typeof s.addEventListener === "function" && typeof s.removeEventListener === "function";
|
|
315
|
-
} catch {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
/** Validates per-evaluation options the way `limits` are validated; nothing fails open. */
|
|
320
|
-
function evaluationLimits(options, settings) {
|
|
321
|
-
if (options === void 0) return {
|
|
322
|
-
maxSteps: settings.runtimeLimits.maxSteps,
|
|
323
|
-
timeout: settings.timeout,
|
|
324
|
-
signal: void 0,
|
|
325
|
-
now: void 0,
|
|
326
|
-
validateContext: void 0
|
|
327
|
-
};
|
|
328
|
-
let o;
|
|
329
|
-
try {
|
|
330
|
-
assertKeys(options, EVALUATE_OPTION_KEYS, "Evaluate option");
|
|
331
|
-
const read = options;
|
|
332
|
-
o = {
|
|
333
|
-
maxSteps: read.maxSteps,
|
|
334
|
-
timeout: read.timeout,
|
|
335
|
-
signal: read.signal,
|
|
336
|
-
now: read.now,
|
|
337
|
-
validateContext: read.validateContext
|
|
338
|
-
};
|
|
339
|
-
} catch (error) {
|
|
340
|
-
if (error instanceof TypeError || error instanceof RangeError) throw error;
|
|
341
|
-
throw new TypeError(`Evaluate options could not be read: ${errorText(error)}`, { cause: error });
|
|
342
|
-
}
|
|
343
|
-
const signal = o.signal;
|
|
344
|
-
if (signal !== void 0 && (typeof signal !== "object" || signal === null || !looksLikeSignal(signal))) throw new TypeError("signal must be an AbortSignal");
|
|
345
|
-
if (o.now !== void 0 && !isValidDate(o.now)) throw new TypeError("now must be a valid Date");
|
|
346
|
-
if (o.validateContext !== void 0 && typeof o.validateContext !== "boolean") throw new TypeError("validateContext must be a boolean");
|
|
347
|
-
return {
|
|
348
|
-
maxSteps: numberOption("maxSteps", o.maxSteps, settings.runtimeLimits.maxSteps, 0),
|
|
349
|
-
timeout: timeoutOption("timeout", o.timeout, settings.timeout),
|
|
350
|
-
signal,
|
|
351
|
-
now: o.now,
|
|
352
|
-
validateContext: o.validateContext
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
269
|
/** Freezes a syntax tree (or any tree of plain objects and arrays) in place, without recursion. */
|
|
356
270
|
function deepFreeze(root) {
|
|
357
271
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -365,87 +279,6 @@ function deepFreeze(root) {
|
|
|
365
279
|
}
|
|
366
280
|
return root;
|
|
367
281
|
}
|
|
368
|
-
function validationBudget(source, limits) {
|
|
369
|
-
return {
|
|
370
|
-
maxDepth: limits.maxDepth,
|
|
371
|
-
remaining: limits.maxSteps > 0 ? limits.maxSteps : Number.POSITIVE_INFINITY,
|
|
372
|
-
deadline: limits.timeout > 0 ? performance.now() + limits.timeout : 0,
|
|
373
|
-
onExhausted: (reason) => {
|
|
374
|
-
if (reason === "depth") throw raised(new BonsaiLimitError("VALUE_DEPTH_LIMIT", `Context nests deeper than ${limits.maxDepth}`, { source }));
|
|
375
|
-
if (reason === "time") throw raised(new BonsaiLimitError("TIMEOUT", "Context validation timed out", { source }));
|
|
376
|
-
throw raised(new BonsaiLimitError("STEP_LIMIT", "Context validation exceeded the step limit", { source }));
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
function invalidContext(problem, source) {
|
|
381
|
-
if (problem !== void 0) throw raised(new BonsaiRuntimeError("INVALID_CONTEXT", `Invalid context: ${problem}`, { source }));
|
|
382
|
-
}
|
|
383
|
-
function validateContext(ctx, variables, source, limits) {
|
|
384
|
-
const budget = validationBudget(source, limits);
|
|
385
|
-
for (const [name, type] of Object.entries(variables)) {
|
|
386
|
-
const value = Object.hasOwn(ctx, name) ? ctx[name] : void 0;
|
|
387
|
-
invalidContext(describeMismatch(value, type, name, budget), source);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Why the value read along `segments` (from index `at`) does not conform, or
|
|
392
|
-
* undefined. Each step is judged by its own declared type: an absent or null
|
|
393
|
-
* value is fine only where that type is nullable; a declared field is followed
|
|
394
|
-
* (a missing required one fails); an undeclared key of an object (objects are
|
|
395
|
-
* open) or an absent record entry ends the check; past a type that is not a
|
|
396
|
-
* map, and at the end of the path, the value is checked whole, as a context is.
|
|
397
|
-
* Fields the path does not name are not checked.
|
|
398
|
-
*/
|
|
399
|
-
function describeAlong(value, type, segments, at, path, budget) {
|
|
400
|
-
if (at === segments.length) return describeMismatch(value, type, path, budget);
|
|
401
|
-
if (--budget.remaining < 0) budget.onExhausted("steps");
|
|
402
|
-
if (type.kind === "union") {
|
|
403
|
-
let first;
|
|
404
|
-
for (const member of type.types) {
|
|
405
|
-
const problem = describeAlong(value, member, segments, at, path, budget);
|
|
406
|
-
if (problem === void 0) return void 0;
|
|
407
|
-
first ??= problem;
|
|
408
|
-
}
|
|
409
|
-
return first;
|
|
410
|
-
}
|
|
411
|
-
const actual = value === void 0 ? null : value;
|
|
412
|
-
if (type.kind !== "map" || !isMap(actual)) return describeMismatch(value, type, path, budget);
|
|
413
|
-
const key = segments[at];
|
|
414
|
-
const field = Object.hasOwn(actual, key) ? actual[key] : void 0;
|
|
415
|
-
if (Object.hasOwn(type.fields, key)) return describeAlong(field, type.fields[key], segments, at + 1, `${path}.${key}`, budget);
|
|
416
|
-
if (type.rest === void 0 || field === void 0) return void 0;
|
|
417
|
-
return describeAlong(field, type.rest, segments, at + 1, `${path}.${key}`, budget);
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Validates the values a residual reads: each path in `dependsOn`, step by
|
|
421
|
-
* step against the types declared along it (see describeAlong), so a context
|
|
422
|
-
* holding exactly those paths passes and invalid data on them fails as it
|
|
423
|
-
* would for the program.
|
|
424
|
-
*/
|
|
425
|
-
function validatePaths(ctx, variables, paths, source, limits) {
|
|
426
|
-
const budget = validationBudget(source, limits);
|
|
427
|
-
for (const path of paths) {
|
|
428
|
-
const segments = path.split(".");
|
|
429
|
-
const name = segments[0];
|
|
430
|
-
if (!Object.hasOwn(variables, name)) continue;
|
|
431
|
-
invalidContext(describeAlong(Object.hasOwn(ctx, name) ? ctx[name] : void 0, variables[name], segments, 1, name, budget), source);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Reading host data (a getter, a Proxy trap, a `then` hook) can throw. Such a
|
|
436
|
-
* failure is a HOST_ERROR, never a raw JavaScript error. Only the errors this
|
|
437
|
-
* evaluation raised pass through: any other Bonsai error (from another
|
|
438
|
-
* evaluation, from parsing, checking, or translating, or built by the host)
|
|
439
|
-
* that host data threw is the host data's failure too.
|
|
440
|
-
*/
|
|
441
|
-
function hostDataFailure(error, source) {
|
|
442
|
-
if (isOwn(error)) return error;
|
|
443
|
-
if (isStackOverflow(error)) return raised(tooDeep(source, error));
|
|
444
|
-
return raised(new BonsaiRuntimeError("HOST_ERROR", `Reading host data failed: ${errorText(error)}`, {
|
|
445
|
-
source,
|
|
446
|
-
cause: error
|
|
447
|
-
}));
|
|
448
|
-
}
|
|
449
282
|
const systemClock = () => /* @__PURE__ */ new Date();
|
|
450
283
|
/** A host clock whose results are checked: now() must be a valid timestamp. */
|
|
451
284
|
function checkedClock(clock) {
|
|
@@ -460,53 +293,41 @@ function checkedClock(clock) {
|
|
|
460
293
|
return value;
|
|
461
294
|
};
|
|
462
295
|
}
|
|
463
|
-
/**
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
* Loads the time zone data a program's calls need, so its first evaluation
|
|
467
|
-
* does not pay for it inside its deadline. Only built-in functions that take a
|
|
468
|
-
* zone count: a zone written as text is loaded itself, and any other zone
|
|
469
|
-
* argument loads the data every zone shares. Each zone is warmed once, and at
|
|
470
|
-
* most WARMED_ZONES of them, so a source full of zone names compiles quickly.
|
|
471
|
-
*/
|
|
472
|
-
function warmZones(analysis) {
|
|
296
|
+
/** Whether a type has an exact map type anywhere in it. */
|
|
297
|
+
function holdsExact(type) {
|
|
298
|
+
const stack = [type];
|
|
473
299
|
const seen = /* @__PURE__ */ new Set();
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
300
|
+
while (stack.length > 0) {
|
|
301
|
+
const next = stack.pop();
|
|
302
|
+
if (seen.has(next)) continue;
|
|
303
|
+
seen.add(next);
|
|
304
|
+
if (isExact(next)) return true;
|
|
305
|
+
if (next.kind === "list") stack.push(next.element);
|
|
306
|
+
else if (next.kind === "union") stack.push(...next.types);
|
|
307
|
+
else if (next.kind === "map") {
|
|
308
|
+
stack.push(...Object.values(next.fields));
|
|
309
|
+
if (next.rest !== void 0) stack.push(next.rest);
|
|
310
|
+
}
|
|
483
311
|
}
|
|
312
|
+
return false;
|
|
484
313
|
}
|
|
485
|
-
/** Reads compile() and
|
|
486
|
-
function
|
|
314
|
+
/** Reads compile(), check(), and the runtime's load() options: only `expect`, which must be a type built with t. Returns a frozen copy of it. */
|
|
315
|
+
function expectOption(options, what) {
|
|
487
316
|
if (options === void 0) return void 0;
|
|
488
|
-
if (typeof options !== "object" || options === null || Array.isArray(options)) throw new TypeError(
|
|
317
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) throw new TypeError(`${what} options must be an object`);
|
|
489
318
|
let expect;
|
|
490
319
|
try {
|
|
491
|
-
for (const key of Object.keys(options)) if (key !== "expect") throw new TypeError(`Unknown
|
|
320
|
+
for (const key of Object.keys(options)) if (key !== "expect") throw new TypeError(`Unknown ${what.toLowerCase()} option key "${key}" (expected one of: expect)`);
|
|
492
321
|
expect = options.expect;
|
|
493
322
|
} catch (error) {
|
|
494
323
|
if (error instanceof TypeError) throw error;
|
|
495
|
-
throw new TypeError(
|
|
496
|
-
}
|
|
497
|
-
if (expect !== void 0) assertType(expect, "Compile option \"expect\"");
|
|
498
|
-
return expect;
|
|
499
|
-
}
|
|
500
|
-
function contextOf(value) {
|
|
501
|
-
if (value === void 0 || value === null) return EMPTY_CONTEXT;
|
|
502
|
-
let map;
|
|
503
|
-
try {
|
|
504
|
-
map = typeof value === "object" && isMap(value);
|
|
505
|
-
} catch (error) {
|
|
506
|
-
throw new BonsaiRuntimeError("HOST_ERROR", "Reading the evaluation context failed", { cause: error });
|
|
324
|
+
throw new TypeError(`${what} options could not be read`, { cause: error });
|
|
507
325
|
}
|
|
508
|
-
if (
|
|
509
|
-
|
|
326
|
+
if (expect === void 0) return void 0;
|
|
327
|
+
assertType(expect, `${what} option "expect"`);
|
|
328
|
+
const own = typeData(expect);
|
|
329
|
+
if (own !== expect) assertType(own, `${what} option "expect"`);
|
|
330
|
+
return own;
|
|
510
331
|
}
|
|
511
332
|
function createEnvironment(settings) {
|
|
512
333
|
const checkEnv = {
|
|
@@ -539,101 +360,14 @@ function createEnvironment(settings) {
|
|
|
539
360
|
function makeProgram(source, analysis, expect, constants, residualOf) {
|
|
540
361
|
if (residualOf === void 0) warmZones(analysis);
|
|
541
362
|
const guard = expect !== void 0 && analysis.checkResult ? expect : void 0;
|
|
542
|
-
const checked
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
let syncCode;
|
|
548
|
-
let asyncCode;
|
|
549
|
-
let pooled;
|
|
550
|
-
let pooledInUse = false;
|
|
551
|
-
const compileOptions = constants === void 0 ? {} : {
|
|
363
|
+
const { prepare, checked, settle, runSync, runAsync, compileOptions } = programRunner({
|
|
364
|
+
source,
|
|
365
|
+
analysis,
|
|
366
|
+
guard,
|
|
367
|
+
settings,
|
|
552
368
|
constants,
|
|
553
|
-
|
|
554
|
-
};
|
|
555
|
-
const syncProgram = () => syncCode ??= compileProgram(analysis, "sync", compileOptions);
|
|
556
|
-
const asyncProgram = () => asyncCode ??= analysis.async ? compileProgram(analysis, "async", compileOptions) : syncProgram();
|
|
557
|
-
function prepare(state, ctx, limits, locals) {
|
|
558
|
-
state.reset(ctx, source, locals, limits.maxSteps, limits.timeout, limits.signal);
|
|
559
|
-
if (limits.now !== void 0) state.nowValue = limits.now;
|
|
560
|
-
const residual = residualOf;
|
|
561
|
-
if (residual !== void 0 && residual.readsContext) {
|
|
562
|
-
const roots = residual.knownRoots;
|
|
563
|
-
state.charge(roots.length);
|
|
564
|
-
const declared = settings.strict ? settings.variables : void 0;
|
|
565
|
-
const missing = roots.find((name) => {
|
|
566
|
-
if (declared !== void 0 && !Object.hasOwn(declared, name)) return false;
|
|
567
|
-
const descriptor = Object.getOwnPropertyDescriptor(ctx, name);
|
|
568
|
-
return descriptor === void 0 || "value" in descriptor && descriptor.value === void 0;
|
|
569
|
-
});
|
|
570
|
-
if (missing !== void 0) throw raised(new BonsaiRuntimeError("INVALID_CONTEXT", `Invalid context: ${missing} is missing; this residual calls a call: true host function, so pass the full context, as for the program`, { source }));
|
|
571
|
-
}
|
|
572
|
-
const validation = limits.validateContext ?? settings.validateContext ? settings.variables : void 0;
|
|
573
|
-
if (validation !== void 0) {
|
|
574
|
-
const validationLimits = {
|
|
575
|
-
maxDepth: settings.runtimeLimits.maxValueDepth,
|
|
576
|
-
maxSteps: limits.maxSteps,
|
|
577
|
-
timeout: limits.timeout
|
|
578
|
-
};
|
|
579
|
-
if (residual === void 0 || residual.readsContext) validateContext(ctx, validation, source, validationLimits);
|
|
580
|
-
else validatePaths(ctx, validation, residual.dependsOn, source, validationLimits);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
function runSync(context, options) {
|
|
584
|
-
if (analysis.async) throw escaping(new BonsaiRuntimeError("ASYNC_IN_SYNC", "This expression calls an async function; use evaluate() instead of evaluateSync()", { source }));
|
|
585
|
-
const code = syncProgram();
|
|
586
|
-
const limits = limitsOf(options);
|
|
587
|
-
const ctx = contextOf(context);
|
|
588
|
-
let state;
|
|
589
|
-
const reuse = !pooledInUse;
|
|
590
|
-
if (reuse) {
|
|
591
|
-
pooled ??= new State(settings.runtimeLimits, settings.clock);
|
|
592
|
-
state = pooled;
|
|
593
|
-
pooledInUse = true;
|
|
594
|
-
} else state = new State(settings.runtimeLimits, settings.clock);
|
|
595
|
-
try {
|
|
596
|
-
prepare(state, ctx, limits, code.localCount);
|
|
597
|
-
const result = code.run(state);
|
|
598
|
-
checked(result, state);
|
|
599
|
-
state.checkTime();
|
|
600
|
-
return result;
|
|
601
|
-
} catch (error) {
|
|
602
|
-
throw escaping(hostDataFailure(error, source));
|
|
603
|
-
} finally {
|
|
604
|
-
state.release();
|
|
605
|
-
if (reuse) pooledInUse = false;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
async function runAsync(context, options) {
|
|
609
|
-
if (!analysis.async) {
|
|
610
|
-
const result = runSync(context, options);
|
|
611
|
-
try {
|
|
612
|
-
return settle(result);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
throw escaping(hostDataFailure(error, source));
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
const code = asyncProgram();
|
|
618
|
-
const limits = limitsOf(options);
|
|
619
|
-
const ctx = contextOf(context);
|
|
620
|
-
const state = new State(settings.runtimeLimits, settings.clock);
|
|
621
|
-
try {
|
|
622
|
-
prepare(state, ctx, limits, code.localCount);
|
|
623
|
-
const result = (await code.run(state)).v;
|
|
624
|
-
checked(result, state);
|
|
625
|
-
state.checkTime();
|
|
626
|
-
return settle(result);
|
|
627
|
-
} catch (error) {
|
|
628
|
-
throw escaping(hostDataFailure(error, source));
|
|
629
|
-
} finally {
|
|
630
|
-
state.release();
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
function settle(result) {
|
|
634
|
-
if (result !== null && (typeof result === "object" || typeof result === "function") && typeof result.then === "function") throw raised(new BonsaiRuntimeError("TYPE_ERROR", "evaluate() cannot return a promise-like value; use evaluateSync()", { source }));
|
|
635
|
-
return result;
|
|
636
|
-
}
|
|
369
|
+
residualOf
|
|
370
|
+
});
|
|
637
371
|
const features = settings.features?.program({
|
|
638
372
|
source,
|
|
639
373
|
analysis,
|
|
@@ -674,6 +408,13 @@ function createEnvironment(settings) {
|
|
|
674
408
|
evaluateSync: (...args) => runSync(args[0], args[1])
|
|
675
409
|
});
|
|
676
410
|
recordDeclared(program, settings.variables);
|
|
411
|
+
recordCompiled(program, {
|
|
412
|
+
source,
|
|
413
|
+
analysis,
|
|
414
|
+
expect,
|
|
415
|
+
lookup: checkEnv.lookup,
|
|
416
|
+
variables: settings.variables
|
|
417
|
+
});
|
|
677
418
|
return program;
|
|
678
419
|
}
|
|
679
420
|
function compile(source, expect) {
|
|
@@ -684,10 +425,12 @@ function createEnvironment(settings) {
|
|
|
684
425
|
}
|
|
685
426
|
/**
|
|
686
427
|
* compile(), through the program cache: programs are immutable, so the same
|
|
687
|
-
* source with an equal expected type
|
|
428
|
+
* source with an equal expected type shares one. `expect` is expectOption's
|
|
429
|
+
* plain copy, so its JSON is the type the program is checked against.
|
|
688
430
|
*/
|
|
689
431
|
function cachedCompile(source, expect) {
|
|
690
432
|
if (typeof source !== "string") throw new TypeError("An expression must be a string");
|
|
433
|
+
if (expect !== void 0 && holdsExact(expect)) return compile(source, expect);
|
|
691
434
|
const expectKey = expect === void 0 ? void 0 : JSON.stringify(expect);
|
|
692
435
|
const key = expectKey === void 0 ? source : `${source}\u0000${expectKey}`;
|
|
693
436
|
const entry = cache.get(key);
|
|
@@ -724,7 +467,7 @@ function createEnvironment(settings) {
|
|
|
724
467
|
strict: settings.strict,
|
|
725
468
|
parse: parseSource,
|
|
726
469
|
check(source, options) {
|
|
727
|
-
const expect =
|
|
470
|
+
const expect = expectOption(options, "Compile");
|
|
728
471
|
let analysis;
|
|
729
472
|
try {
|
|
730
473
|
analysis = analyzeSource(source, expect);
|
|
@@ -745,7 +488,7 @@ function createEnvironment(settings) {
|
|
|
745
488
|
let program;
|
|
746
489
|
return Object.freeze(new CheckSuccess(type, diagnostics, analysis, () => program ??= makeProgram(source, analysis, expect)));
|
|
747
490
|
},
|
|
748
|
-
compile: (source, options) => cachedCompile(source,
|
|
491
|
+
compile: (source, options) => cachedCompile(source, expectOption(options, "Compile")),
|
|
749
492
|
...settings.features === void 0 ? {} : { partial: (source, known, options) => cached(source).partial(known, options) },
|
|
750
493
|
evaluate(source, ...args) {
|
|
751
494
|
try {
|
|
@@ -794,6 +537,6 @@ function environmentWith(features, options) {
|
|
|
794
537
|
});
|
|
795
538
|
}
|
|
796
539
|
//#endregion
|
|
797
|
-
export {
|
|
540
|
+
export { ProgramCache, environmentWith, expectOption, fn, internalsOf, settingsFrom, withContext };
|
|
798
541
|
|
|
799
542
|
//# sourceMappingURL=environment.mjs.map
|