liminate 0.1.0__py3-none-any.whl
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.
- liminate/__init__.py +49 -0
- liminate/__main__.py +6 -0
- liminate/adapter.py +449 -0
- liminate/analyzer.py +1271 -0
- liminate/build.py +638 -0
- liminate/cli.py +971 -0
- liminate/inspect_cmd.py +159 -0
- liminate/interpreter.py +1184 -0
- liminate/lexer.py +244 -0
- liminate/listener.py +688 -0
- liminate/packs/__init__.py +9 -0
- liminate/packs/file_watcher.py +280 -0
- liminate/packs/stdin.py +147 -0
- liminate/packs/timer.py +185 -0
- liminate/parser.py +1685 -0
- liminate/renderer.py +370 -0
- liminate/reorderer.py +129 -0
- liminate/result.py +54 -0
- liminate/vocabulary.py +240 -0
- liminate-0.1.0.dist-info/METADATA +20 -0
- liminate-0.1.0.dist-info/RECORD +25 -0
- liminate-0.1.0.dist-info/WHEEL +5 -0
- liminate-0.1.0.dist-info/entry_points.txt +2 -0
- liminate-0.1.0.dist-info/licenses/LICENSE +201 -0
- liminate-0.1.0.dist-info/top_level.txt +1 -0
liminate/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Liminate Programming Language v1 / v2a / v2b / v2c / v2d / v3a."""
|
|
2
|
+
|
|
3
|
+
from .adapter import (
|
|
4
|
+
Adapter,
|
|
5
|
+
AdapterDone,
|
|
6
|
+
AdapterFailure,
|
|
7
|
+
AdapterUpdate,
|
|
8
|
+
DomainPack,
|
|
9
|
+
LiveValueDeclaration,
|
|
10
|
+
LiveValueEntry,
|
|
11
|
+
LiveValueRegistry,
|
|
12
|
+
TestAdapter,
|
|
13
|
+
TestDomainPack,
|
|
14
|
+
)
|
|
15
|
+
from .analyzer import SymbolEntry, analyze
|
|
16
|
+
from .cli import Session
|
|
17
|
+
from .interpreter import execute
|
|
18
|
+
from .listener import listen
|
|
19
|
+
from .lexer import leading_indent, tokenize
|
|
20
|
+
from .parser import parse, parse_when_block
|
|
21
|
+
from .renderer import render
|
|
22
|
+
from .reorderer import reorder
|
|
23
|
+
from .result import LiminateResult, ResultStatus
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Adapter",
|
|
27
|
+
"AdapterDone",
|
|
28
|
+
"AdapterFailure",
|
|
29
|
+
"AdapterUpdate",
|
|
30
|
+
"DomainPack",
|
|
31
|
+
"LiminateResult",
|
|
32
|
+
"LiveValueDeclaration",
|
|
33
|
+
"LiveValueEntry",
|
|
34
|
+
"LiveValueRegistry",
|
|
35
|
+
"ResultStatus",
|
|
36
|
+
"Session",
|
|
37
|
+
"SymbolEntry",
|
|
38
|
+
"TestAdapter",
|
|
39
|
+
"TestDomainPack",
|
|
40
|
+
"analyze",
|
|
41
|
+
"execute",
|
|
42
|
+
"leading_indent",
|
|
43
|
+
"listen",
|
|
44
|
+
"parse",
|
|
45
|
+
"parse_when_block",
|
|
46
|
+
"render",
|
|
47
|
+
"reorder",
|
|
48
|
+
"tokenize",
|
|
49
|
+
]
|
liminate/__main__.py
ADDED
liminate/adapter.py
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""Adapter infrastructure for Liminate v3a event-driven execution.
|
|
2
|
+
|
|
3
|
+
Sources:
|
|
4
|
+
- v3a §116 (event sources: adapter contract — declaration, adapter, lifecycle)
|
|
5
|
+
- v3a §117 (live value lifecycle: declared → unset → active → inactive)
|
|
6
|
+
- v3a §118 (domain pack registration via constructor/CLI, not language syntax)
|
|
7
|
+
- v3a §119 (single-threaded event queue: adapters enqueue; interpreter
|
|
8
|
+
processes one update to completion before next dequeue)
|
|
9
|
+
- v3a §120 (adapter failure isolation: single crash doesn't kill the
|
|
10
|
+
interpreter; dependent handlers disable; queue keeps draining)
|
|
11
|
+
|
|
12
|
+
This module defines the contract by which external event sources (domain
|
|
13
|
+
packs) feed the Phase 2 reactive interpreter. v3a ships exactly one
|
|
14
|
+
adapter implementation: `TestAdapter`, used by integration tests and the
|
|
15
|
+
dogfood program. Real-world domain packs (healthcare, smart home, game)
|
|
16
|
+
are explicitly out of scope (§126).
|
|
17
|
+
|
|
18
|
+
The interpreter owns the event queue and the live-value registry; each
|
|
19
|
+
adapter contributes updates and declarations. Adapters never read from
|
|
20
|
+
the queue themselves — they only push.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from abc import ABC, abstractmethod
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from queue import Queue
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from .vocabulary import (
|
|
31
|
+
PackVerbExecution,
|
|
32
|
+
PackVerbSignature,
|
|
33
|
+
PackVerbSlot,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Declarations and queue messages (§116)
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class LiveValueDeclaration:
|
|
44
|
+
"""A live value declared by a domain pack (§116).
|
|
45
|
+
|
|
46
|
+
`value_type` matches the analyzer's type strings: "number", "string",
|
|
47
|
+
"record", "list_of_numbers", "list_of_strings", "list_of_records".
|
|
48
|
+
For broad declarations the adapter's first update establishes the
|
|
49
|
+
final shape (§116).
|
|
50
|
+
"""
|
|
51
|
+
name: str
|
|
52
|
+
value_type: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class AdapterUpdate:
|
|
57
|
+
"""A `(name, new_value)` pair pushed by an adapter (§119).
|
|
58
|
+
|
|
59
|
+
The interpreter consumes one update at a time, runs change detection
|
|
60
|
+
(§113 — deep value equality), and fires eligible handlers before
|
|
61
|
+
dequeuing the next."""
|
|
62
|
+
name: str
|
|
63
|
+
value: Any
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class AdapterDone:
|
|
68
|
+
"""Marker that an adapter has finished normally (§118 — "[done]"
|
|
69
|
+
in test sentence notation). The interpreter counts these against
|
|
70
|
+
the active-adapter set to decide when to shut down."""
|
|
71
|
+
adapter_name: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class AdapterFailure:
|
|
76
|
+
"""An adapter failure isolated to that adapter (§120). The
|
|
77
|
+
interpreter marks the adapter's live values inactive and disables
|
|
78
|
+
handlers that depend solely on them; other adapters keep running."""
|
|
79
|
+
adapter_name: str
|
|
80
|
+
reason: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Adapter and DomainPack ABCs (§116/§118)
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Adapter(ABC):
|
|
89
|
+
"""Adapter contract (§116).
|
|
90
|
+
|
|
91
|
+
An adapter pushes `(name, value)` updates into a shared, thread-safe
|
|
92
|
+
queue owned by the interpreter. Concrete adapters override `start`
|
|
93
|
+
(begin pushing) and `stop` (cease pushing). `attach_queue` is called
|
|
94
|
+
by the interpreter before `start` to wire up the queue.
|
|
95
|
+
|
|
96
|
+
`name` is a human-readable identifier surfaced in error and
|
|
97
|
+
shutdown metadata (§120 / §122).
|
|
98
|
+
|
|
99
|
+
**Termination contract.** For the listener's Phase 2 drain loop to
|
|
100
|
+
exit normally, every adapter must eventually push exactly one
|
|
101
|
+
`AdapterDone(adapter_name=self.name)` (natural completion) or one
|
|
102
|
+
`AdapterFailure(...)` (error). Adapters that run forever and never
|
|
103
|
+
signal completion will keep the listener alive until the user's
|
|
104
|
+
program calls `finish` (which triggers stop() from inside the
|
|
105
|
+
listener) or the process is interrupted externally.
|
|
106
|
+
|
|
107
|
+
`stop()` is invoked by the listener — either from `_shutdown_finish`
|
|
108
|
+
after a `finish` propagates, or from the final cleanup loop after
|
|
109
|
+
the drain has already returned. Concrete adapters should treat
|
|
110
|
+
`stop()` as "cease pushing as soon as practical and tear down any
|
|
111
|
+
threads or resources." They do not need to push a terminal
|
|
112
|
+
`AdapterDone` from within `stop()` itself — by that point the
|
|
113
|
+
listener has already decided to shut down.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, name: str = "adapter"):
|
|
117
|
+
self.name = name
|
|
118
|
+
self.queue: Queue | None = None
|
|
119
|
+
self.started = False
|
|
120
|
+
self.stopped = False
|
|
121
|
+
|
|
122
|
+
def attach_queue(self, queue: Queue) -> None:
|
|
123
|
+
"""Called by the interpreter before `start`. The adapter
|
|
124
|
+
uses `self.queue.put(...)` to enqueue updates from this point."""
|
|
125
|
+
self.queue = queue
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def start(self) -> None:
|
|
129
|
+
"""Begin pushing updates into the attached queue. Must not be
|
|
130
|
+
called before `attach_queue`."""
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def stop(self) -> None:
|
|
135
|
+
"""Cease pushing. Called by the interpreter on `finish` (§112),
|
|
136
|
+
on global completion (§120), or on external termination."""
|
|
137
|
+
raise NotImplementedError
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class DomainPack(ABC):
|
|
141
|
+
"""Domain pack contract (§118).
|
|
142
|
+
|
|
143
|
+
A pack groups one or more live value declarations with the adapter
|
|
144
|
+
that produces them. Packs are registered with the interpreter at
|
|
145
|
+
construction time — no Liminate-level `use`/`load` verb in v3a
|
|
146
|
+
(§118).
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def name(self) -> str:
|
|
151
|
+
"""Human-readable identifier — used in error messages and
|
|
152
|
+
shutdown metadata."""
|
|
153
|
+
raise NotImplementedError
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def declarations(self) -> list[LiveValueDeclaration]:
|
|
157
|
+
"""Return the live values this pack provides. The interpreter
|
|
158
|
+
registers each name in the symbol table before Phase 1 begins
|
|
159
|
+
(§116/§117)."""
|
|
160
|
+
raise NotImplementedError
|
|
161
|
+
|
|
162
|
+
@abstractmethod
|
|
163
|
+
def adapter(self) -> Adapter:
|
|
164
|
+
"""Return the adapter that will push updates for this pack's
|
|
165
|
+
declared live values."""
|
|
166
|
+
raise NotImplementedError
|
|
167
|
+
|
|
168
|
+
# v4a §137 — optional pack contributions to the active vocabulary.
|
|
169
|
+
# Default to empty so existing packs (which override only the three
|
|
170
|
+
# required methods above) keep working unchanged. Backward-compatible.
|
|
171
|
+
|
|
172
|
+
def vocabulary(self) -> list[tuple[str, str]]:
|
|
173
|
+
"""Pack-contributed vocabulary as `(word, category)` pairs. v4a
|
|
174
|
+
only consumes the noun category; other categories are reserved
|
|
175
|
+
for future extension."""
|
|
176
|
+
return []
|
|
177
|
+
|
|
178
|
+
def verbs(self) -> list[PackVerbSignature]:
|
|
179
|
+
"""Pack-contributed verbs and their slot signatures (v4a §137).
|
|
180
|
+
Empty by default."""
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
# v4a §137 — JSON pack verb deserialization helper
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def parse_pack_verb_signature(definition: dict) -> PackVerbSignature:
|
|
190
|
+
"""Convert a single JSON `verbs[]` entry into a PackVerbSignature.
|
|
191
|
+
|
|
192
|
+
The JSON schema (§137):
|
|
193
|
+
{
|
|
194
|
+
"word": "<verb-name>",
|
|
195
|
+
"slots": [
|
|
196
|
+
{"name": "...", "connective": "...", "required": true,
|
|
197
|
+
"type_constraint": "..."}
|
|
198
|
+
],
|
|
199
|
+
"execution": {"type": "set_value", "target_name": "...",
|
|
200
|
+
"source_slot": "..."}
|
|
201
|
+
}
|
|
202
|
+
"""
|
|
203
|
+
word = definition["word"]
|
|
204
|
+
raw_slots = definition.get("slots", [])
|
|
205
|
+
slots: list[PackVerbSlot] = []
|
|
206
|
+
for s in raw_slots:
|
|
207
|
+
slots.append(
|
|
208
|
+
PackVerbSlot(
|
|
209
|
+
name=s["name"],
|
|
210
|
+
connective=s["connective"],
|
|
211
|
+
required=bool(s.get("required", True)),
|
|
212
|
+
type_constraint=s.get("type_constraint"),
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
exec_def = definition.get("execution") or {}
|
|
216
|
+
execution = PackVerbExecution(
|
|
217
|
+
type=exec_def.get("type", ""),
|
|
218
|
+
target_name=exec_def.get("target_name"),
|
|
219
|
+
source_slot=exec_def.get("source_slot"),
|
|
220
|
+
)
|
|
221
|
+
return PackVerbSignature(
|
|
222
|
+
word=word, slots=tuple(slots), execution=execution,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
# TestAdapter / TestDomainPack — the v3a-shipped test surface (§118)
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestAdapter(Adapter):
|
|
232
|
+
"""Adapter that pushes a scripted, finite sequence of updates.
|
|
233
|
+
|
|
234
|
+
The script is a list of items, each one of:
|
|
235
|
+
- `("name", value)` — an `AdapterUpdate` push
|
|
236
|
+
- `"[done]"` — an explicit `AdapterDone` push
|
|
237
|
+
|
|
238
|
+
`start()` drains the entire script onto the queue synchronously
|
|
239
|
+
(single-threaded test semantics — the interpreter consumes them in
|
|
240
|
+
order). If the script does not end with `"[done]"`, an
|
|
241
|
+
`AdapterDone` is appended automatically so the interpreter sees
|
|
242
|
+
normal completion (§118).
|
|
243
|
+
|
|
244
|
+
`stop()` is idempotent — repeated calls are safe.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
# pytest collects classes whose names begin with "Test" by default;
|
|
248
|
+
# opt this Adapter implementation out so it isn't mistaken for a
|
|
249
|
+
# test suite.
|
|
250
|
+
__test__ = False
|
|
251
|
+
|
|
252
|
+
def __init__(
|
|
253
|
+
self,
|
|
254
|
+
script: list[tuple[str, Any] | str],
|
|
255
|
+
*,
|
|
256
|
+
name: str = "test-adapter",
|
|
257
|
+
):
|
|
258
|
+
super().__init__(name=name)
|
|
259
|
+
self._script: list[tuple[str, Any] | str] = list(script)
|
|
260
|
+
|
|
261
|
+
def start(self) -> None:
|
|
262
|
+
if self.queue is None:
|
|
263
|
+
raise RuntimeError(
|
|
264
|
+
"TestAdapter.start() called before attach_queue()."
|
|
265
|
+
)
|
|
266
|
+
if self.started:
|
|
267
|
+
return
|
|
268
|
+
self.started = True
|
|
269
|
+
ended_with_done = False
|
|
270
|
+
for entry in self._script:
|
|
271
|
+
if isinstance(entry, str) and entry == "[done]":
|
|
272
|
+
self.queue.put(AdapterDone(adapter_name=self.name))
|
|
273
|
+
ended_with_done = True
|
|
274
|
+
continue
|
|
275
|
+
if isinstance(entry, tuple) and len(entry) == 2:
|
|
276
|
+
self.queue.put(
|
|
277
|
+
AdapterUpdate(name=entry[0], value=entry[1])
|
|
278
|
+
)
|
|
279
|
+
ended_with_done = False
|
|
280
|
+
continue
|
|
281
|
+
# Malformed script entry — surface as adapter failure so the
|
|
282
|
+
# interpreter can isolate it (§120).
|
|
283
|
+
self.queue.put(
|
|
284
|
+
AdapterFailure(
|
|
285
|
+
adapter_name=self.name,
|
|
286
|
+
reason=f"malformed script entry: {entry!r}",
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
return
|
|
290
|
+
if not ended_with_done:
|
|
291
|
+
# §118: every adapter must eventually signal normal
|
|
292
|
+
# completion (or fail) for the interpreter to decide when
|
|
293
|
+
# to shut down.
|
|
294
|
+
self.queue.put(AdapterDone(adapter_name=self.name))
|
|
295
|
+
|
|
296
|
+
def stop(self) -> None:
|
|
297
|
+
self.stopped = True
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class TestDomainPack(DomainPack):
|
|
301
|
+
"""DomainPack wrapping a TestAdapter and a fixed declaration list.
|
|
302
|
+
|
|
303
|
+
Used by Phase 11 integration tests and the v3a dogfood program to
|
|
304
|
+
drive event-driven sentences deterministically."""
|
|
305
|
+
|
|
306
|
+
# pytest collects classes whose names begin with "Test" by default;
|
|
307
|
+
# opt this DomainPack implementation out so it isn't mistaken for
|
|
308
|
+
# a test suite.
|
|
309
|
+
__test__ = False
|
|
310
|
+
|
|
311
|
+
def __init__(
|
|
312
|
+
self,
|
|
313
|
+
declarations: list[tuple[str, str]] | list[LiveValueDeclaration],
|
|
314
|
+
script: list[tuple[str, Any] | str],
|
|
315
|
+
*,
|
|
316
|
+
name: str = "test-pack",
|
|
317
|
+
vocabulary: list[tuple[str, str]] | None = None,
|
|
318
|
+
verbs: list[PackVerbSignature] | None = None,
|
|
319
|
+
):
|
|
320
|
+
self._name = name
|
|
321
|
+
self._declarations: list[LiveValueDeclaration] = [
|
|
322
|
+
d if isinstance(d, LiveValueDeclaration)
|
|
323
|
+
else LiveValueDeclaration(name=d[0], value_type=d[1])
|
|
324
|
+
for d in declarations
|
|
325
|
+
]
|
|
326
|
+
self._script = script
|
|
327
|
+
self._adapter: TestAdapter | None = None
|
|
328
|
+
self._vocabulary: list[tuple[str, str]] = list(vocabulary or [])
|
|
329
|
+
self._verbs: list[PackVerbSignature] = list(verbs or [])
|
|
330
|
+
|
|
331
|
+
def name(self) -> str:
|
|
332
|
+
return self._name
|
|
333
|
+
|
|
334
|
+
def declarations(self) -> list[LiveValueDeclaration]:
|
|
335
|
+
return list(self._declarations)
|
|
336
|
+
|
|
337
|
+
def adapter(self) -> Adapter:
|
|
338
|
+
if self._adapter is None:
|
|
339
|
+
self._adapter = TestAdapter(self._script, name=self._name)
|
|
340
|
+
return self._adapter
|
|
341
|
+
|
|
342
|
+
def vocabulary(self) -> list[tuple[str, str]]:
|
|
343
|
+
return list(self._vocabulary)
|
|
344
|
+
|
|
345
|
+
def verbs(self) -> list[PackVerbSignature]:
|
|
346
|
+
return list(self._verbs)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# ---------------------------------------------------------------------------
|
|
350
|
+
# Live-value registry (§117)
|
|
351
|
+
# ---------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@dataclass
|
|
355
|
+
class LiveValueEntry:
|
|
356
|
+
"""Per-live-value bookkeeping the interpreter consults during
|
|
357
|
+
Phase 2 (§117).
|
|
358
|
+
|
|
359
|
+
`status` transitions:
|
|
360
|
+
unset -> initial state; conditions involving this name
|
|
361
|
+
evaluate as false (§113).
|
|
362
|
+
active -> at least one value has been received (Phase 1 init
|
|
363
|
+
or first adapter update); conditions evaluate
|
|
364
|
+
normally.
|
|
365
|
+
inactive -> the owning adapter failed (§117/§120); dependent
|
|
366
|
+
handlers are disabled.
|
|
367
|
+
"""
|
|
368
|
+
name: str
|
|
369
|
+
value_type: str
|
|
370
|
+
adapter_name: str
|
|
371
|
+
status: str = "unset"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class LiveValueRegistry:
|
|
375
|
+
"""Tracks which symbol-table names are adapter-owned and what
|
|
376
|
+
state their adapter is in (§117/§120).
|
|
377
|
+
|
|
378
|
+
Used by:
|
|
379
|
+
- The analyzer (via `live_value_names`) to enforce ownership rules
|
|
380
|
+
(§111: `remember`/`filter` restrictions).
|
|
381
|
+
- The interpreter to update status on adapter updates and failures,
|
|
382
|
+
and to disable handlers when adapters die.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
def __init__(self) -> None:
|
|
386
|
+
self._entries: dict[str, LiveValueEntry] = {}
|
|
387
|
+
|
|
388
|
+
def declare(
|
|
389
|
+
self, decl: LiveValueDeclaration, adapter_name: str,
|
|
390
|
+
) -> None:
|
|
391
|
+
"""Register a live value before Phase 1 (§117 step 1)."""
|
|
392
|
+
if decl.name in self._entries:
|
|
393
|
+
raise ValueError(
|
|
394
|
+
f"live value '{decl.name}' was already declared by "
|
|
395
|
+
f"'{self._entries[decl.name].adapter_name}' — v3a §116 "
|
|
396
|
+
f"disallows multiple adapters providing the same name."
|
|
397
|
+
)
|
|
398
|
+
self._entries[decl.name] = LiveValueEntry(
|
|
399
|
+
name=decl.name,
|
|
400
|
+
value_type=decl.value_type,
|
|
401
|
+
adapter_name=adapter_name,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def names(self) -> set[str]:
|
|
405
|
+
"""All declared live-value names (for the analyzer)."""
|
|
406
|
+
return set(self._entries.keys())
|
|
407
|
+
|
|
408
|
+
def active_names(self) -> set[str]:
|
|
409
|
+
"""Live-value names whose owning adapter is still active —
|
|
410
|
+
excludes any whose adapter has failed (§120). The interpreter
|
|
411
|
+
uses this to decide which handlers remain eligible."""
|
|
412
|
+
return {
|
|
413
|
+
n for n, e in self._entries.items() if e.status != "inactive"
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
def entry(self, name: str) -> LiveValueEntry | None:
|
|
417
|
+
return self._entries.get(name)
|
|
418
|
+
|
|
419
|
+
def mark_active(self, name: str) -> None:
|
|
420
|
+
"""Called by the interpreter after the first valid value is
|
|
421
|
+
seen for `name` (§117 — `unset` → `active`)."""
|
|
422
|
+
entry = self._entries.get(name)
|
|
423
|
+
if entry is None:
|
|
424
|
+
return
|
|
425
|
+
if entry.status == "unset":
|
|
426
|
+
entry.status = "active"
|
|
427
|
+
|
|
428
|
+
def mark_inactive_for_adapter(self, adapter_name: str) -> list[str]:
|
|
429
|
+
"""Mark every live value owned by the named adapter as inactive
|
|
430
|
+
(§120). Returns the affected names so the caller can disable
|
|
431
|
+
dependent handlers."""
|
|
432
|
+
affected: list[str] = []
|
|
433
|
+
for entry in self._entries.values():
|
|
434
|
+
if entry.adapter_name == adapter_name and entry.status != "inactive":
|
|
435
|
+
entry.status = "inactive"
|
|
436
|
+
affected.append(entry.name)
|
|
437
|
+
return affected
|
|
438
|
+
|
|
439
|
+
def is_unset(self, name: str) -> bool:
|
|
440
|
+
"""True if `name` is a declared live value with no value yet —
|
|
441
|
+
conditions involving it evaluate as false per §113."""
|
|
442
|
+
entry = self._entries.get(name)
|
|
443
|
+
return entry is not None and entry.status == "unset"
|
|
444
|
+
|
|
445
|
+
def __contains__(self, name: str) -> bool:
|
|
446
|
+
return name in self._entries
|
|
447
|
+
|
|
448
|
+
def __len__(self) -> int:
|
|
449
|
+
return len(self._entries)
|