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/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)