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/sandbox.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""In-process sandbox for God Code v3.0 — Pillar 1 ("Strong Foundations").
|
|
2
|
+
|
|
3
|
+
Runs a God Code creation under a deny-by-default :class:`SandboxPolicy`:
|
|
4
|
+
every side-effecting power (scroll imports outside the consecrated paths,
|
|
5
|
+
the ASK rite that speaks with the outer world) is withheld unless the
|
|
6
|
+
policy explicitly grants it. File-writing rites keep their meaning but
|
|
7
|
+
not their reach: ANCHOR is answered with an ephemeral in-memory chain,
|
|
8
|
+
so nothing is ever written to disk. A step budget and a wall-clock grant
|
|
9
|
+
bound runaway creations.
|
|
10
|
+
|
|
11
|
+
Entry points
|
|
12
|
+
------------
|
|
13
|
+
``run_sandboxed(source, policy, source_name)``
|
|
14
|
+
Lex, parse, and run ``source`` under ``policy``; returns the list of
|
|
15
|
+
REVEAL lines captured during the run.
|
|
16
|
+
|
|
17
|
+
``apply_policy(interpreter, policy)``
|
|
18
|
+
Install the sandbox hooks on an existing ``Interpreter``; returns the
|
|
19
|
+
``Sandbox`` guard.
|
|
20
|
+
|
|
21
|
+
``SandboxPolicy.strict(...)``
|
|
22
|
+
The policy the CLI uses: no writes, no network, no subprocesses, no
|
|
23
|
+
stdin, scroll imports limited to the stdlib scrolls and (optionally)
|
|
24
|
+
the creation's own directory, a 5-second time grant, and a 100,000
|
|
25
|
+
step budget.
|
|
26
|
+
|
|
27
|
+
Honest limits (see docs/sandbox.md): this is an *in-process* sandbox —
|
|
28
|
+
a cooperative audit of the tree-walker, not OS-level isolation. It
|
|
29
|
+
cannot contain a hostile program that escapes the interpreter, and it
|
|
30
|
+
cannot cap memory.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import contextlib
|
|
36
|
+
import os
|
|
37
|
+
import signal
|
|
38
|
+
import time
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any, Callable
|
|
42
|
+
|
|
43
|
+
from godcode.errors import GodCodeError, SandboxViolation
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"SandboxPolicy",
|
|
47
|
+
"Sandbox",
|
|
48
|
+
"SandboxViolation",
|
|
49
|
+
"apply_policy",
|
|
50
|
+
"run_sandboxed",
|
|
51
|
+
"run_sandboxed_with_interpreter",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _stdlib_scrolls_dir() -> str:
|
|
56
|
+
return str(Path(__file__).parent / "scrolls")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Policy
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
@dataclass
|
|
63
|
+
class SandboxPolicy:
|
|
64
|
+
"""What a sandboxed creation is permitted to do. Deny-by-default.
|
|
65
|
+
|
|
66
|
+
``allow_read_paths``
|
|
67
|
+
Approved directories for filesystem *reads*. ``None`` (the
|
|
68
|
+
default) denies all reads; a tuple of directories grants reads
|
|
69
|
+
under those trees. (No file-reading rites exist in v2.0; this is
|
|
70
|
+
enforced should any be added.)
|
|
71
|
+
``allow_write`` / ``allow_network`` / ``allow_subprocess``
|
|
72
|
+
Filesystem writes, network access, subprocess spawning. All deny
|
|
73
|
+
by default. The ANCHOR rite is the one file-writing rite: under a
|
|
74
|
+
policy that denies writes it is answered with an ephemeral
|
|
75
|
+
in-memory chain instead of being refused, so creations keep
|
|
76
|
+
their meaning without touching the disk.
|
|
77
|
+
``allow_stdin``
|
|
78
|
+
Whether the ASK rite may speak with the outer world (``input()``).
|
|
79
|
+
Denied by default.
|
|
80
|
+
``allowed_import_paths``
|
|
81
|
+
Approved directories for IMPORT of scrolls. Empty (the default)
|
|
82
|
+
denies every import. Each entry is a directory; a scroll is
|
|
83
|
+
allowed if its real path lies under one of them.
|
|
84
|
+
``timeout_seconds``
|
|
85
|
+
Wall-clock grant for the whole run. ``None`` or ``<= 0``
|
|
86
|
+
disables the grant (not recommended).
|
|
87
|
+
``max_steps``
|
|
88
|
+
Step budget: every statement dispatch and every expression
|
|
89
|
+
evaluation counts one step, bounding both runaway loops and
|
|
90
|
+
unbounded rite recursion.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
allow_read_paths: tuple[str, ...] | None = None
|
|
94
|
+
allow_write: bool = False
|
|
95
|
+
allow_network: bool = False
|
|
96
|
+
allow_subprocess: bool = False
|
|
97
|
+
allow_stdin: bool = False
|
|
98
|
+
allowed_import_paths: tuple[str, ...] = ()
|
|
99
|
+
timeout_seconds: float | None = 5.0
|
|
100
|
+
max_steps: int = 100_000
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def strict(
|
|
104
|
+
cls,
|
|
105
|
+
*,
|
|
106
|
+
source_dir: str | None = None,
|
|
107
|
+
timeout_seconds: float | None = 5.0,
|
|
108
|
+
) -> "SandboxPolicy":
|
|
109
|
+
"""The CLI's strict policy: deny everything, bless the stdlib.
|
|
110
|
+
|
|
111
|
+
Scroll imports are allowed from the bundled stdlib scrolls and,
|
|
112
|
+
when given, the creation's own directory — nothing else.
|
|
113
|
+
"""
|
|
114
|
+
paths = [_stdlib_scrolls_dir()]
|
|
115
|
+
if source_dir:
|
|
116
|
+
paths.append(str(source_dir))
|
|
117
|
+
return cls(
|
|
118
|
+
allowed_import_paths=tuple(paths),
|
|
119
|
+
timeout_seconds=timeout_seconds,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# The guard
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Builtin rite name -> SandboxPolicy attribute that must be truthy for the
|
|
127
|
+
# rite to run. ANCHOR is deliberately absent: under a policy that denies
|
|
128
|
+
# writes it is answered with an ephemeral in-memory chain (see
|
|
129
|
+
# run_sandboxed_with_interpreter), so the rite itself never reaches the
|
|
130
|
+
# filesystem and needs no grant.
|
|
131
|
+
_SIDE_EFFECT_RITES: dict[str, str] = {
|
|
132
|
+
"ASK": "allow_stdin",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_HOOKS = ("_exec_stmt", "_eval_expr", "_call", "_exec_import")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Sandbox:
|
|
139
|
+
"""Enforces a :class:`SandboxPolicy` on a live Interpreter.
|
|
140
|
+
|
|
141
|
+
Installed hooks (see :meth:`install`):
|
|
142
|
+
|
|
143
|
+
* ``_exec_stmt`` / ``_eval_expr`` — count one step per statement
|
|
144
|
+
dispatch and expression evaluation; check the step budget and the
|
|
145
|
+
wall-clock grant; record the current line for divine errors.
|
|
146
|
+
* ``_call`` — refuse side-effecting rites the policy does not grant
|
|
147
|
+
(ASK today; the registry above grows with the language).
|
|
148
|
+
* ``_exec_import`` — resolve the scroll, then refuse any path
|
|
149
|
+
outside ``allowed_import_paths``.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(self, interpreter: Any, policy: SandboxPolicy):
|
|
153
|
+
self.interpreter = interpreter
|
|
154
|
+
self.policy = policy
|
|
155
|
+
self.steps = 0
|
|
156
|
+
self.started_at: float | None = None
|
|
157
|
+
self.current_line: int | None = None
|
|
158
|
+
self._originals: dict[str, Callable] = {}
|
|
159
|
+
self._allowed_import_dirs = [
|
|
160
|
+
os.path.realpath(p) for p in policy.allowed_import_paths
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
# -- installation ---------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def install(self) -> "Sandbox":
|
|
166
|
+
"""Wrap the interpreter's dispatch points. Idempotent."""
|
|
167
|
+
interp = self.interpreter
|
|
168
|
+
if self._originals:
|
|
169
|
+
return self
|
|
170
|
+
for name in _HOOKS:
|
|
171
|
+
original = getattr(interp, name)
|
|
172
|
+
self._originals[name] = original
|
|
173
|
+
setattr(interp, name, self._wrap(name, original))
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
def uninstall(self) -> None:
|
|
177
|
+
"""Restore the interpreter's original dispatch points."""
|
|
178
|
+
for name, original in self._originals.items():
|
|
179
|
+
setattr(self.interpreter, name, original)
|
|
180
|
+
self._originals.clear()
|
|
181
|
+
|
|
182
|
+
def _wrap(self, name: str, original: Callable) -> Callable:
|
|
183
|
+
if name == "_exec_stmt":
|
|
184
|
+
def exec_stmt(stmt, env):
|
|
185
|
+
self.tick(stmt)
|
|
186
|
+
return original(stmt, env)
|
|
187
|
+
return exec_stmt
|
|
188
|
+
if name == "_eval_expr":
|
|
189
|
+
def eval_expr(expr, env):
|
|
190
|
+
self.tick(expr)
|
|
191
|
+
return original(expr, env)
|
|
192
|
+
return eval_expr
|
|
193
|
+
if name == "_call":
|
|
194
|
+
def call(rite_name, args, env, line):
|
|
195
|
+
self.check_rite(rite_name, line)
|
|
196
|
+
return original(rite_name, args, env, line)
|
|
197
|
+
return call
|
|
198
|
+
if name == "_exec_import":
|
|
199
|
+
def exec_import(stmt, env):
|
|
200
|
+
line = getattr(stmt, "line", None)
|
|
201
|
+
path = self.interpreter._resolve_import(stmt.path, line)
|
|
202
|
+
self.check_import_path(path, stmt.path, line)
|
|
203
|
+
return original(stmt, env)
|
|
204
|
+
return exec_import
|
|
205
|
+
raise AssertionError(f"unknown hook: {name}") # pragma: no cover
|
|
206
|
+
|
|
207
|
+
# -- enforcement -----------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def tick(self, node: Any) -> None:
|
|
210
|
+
"""Count one interpreter step; enforce budget and time grant."""
|
|
211
|
+
self.steps += 1
|
|
212
|
+
line = getattr(node, "line", None)
|
|
213
|
+
if line is not None:
|
|
214
|
+
self.current_line = line
|
|
215
|
+
if self.steps > self.policy.max_steps:
|
|
216
|
+
raise SandboxViolation(
|
|
217
|
+
"The sandbox withholds this power: the creation has taken "
|
|
218
|
+
f"more than {self.policy.max_steps:,} steps — "
|
|
219
|
+
"the step budget is spent, and the cycle is released.",
|
|
220
|
+
self.current_line,
|
|
221
|
+
)
|
|
222
|
+
if self.started_at is not None and self.policy.timeout_seconds:
|
|
223
|
+
elapsed = time.monotonic() - self.started_at
|
|
224
|
+
if elapsed > self.policy.timeout_seconds:
|
|
225
|
+
raise SandboxViolation(
|
|
226
|
+
"The sandbox withholds this power: the appointed time "
|
|
227
|
+
f"({self.policy.timeout_seconds:g}s) is spent — "
|
|
228
|
+
"the creation is released in peace.",
|
|
229
|
+
self.current_line,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def check_rite(self, name: str, line: int | None) -> None:
|
|
233
|
+
"""Refuse side-effecting rites the policy does not grant."""
|
|
234
|
+
need = _SIDE_EFFECT_RITES.get(str(name).upper())
|
|
235
|
+
if need is not None and not getattr(self.policy, need, False):
|
|
236
|
+
divine = {
|
|
237
|
+
"ASK": "the rite ASK would speak with the outer world",
|
|
238
|
+
}.get(str(name).upper(), f"the rite {name}")
|
|
239
|
+
raise SandboxViolation(
|
|
240
|
+
f"The sandbox withholds this power: {divine} — "
|
|
241
|
+
"it is not granted.",
|
|
242
|
+
line,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def check_import_path(
|
|
246
|
+
self, path: Path, requested: str, line: int | None
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Refuse scrolls outside the consecrated import paths."""
|
|
249
|
+
resolved = os.path.realpath(path)
|
|
250
|
+
for allowed in self._allowed_import_dirs:
|
|
251
|
+
if resolved == allowed or resolved.startswith(allowed + os.sep):
|
|
252
|
+
return
|
|
253
|
+
raise SandboxViolation(
|
|
254
|
+
"The sandbox withholds this power: the scroll "
|
|
255
|
+
f"'{requested}' lies outside the consecrated paths — "
|
|
256
|
+
"it may not be breathed in.",
|
|
257
|
+
line,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def apply_policy(interpreter: Any, policy: SandboxPolicy) -> Sandbox:
|
|
262
|
+
"""Install sandbox hooks on ``interpreter``; return the guard.
|
|
263
|
+
|
|
264
|
+
One guard per interpreter; the hooks stay installed until
|
|
265
|
+
``guard.uninstall()`` is called.
|
|
266
|
+
"""
|
|
267
|
+
return Sandbox(interpreter, policy).install()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# Timeout: POSIX signal alarm with a wall-clock fallback
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
@contextlib.contextmanager
|
|
274
|
+
def _time_limit(seconds: float | None, guard: Sandbox):
|
|
275
|
+
"""Raise SandboxViolation from a POSIX alarm when the grant expires.
|
|
276
|
+
|
|
277
|
+
The wall-clock check in :meth:`Sandbox.tick` is the fallback (and the
|
|
278
|
+
only enforcement on platforms without ``setitimer``, or outside the
|
|
279
|
+
main thread, where signals cannot be armed).
|
|
280
|
+
"""
|
|
281
|
+
armed = False
|
|
282
|
+
old_handler = None
|
|
283
|
+
|
|
284
|
+
def _handler(signum, frame): # noqa: ARG001
|
|
285
|
+
raise SandboxViolation(
|
|
286
|
+
"The sandbox withholds this power: the appointed time "
|
|
287
|
+
f"({seconds:g}s) is spent — the creation is released in peace.",
|
|
288
|
+
guard.current_line,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
if seconds and hasattr(signal, "setitimer"):
|
|
293
|
+
old_handler = signal.signal(signal.SIGALRM, _handler)
|
|
294
|
+
signal.setitimer(signal.ITIMER_REAL, seconds)
|
|
295
|
+
armed = True
|
|
296
|
+
except (ValueError, OSError, RuntimeError):
|
|
297
|
+
armed = False # not the main thread, or signals unavailable
|
|
298
|
+
try:
|
|
299
|
+
yield
|
|
300
|
+
finally:
|
|
301
|
+
if armed:
|
|
302
|
+
try:
|
|
303
|
+
signal.setitimer(signal.ITIMER_REAL, 0)
|
|
304
|
+
signal.signal(signal.SIGALRM, old_handler)
|
|
305
|
+
except (ValueError, OSError, RuntimeError):
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
# High-level entry point
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
def run_sandboxed_with_interpreter(
|
|
313
|
+
source: str,
|
|
314
|
+
policy: SandboxPolicy | None = None,
|
|
315
|
+
source_name: str = "<sandbox>",
|
|
316
|
+
) -> tuple[list[str], Any]:
|
|
317
|
+
"""Run God Code ``source`` under ``policy``; return (REVEAL lines, interpreter).
|
|
318
|
+
|
|
319
|
+
The interpreter is returned so callers can read machine state the run
|
|
320
|
+
gathered (e.g. ``intent_checks`` for the v4.0 intent layer).
|
|
321
|
+
"""
|
|
322
|
+
from godcode import plugins
|
|
323
|
+
from godcode.chain import MemoryChainAdapter
|
|
324
|
+
from godcode.interpreter import Interpreter
|
|
325
|
+
from godcode.spirit import SpiritEngine
|
|
326
|
+
|
|
327
|
+
policy = policy or SandboxPolicy.strict()
|
|
328
|
+
env_var = plugins.DISABLE_ENV_VAR
|
|
329
|
+
previous = os.environ.get(env_var)
|
|
330
|
+
os.environ[env_var] = "1"
|
|
331
|
+
try:
|
|
332
|
+
interpreter = Interpreter(log_path=None, interactive=False)
|
|
333
|
+
finally:
|
|
334
|
+
if previous is None:
|
|
335
|
+
os.environ.pop(env_var, None)
|
|
336
|
+
else:
|
|
337
|
+
os.environ[env_var] = previous
|
|
338
|
+
# v4.0: the covenant ledger and the audit log stay unbound (they write
|
|
339
|
+
# to the host world, which the sandbox does not permit). The Spirit
|
|
340
|
+
# only reads its dataset, so it is bound: CONSULT and the DECLARE
|
|
341
|
+
# INTENT discernment need it. ANCHOR is answered with an ephemeral
|
|
342
|
+
# in-memory chain, so no file is ever written.
|
|
343
|
+
try:
|
|
344
|
+
interpreter.spirit = SpiritEngine()
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
if not policy.allow_write:
|
|
348
|
+
interpreter.chain_adapters = {"simulated": MemoryChainAdapter()}
|
|
349
|
+
guard = apply_policy(interpreter, policy)
|
|
350
|
+
guard.started_at = time.monotonic()
|
|
351
|
+
try:
|
|
352
|
+
with _time_limit(policy.timeout_seconds, guard):
|
|
353
|
+
interpreter.run_source(source, source_name=source_name)
|
|
354
|
+
except RecursionError:
|
|
355
|
+
raise SandboxViolation(
|
|
356
|
+
"The sandbox withholds this power: the rites called upon "
|
|
357
|
+
"themselves past the deep places — the recursion budget is "
|
|
358
|
+
"spent, and the cycle is released.",
|
|
359
|
+
guard.current_line,
|
|
360
|
+
) from None
|
|
361
|
+
finally:
|
|
362
|
+
guard.started_at = None
|
|
363
|
+
return list(interpreter.output), interpreter
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def run_sandboxed(
|
|
367
|
+
source: str,
|
|
368
|
+
policy: SandboxPolicy | None = None,
|
|
369
|
+
source_name: str = "<sandbox>",
|
|
370
|
+
) -> list[str]:
|
|
371
|
+
"""Run God Code ``source`` under ``policy``; return captured REVEAL lines.
|
|
372
|
+
|
|
373
|
+
Uses the strict policy when none is given. The covenant ledger and
|
|
374
|
+
the audit log stay unbound -- they write to the host world, which
|
|
375
|
+
the sandbox does not permit. The Spirit is bound (it only reads its
|
|
376
|
+
dataset), and ANCHOR answers with an ephemeral in-memory chain, so
|
|
377
|
+
CONSULT and DECLARE INTENT work while no file is ever written. Host
|
|
378
|
+
plugin auto-loading is disabled for the run (via the plugin system's
|
|
379
|
+
own opt-out): plugins are trusted host code that runs outside any
|
|
380
|
+
policy, so a deny-by-default sandbox must not breathe them in unasked.
|
|
381
|
+
Raises :class:`SandboxViolation` (a GodCodeError, line-numbered)
|
|
382
|
+
when the creation reaches beyond its grant.
|
|
383
|
+
"""
|
|
384
|
+
output, _ = run_sandboxed_with_interpreter(source, policy, source_name)
|
|
385
|
+
return output
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# The Scroll of Multitudes -- reckonings over gathered lists.
|
|
2
|
+
# Rites: SUM, AVG, CONTAINS, SECOND, TAIL, COUNT.
|
|
3
|
+
|
|
4
|
+
DEFINE RITE SUM(xs)
|
|
5
|
+
DECLARE total AS 0
|
|
6
|
+
DECLARE i AS 0
|
|
7
|
+
WHILE i < LEN(xs) DO
|
|
8
|
+
DECLARE total AS total + xs[i]
|
|
9
|
+
DECLARE i AS i + 1
|
|
10
|
+
ENDWHILE
|
|
11
|
+
RETURN total
|
|
12
|
+
END RITE
|
|
13
|
+
|
|
14
|
+
DEFINE RITE AVG(xs)
|
|
15
|
+
RETURN SUM(xs) / LEN(xs)
|
|
16
|
+
END RITE
|
|
17
|
+
|
|
18
|
+
DEFINE RITE CONTAINS(xs, x)
|
|
19
|
+
DECLARE i AS 0
|
|
20
|
+
WHILE i < LEN(xs) DO
|
|
21
|
+
IF xs[i] IS x THEN
|
|
22
|
+
RETURN 1
|
|
23
|
+
ENDIF
|
|
24
|
+
DECLARE i AS i + 1
|
|
25
|
+
ENDWHILE
|
|
26
|
+
RETURN 0
|
|
27
|
+
END RITE
|
|
28
|
+
|
|
29
|
+
DEFINE RITE SECOND(xs)
|
|
30
|
+
RETURN xs[1]
|
|
31
|
+
END RITE
|
|
32
|
+
|
|
33
|
+
DEFINE RITE TAIL(xs)
|
|
34
|
+
DECLARE tail AS []
|
|
35
|
+
DECLARE i AS 1
|
|
36
|
+
WHILE i < LEN(xs) DO
|
|
37
|
+
DECLARE tail AS PUSH(tail, xs[i])
|
|
38
|
+
DECLARE i AS i + 1
|
|
39
|
+
ENDWHILE
|
|
40
|
+
RETURN tail
|
|
41
|
+
END RITE
|
|
42
|
+
|
|
43
|
+
DEFINE RITE COUNT(xs, x)
|
|
44
|
+
DECLARE n AS 0
|
|
45
|
+
DECLARE i AS 0
|
|
46
|
+
WHILE i < LEN(xs) DO
|
|
47
|
+
IF xs[i] IS x THEN
|
|
48
|
+
DECLARE n AS n + 1
|
|
49
|
+
ENDIF
|
|
50
|
+
DECLARE i AS i + 1
|
|
51
|
+
ENDWHILE
|
|
52
|
+
RETURN n
|
|
53
|
+
END RITE
|
godcode/scrolls/math.god
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# The Scroll of Numbers -- divine arithmetic for the children of creation.
|
|
2
|
+
# Rites: SQRT, POW, ABS, MIN, MAX, FACTORIAL, IS_EVEN.
|
|
3
|
+
|
|
4
|
+
DEFINE RITE SQRT(x)
|
|
5
|
+
IF x IS 0 THEN
|
|
6
|
+
RETURN 0
|
|
7
|
+
ENDIF
|
|
8
|
+
IF x < 0 THEN
|
|
9
|
+
RETURN 0
|
|
10
|
+
ENDIF
|
|
11
|
+
DECLARE guess AS x
|
|
12
|
+
WHILE ABS(guess * guess - x) > 0.000000001 DO
|
|
13
|
+
DECLARE guess AS guess - (guess * guess - x) / (2 * guess)
|
|
14
|
+
ENDWHILE
|
|
15
|
+
RETURN guess
|
|
16
|
+
END RITE
|
|
17
|
+
|
|
18
|
+
DEFINE RITE POW(b, e)
|
|
19
|
+
DECLARE result AS 1
|
|
20
|
+
DECLARE i AS 0
|
|
21
|
+
WHILE i < e DO
|
|
22
|
+
DECLARE result AS result * b
|
|
23
|
+
DECLARE i AS i + 1
|
|
24
|
+
ENDWHILE
|
|
25
|
+
RETURN result
|
|
26
|
+
END RITE
|
|
27
|
+
|
|
28
|
+
DEFINE RITE ABS(x)
|
|
29
|
+
IF x < 0 THEN
|
|
30
|
+
RETURN 0 - x
|
|
31
|
+
ENDIF
|
|
32
|
+
RETURN x
|
|
33
|
+
END RITE
|
|
34
|
+
|
|
35
|
+
DEFINE RITE MIN(a, b)
|
|
36
|
+
IF a < b THEN
|
|
37
|
+
RETURN a
|
|
38
|
+
ENDIF
|
|
39
|
+
RETURN b
|
|
40
|
+
END RITE
|
|
41
|
+
|
|
42
|
+
DEFINE RITE MAX(a, b)
|
|
43
|
+
IF a > b THEN
|
|
44
|
+
RETURN a
|
|
45
|
+
ENDIF
|
|
46
|
+
RETURN b
|
|
47
|
+
END RITE
|
|
48
|
+
|
|
49
|
+
DEFINE RITE FACTORIAL(n)
|
|
50
|
+
DECLARE result AS 1
|
|
51
|
+
DECLARE i AS 1
|
|
52
|
+
WHILE i <= n DO
|
|
53
|
+
DECLARE result AS result * i
|
|
54
|
+
DECLARE i AS i + 1
|
|
55
|
+
ENDWHILE
|
|
56
|
+
RETURN result
|
|
57
|
+
END RITE
|
|
58
|
+
|
|
59
|
+
DEFINE RITE IS_EVEN(n)
|
|
60
|
+
IF n % 2 IS 0 THEN
|
|
61
|
+
RETURN 1
|
|
62
|
+
ENDIF
|
|
63
|
+
RETURN 0
|
|
64
|
+
END RITE
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# The Scroll of Lots -- discerning choice by sacred chance.
|
|
2
|
+
# Rites: PROPHESY_NUMBER, CAST_LOTS, CHOOSE.
|
|
3
|
+
|
|
4
|
+
DEFINE RITE PROPHESY_NUMBER(n)
|
|
5
|
+
RETURN RANDOM(n)
|
|
6
|
+
END RITE
|
|
7
|
+
|
|
8
|
+
DEFINE RITE CAST_LOTS()
|
|
9
|
+
RETURN RANDOM(2)
|
|
10
|
+
END RITE
|
|
11
|
+
|
|
12
|
+
DEFINE RITE CHOOSE(xs)
|
|
13
|
+
RETURN xs[RANDOM(LEN(xs))]
|
|
14
|
+
END RITE
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# The Scroll of Tongues -- words shaped, lifted up, and proclaimed.
|
|
2
|
+
# Rites: SHOUT, WHISPER, WORDS, CHARS, FIRST, LAST.
|
|
3
|
+
|
|
4
|
+
DEFINE RITE SHOUT(s)
|
|
5
|
+
RETURN UPPER(s) + "!"
|
|
6
|
+
END RITE
|
|
7
|
+
|
|
8
|
+
DEFINE RITE WHISPER(s)
|
|
9
|
+
RETURN LOWER(s)
|
|
10
|
+
END RITE
|
|
11
|
+
|
|
12
|
+
DEFINE RITE WORDS(s)
|
|
13
|
+
RETURN SPLIT(s, " ")
|
|
14
|
+
END RITE
|
|
15
|
+
|
|
16
|
+
DEFINE RITE CHARS(s)
|
|
17
|
+
DECLARE chars AS []
|
|
18
|
+
DECLARE i AS 0
|
|
19
|
+
WHILE i < LEN(s) DO
|
|
20
|
+
DECLARE chars AS PUSH(chars, s[i])
|
|
21
|
+
DECLARE i AS i + 1
|
|
22
|
+
ENDWHILE
|
|
23
|
+
RETURN chars
|
|
24
|
+
END RITE
|
|
25
|
+
|
|
26
|
+
DEFINE RITE FIRST(s)
|
|
27
|
+
RETURN s[0]
|
|
28
|
+
END RITE
|
|
29
|
+
|
|
30
|
+
DEFINE RITE LAST(s)
|
|
31
|
+
RETURN s[LEN(s) - 1]
|
|
32
|
+
END RITE
|
godcode/scrolls/time.god
ADDED