godcode-engine 4.0.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.
godcode/environment.py ADDED
@@ -0,0 +1,74 @@
1
+ """Lexically scoped environments for God Code.
2
+
3
+ Scope rule: names resolve outward through parent environments. An unbound
4
+ name does not raise — it resolves to a Symbol of that name, because in God
5
+ Code every unnamed thing is still a named spirit (this is what makes
6
+ `DECLARE seeker AS worthy` / `IF seeker IS worthy` work with no prior
7
+ binding of `worthy`).
8
+
9
+ DECLARE always defines in the *current* environment; it never rebinds an
10
+ outer scope. Use set_existing() to reshape a name that must already exist.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Iterator
16
+
17
+ from godcode.errors import GodRuntimeError
18
+ from godcode.values import Symbol
19
+
20
+
21
+ class Environment:
22
+ def __init__(self, parent: "Environment | None" = None):
23
+ self.parent = parent
24
+ self._bindings: dict[str, object] = {}
25
+
26
+ def define(self, name: str, value: object) -> None:
27
+ """Bind name in this environment, shadowing any outer binding."""
28
+ self._bindings[name] = value
29
+
30
+ def get(self, name: str) -> object:
31
+ """Walk outward for name; return Symbol(name) if nowhere bound."""
32
+ env: Environment | None = self
33
+ while env is not None:
34
+ if name in env._bindings:
35
+ return env._bindings[name]
36
+ env = env.parent
37
+ return Symbol(name)
38
+
39
+ def is_bound(self, name: str) -> bool:
40
+ """True if name is bound in this environment or any ancestor."""
41
+ env: Environment | None = self
42
+ while env is not None:
43
+ if name in env._bindings:
44
+ return True
45
+ env = env.parent
46
+ return False
47
+
48
+ def set_existing(self, name: str, value: object) -> None:
49
+ """Reshape an already-bound name where it lives; error if unbound."""
50
+ env: Environment | None = self
51
+ while env is not None:
52
+ if name in env._bindings:
53
+ env._bindings[name] = value
54
+ return
55
+ env = env.parent
56
+ raise GodRuntimeError(
57
+ f"There is no '{name}' to reshape — it was never spoken into being."
58
+ )
59
+
60
+ def names(self) -> list[str]:
61
+ """All visible names, innermost scope first, without duplicates."""
62
+ seen: list[str] = []
63
+ env: Environment | None = self
64
+ while env is not None:
65
+ for key in env._bindings:
66
+ if key not in seen:
67
+ seen.append(key)
68
+ env = env.parent
69
+ return seen
70
+
71
+ def items(self) -> Iterator[tuple[str, object]]:
72
+ """(name, value) pairs for every visible name, innermost first."""
73
+ for name in self.names():
74
+ yield name, self.get(name)
godcode/errors.py ADDED
@@ -0,0 +1,69 @@
1
+ """Error hierarchy for God Code v2.0.
2
+
3
+ All errors carry line (and where known, column) numbers so the faithful
4
+ can find where the heavens objected. Messages are divine-flavored but
5
+ genuinely helpful -- never mocking.
6
+
7
+ Control-flow signals (ReturnSignal / AscendSignal) are plain Exceptions,
8
+ *not* GodCodeError subclasses, so a broad ``except GodCodeError`` in the
9
+ interpreter can never accidentally swallow a return or an ascension.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+
15
+ class GodCodeError(Exception):
16
+ """Base class for every God Code failure. ``line``/``col`` are 1-based."""
17
+
18
+ def __init__(self, msg: str, line: int | None = None, col: int | None = None):
19
+ super().__init__(msg)
20
+ self.msg = msg
21
+ self.line = line
22
+ self.col = col
23
+
24
+ def __str__(self) -> str:
25
+ if self.line is None or f"line {self.line}" in self.msg:
26
+ return self.msg
27
+ loc = f" (line {self.line}"
28
+ if self.col is not None:
29
+ loc += f", col {self.col}"
30
+ return self.msg + loc + ")"
31
+
32
+
33
+ class LexerError(GodCodeError):
34
+ """The source could not be turned into tokens."""
35
+
36
+
37
+ class ParseError(GodCodeError):
38
+ """The tokens did not form a holy grammar."""
39
+
40
+
41
+ class GodRuntimeError(GodCodeError):
42
+ """The creation failed while it was being brought to life.
43
+
44
+ Named to avoid clashing with the builtin RuntimeError.
45
+ """
46
+
47
+
48
+ class SandboxViolation(GodCodeError):
49
+ """A power the sandbox withholds.
50
+
51
+ Raised when a creation running under ``godcode.sandbox`` reaches for
52
+ something its policy does not grant: a forbidden scroll, a denied
53
+ rite (such as ASK when stdin is closed), a spent step budget, or an
54
+ expired time grant. Carries a line number like every other
55
+ GodCodeError, so the faithful can see exactly where the heavens
56
+ objected.
57
+ """
58
+
59
+
60
+ class ReturnSignal(Exception):
61
+ """Carries a rite's return value up to the rite-call boundary."""
62
+
63
+ def __init__(self, value=None):
64
+ super().__init__(value)
65
+ self.value = value
66
+
67
+
68
+ class AscendSignal(Exception):
69
+ """Raised by ASCEND; ends the creation in peace."""