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/__init__.py ADDED
@@ -0,0 +1,86 @@
1
+ """God Code v3.0 — the language of divine computation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ __version__ = "4.0.0"
10
+
11
+
12
+ @dataclass
13
+ class RunResult:
14
+ """The outcome of running God Code from a host program.
15
+
16
+ ``output`` is the captured REVEAL output (lines joined with ``\\n``);
17
+ ``return_value`` is the value of a top-level ``RETURN`` (if the scroll
18
+ ascends that way) or of the last expression statement evaluated, else
19
+ ``None``; ``error`` is the divine error message (with line number) when
20
+ the run failed; ``ok`` tells whether the run completed.
21
+ """
22
+
23
+ output: str = ""
24
+ return_value: Any = None
25
+ error: str | None = None
26
+ ok: bool = False
27
+
28
+
29
+ def run_source(source: str, *, filename: str = "<string>") -> RunResult:
30
+ """Run God Code source from a host program and capture the outcome.
31
+
32
+ REVEAL output is captured per-interpreter (via the interpreter's ``emit``
33
+ hook), so this is safe to call many times in one process. Plugin
34
+ auto-loading still applies unless ``GODCODE_NO_PLUGINS=1`` is set.
35
+ Embedding runs do not write the audit log.
36
+ """
37
+ from godcode.errors import GodCodeError, ReturnSignal
38
+ from godcode.interpreter import Interpreter
39
+
40
+ lines: list[str] = []
41
+ interpreter = Interpreter(log_path=None)
42
+ interpreter.emit = lines.append
43
+ try:
44
+ interpreter.run_source(source, source_name=filename)
45
+ except ReturnSignal as ret:
46
+ # A top-level RETURN becomes the run's return value.
47
+ return RunResult(
48
+ output="\n".join(lines), return_value=ret.value, error=None, ok=True
49
+ )
50
+ except GodCodeError as err:
51
+ return RunResult(
52
+ output="\n".join(lines),
53
+ return_value=None,
54
+ error=str(err),
55
+ ok=False,
56
+ )
57
+ except Exception as exc: # a host/plugin failure outside the divine errors
58
+ return RunResult(
59
+ output="\n".join(lines),
60
+ return_value=None,
61
+ error=f"the outer world faltered: {exc!r}",
62
+ ok=False,
63
+ )
64
+ return RunResult(
65
+ output="\n".join(lines),
66
+ return_value=interpreter.last_value,
67
+ error=None,
68
+ ok=True,
69
+ )
70
+
71
+
72
+ def run_file(path: str | Path) -> RunResult:
73
+ """Run a God Code scroll file from a host program; see :func:`run_source`."""
74
+ try:
75
+ source = Path(path).read_text(encoding="utf-8")
76
+ except OSError as exc:
77
+ return RunResult(
78
+ output="",
79
+ return_value=None,
80
+ error=f"cannot read '{path}': {exc.strerror or exc}",
81
+ ok=False,
82
+ )
83
+ return run_source(source, filename=str(path))
84
+
85
+
86
+ __all__ = ["__version__", "RunResult", "run_source", "run_file"]
godcode/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Allow ``python -m godcode <subcommand>``."""
2
+ import sys
3
+
4
+ from godcode.cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
godcode/agentics.py ADDED
@@ -0,0 +1,235 @@
1
+ """Machine-readable output for AI agents (Mini-Pillar 5 — Agentics).
2
+
3
+ This module turns God Code diagnostics and run results into stable JSON
4
+ payloads an agent can parse without scraping divine prose. The CLI wires
5
+ it in behind ``--json`` on the ``run`` and ``check`` commands.
6
+
7
+ Error-code mapping choice
8
+ -------------------------
9
+ Codes are a small, stable, language-agnostic vocabulary derived from the
10
+ v2.0 exception hierarchy in :mod:`godcode.errors` (there is no
11
+ ``GodCodeSyntaxError`` class in v2.0, so the mapping below is explicit):
12
+
13
+ LexerError -> LEXER_ERROR
14
+ ParseError -> PARSE_ERROR
15
+ GodRuntimeError -> RUNTIME_ERROR
16
+
17
+ Any other ``GodCodeError`` subclass falls back to a mechanical
18
+ CamelCase -> UPPER_SNAKE conversion of its class name
19
+ (e.g. ``FooBarError`` -> ``FOO_BAR_ERROR``). Failures that are not God
20
+ Code failures at all (an unreadable scroll file) use ``FILE_ERROR``,
21
+ which is not an exception class.
22
+
23
+ Capture choice
24
+ --------------
25
+ ``run`` output is captured with :func:`contextlib.redirect_stdout` into
26
+ a :class:`io.StringIO` around the interpreter call — no dependency on
27
+ any capture hooks from sibling pillars.
28
+
29
+ Seals
30
+ -----
31
+ After the run we diff the interpreter's bound ``ledger`` (when present)
32
+ against its block count before the run. Blocks sealed during the run are
33
+ reported as ``{"block": <index>, "hash": <sha>}``. When no ledger is
34
+ bound or it cannot be read, ``seals`` is ``[]`` — never an error.
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import contextlib
39
+ import io
40
+ import json
41
+ import re
42
+ import sys
43
+ import time
44
+ from pathlib import Path
45
+
46
+ TOOL_NAME = "godcode"
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # error codes
50
+ # ---------------------------------------------------------------------------
51
+ _CODE_MAP = {
52
+ "LexerError": "LEXER_ERROR",
53
+ "ParseError": "PARSE_ERROR",
54
+ "GodRuntimeError": "RUNTIME_ERROR",
55
+ }
56
+
57
+ _HINTS = {
58
+ "LEXER_ERROR": (
59
+ "Scan the reported line for stray symbols or an unterminated "
60
+ "string; God Code strings use double quotes."
61
+ ),
62
+ "PARSE_ERROR": (
63
+ "Every scroll needs BEGIN CREATION ... END CREATION; comparisons "
64
+ "use IS / IS NOT, and every block needs its closer "
65
+ "(ENDIF, ENDFOR, ENDWHILE, END RITE)."
66
+ ),
67
+ "RUNTIME_ERROR": (
68
+ "The scroll parsed but stumbled while running — DECLARE every "
69
+ "name before use and re-read the reported line."
70
+ ),
71
+ "FILE_ERROR": "Check the path — the scroll must exist and be readable.",
72
+ }
73
+
74
+
75
+ def _to_snake(name: str) -> str:
76
+ return re.sub(r"(?<!^)(?=[A-Z])", "_", name).upper()
77
+
78
+
79
+ def error_code(err) -> str:
80
+ """Map a God Code exception to its stable machine code."""
81
+ name = type(err).__name__
82
+ if name in _CODE_MAP:
83
+ return _CODE_MAP[name]
84
+ snake = _to_snake(name)
85
+ return snake if snake.endswith("_ERROR") else snake + "_ERROR"
86
+
87
+
88
+ def hint_for(code: str) -> str | None:
89
+ """One-line actionable suggestion for a code, or None."""
90
+ return _HINTS.get(code)
91
+
92
+
93
+ def diagnostic(err, *, severity: str = "error") -> dict:
94
+ """Turn a GodCodeError into a JSON-serializable diagnostic dict."""
95
+ code = error_code(err)
96
+ # err.msg is the clean message; str(err) may append "(line N)" which
97
+ # would duplicate the structured line/col fields below.
98
+ return {
99
+ "line": getattr(err, "line", None),
100
+ "col": getattr(err, "col", None),
101
+ "code": code,
102
+ "severity": severity,
103
+ "message": getattr(err, "msg", None) or str(err),
104
+ "hint": hint_for(code),
105
+ }
106
+
107
+
108
+ def _file_error_diagnostic(path: str, exc: OSError) -> dict:
109
+ detail = exc.strerror or str(exc)
110
+ return {
111
+ "line": None,
112
+ "col": None,
113
+ "code": "FILE_ERROR",
114
+ "severity": "error",
115
+ "message": f"godcode: cannot read '{path}': {detail}",
116
+ "hint": hint_for("FILE_ERROR"),
117
+ }
118
+
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # payload builders
122
+ # ---------------------------------------------------------------------------
123
+ def check_payload(file: str, ok: bool, diagnostics: list[dict]) -> dict:
124
+ return {
125
+ "tool": TOOL_NAME,
126
+ "command": "check",
127
+ "file": file,
128
+ "ok": ok,
129
+ "diagnostics": diagnostics,
130
+ }
131
+
132
+
133
+ def run_payload(file: str, ok: bool, output: list[str],
134
+ seals: list[dict], error: dict | None, ms: int,
135
+ intents: list[dict] | None = None) -> dict:
136
+ return {
137
+ "tool": TOOL_NAME,
138
+ "command": "run",
139
+ "file": file,
140
+ "ok": ok,
141
+ "output": output,
142
+ "seals": seals,
143
+ "intents": intents if intents is not None else [],
144
+ "error": error,
145
+ "stats": {"ms": ms},
146
+ }
147
+
148
+
149
+ def emit(payload: dict) -> None:
150
+ """Write one JSON document to stdout; nothing else may be printed."""
151
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
152
+ sys.stdout.flush()
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # --json command implementations (called from godcode.cli)
157
+ # ---------------------------------------------------------------------------
158
+ def cmd_check_json(args) -> int:
159
+ """`godcode check --json FILE`. Exit 0 if pure, 1 if diagnostics."""
160
+ from godcode.errors import GodCodeError
161
+ from godcode.lexer import Lexer
162
+ from godcode.parser import Parser
163
+
164
+ try:
165
+ source = Path(args.file).read_text(encoding="utf-8")
166
+ except OSError as exc:
167
+ emit(check_payload(args.file, False, [_file_error_diagnostic(args.file, exc)]))
168
+ return 1
169
+ try:
170
+ Parser(Lexer(source).lex()).parse()
171
+ except GodCodeError as err:
172
+ emit(check_payload(args.file, False, [diagnostic(err)]))
173
+ return 1
174
+ emit(check_payload(args.file, True, []))
175
+ return 0
176
+
177
+
178
+ def _ledger_block_count(interp) -> int | None:
179
+ ledger = getattr(interp, "ledger", None)
180
+ if ledger is None:
181
+ return None
182
+ try:
183
+ return len(ledger.read_all())
184
+ except Exception:
185
+ return None
186
+
187
+
188
+ def _new_seals(interp, before: int | None) -> list[dict]:
189
+ """Best-effort list of covenant blocks sealed during the run."""
190
+ if before is None:
191
+ return []
192
+ ledger = getattr(interp, "ledger", None)
193
+ if ledger is None:
194
+ return []
195
+ try:
196
+ blocks = ledger.read_all()[before:]
197
+ except Exception:
198
+ return []
199
+ seals = []
200
+ for block in blocks:
201
+ if isinstance(block, dict) and "hash" in block:
202
+ seals.append({"block": block.get("index"), "hash": block.get("hash")})
203
+ return seals
204
+
205
+
206
+ def cmd_run_json(args) -> int:
207
+ """`godcode run --json FILE`. Exit 0 on success, 1 on failure."""
208
+ from godcode.errors import GodCodeError
209
+ from godcode.cli import _make_interpreter # shared with the plain run path
210
+
211
+ try:
212
+ source = Path(args.file).read_text(encoding="utf-8")
213
+ except OSError as exc:
214
+ emit(run_payload(args.file, False, [], [], _file_error_diagnostic(args.file, exc), 0))
215
+ return 1
216
+
217
+ interp = _make_interpreter(args.log)
218
+ seals_before = _ledger_block_count(interp)
219
+
220
+ buf = io.StringIO()
221
+ error: dict | None = None
222
+ start = time.perf_counter()
223
+ with contextlib.redirect_stdout(buf):
224
+ try:
225
+ interp.run_source(source, source_name=args.file)
226
+ except GodCodeError as err:
227
+ error = diagnostic(err)
228
+ ms = int((time.perf_counter() - start) * 1000)
229
+
230
+ output = buf.getvalue().splitlines()
231
+ seals = _new_seals(interp, seals_before)
232
+ intents = list(getattr(interp, "intent_checks", []) or [])
233
+ emit(run_payload(args.file, error is None, output, seals, error, ms,
234
+ intents=intents))
235
+ return 0 if error is None else 1
godcode/ast.py ADDED
@@ -0,0 +1,227 @@
1
+ """Abstract syntax tree nodes for God Code v2.0.
2
+
3
+ Every node carries 1-based ``line``/``col`` of the token that opened it.
4
+ Expression fields are annotated with the ``Expr`` alias (a union of all
5
+ expression node types).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Union
12
+
13
+ # Filled in below once the classes exist; string form keeps runtime cheap.
14
+ Expr = Union[
15
+ "BinaryOp", "UnaryOp", "Literal", "Identifier",
16
+ "ListLiteral", "Index", "CallExpr",
17
+ ]
18
+
19
+
20
+ # -- program structure -------------------------------------------------------
21
+
22
+ @dataclass
23
+ class Program:
24
+ statements: list = field(default_factory=list)
25
+ line: int = 1
26
+ col: int = 1
27
+
28
+
29
+ @dataclass
30
+ class CreationBlock:
31
+ statements: list = field(default_factory=list)
32
+ line: int = 1
33
+ col: int = 1
34
+
35
+
36
+ # -- statements --------------------------------------------------------------
37
+
38
+ @dataclass
39
+ class Declare:
40
+ name: str
41
+ value: Expr
42
+ line: int = 1
43
+ col: int = 1
44
+
45
+
46
+ @dataclass
47
+ class DeclareIntent:
48
+ """DECLARE INTENT "words..." ON rite_name -- the v4.0 intent layer."""
49
+ text: str
50
+ rite: str
51
+ line: int = 1
52
+ col: int = 1
53
+
54
+
55
+ @dataclass
56
+ class Breathe:
57
+ name: str
58
+ line: int = 1
59
+ col: int = 1
60
+
61
+
62
+ @dataclass
63
+ class Reveal:
64
+ expr: Expr
65
+ line: int = 1
66
+ col: int = 1
67
+
68
+
69
+ @dataclass
70
+ class Prophesy:
71
+ text: str
72
+ line: int = 1
73
+ col: int = 1
74
+
75
+
76
+ @dataclass
77
+ class Ascend:
78
+ line: int = 1
79
+ col: int = 1
80
+
81
+
82
+ @dataclass
83
+ class Reflect:
84
+ line: int = 1
85
+ col: int = 1
86
+
87
+
88
+ @dataclass
89
+ class Bless:
90
+ name: str
91
+ line: int = 1
92
+ col: int = 1
93
+
94
+
95
+ @dataclass
96
+ class Anoint:
97
+ name: str
98
+ line: int = 1
99
+ col: int = 1
100
+
101
+
102
+ @dataclass
103
+ class SealStmt:
104
+ expr: Expr
105
+ line: int = 1
106
+ col: int = 1
107
+
108
+
109
+ @dataclass
110
+ class Testify:
111
+ expr: Expr
112
+ line: int = 1
113
+ col: int = 1
114
+
115
+
116
+ @dataclass
117
+ class IfStmt:
118
+ cond: Expr
119
+ then_body: list = field(default_factory=list)
120
+ else_body: list = field(default_factory=list)
121
+ line: int = 1
122
+ col: int = 1
123
+
124
+
125
+ @dataclass
126
+ class ForLoop:
127
+ var: str
128
+ iterable: Expr
129
+ body: list = field(default_factory=list)
130
+ line: int = 1
131
+ col: int = 1
132
+
133
+
134
+ @dataclass
135
+ class WhileLoop:
136
+ cond: Expr
137
+ body: list = field(default_factory=list)
138
+ line: int = 1
139
+ col: int = 1
140
+
141
+
142
+ @dataclass
143
+ class DefineRite:
144
+ name: str
145
+ params: list = field(default_factory=list) # list[str]
146
+ body: list = field(default_factory=list)
147
+ line: int = 1
148
+ col: int = 1
149
+
150
+
151
+ @dataclass
152
+ class Return:
153
+ expr: Expr | None = None
154
+ line: int = 1
155
+ col: int = 1
156
+
157
+
158
+ @dataclass
159
+ class Import:
160
+ path: str
161
+ line: int = 1
162
+ col: int = 1
163
+
164
+
165
+ @dataclass
166
+ class ExprStmt:
167
+ expr: Expr
168
+ line: int = 1
169
+ col: int = 1
170
+
171
+
172
+ # -- expressions -------------------------------------------------------------
173
+
174
+ @dataclass
175
+ class BinaryOp:
176
+ # op: '+','-','*','/','%', '==','!=','<','>','<=','>=', 'and','or'
177
+ op: str
178
+ left: Expr
179
+ right: Expr
180
+ line: int = 1
181
+ col: int = 1
182
+
183
+
184
+ @dataclass
185
+ class UnaryOp:
186
+ # op: 'not', '-'
187
+ op: str
188
+ operand: Expr
189
+ line: int = 1
190
+ col: int = 1
191
+
192
+
193
+ @dataclass
194
+ class Literal:
195
+ value: object # int | float | str | bool | None
196
+ line: int = 1
197
+ col: int = 1
198
+
199
+
200
+ @dataclass
201
+ class Identifier:
202
+ name: str
203
+ line: int = 1
204
+ col: int = 1
205
+
206
+
207
+ @dataclass
208
+ class ListLiteral:
209
+ items: list = field(default_factory=list) # list[Expr]
210
+ line: int = 1
211
+ col: int = 1
212
+
213
+
214
+ @dataclass
215
+ class Index:
216
+ obj: Expr
217
+ index: Expr
218
+ line: int = 1
219
+ col: int = 1
220
+
221
+
222
+ @dataclass
223
+ class CallExpr:
224
+ callee: str
225
+ args: list = field(default_factory=list) # list[Expr]
226
+ line: int = 1
227
+ col: int = 1