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 +86 -0
- godcode/__main__.py +7 -0
- godcode/agentics.py +235 -0
- godcode/ast.py +227 -0
- godcode/chain.py +210 -0
- godcode/cli.py +698 -0
- godcode/environment.py +74 -0
- godcode/errors.py +69 -0
- godcode/interpreter.py +1104 -0
- godcode/ledger.py +88 -0
- godcode/lexer.py +218 -0
- godcode/lsp.py +677 -0
- godcode/parser.py +525 -0
- godcode/plugins.py +255 -0
- godcode/registry.py +515 -0
- godcode/sandbox.py +385 -0
- godcode/scrolls/covenant.god +10 -0
- godcode/scrolls/covenant.toml +6 -0
- godcode/scrolls/lists.god +53 -0
- godcode/scrolls/lists.toml +6 -0
- godcode/scrolls/math.god +64 -0
- godcode/scrolls/math.toml +6 -0
- godcode/scrolls/prophecy.god +14 -0
- godcode/scrolls/prophecy.toml +6 -0
- godcode/scrolls/strings.god +32 -0
- godcode/scrolls/strings.toml +6 -0
- godcode/scrolls/time.god +10 -0
- godcode/scrolls/time.toml +6 -0
- godcode/spirit.py +180 -0
- godcode/tokens.py +91 -0
- godcode/tools.py +378 -0
- godcode/values.py +56 -0
- godcode_engine-4.0.0.dist-info/METADATA +212 -0
- godcode_engine-4.0.0.dist-info/RECORD +38 -0
- godcode_engine-4.0.0.dist-info/WHEEL +5 -0
- godcode_engine-4.0.0.dist-info/entry_points.txt +2 -0
- godcode_engine-4.0.0.dist-info/licenses/LICENSE +201 -0
- godcode_engine-4.0.0.dist-info/top_level.txt +1 -0
godcode/ledger.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""CovenantLedger -- an append-only, hash-chained record of sealed covenants. 🔒
|
|
2
|
+
|
|
3
|
+
Each sealed record becomes a block in a JSONL chain file. A block's hash is
|
|
4
|
+
the sha256 of the canonical JSON of {index, timestamp, record, prev_hash};
|
|
5
|
+
the genesis block's prev_hash is "GENESIS". verify() recomputes every link.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
GENESIS_PREV_HASH = "GENESIS"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _canonical(payload: dict) -> bytes:
|
|
19
|
+
"""Canonical JSON bytes used for hashing (stable key order, no whitespace)."""
|
|
20
|
+
return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _block_hash(index: int, timestamp: str, record: dict, prev_hash: str) -> str:
|
|
24
|
+
return hashlib.sha256(
|
|
25
|
+
_canonical(
|
|
26
|
+
{"index": index, "timestamp": timestamp, "record": record, "prev_hash": prev_hash}
|
|
27
|
+
)
|
|
28
|
+
).hexdigest()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CovenantLedger:
|
|
32
|
+
"""Append-only covenant chain persisted as JSONL."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, path: str | Path = "covenant.chain") -> None:
|
|
35
|
+
self.path = Path(path)
|
|
36
|
+
if str(self.path.parent) not in ("", "."):
|
|
37
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
|
|
39
|
+
def seal(self, record: dict) -> dict:
|
|
40
|
+
"""Append a record as a new block; return the block dict."""
|
|
41
|
+
blocks = self.read_all()
|
|
42
|
+
index = len(blocks)
|
|
43
|
+
prev_hash = blocks[-1]["hash"] if blocks else GENESIS_PREV_HASH
|
|
44
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
45
|
+
block = {
|
|
46
|
+
"index": index,
|
|
47
|
+
"timestamp": timestamp,
|
|
48
|
+
"record": record,
|
|
49
|
+
"prev_hash": prev_hash,
|
|
50
|
+
"hash": _block_hash(index, timestamp, record, prev_hash),
|
|
51
|
+
}
|
|
52
|
+
with open(self.path, "a", encoding="utf-8") as f:
|
|
53
|
+
f.write(json.dumps(block, ensure_ascii=False) + "\n")
|
|
54
|
+
return block
|
|
55
|
+
|
|
56
|
+
def read_all(self) -> list[dict]:
|
|
57
|
+
"""Return every block in chain order (empty list if no chain file yet)."""
|
|
58
|
+
if not self.path.exists():
|
|
59
|
+
return []
|
|
60
|
+
blocks: list[dict] = []
|
|
61
|
+
with open(self.path, encoding="utf-8") as f:
|
|
62
|
+
for line in f:
|
|
63
|
+
line = line.strip()
|
|
64
|
+
if line:
|
|
65
|
+
blocks.append(json.loads(line))
|
|
66
|
+
return blocks
|
|
67
|
+
|
|
68
|
+
def verify(self) -> tuple[bool, str]:
|
|
69
|
+
"""Recompute the chain. (True, 'N covenants intact 🔒') or (False, 'chain broken at block K')."""
|
|
70
|
+
blocks = self.read_all()
|
|
71
|
+
prev_hash = GENESIS_PREV_HASH
|
|
72
|
+
for expected, block in enumerate(blocks):
|
|
73
|
+
index = block.get("index")
|
|
74
|
+
payload_ok = (
|
|
75
|
+
index == expected
|
|
76
|
+
and block.get("prev_hash") == prev_hash
|
|
77
|
+
and _block_hash(
|
|
78
|
+
block.get("index"),
|
|
79
|
+
block.get("timestamp"),
|
|
80
|
+
block.get("record"),
|
|
81
|
+
block.get("prev_hash"),
|
|
82
|
+
)
|
|
83
|
+
== block.get("hash")
|
|
84
|
+
)
|
|
85
|
+
if not payload_ok:
|
|
86
|
+
return False, f"chain broken at block {index} 🔗💔"
|
|
87
|
+
prev_hash = block["hash"]
|
|
88
|
+
return True, f"{len(blocks)} covenants intact 🔒"
|
godcode/lexer.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Lexer for God Code v2.0.
|
|
2
|
+
|
|
3
|
+
``Lexer(source).lex()`` turns source text into a list of Tokens with
|
|
4
|
+
1-based line/column positions, NEWLINE tokens for each physical line
|
|
5
|
+
break, and a final EOF token. ``#`` starts a comment to end of line.
|
|
6
|
+
Keywords are matched case-insensitively (canonical value = UPPER);
|
|
7
|
+
identifiers preserve their casing.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .errors import LexerError
|
|
13
|
+
from .tokens import Token, TokenType
|
|
14
|
+
|
|
15
|
+
# Every alphabetic TokenType name that is not a literal/structural/operator
|
|
16
|
+
# token is a keyword. New keyword TokenTypes are picked up automatically.
|
|
17
|
+
_NON_KEYWORD_NAMES = {
|
|
18
|
+
"NUMBER", "STRING", "IDENT", "NEWLINE", "EOF",
|
|
19
|
+
"PLUS", "MINUS", "STAR", "SLASH", "PERCENT",
|
|
20
|
+
"EQ", "NEQ", "LT", "GT", "LTE", "GTE",
|
|
21
|
+
"LPAREN", "RPAREN", "LBRACKET", "RBRACKET", "COMMA",
|
|
22
|
+
}
|
|
23
|
+
KEYWORDS: dict[str, TokenType] = {
|
|
24
|
+
tt.name: tt for tt in TokenType if tt.name not in _NON_KEYWORD_NAMES
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_ESCAPES = {'"': '"', "\\": "\\", "n": "\n", "t": "\t"}
|
|
28
|
+
|
|
29
|
+
_SINGLE_CHAR_TOKENS = {
|
|
30
|
+
"+": TokenType.PLUS,
|
|
31
|
+
"-": TokenType.MINUS,
|
|
32
|
+
"*": TokenType.STAR,
|
|
33
|
+
"/": TokenType.SLASH,
|
|
34
|
+
"%": TokenType.PERCENT,
|
|
35
|
+
"=": TokenType.EQ,
|
|
36
|
+
"<": TokenType.LT,
|
|
37
|
+
">": TokenType.GT,
|
|
38
|
+
"(": TokenType.LPAREN,
|
|
39
|
+
")": TokenType.RPAREN,
|
|
40
|
+
"[": TokenType.LBRACKET,
|
|
41
|
+
"]": TokenType.RBRACKET,
|
|
42
|
+
",": TokenType.COMMA,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_MULTI_CHAR_TOKENS = {
|
|
46
|
+
"!=": TokenType.NEQ,
|
|
47
|
+
"<=": TokenType.LTE,
|
|
48
|
+
">=": TokenType.GTE,
|
|
49
|
+
"==": TokenType.EQ, # both = and == spell equality
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Lexer:
|
|
54
|
+
def __init__(self, source: str):
|
|
55
|
+
self.source = source
|
|
56
|
+
|
|
57
|
+
def lex(self) -> list[Token]:
|
|
58
|
+
src = self.source
|
|
59
|
+
n = len(src)
|
|
60
|
+
tokens: list[Token] = []
|
|
61
|
+
i = 0
|
|
62
|
+
line = 1
|
|
63
|
+
col = 1
|
|
64
|
+
|
|
65
|
+
def here() -> tuple[int, int]:
|
|
66
|
+
return line, col
|
|
67
|
+
|
|
68
|
+
while i < n:
|
|
69
|
+
c = src[i]
|
|
70
|
+
|
|
71
|
+
# whitespace (but not newlines)
|
|
72
|
+
if c == " " or c == "\t":
|
|
73
|
+
i += 1
|
|
74
|
+
col += 1
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# line breaks: collapse \r\n, tolerate lone \r
|
|
78
|
+
if c == "\r" or c == "\n":
|
|
79
|
+
ln, cl = here()
|
|
80
|
+
if c == "\r" and i + 1 < n and src[i + 1] == "\n":
|
|
81
|
+
i += 2
|
|
82
|
+
else:
|
|
83
|
+
i += 1
|
|
84
|
+
tokens.append(Token(TokenType.NEWLINE, "\n", ln, cl))
|
|
85
|
+
line += 1
|
|
86
|
+
col = 1
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# comments run to end of line (the newline itself is still lexed)
|
|
90
|
+
if c == "#":
|
|
91
|
+
while i < n and src[i] != "\n" and src[i] != "\r":
|
|
92
|
+
i += 1
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# strings
|
|
96
|
+
if c == '"':
|
|
97
|
+
tok, i, col = self._lex_string(src, i, line, col)
|
|
98
|
+
tokens.append(tok)
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# numbers
|
|
102
|
+
if c.isdigit():
|
|
103
|
+
tok, i, col = self._lex_number(src, i, line, col)
|
|
104
|
+
tokens.append(tok)
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
# words: keywords (case-insensitive) or identifiers
|
|
108
|
+
if c.isalpha() or c == "_":
|
|
109
|
+
start = i
|
|
110
|
+
ln, cl = here()
|
|
111
|
+
while i < n and (src[i].isalnum() or src[i] == "_"):
|
|
112
|
+
i += 1
|
|
113
|
+
word = src[start:i]
|
|
114
|
+
col += i - start
|
|
115
|
+
upper = word.upper()
|
|
116
|
+
if upper in KEYWORDS:
|
|
117
|
+
tokens.append(Token(KEYWORDS[upper], upper, ln, cl))
|
|
118
|
+
else:
|
|
119
|
+
tokens.append(Token(TokenType.IDENT, word, ln, cl))
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# operators: multi-char first, then single-char
|
|
123
|
+
two = src[i : i + 2]
|
|
124
|
+
if two in _MULTI_CHAR_TOKENS:
|
|
125
|
+
ln, cl = here()
|
|
126
|
+
tokens.append(Token(_MULTI_CHAR_TOKENS[two], two, ln, cl))
|
|
127
|
+
i += 2
|
|
128
|
+
col += 2
|
|
129
|
+
continue
|
|
130
|
+
if c in _SINGLE_CHAR_TOKENS:
|
|
131
|
+
ln, cl = here()
|
|
132
|
+
tokens.append(Token(_SINGLE_CHAR_TOKENS[c], c, ln, cl))
|
|
133
|
+
i += 1
|
|
134
|
+
col += 1
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
raise LexerError(
|
|
138
|
+
f"The heavens do not recognize the character {c!r}; "
|
|
139
|
+
"it has no place in the holy tongue",
|
|
140
|
+
line=line,
|
|
141
|
+
col=col,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
tokens.append(Token(TokenType.EOF, "", line, col))
|
|
145
|
+
return tokens
|
|
146
|
+
|
|
147
|
+
# -- helpers -----------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _lex_string(src: str, i: int, line: int, col: int):
|
|
151
|
+
"""Lex a string starting at the opening quote. Returns (token, i, col)."""
|
|
152
|
+
n = len(src)
|
|
153
|
+
open_line, open_col = line, col
|
|
154
|
+
i += 1 # opening quote
|
|
155
|
+
col += 1
|
|
156
|
+
buf: list[str] = []
|
|
157
|
+
while i < n and src[i] != '"':
|
|
158
|
+
ch = src[i]
|
|
159
|
+
if ch == "\n" or ch == "\r":
|
|
160
|
+
raise LexerError(
|
|
161
|
+
"The utterance was never finished: string runs past the "
|
|
162
|
+
"end of the line without a closing quote",
|
|
163
|
+
line=open_line,
|
|
164
|
+
col=open_col,
|
|
165
|
+
)
|
|
166
|
+
if ch == "\\":
|
|
167
|
+
if i + 1 >= n:
|
|
168
|
+
break
|
|
169
|
+
esc = src[i + 1]
|
|
170
|
+
if esc not in _ESCAPES:
|
|
171
|
+
raise LexerError(
|
|
172
|
+
f"Unknown escape '\\{esc}'; the holy escapes are "
|
|
173
|
+
'\\\\ \\" \\n \\t',
|
|
174
|
+
line=line,
|
|
175
|
+
col=col,
|
|
176
|
+
)
|
|
177
|
+
buf.append(_ESCAPES[esc])
|
|
178
|
+
i += 2
|
|
179
|
+
col += 2
|
|
180
|
+
else:
|
|
181
|
+
buf.append(ch)
|
|
182
|
+
i += 1
|
|
183
|
+
col += 1
|
|
184
|
+
if i >= n:
|
|
185
|
+
raise LexerError(
|
|
186
|
+
"The utterance was never finished: unterminated string",
|
|
187
|
+
line=open_line,
|
|
188
|
+
col=open_col,
|
|
189
|
+
)
|
|
190
|
+
i += 1 # closing quote
|
|
191
|
+
col += 1
|
|
192
|
+
return Token(TokenType.STRING, "".join(buf), open_line, open_col), i, col
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _lex_number(src: str, i: int, line: int, col: int):
|
|
196
|
+
"""Lex an int or float starting at a digit. Returns (token, i, col)."""
|
|
197
|
+
n = len(src)
|
|
198
|
+
start = i
|
|
199
|
+
while i < n and src[i].isdigit():
|
|
200
|
+
i += 1
|
|
201
|
+
is_float = False
|
|
202
|
+
if i < n and src[i] == ".":
|
|
203
|
+
if i + 1 < n and src[i + 1].isdigit():
|
|
204
|
+
is_float = True
|
|
205
|
+
i += 1
|
|
206
|
+
while i < n and src[i].isdigit():
|
|
207
|
+
i += 1
|
|
208
|
+
else:
|
|
209
|
+
raise LexerError(
|
|
210
|
+
f"Malformed number {src[start:i+1]!r}; a decimal point "
|
|
211
|
+
"must be followed by digits",
|
|
212
|
+
line=line,
|
|
213
|
+
col=col,
|
|
214
|
+
)
|
|
215
|
+
text = src[start:i]
|
|
216
|
+
value: int | float = float(text) if is_float else int(text)
|
|
217
|
+
tok = Token(TokenType.NUMBER, value, line, col)
|
|
218
|
+
return tok, i, col + (i - start)
|