@mark1russell7/coeval 0.23.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/build/index.d.ts +200 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +901 -79
- package/build/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agree.ts +105 -2
- package/src/conform.ts +1 -1
- package/src/effects.ts +1 -1
- package/src/evaluate.ts +182 -8
- package/src/formula.ts +1 -0
- package/src/index.ts +10 -2
- package/src/reference.ts +55 -0
- package/src/schedule.ts +11 -2
- package/src/static.ts +61 -2
- package/src/stdlib.ts +89 -11
- package/src/syntax.ts +322 -20
- package/src/typing.ts +33 -3
- package/src/world.ts +7 -1
package/src/schedule.ts
CHANGED
|
@@ -294,6 +294,8 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
294
294
|
// The nodes that finished in a kept read, and if that read met a node in evaluation below it (D143).
|
|
295
295
|
const keptNodes: string[] = [];
|
|
296
296
|
let keptMet = false;
|
|
297
|
+
// The count of the visits at the start of the kept read. A node with a lower number started below the read.
|
|
298
|
+
let keptFrom = 0;
|
|
297
299
|
let count = 0;
|
|
298
300
|
let ahead = 0;
|
|
299
301
|
// The node that stopped the evaluation of a walk, because the walk has no result for it.
|
|
@@ -370,8 +372,12 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
370
372
|
const reader = stack[stack.length - 1];
|
|
371
373
|
const known = epoch.values.get(id);
|
|
372
374
|
if (known !== undefined) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
+
if (reader !== undefined && onOpen.has(id)) {
|
|
376
|
+
// The path from that component to a kept read has a read that no evaluation asked for (SCHED.KEPT-OPEN).
|
|
377
|
+
if (keptAt >= 0 && (order.get(id) as number) < keptFrom) keptMet = true;
|
|
378
|
+
// A finished node of an open component is on a cycle with its reader too.
|
|
379
|
+
else lower(reader, order.get(id) as number);
|
|
380
|
+
}
|
|
375
381
|
return known;
|
|
376
382
|
}
|
|
377
383
|
if (busy.has(id)) {
|
|
@@ -592,9 +598,11 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
592
598
|
keeping(read) {
|
|
593
599
|
const outer = keptAt;
|
|
594
600
|
const outerMet = keptMet;
|
|
601
|
+
const outerFrom = keptFrom;
|
|
595
602
|
const from = keptNodes.length;
|
|
596
603
|
keptAt = stack.length;
|
|
597
604
|
keptMet = false;
|
|
605
|
+
keptFrom = count;
|
|
598
606
|
try {
|
|
599
607
|
return read();
|
|
600
608
|
} finally {
|
|
@@ -609,6 +617,7 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
609
617
|
}
|
|
610
618
|
keptAt = outer;
|
|
611
619
|
keptMet = outerMet;
|
|
620
|
+
keptFrom = outerFrom;
|
|
612
621
|
}
|
|
613
622
|
},
|
|
614
623
|
});
|
package/src/static.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { answerOf, ask } from "./ladder.ts";
|
|
|
4
4
|
import { type PartSlot, partsOf, rulesCover, scopesOf } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
|
|
6
6
|
import type { ErrorCode } from "./errors.ts";
|
|
7
|
-
import { type Machine, partCaps, permits, waitedReason } from "./evaluate.ts";
|
|
7
|
+
import { type Machine, partCaps, partGrants, permits, waitedReason } from "./evaluate.ts";
|
|
8
8
|
import type { ValueType } from "./types.ts";
|
|
9
9
|
import { type RootTypes, type TypeNode, type Typed, machineTypeIssues, typePass } from "./typing.ts";
|
|
10
10
|
import { cycleIssues } from "./instances.ts";
|
|
@@ -41,6 +41,8 @@ export interface DocVisit {
|
|
|
41
41
|
* around it (`Kind.caps`). A walk without capabilities gives none here.
|
|
42
42
|
*/
|
|
43
43
|
readonly caps?: readonly Cap[] | undefined;
|
|
44
|
+
/** The kinds of effects that a handler around the document answers (CAP.GRANT, D146). Such an effect does not reach the host. */
|
|
45
|
+
readonly handled?: readonly Cap[] | undefined;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/** A position that a walk cannot read as a document. */
|
|
@@ -114,6 +116,7 @@ class Visit implements DocVisit {
|
|
|
114
116
|
readonly typePath: readonly Key[] | undefined;
|
|
115
117
|
readonly address: true | undefined;
|
|
116
118
|
readonly caps: readonly Cap[] | undefined;
|
|
119
|
+
readonly handled: readonly Cap[] | undefined;
|
|
117
120
|
readonly #place: Trail | undefined;
|
|
118
121
|
readonly #at: Trail | undefined;
|
|
119
122
|
readonly #bound: readonly string[];
|
|
@@ -135,6 +138,7 @@ class Visit implements DocVisit {
|
|
|
135
138
|
caps: readonly Cap[] | undefined,
|
|
136
139
|
bound: readonly string[],
|
|
137
140
|
expect: ValueType | undefined,
|
|
141
|
+
handled: readonly Cap[] | undefined,
|
|
138
142
|
) {
|
|
139
143
|
this.doc = doc;
|
|
140
144
|
this.declared = declared;
|
|
@@ -147,6 +151,7 @@ class Visit implements DocVisit {
|
|
|
147
151
|
this.typePath = typePath;
|
|
148
152
|
this.address = address;
|
|
149
153
|
this.caps = caps;
|
|
154
|
+
this.handled = handled;
|
|
150
155
|
this.#place = place;
|
|
151
156
|
this.#at = at;
|
|
152
157
|
this.#bound = bound;
|
|
@@ -199,6 +204,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
199
204
|
readonly caps: readonly Cap[] | undefined;
|
|
200
205
|
readonly bound: readonly string[];
|
|
201
206
|
readonly expect: ValueType | undefined;
|
|
207
|
+
readonly handled: readonly Cap[] | undefined;
|
|
202
208
|
}
|
|
203
209
|
const stack: Item[] = [
|
|
204
210
|
{
|
|
@@ -213,6 +219,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
213
219
|
slot: [],
|
|
214
220
|
parent: undefined,
|
|
215
221
|
caps: options.caps,
|
|
222
|
+
handled: undefined,
|
|
216
223
|
bound: [],
|
|
217
224
|
expect: undefined,
|
|
218
225
|
},
|
|
@@ -241,6 +248,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
241
248
|
item.caps,
|
|
242
249
|
item.bound,
|
|
243
250
|
item.expect,
|
|
251
|
+
item.handled,
|
|
244
252
|
);
|
|
245
253
|
const answer = visit(here);
|
|
246
254
|
if (answer === "skip" || kind === undefined) continue;
|
|
@@ -250,10 +258,14 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
250
258
|
continue;
|
|
251
259
|
}
|
|
252
260
|
const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
|
|
261
|
+
// The rules of a grant are read one time for each kind, not for each part. A kind without such a rule gives no list (K238).
|
|
262
|
+
const grantRules = kind.caps !== undefined && kind.caps.some((rule) => rule.rule === "grant") ? kind.caps : undefined;
|
|
253
263
|
for (let index = split.parts.length - 1; index >= 0; index -= 1) {
|
|
254
264
|
const slot = split.parts[index] as PartSlot;
|
|
255
265
|
const extra = sees[index] ?? [];
|
|
256
266
|
const hidden = later[index] ?? [];
|
|
267
|
+
const grants = grantRules === undefined ? undefined : partGrants(grantRules, slot.path, doc.value);
|
|
268
|
+
const granted = grants !== undefined && grants.length > 0 ? grants : undefined;
|
|
257
269
|
stack.push({
|
|
258
270
|
value: slot.item,
|
|
259
271
|
place: slot.path.length === 0 ? item.place : { up: item.place, keys: slot.path },
|
|
@@ -265,7 +277,14 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
265
277
|
address: slot.address === true,
|
|
266
278
|
slot: slot.path,
|
|
267
279
|
parent: here,
|
|
268
|
-
|
|
280
|
+
// A part with no grant keeps the list of partCaps, with no copy (K238).
|
|
281
|
+
caps:
|
|
282
|
+
item.caps === undefined || kind.caps === undefined
|
|
283
|
+
? item.caps
|
|
284
|
+
: granted === undefined
|
|
285
|
+
? partCaps(kind.caps, slot.path, item.caps)
|
|
286
|
+
: [...partCaps(kind.caps, slot.path, item.caps), ...granted],
|
|
287
|
+
handled: granted === undefined ? item.handled : [...(item.handled ?? []), ...granted],
|
|
269
288
|
bound: extra,
|
|
270
289
|
expect: slot.expect,
|
|
271
290
|
});
|
|
@@ -361,6 +380,45 @@ export function mapRefs(world: World, doc: unknown, change: (use: RefUse) => unk
|
|
|
361
380
|
return out;
|
|
362
381
|
}
|
|
363
382
|
|
|
383
|
+
/**
|
|
384
|
+
* The answer of the function of `mapDocs` for one document.
|
|
385
|
+
*
|
|
386
|
+
* - `undefined`: keep the document and go into its parts.
|
|
387
|
+
* - `"skip"`: keep the document and stop there.
|
|
388
|
+
* - `{ to }`: put `to` in the place of the document and stop there.
|
|
389
|
+
* - `{ to, into: true }`: put `to` in the place of the document, then go into the parts of the old document. Thus
|
|
390
|
+
* `to` must keep the path of each such part.
|
|
391
|
+
*/
|
|
392
|
+
export type DocChange = undefined | "skip" | { readonly to: unknown; readonly into?: boolean };
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* This function changes the documents of a document, from the root to the parts (STATIC.MAPDOCS). The function
|
|
396
|
+
* `change` reads only the original documents. It decides at each node, and it can stop at a node. The kinds of a
|
|
397
|
+
* position come from the world, thus this function names no kind. The document is not changed: the function gives a
|
|
398
|
+
* new document that shares each part without a change.
|
|
399
|
+
*
|
|
400
|
+
* @param world - The world of the kinds.
|
|
401
|
+
* @param doc - The document, as JSON.
|
|
402
|
+
* @param change - The function that gives the answer for each visit. See `DocChange`.
|
|
403
|
+
* @param options - The type of a bare root value, and the names that the host binds at the root.
|
|
404
|
+
* @returns The new document, or `doc` itself when no document changes.
|
|
405
|
+
*/
|
|
406
|
+
export function mapDocs(world: World, doc: unknown, change: (visit: DocVisit) => DocChange, options: WalkOptions = {}): unknown {
|
|
407
|
+
let out = doc;
|
|
408
|
+
walk(
|
|
409
|
+
world,
|
|
410
|
+
doc,
|
|
411
|
+
(visit) => {
|
|
412
|
+
const next = change(visit);
|
|
413
|
+
if (next === undefined || next === "skip") return next;
|
|
414
|
+
out = setAt(out, visit.at, next.to);
|
|
415
|
+
return next.into === true ? undefined : "skip";
|
|
416
|
+
},
|
|
417
|
+
options,
|
|
418
|
+
);
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
|
|
364
422
|
/** A problem that a check finds before a document runs. */
|
|
365
423
|
export interface Issue {
|
|
366
424
|
readonly code: ErrorCode;
|
|
@@ -770,6 +828,7 @@ const KEEPS_SELF: ReadonlySet<string> = /* @__PURE__ */ new Set([
|
|
|
770
828
|
"control.seq",
|
|
771
829
|
"control.pure",
|
|
772
830
|
"control.if",
|
|
831
|
+
"control.handle",
|
|
773
832
|
"list.all",
|
|
774
833
|
"effect.log",
|
|
775
834
|
"effect.write",
|
package/src/stdlib.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type Json, type Key, docEquals, field, isRecord } from "./doc.ts";
|
|
1
|
+
import { type Json, type Key, docEquals, field, isKey, isRecord } from "./doc.ts";
|
|
2
2
|
import {
|
|
3
3
|
type EvalInput,
|
|
4
4
|
type Evaluator,
|
|
5
|
+
type Clause,
|
|
5
6
|
type InputWithRuntime,
|
|
6
7
|
type Machine,
|
|
7
8
|
type Plan,
|
|
@@ -144,6 +145,27 @@ export const STD_WORLD: World = {
|
|
|
144
145
|
lazy: [{ after: 1 }],
|
|
145
146
|
summary: "It evaluates its part without capabilities, thus an effect in it fails.",
|
|
146
147
|
},
|
|
148
|
+
handle: {
|
|
149
|
+
verbs: { eval: "control.handle", view: "view.op" },
|
|
150
|
+
parts: {
|
|
151
|
+
shape: "record",
|
|
152
|
+
fields: {
|
|
153
|
+
with: { shape: "list", item: { shape: "record", fields: { op: { shape: "none" }, do: { shape: "term" }, abort: { shape: "term" } }, required: ["op"], closed: true } },
|
|
154
|
+
return: { shape: "term" },
|
|
155
|
+
body: { shape: "term" },
|
|
156
|
+
},
|
|
157
|
+
required: ["with", "body"],
|
|
158
|
+
closed: true,
|
|
159
|
+
},
|
|
160
|
+
scope: [
|
|
161
|
+
{ names: { fixed: ["payload"] }, in: [["with", "*", "do"], ["with", "*", "abort"]] },
|
|
162
|
+
{ names: { fixed: ["result"] }, in: [["return"]] },
|
|
163
|
+
],
|
|
164
|
+
caps: [{ rule: "grant", from: ["with", "*", "op"], in: [["body"]] }],
|
|
165
|
+
// A clause runs at an effect of the body, and the clause return runs only after a body without a failure.
|
|
166
|
+
lazy: [{ in: [["with", "*", "do"], ["with", "*", "abort"], ["return"]] }],
|
|
167
|
+
summary: "It answers the effects of its body with its clauses, in place, thus a mock, a retry or a log is a document.",
|
|
168
|
+
},
|
|
147
169
|
log: {
|
|
148
170
|
verbs: { eval: "effect.log", view: "view.op" },
|
|
149
171
|
parts: { shape: "list", of: "text" },
|
|
@@ -170,7 +192,8 @@ export const STD_WORLD: World = {
|
|
|
170
192
|
* A literal in a place that takes a document gets its data envelope (K159, D65). Such a place is a part of `if`,
|
|
171
193
|
* `list`, `seq` or `pure`, the value of `write`, or the whole text. The document after a header name is such a place
|
|
172
194
|
* too. Thus `list(1, "a")` evaluates to `[1, "a"]`. The forms `add`, `multiply` and `greater` write an operator document that its operator
|
|
173
|
-
* does not write, for example `add(5)` (K163).
|
|
195
|
+
* does not write, for example `add(5)` (K163). The field `with` of `handle` is a field of data, thus each clause is a
|
|
196
|
+
* record between braces in a list between brackets (D177).
|
|
174
197
|
*
|
|
175
198
|
* A host with more kinds spreads this profile and adds its forms to `calls.forms`. `STD_SYNTAX` stays the profile of
|
|
176
199
|
* the formula kit.
|
|
@@ -201,6 +224,8 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
201
224
|
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
202
225
|
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
203
226
|
{ name: "tree.quote", kind: "tree.quote", args: "literal" },
|
|
227
|
+
// The clauses are data with documents in it, for example handle(with: [{op: "log", do: payload + 1}], body: log(2)) (D177).
|
|
228
|
+
{ name: "handle", kind: "handle", args: "named", fields: ["with", "return", "body"], data: ["with"], wrap: ["return", "body"] },
|
|
204
229
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
205
230
|
{ name: "add", kind: "add" },
|
|
206
231
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -813,6 +838,7 @@ function leaderOnHost(n: LoopNode, key: string, rule: CircularRule & { readonly
|
|
|
813
838
|
/**
|
|
814
839
|
* This function gives an attribute of a node with circular attributes, or an attribute in an open fixed point (D113).
|
|
815
840
|
* The memo and the marks of the node do not hold the attribute. An open fixed point does not use the cells of the host.
|
|
841
|
+
* With the cells of a host, only a circular attribute comes here outside a fixed point. A plain attribute takes its cell.
|
|
816
842
|
*/
|
|
817
843
|
function circularRead(n: LoopNode, key: string): Result | undefined {
|
|
818
844
|
const rule = circularRule(n.tree, key);
|
|
@@ -1199,7 +1225,8 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1199
1225
|
const text = attributeText(key, type, place);
|
|
1200
1226
|
if (marks.has(key)) return busyAt(state, key);
|
|
1201
1227
|
if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
|
|
1202
|
-
|
|
1228
|
+
// Outside a fixed point, a plain attribute of a node with a circular attribute also takes the cell of the host.
|
|
1229
|
+
if (openPoint !== undefined || (circular && circularRule(tree, key) !== undefined)) return circularRead(state, key);
|
|
1203
1230
|
marks.set(key, BUSY);
|
|
1204
1231
|
demands.push({ cells: marks, key, text });
|
|
1205
1232
|
let thrown: unknown = NOT_THROWN;
|
|
@@ -1493,6 +1520,39 @@ function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { read
|
|
|
1493
1520
|
return { values };
|
|
1494
1521
|
}
|
|
1495
1522
|
|
|
1523
|
+
/** The value of a `handle` document, with a part in the place of each part. */
|
|
1524
|
+
interface HandleValue {
|
|
1525
|
+
readonly with: readonly { readonly op: Cap; readonly do?: RunPart; readonly abort?: RunPart }[];
|
|
1526
|
+
readonly return?: RunPart;
|
|
1527
|
+
readonly body?: RunPart;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/** This function tells if a value names a kind of effects: a text, or a list of keys. */
|
|
1531
|
+
function isOp(value: unknown): value is Cap {
|
|
1532
|
+
return typeof value === "string" || (Array.isArray(value) && value.every(isKey));
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* This function gives the runner of an `if` with its own truth (RUN.IF-TRUTH). A branch can wait for the host, thus a
|
|
1537
|
+
* language with the function `truthy` of a profile does not need a copy of `if`. A missing test and a test without a
|
|
1538
|
+
* truth give `#VALUE!`, a failed test gives its failure, and a missing branch gives `null`.
|
|
1539
|
+
*
|
|
1540
|
+
* @param truth - The function that gives the truth of a value, or `undefined` for a value without a truth.
|
|
1541
|
+
* @returns The runner.
|
|
1542
|
+
*/
|
|
1543
|
+
export function ifRunner(truth: (value: unknown) => boolean | undefined): Runner {
|
|
1544
|
+
return function* ({ parts }) {
|
|
1545
|
+
const test = parts.find((part) => part.key === "test");
|
|
1546
|
+
if (test === undefined) return fail("#VALUE!", "if needs a test.");
|
|
1547
|
+
const chosen = yield* test.evaluate();
|
|
1548
|
+
if (!chosen.ok) return chosen;
|
|
1549
|
+
const held = truth(chosen.value);
|
|
1550
|
+
if (held === undefined) return fail("#VALUE!", "The test of if must give a truth value.");
|
|
1551
|
+
const branch = parts.find((part) => part.key === (held ? "then" : "else"));
|
|
1552
|
+
return branch === undefined ? ok(null) : yield* branch.evaluate();
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1496
1556
|
/**
|
|
1497
1557
|
* The runners of the standard world, by name: the kinds that can hold effects. Each one evaluates its parts in
|
|
1498
1558
|
* order. In a run, an effect ends before the next step starts.
|
|
@@ -1516,15 +1576,33 @@ export const STD_RUNNERS: Readonly<Record<string, Runner>> = {
|
|
|
1516
1576
|
if (only === undefined || parts.length > 1) return fail("#VALUE!", "pure needs exactly one part.");
|
|
1517
1577
|
return yield* only.evaluate();
|
|
1518
1578
|
},
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1579
|
+
// A clause gets the context of the handle document, thus it has no more authority than its parent (HANDLE.CLAUSE).
|
|
1580
|
+
"control.handle": function* ({ value, context }) {
|
|
1581
|
+
const { with: list, return: after, body } = value as HandleValue;
|
|
1582
|
+
if (body?.handled === undefined) return fail("#VALUE!", "handle needs a body.");
|
|
1583
|
+
let stopped: Result | undefined;
|
|
1584
|
+
const clauses: Clause[] = [];
|
|
1585
|
+
for (const item of list) {
|
|
1586
|
+
const part = item.do ?? item.abort;
|
|
1587
|
+
if (!isOp(item.op)) return fail("#VALUE!", "The op of a clause of handle must be a text or a list of keys.");
|
|
1588
|
+
if (part === undefined || (item.do !== undefined && item.abort !== undefined)) return fail("#VALUE!", "Each clause of handle needs exactly one of do and abort.");
|
|
1589
|
+
const stops = item.do === undefined;
|
|
1590
|
+
clauses.push({
|
|
1591
|
+
op: item.op,
|
|
1592
|
+
answer: function* (payload) {
|
|
1593
|
+
const result = yield* part.evaluate({ ...context, names: { ...context.names, payload } });
|
|
1594
|
+
if (!stops) return result;
|
|
1595
|
+
stopped ??= result;
|
|
1596
|
+
return fail("#CALC!", "A clause abort of handle stopped the body.");
|
|
1597
|
+
},
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
const result = yield* body.handled(clauses);
|
|
1601
|
+
if (stopped !== undefined) return stopped;
|
|
1602
|
+
if (!result.ok || after === undefined) return result;
|
|
1603
|
+
return yield* after.evaluate({ ...context, names: { ...context.names, result: result.value } });
|
|
1527
1604
|
},
|
|
1605
|
+
"control.if": /* @__PURE__ */ ifRunner((value) => (typeof value === "boolean" ? value : undefined)),
|
|
1528
1606
|
"list.all": function* ({ parts }) {
|
|
1529
1607
|
const all = yield* inOrder(parts);
|
|
1530
1608
|
return "error" in all ? all.error : ok(all.values);
|