@patterkit/runtime 0.18.0 → 0.19.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 +46 -0
- package/dist/index.cjs +80 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -6
- package/dist/index.d.ts +43 -6
- package/dist/index.js +80 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,52 @@ version number always means the same runtime behaviour. This package is versione
|
|
|
33
33
|
|
|
34
34
|
## [Unreleased]
|
|
35
35
|
|
|
36
|
+
## [0.19.0] - 2026-10-06
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`EngineOptions.onError` and `PlayError`.** Each condition or effect that fails while the story plays is
|
|
41
|
+
reported to `onError` with the flow, what failed (`condition`, `effect`, or `best-match`), the node, the
|
|
42
|
+
expression's source, and the error. With no `onError`, each goes to `console.warn`. With the log on, each is
|
|
43
|
+
also a `diagnostic` entry in the decision log.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **A condition or effect that fails no longer stops the story.** A failing condition counts as false, and a
|
|
48
|
+
failing effect is skipped while the rest of its list still runs, including a story write a read-only `@world`
|
|
49
|
+
value refuses. This is the same on all four runtimes; JS used to throw out of `advance()`.
|
|
50
|
+
- **A part of a Best-match condition that fails scores as false**, as on the other three runtimes. JS threw
|
|
51
|
+
while scoring.
|
|
52
|
+
- **A selector evaluates each condition once.** It evaluated each child's condition twice, so a condition
|
|
53
|
+
calling `random(a, b)` drew twice and the logged verdict could disagree with the pick.
|
|
54
|
+
- **A shuffle draws only from the children in its bag that are still eligible.** A child whose condition went
|
|
55
|
+
false after the bag was filled could be drawn, and the group then played nothing. When no child left in the
|
|
56
|
+
bag is eligible, the pass ends as if the bag were empty. Seeded results do not change while every child in the
|
|
57
|
+
bag stays eligible.
|
|
58
|
+
- **`openFlow`, `goto` and the engine's lookups read an address the same way:** a scene's game id first, then
|
|
59
|
+
its internal id, and a block only within its scene. `openFlow` tried the internal id first, so the two could
|
|
60
|
+
land in different scenes when one scene's id was another's game id.
|
|
61
|
+
- **A choice whose every remaining option is greyed out runs dry**, as a choice with no options does: the
|
|
62
|
+
fallback follows if there is one, otherwise the flow moves on and `onDryChoice` fires. It used to be offered
|
|
63
|
+
with nothing the player could take.
|
|
64
|
+
- **An option's prompt beat carries tags**: its own and the option's. A replayed prompt and the outline lost
|
|
65
|
+
them.
|
|
66
|
+
- **`loadGame` checks a save before changing anything.** A save missing its flows used to close every flow and
|
|
67
|
+
load the registry before failing, leaving the engine half-loaded.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- **`@wildwinter/expr` 0.5.1 and `@wildwinter/scoperegistry` 0.8.1 (kernel `k492cf234`):** a listener's own
|
|
72
|
+
error is no longer reported as a read-only refusal, every listener registered when a write starts hears it
|
|
73
|
+
exactly once, and the hot path allocates less, with no leaks.
|
|
74
|
+
- **A rollback after a load keeps a scene's saved `@scene` values.** After a load, a scene the flow is not
|
|
75
|
+
standing in keeps its saved values waiting until the flow enters it. Entering it inside a checkpoint and then
|
|
76
|
+
rolling back used to drop those values, so the next real entry found the defaults and the next save left them
|
|
77
|
+
out. A new corpus case pins this on all four runtimes.
|
|
78
|
+
- **The decision log's `seq` keeps counting after a clear.** The engine's log numbered entries by its length, so
|
|
79
|
+
after `clearLog()` the numbers started again at 0 and entries read either side of the clear could not be put
|
|
80
|
+
in order.
|
|
81
|
+
|
|
36
82
|
## [0.18.0] - 2026-10-05
|
|
37
83
|
|
|
38
84
|
### Changed
|
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,7 @@ function buildTagIndex(bundle) {
|
|
|
54
54
|
const visit = (node, inherited) => {
|
|
55
55
|
const acc = dedupe([...inherited, ...node.tags ?? []]);
|
|
56
56
|
index.set(node.id, acc);
|
|
57
|
+
if (node.type === "group" && node.prompt) index.set(node.prompt.id, dedupe([...acc, ...node.prompt.tags ?? []]));
|
|
57
58
|
if (node.type === "group") {
|
|
58
59
|
for (const child of node.children) visit(child, acc);
|
|
59
60
|
} else {
|
|
@@ -118,6 +119,8 @@ var Engine = class _Engine {
|
|
|
118
119
|
/** The run's ordered stream: every flow's events, each naming its flow. Empty and
|
|
119
120
|
* unwritten unless `options.log` asked for it. */
|
|
120
121
|
engineLog = [];
|
|
122
|
+
/** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */
|
|
123
|
+
engineSeq = 0;
|
|
121
124
|
engineTraceHandlers = /* @__PURE__ */ new Set();
|
|
122
125
|
constructor(bundle, options = {}) {
|
|
123
126
|
this.creationOptions = options;
|
|
@@ -215,6 +218,7 @@ var Engine = class _Engine {
|
|
|
215
218
|
journal: null,
|
|
216
219
|
customRng: options.rng,
|
|
217
220
|
onDryChoice: options.onDryChoice,
|
|
221
|
+
onError: options.onError ?? ((e) => console.warn(`Patterplay: ${e.kind} on ${e.node} in flow '${e.flow}' failed, played through: ${e.message}`)),
|
|
218
222
|
replayPromptOnChoose: options.replayPromptOnChoose ?? false,
|
|
219
223
|
captionsOn: options.closedCaptions ?? true,
|
|
220
224
|
// captions shown by default (full text)
|
|
@@ -408,10 +412,10 @@ var Engine = class _Engine {
|
|
|
408
412
|
*/
|
|
409
413
|
resolveOpenAddress(scene, block) {
|
|
410
414
|
if (scene !== void 0) {
|
|
411
|
-
const sceneId = this.
|
|
412
|
-
if (
|
|
415
|
+
const sceneId = resolveScene(this.host, scene);
|
|
416
|
+
if (sceneId === void 0) throw new Error(`unknown scene: ${scene}`);
|
|
413
417
|
if (block === void 0) return { sceneId };
|
|
414
|
-
const blockId =
|
|
418
|
+
const blockId = resolveBlock(this.host, sceneId, block);
|
|
415
419
|
if (blockId === void 0) throw new Error(`unknown block: ${block}`);
|
|
416
420
|
return { sceneId, blockId };
|
|
417
421
|
}
|
|
@@ -422,21 +426,19 @@ var Engine = class _Engine {
|
|
|
422
426
|
if (Object.keys(this.host.bundle.scenes).length === 0) throw new Error("no scenes in bundle");
|
|
423
427
|
return {};
|
|
424
428
|
}
|
|
425
|
-
/** Resolve a scene reference
|
|
429
|
+
/** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
|
|
430
|
+
* unknown one passes through, for the caller to find nothing. */
|
|
426
431
|
resolveSceneRef(ref) {
|
|
427
432
|
if (ref == null) return void 0;
|
|
428
|
-
|
|
429
|
-
return this.sceneGameIdToId.get(ref) ?? ref;
|
|
433
|
+
return resolveScene(this.host, ref) ?? ref;
|
|
430
434
|
}
|
|
431
|
-
/** Resolve a block reference
|
|
435
|
+
/** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
|
|
436
|
+
* scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
|
|
437
|
+
* since a block's gameId is only unique within its scene. */
|
|
432
438
|
resolveBlockRef(sceneId, ref) {
|
|
433
439
|
if (ref == null) return void 0;
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
const id = this.blockGameIdToId.get(sceneId)?.get(ref);
|
|
437
|
-
if (id) return id;
|
|
438
|
-
}
|
|
439
|
-
return ref;
|
|
440
|
+
if (sceneId == null) return ref;
|
|
441
|
+
return resolveBlock(this.host, sceneId, ref);
|
|
440
442
|
}
|
|
441
443
|
/** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
|
|
442
444
|
* The inverse of the resolve helpers - for a host that wants to display / log the address. */
|
|
@@ -763,7 +765,7 @@ var Engine = class _Engine {
|
|
|
763
765
|
emitEngine(flow, event, scene) {
|
|
764
766
|
for (const h of this.engineTraceHandlers) h(flow, event);
|
|
765
767
|
if (!this.host.logEnabled) return;
|
|
766
|
-
this.engineLog.push({ ...event, flow, seq: this.
|
|
768
|
+
this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...scene ? { scene } : {} });
|
|
767
769
|
}
|
|
768
770
|
listProperties() {
|
|
769
771
|
return this.host.patterSharedDecls.map((d) => ({
|
|
@@ -829,6 +831,8 @@ var Engine = class _Engine {
|
|
|
829
831
|
this.refuseInCheckpoint("loadGame");
|
|
830
832
|
const version = save.version;
|
|
831
833
|
if (version !== 2 && version !== import_model2.SAVE_VERSION) throw new Error(`unsupported save version: ${String(version)}`);
|
|
834
|
+
const flows = save.flows;
|
|
835
|
+
if (typeof flows !== "object" || flows === null || Array.isArray(flows)) throw new Error("malformed save: no flows");
|
|
832
836
|
this.assertExternalScopes();
|
|
833
837
|
const reg = this.host.registry;
|
|
834
838
|
for (const [id, flow] of this.flowsById) {
|
|
@@ -1052,11 +1056,11 @@ var Flow = class {
|
|
|
1052
1056
|
this.stack = [];
|
|
1053
1057
|
return true;
|
|
1054
1058
|
}
|
|
1055
|
-
const sceneId =
|
|
1059
|
+
const sceneId = resolveScene(this.host, scene);
|
|
1056
1060
|
if (sceneId === void 0) return false;
|
|
1057
1061
|
let blockId;
|
|
1058
1062
|
if (block !== void 0) {
|
|
1059
|
-
blockId =
|
|
1063
|
+
blockId = resolveBlock(this.host, sceneId, block);
|
|
1060
1064
|
if (blockId === void 0) return false;
|
|
1061
1065
|
}
|
|
1062
1066
|
if (!this.started) {
|
|
@@ -1166,7 +1170,7 @@ var Flow = class {
|
|
|
1166
1170
|
if (this.flowEnded || this.pendingChoice) return;
|
|
1167
1171
|
if (this.activeSnippet) {
|
|
1168
1172
|
if (this.beatIndex < (this.activeSnippet.beats?.length ?? 0)) return;
|
|
1169
|
-
this.runEffects(this.activeSnippet.onExit);
|
|
1173
|
+
this.runEffects(this.activeSnippet.onExit, this.activeSnippet.id);
|
|
1170
1174
|
const jump = this.activeSnippet.jump;
|
|
1171
1175
|
this.activeSnippet = null;
|
|
1172
1176
|
this.beatIndex = 0;
|
|
@@ -1382,7 +1386,7 @@ var Flow = class {
|
|
|
1382
1386
|
this.currentSceneId = sceneId;
|
|
1383
1387
|
this.enter(sceneId);
|
|
1384
1388
|
this.seedScene(scene);
|
|
1385
|
-
this.runEffects(scene.onEntry);
|
|
1389
|
+
this.runEffects(scene.onEntry, scene.id);
|
|
1386
1390
|
}
|
|
1387
1391
|
/**
|
|
1388
1392
|
* Play one child of the active run. A snippet begins delivering. A group is
|
|
@@ -1418,7 +1422,7 @@ var Flow = class {
|
|
|
1418
1422
|
return void 0;
|
|
1419
1423
|
}
|
|
1420
1424
|
beginSnippet(snippet) {
|
|
1421
|
-
this.runEffects(snippet.onEnter);
|
|
1425
|
+
this.runEffects(snippet.onEnter, snippet.id);
|
|
1422
1426
|
this.activeSnippet = snippet;
|
|
1423
1427
|
this.beatIndex = 0;
|
|
1424
1428
|
}
|
|
@@ -1438,7 +1442,7 @@ var Flow = class {
|
|
|
1438
1442
|
options.push({ id: child.id, prompt: this.promptFor(child), eligible, gameData: child.gameData });
|
|
1439
1443
|
byId.set(child.id, child);
|
|
1440
1444
|
}
|
|
1441
|
-
if (options.
|
|
1445
|
+
if (options.some((o) => o.eligible)) {
|
|
1442
1446
|
this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
|
|
1443
1447
|
this.pendingChoice = { groupId: group.id, options, byId };
|
|
1444
1448
|
return;
|
|
@@ -1496,7 +1500,7 @@ var Flow = class {
|
|
|
1496
1500
|
// -- Selectors ------------------------------------------------------------
|
|
1497
1501
|
selectChild(group) {
|
|
1498
1502
|
const verdicts = group.children.map((c) => ({ id: c.id, eligible: this.eligible(c) }));
|
|
1499
|
-
const eligible = group.children.filter((
|
|
1503
|
+
const eligible = group.children.filter((_, i) => verdicts[i].eligible);
|
|
1500
1504
|
const order = group.options?.order ?? "sequential";
|
|
1501
1505
|
const exhaust = group.options?.exhaust ?? "once";
|
|
1502
1506
|
const trace = (picked) => {
|
|
@@ -1543,8 +1547,9 @@ var Flow = class {
|
|
|
1543
1547
|
const len = eligible.length;
|
|
1544
1548
|
const stick = exhaust === "stick";
|
|
1545
1549
|
const fill = () => (stick ? eligible.slice(0, len - 1) : eligible).map((c) => c.id);
|
|
1550
|
+
const eligibleIds = new Set(eligible.map((c) => c.id));
|
|
1546
1551
|
if (st.bag === void 0) st.bag = fill();
|
|
1547
|
-
if (st.bag.
|
|
1552
|
+
if (!st.bag.some((id2) => eligibleIds.has(id2))) {
|
|
1548
1553
|
if (exhaust === "once") return null;
|
|
1549
1554
|
if (stick) {
|
|
1550
1555
|
const last = eligible[len - 1];
|
|
@@ -1552,13 +1557,14 @@ var Flow = class {
|
|
|
1552
1557
|
return last;
|
|
1553
1558
|
}
|
|
1554
1559
|
st.bag = fill();
|
|
1560
|
+
if (st.bag.length === 0) return null;
|
|
1555
1561
|
}
|
|
1556
|
-
const pool = st.bag;
|
|
1562
|
+
const pool = st.bag.filter((id2) => eligibleIds.has(id2));
|
|
1557
1563
|
const p = st.last !== void 0 && pool.length > 1 ? pool.indexOf(st.last) : -1;
|
|
1558
1564
|
let i = Math.floor(this.rng() * (p >= 0 ? pool.length - 1 : pool.length));
|
|
1559
1565
|
if (p >= 0 && i >= p) i++;
|
|
1560
1566
|
const id = pool[i];
|
|
1561
|
-
|
|
1567
|
+
st.bag.splice(st.bag.indexOf(id), 1);
|
|
1562
1568
|
st.last = id;
|
|
1563
1569
|
return eligible.find((c) => c.id === id);
|
|
1564
1570
|
}
|
|
@@ -1607,7 +1613,7 @@ var Flow = class {
|
|
|
1607
1613
|
/** A child's Best-match score against the current state: 0 when it has no condition (the filler
|
|
1608
1614
|
* tier), else the specificity of its (already-passing) condition. */
|
|
1609
1615
|
specScore(node) {
|
|
1610
|
-
return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true) : 0;
|
|
1616
|
+
return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true, node) : 0;
|
|
1611
1617
|
}
|
|
1612
1618
|
/**
|
|
1613
1619
|
* The **matched-specificity** metric (parity contract): how many atomic constraints are actively
|
|
@@ -1617,8 +1623,15 @@ var Flow = class {
|
|
|
1617
1623
|
* to hold?" (true at the root). Only `and`/`or`/`not`/`check_flags` are structural; every other
|
|
1618
1624
|
* node (comparisons, scoped vars, literals, other calls) is an atom, evaluated whole.
|
|
1619
1625
|
*/
|
|
1620
|
-
matchedSpec(node, want) {
|
|
1621
|
-
const evalTruthy = (n) =>
|
|
1626
|
+
matchedSpec(node, want, owner) {
|
|
1627
|
+
const evalTruthy = (n) => {
|
|
1628
|
+
try {
|
|
1629
|
+
return truthy((0, import_expr.evaluate)(n, this.context(), import_dialect.patterDialect));
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
this.reportError("best-match", owner.id, owner.condition, err);
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1622
1635
|
return (0, import_expr_specificity.matchedSpecificity)(node, evalTruthy, { want });
|
|
1623
1636
|
}
|
|
1624
1637
|
/** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
|
|
@@ -1650,17 +1663,36 @@ var Flow = class {
|
|
|
1650
1663
|
return st;
|
|
1651
1664
|
}
|
|
1652
1665
|
// -- Effects + expressions ------------------------------------------------
|
|
1653
|
-
|
|
1666
|
+
/** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
|
|
1667
|
+
runEffects(effects, owner) {
|
|
1654
1668
|
for (const e of effects ?? []) {
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1669
|
+
let value;
|
|
1670
|
+
try {
|
|
1671
|
+
value = this.evalExpr(e.value);
|
|
1672
|
+
const prev = this.host.logEnabled ? this.getProperty(e.target) : void 0;
|
|
1673
|
+
this.writeProperty(e.target, value, false);
|
|
1674
|
+
this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
this.reportError("effect", owner, e.value, err);
|
|
1677
|
+
}
|
|
1659
1678
|
}
|
|
1660
1679
|
}
|
|
1680
|
+
/** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
|
|
1661
1681
|
eligible(node) {
|
|
1662
1682
|
if (!node.condition) return true;
|
|
1663
|
-
|
|
1683
|
+
try {
|
|
1684
|
+
return truthy(this.evalExpr(node.condition));
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
this.reportError("condition", node.id, node.condition, err);
|
|
1687
|
+
return false;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
/** Report a content error the engine is playing through: to the host's onError, and to the log. */
|
|
1691
|
+
reportError(kind, node, expr, err) {
|
|
1692
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1693
|
+
const source = expr?.src;
|
|
1694
|
+
this.host.onError({ flow: this.id, kind, node, ...source ? { source } : {}, message });
|
|
1695
|
+
this.emit({ type: "diagnostic", kind, node, ...source ? { source } : {}, message });
|
|
1664
1696
|
}
|
|
1665
1697
|
evalExpr(expr) {
|
|
1666
1698
|
return (0, import_expr.evaluate)(this.conditionAst(expr), this.context(), import_dialect.patterDialect);
|
|
@@ -1856,23 +1888,23 @@ var Flow = class {
|
|
|
1856
1888
|
if (!this.sceneBags.has(s)) {
|
|
1857
1889
|
const bag = new import_scoperegistry2.PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
1858
1890
|
const key = keys.flowScene(this.id, s);
|
|
1859
|
-
this.host.registry
|
|
1891
|
+
const claimed = mountClaims(this.host.registry, key, bag, journal !== null);
|
|
1860
1892
|
this.registered.add(key);
|
|
1861
1893
|
this.sceneBags.set(s, bag);
|
|
1862
1894
|
if (journal) journal.undo.push(() => {
|
|
1863
1895
|
if (this.sceneBags.get(s) !== bag) return;
|
|
1864
|
-
this.host.registry.remove(key);
|
|
1896
|
+
this.host.registry.remove(key, claimed ? { keep: true } : void 0);
|
|
1865
1897
|
this.registered.delete(key);
|
|
1866
1898
|
this.sceneBags.delete(s);
|
|
1867
1899
|
});
|
|
1868
1900
|
}
|
|
1869
1901
|
if (!this.host.stageBags.has(s)) {
|
|
1870
1902
|
const bag = new import_scoperegistry2.PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
1871
|
-
this.host.registry
|
|
1903
|
+
const claimed = mountClaims(this.host.registry, keys.stage(s), bag, journal !== null);
|
|
1872
1904
|
this.host.stageBags.set(s, bag);
|
|
1873
1905
|
if (journal) journal.undo.push(() => {
|
|
1874
1906
|
if (this.host.stageBags.get(s) !== bag) return;
|
|
1875
|
-
this.host.registry.remove(keys.stage(s));
|
|
1907
|
+
this.host.registry.remove(keys.stage(s), claimed ? { keep: true } : void 0);
|
|
1876
1908
|
this.host.stageBags.delete(s);
|
|
1877
1909
|
});
|
|
1878
1910
|
}
|
|
@@ -1939,6 +1971,11 @@ function deserialiseSelectors(rec) {
|
|
|
1939
1971
|
}
|
|
1940
1972
|
return map;
|
|
1941
1973
|
}
|
|
1974
|
+
function mountClaims(registry, key, bag, ask) {
|
|
1975
|
+
const before = ask ? JSON.stringify(bag.save()) : "";
|
|
1976
|
+
registry.mountOwned(key, bag, { owner: OWNER });
|
|
1977
|
+
return ask && JSON.stringify(bag.save()) !== before;
|
|
1978
|
+
}
|
|
1942
1979
|
function toDecl(decl) {
|
|
1943
1980
|
return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };
|
|
1944
1981
|
}
|
|
@@ -1950,6 +1987,12 @@ function selfBackedDecl(decl, scopeWritable) {
|
|
|
1950
1987
|
const writable = decl.writable ?? scopeWritable;
|
|
1951
1988
|
return writable === void 0 ? d : { ...d, writable };
|
|
1952
1989
|
}
|
|
1990
|
+
function resolveScene(host, ref) {
|
|
1991
|
+
return host.sceneGameIdToId.get(ref) ?? (host.bundle.scenes[ref] ? ref : void 0);
|
|
1992
|
+
}
|
|
1993
|
+
function resolveBlock(host, sceneId, ref) {
|
|
1994
|
+
return host.blockGameIdToId.get(sceneId)?.get(ref) ?? (host.blockIndex.get(ref)?.sceneId === sceneId ? ref : void 0);
|
|
1995
|
+
}
|
|
1953
1996
|
function splitHostRef(host, ref) {
|
|
1954
1997
|
if (host.refSplitRevision !== host.registry.revision) {
|
|
1955
1998
|
host.refSplitCache.clear();
|