prometheus-lang 0.2.2__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.
prom/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Prometheus bootstrap toolchain."""
2
+
3
+ __version__ = "0.2.2"
prom/ast.py ADDED
@@ -0,0 +1,249 @@
1
+ """Abstract syntax tree for Prometheus.
2
+
3
+ The AST is deliberately small: Prometheus is an expression language with a
4
+ thin declaration layer. Every node carries a Span for diagnostics.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+
11
+ from .errors import Span
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Type annotations (surface syntax for types)
15
+ # ---------------------------------------------------------------------------
16
+
17
+
18
+ @dataclass
19
+ class TAnn:
20
+ span: Span = field(default_factory=Span.unknown)
21
+
22
+
23
+ @dataclass
24
+ class TAName(TAnn):
25
+ name: str = ""
26
+ args: list["TAnn"] = field(default_factory=list)
27
+
28
+
29
+ @dataclass
30
+ class TAFun(TAnn):
31
+ params: list[TAnn] = field(default_factory=list)
32
+ ret: TAnn | None = None
33
+
34
+
35
+ @dataclass
36
+ class TARecord(TAnn):
37
+ fields: list[tuple[str, TAnn]] = field(default_factory=list)
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Expressions
42
+ # ---------------------------------------------------------------------------
43
+
44
+
45
+ @dataclass
46
+ class Expr:
47
+ span: Span = field(default_factory=Span.unknown)
48
+
49
+
50
+ @dataclass
51
+ class Lit(Expr):
52
+ value: object = None
53
+ type_name: str = "" # Int | Float | Bool | Unit
54
+
55
+
56
+ @dataclass
57
+ class Str(Expr):
58
+ # parts: list of ("text", str) or ("expr", Expr)
59
+ parts: list[tuple[str, object]] = field(default_factory=list)
60
+
61
+
62
+ @dataclass
63
+ class Var(Expr):
64
+ name: str = ""
65
+
66
+
67
+ @dataclass
68
+ class ListLit(Expr):
69
+ items: list[Expr] = field(default_factory=list)
70
+
71
+
72
+ @dataclass
73
+ class RecordLit(Expr):
74
+ fields: list[tuple[str, Expr]] = field(default_factory=list)
75
+
76
+
77
+ @dataclass
78
+ class Field(Expr):
79
+ obj: Expr | None = None
80
+ name: str = ""
81
+
82
+
83
+ @dataclass
84
+ class Call(Expr):
85
+ fn: Expr | None = None
86
+ args: list[Expr] = field(default_factory=list)
87
+
88
+
89
+ @dataclass
90
+ class Lambda(Expr):
91
+ params: list[str] = field(default_factory=list)
92
+ body: Expr | None = None
93
+
94
+
95
+ @dataclass
96
+ class BinOp(Expr):
97
+ op: str = ""
98
+ left: Expr | None = None
99
+ right: Expr | None = None
100
+
101
+
102
+ @dataclass
103
+ class UnOp(Expr):
104
+ op: str = ""
105
+ operand: Expr | None = None
106
+
107
+
108
+ @dataclass
109
+ class If(Expr):
110
+ cond: Expr | None = None
111
+ then: Expr | None = None
112
+ els: Expr | None = None
113
+
114
+
115
+ @dataclass
116
+ class MatchArm:
117
+ pattern: "Pattern"
118
+ body: Expr
119
+ span: Span
120
+
121
+
122
+ @dataclass
123
+ class Match(Expr):
124
+ subject: Expr | None = None
125
+ arms: list[MatchArm] = field(default_factory=list)
126
+
127
+
128
+ @dataclass
129
+ class Bind:
130
+ name: str
131
+ ann: TAnn | None
132
+ expr: Expr
133
+ span: Span
134
+
135
+
136
+ @dataclass
137
+ class ParBlock:
138
+ """Bindings whose right-hand sides may evaluate in parallel.
139
+
140
+ Semantics are defined to be identical to sequential evaluation; the
141
+ expressions must be pure (the runtime enforces this dynamically in the
142
+ bootstrap, the native compiler will enforce it statically).
143
+ """
144
+
145
+ binds: list[Bind]
146
+ span: Span
147
+
148
+
149
+ @dataclass
150
+ class Block(Expr):
151
+ # items: Bind | ParBlock | Expr ; the final item must be an Expr and is
152
+ # the block's value.
153
+ items: list[object] = field(default_factory=list)
154
+
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Patterns
158
+ # ---------------------------------------------------------------------------
159
+
160
+
161
+ @dataclass
162
+ class Pattern:
163
+ span: Span = field(default_factory=Span.unknown)
164
+
165
+
166
+ @dataclass
167
+ class PWild(Pattern):
168
+ pass
169
+
170
+
171
+ @dataclass
172
+ class PVar(Pattern):
173
+ name: str = ""
174
+
175
+
176
+ @dataclass
177
+ class PLit(Pattern):
178
+ value: object = None
179
+ type_name: str = ""
180
+
181
+
182
+ @dataclass
183
+ class PCtor(Pattern):
184
+ name: str = ""
185
+ args: list[Pattern] = field(default_factory=list)
186
+
187
+
188
+ @dataclass
189
+ class PList(Pattern):
190
+ items: list[Pattern] = field(default_factory=list)
191
+ rest: str | None = None # name bound to the tail, or None
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # Declarations
196
+ # ---------------------------------------------------------------------------
197
+
198
+
199
+ @dataclass
200
+ class Decl:
201
+ span: Span = field(default_factory=Span.unknown)
202
+
203
+
204
+ @dataclass
205
+ class DType(Decl):
206
+ name: str = ""
207
+ params: list[str] = field(default_factory=list)
208
+ ctors: list[tuple[str, list[TAnn]]] = field(default_factory=list)
209
+
210
+
211
+ @dataclass
212
+ class DFunc(Decl):
213
+ name: str = ""
214
+ params: list[tuple[str, TAnn | None]] = field(default_factory=list)
215
+ ret_ann: TAnn | None = None
216
+ body: Expr | None = None
217
+ intent: str | None = None
218
+
219
+
220
+ @dataclass
221
+ class DVal(Decl):
222
+ name: str = ""
223
+ ann: TAnn | None = None
224
+ expr: Expr | None = None
225
+ intent: str | None = None
226
+
227
+
228
+ @dataclass
229
+ class DUse(Decl):
230
+ """use path.to.module — binds the module under its last path segment."""
231
+
232
+ parts: list[str] = field(default_factory=list)
233
+
234
+ @property
235
+ def alias(self) -> str:
236
+ return self.parts[-1]
237
+
238
+
239
+ @dataclass
240
+ class DCheck(Decl):
241
+ label: str | None = None
242
+ expr: Expr | None = None
243
+
244
+
245
+ @dataclass
246
+ class Program:
247
+ decls: list[Decl]
248
+ source: str
249
+ filename: str
prom/builtins.py ADDED
@@ -0,0 +1,437 @@
1
+ """Built-in functions: the primitive layer beneath the Prometheus prelude.
2
+
3
+ Kept deliberately small — everything that *can* be written in Prometheus
4
+ lives in stdlib/prelude.prom instead. Effectful builtins take a capability
5
+ as their first argument; without the capability value they are uncallable,
6
+ which is the whole security model in one sentence.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import math
12
+ import os
13
+ import random
14
+ import time
15
+
16
+ from .errors import RuntimeError_, Span
17
+ from .eval import (
18
+ Builtin, Capability, Data, MapValue, list_iter, make_list, show_value,
19
+ )
20
+ from .types import (
21
+ BOOL, FLOAT, INT, TEXT, UNIT, Scheme, TCon, TFun, TRecord, TVar, tlist,
22
+ )
23
+
24
+
25
+ def _ok(v):
26
+ return Data("Ok", (v,))
27
+
28
+
29
+ def _err(msg: str):
30
+ return Data("Err", (msg,))
31
+
32
+
33
+ def _some(v):
34
+ return Data("Some", (v,))
35
+
36
+
37
+ NONE = Data("None")
38
+
39
+ SCALARS = (int, float, bool, str)
40
+
41
+
42
+ def _require_scalar_key(k, span):
43
+ if not isinstance(k, SCALARS):
44
+ raise RuntimeError_(
45
+ f"map keys must be scalars (Int, Float, Bool, Text) in v0.2, got {show_value(k)}",
46
+ span,
47
+ )
48
+
49
+
50
+ def _pure(name, arity, fn):
51
+ return Builtin(name, arity, lambda rt, args, span: fn(args, span), effectful=False)
52
+
53
+
54
+ def _effect(name, arity, fn):
55
+ return Builtin(name, arity, fn, effectful=True)
56
+
57
+
58
+ def _check_cap(args, kind: str, name: str, span: Span):
59
+ cap = args[0]
60
+ if not isinstance(cap, Capability) or cap.kind != kind:
61
+ raise RuntimeError_(f"{name} requires the {kind} capability", span)
62
+
63
+
64
+ # -- pure primitives ---------------------------------------------------------
65
+
66
+
67
+ def _show(args, span):
68
+ return show_value(args[0])
69
+
70
+
71
+ def _length(args, span):
72
+ return sum(1 for _ in list_iter(args[0]))
73
+
74
+
75
+ def _text_length(args, span):
76
+ return len(args[0])
77
+
78
+
79
+ def _range(args, span):
80
+ lo, hi = args
81
+ return make_list(range(lo, hi))
82
+
83
+
84
+ def _split(args, span):
85
+ text, sep = args
86
+ if sep == "":
87
+ raise RuntimeError_("split separator must not be empty", span)
88
+ return make_list(text.split(sep))
89
+
90
+
91
+ def _join(args, span):
92
+ parts, sep = args
93
+ return sep.join(list_iter(parts))
94
+
95
+
96
+ def _to_float(args, span):
97
+ return float(args[0])
98
+
99
+
100
+ def _floor(args, span):
101
+ return math.floor(args[0])
102
+
103
+
104
+ def _sqrt(args, span):
105
+ if args[0] < 0.0:
106
+ raise RuntimeError_("sqrt of a negative number", span)
107
+ return math.sqrt(args[0])
108
+
109
+
110
+ def _contains(args, span):
111
+ return args[1] in args[0]
112
+
113
+
114
+ def _starts_with(args, span):
115
+ return args[0].startswith(args[1])
116
+
117
+
118
+ def _ends_with(args, span):
119
+ return args[0].endswith(args[1])
120
+
121
+
122
+ def _replace(args, span):
123
+ text, old, new = args
124
+ if old == "":
125
+ raise RuntimeError_("replace: the text to replace must not be empty", span)
126
+ return text.replace(old, new)
127
+
128
+
129
+ def _trim(args, span):
130
+ return args[0].strip()
131
+
132
+
133
+ def _upper(args, span):
134
+ return args[0].upper()
135
+
136
+
137
+ def _lower(args, span):
138
+ return args[0].lower()
139
+
140
+
141
+ def _slice_text(args, span):
142
+ text, start, end = args
143
+ n = len(text)
144
+ start = max(0, min(start, n))
145
+ end = max(start, min(end, n))
146
+ return text[start:end]
147
+
148
+
149
+ def _parse_int(args, span):
150
+ t = args[0].strip()
151
+ sign_ok = t and (t[0] in "+-" and t[1:].isdigit() or t.isdigit())
152
+ return _some(int(t)) if sign_ok else NONE
153
+
154
+
155
+ def _parse_float(args, span):
156
+ try:
157
+ return _some(float(args[0].strip()))
158
+ except ValueError:
159
+ return NONE
160
+
161
+
162
+ def _sort(args, span):
163
+ items = list(list_iter(args[0]))
164
+ kinds = {("text" if isinstance(x, str) else "num" if isinstance(x, (int, float))
165
+ and not isinstance(x, bool) else "other") for x in items}
166
+ if kinds - {"text"} and kinds - {"num"}:
167
+ raise RuntimeError_(
168
+ "sort works on lists of numbers or lists of Text (v0.2)", span
169
+ )
170
+ return make_list(sorted(items))
171
+
172
+
173
+ def _abs(args, span):
174
+ return abs(args[0])
175
+
176
+
177
+ def _min(args, span):
178
+ return min(args[0], args[1])
179
+
180
+
181
+ def _max(args, span):
182
+ return max(args[0], args[1])
183
+
184
+
185
+ # -- maps ---------------------------------------------------------------------
186
+
187
+
188
+ def _put(args, span):
189
+ m, k, v = args
190
+ _require_scalar_key(k, span)
191
+ entries = dict(m.entries)
192
+ entries[k] = v
193
+ return MapValue(entries)
194
+
195
+
196
+ def _get(args, span):
197
+ m, k = args
198
+ _require_scalar_key(k, span)
199
+ return _some(m.entries[k]) if k in m.entries else NONE
200
+
201
+
202
+ def _remove(args, span):
203
+ m, k = args
204
+ _require_scalar_key(k, span)
205
+ if k not in m.entries:
206
+ return m
207
+ entries = dict(m.entries)
208
+ del entries[k]
209
+ return MapValue(entries)
210
+
211
+
212
+ def _has(args, span):
213
+ m, k = args
214
+ _require_scalar_key(k, span)
215
+ return k in m.entries
216
+
217
+
218
+ def _keys(args, span):
219
+ return make_list(sorted(args[0].entries.keys(), key=repr))
220
+
221
+
222
+ def _size(args, span):
223
+ return len(args[0].entries)
224
+
225
+
226
+ # -- effectful primitives (capability-gated) ---------------------------------
227
+
228
+
229
+ def _print(rt, args, span):
230
+ _check_cap(args, "Io", "print", span)
231
+ rt.stdout.write(show_value(args[1]) + "\n")
232
+ from .eval import UNIT as U
233
+
234
+ return U
235
+
236
+
237
+ def _read_line(rt, args, span):
238
+ _check_cap(args, "Io", "read_line", span)
239
+ line = rt.stdin.readline()
240
+ return line[:-1] if line.endswith("\n") else line
241
+
242
+
243
+ def _read_file(rt, args, span):
244
+ _check_cap(args, "Fs", "read_file", span)
245
+ try:
246
+ with open(args[1], "r", encoding="utf-8") as f:
247
+ return _ok(f.read())
248
+ except OSError as err:
249
+ return _err(f"cannot read {args[1]}: {err.strerror or err}")
250
+
251
+
252
+ def _write_file(rt, args, span):
253
+ _check_cap(args, "Fs", "write_file", span)
254
+ from .eval import UNIT as U
255
+
256
+ try:
257
+ with open(args[1], "w", encoding="utf-8") as f:
258
+ f.write(args[2])
259
+ return _ok(U)
260
+ except OSError as err:
261
+ return _err(f"cannot write {args[1]}: {err.strerror or err}")
262
+
263
+
264
+ def _list_dir(rt, args, span):
265
+ _check_cap(args, "Fs", "list_dir", span)
266
+ try:
267
+ return _ok(make_list(sorted(os.listdir(args[1]))))
268
+ except OSError as err:
269
+ return _err(f"cannot list {args[1]}: {err.strerror or err}")
270
+
271
+
272
+ def _exists(rt, args, span):
273
+ _check_cap(args, "Fs", "exists", span)
274
+ return os.path.exists(args[1])
275
+
276
+
277
+ def _get_env(rt, args, span):
278
+ _check_cap(args, "Env", "get_env", span)
279
+ v = os.environ.get(args[1])
280
+ return NONE if v is None else _some(v)
281
+
282
+
283
+ def _http_get(rt, args, span):
284
+ _check_cap(args, "Net", "http_get", span)
285
+ import urllib.error
286
+ import urllib.request
287
+
288
+ url = args[1]
289
+ if not url.startswith(("http://", "https://")):
290
+ return _err(f"http_get: unsupported URL scheme in {url}")
291
+ try:
292
+ with urllib.request.urlopen(url, timeout=30) as resp:
293
+ return _ok(resp.read().decode("utf-8", errors="replace"))
294
+ except (urllib.error.URLError, OSError, ValueError) as err:
295
+ return _err(f"http_get {url}: {err}")
296
+
297
+
298
+ def _rand_int(rt, args, span):
299
+ _check_cap(args, "Rand", "rand_int", span)
300
+ lo, hi = args[1], args[2]
301
+ if lo > hi:
302
+ raise RuntimeError_(f"rand_int: empty range {lo}..{hi}", span)
303
+ return random.randint(lo, hi)
304
+
305
+
306
+ def _rand_float(rt, args, span):
307
+ _check_cap(args, "Rand", "rand_float", span)
308
+ return random.random()
309
+
310
+
311
+ def _now_ms(rt, args, span):
312
+ _check_cap(args, "Clock", "now_ms", span)
313
+ return int(time.time() * 1000)
314
+
315
+
316
+ IO = TCon("Io")
317
+ FS = TCon("Fs")
318
+ CLOCK = TCon("Clock")
319
+ ENV = TCon("Env")
320
+ NET = TCon("Net")
321
+ RAND = TCon("Rand")
322
+
323
+
324
+ def _result(ok, err):
325
+ return TCon("Result", [ok, err])
326
+
327
+
328
+ def _option(a):
329
+ return TCon("Option", [a])
330
+
331
+
332
+ def _map_t(k, v):
333
+ return TCon("Map", [k, v])
334
+
335
+
336
+ def make_builtins() -> tuple[dict[str, object], dict[str, Scheme]]:
337
+ """Returns (value environment, type environment) for the primitives."""
338
+ a = TVar()
339
+ b = TVar()
340
+ values: dict[str, object] = {
341
+ "show": _pure("show", 1, _show),
342
+ "length": _pure("length", 1, _length),
343
+ "text_length": _pure("text_length", 1, _text_length),
344
+ "range": _pure("range", 2, _range),
345
+ "split": _pure("split", 2, _split),
346
+ "join": _pure("join", 2, _join),
347
+ "to_float": _pure("to_float", 1, _to_float),
348
+ "floor": _pure("floor", 1, _floor),
349
+ "sqrt": _pure("sqrt", 1, _sqrt),
350
+ "text_contains": _pure("text_contains", 2, _contains),
351
+ "starts_with": _pure("starts_with", 2, _starts_with),
352
+ "ends_with": _pure("ends_with", 2, _ends_with),
353
+ "replace": _pure("replace", 3, _replace),
354
+ "trim": _pure("trim", 1, _trim),
355
+ "upper": _pure("upper", 1, _upper),
356
+ "lower": _pure("lower", 1, _lower),
357
+ "slice": _pure("slice", 3, _slice_text),
358
+ "parse_int": _pure("parse_int", 1, _parse_int),
359
+ "parse_float": _pure("parse_float", 1, _parse_float),
360
+ "sort": _pure("sort", 1, _sort),
361
+ "abs": _pure("abs", 1, _abs),
362
+ "min": _pure("min", 2, _min),
363
+ "max": _pure("max", 2, _max),
364
+ "empty_map": MapValue({}),
365
+ "put": _pure("put", 3, _put),
366
+ "get": _pure("get", 2, _get),
367
+ "remove": _pure("remove", 2, _remove),
368
+ "has": _pure("has", 2, _has),
369
+ "keys": _pure("keys", 1, _keys),
370
+ "size": _pure("size", 1, _size),
371
+ "print": _effect("print", 2, _print),
372
+ "read_line": _effect("read_line", 1, _read_line),
373
+ "read_file": _effect("read_file", 2, _read_file),
374
+ "write_file": _effect("write_file", 3, _write_file),
375
+ "list_dir": _effect("list_dir", 2, _list_dir),
376
+ "exists": _effect("exists", 2, _exists),
377
+ "get_env": _effect("get_env", 2, _get_env),
378
+ "http_get": _effect("http_get", 2, _http_get),
379
+ "rand_int": _effect("rand_int", 3, _rand_int),
380
+ "rand_float": _effect("rand_float", 1, _rand_float),
381
+ "now_ms": _effect("now_ms", 1, _now_ms),
382
+ }
383
+ n1 = TVar(numeric=True)
384
+ n2 = TVar(numeric=True)
385
+ n3 = TVar(numeric=True)
386
+ s1 = TVar()
387
+ mk, mv = TVar(), TVar()
388
+ mk2, mv2 = TVar(), TVar()
389
+ mk3, mv3 = TVar(), TVar()
390
+ mk4, mv4 = TVar(), TVar()
391
+ mk5, mv5 = TVar(), TVar()
392
+ mk6, mv6 = TVar(), TVar()
393
+ mk7, mv7 = TVar(), TVar()
394
+ types: dict[str, Scheme] = {
395
+ "show": Scheme([a], TFun([a], TEXT)),
396
+ "length": Scheme([b], TFun([tlist(b)], INT)),
397
+ "text_length": Scheme([], TFun([TEXT], INT)),
398
+ "range": Scheme([], TFun([INT, INT], tlist(INT))),
399
+ "split": Scheme([], TFun([TEXT, TEXT], tlist(TEXT))),
400
+ "join": Scheme([], TFun([tlist(TEXT), TEXT], TEXT)),
401
+ "to_float": Scheme([], TFun([INT], FLOAT)),
402
+ "floor": Scheme([], TFun([FLOAT], INT)),
403
+ "sqrt": Scheme([], TFun([FLOAT], FLOAT)),
404
+ "text_contains": Scheme([], TFun([TEXT, TEXT], BOOL)),
405
+ "starts_with": Scheme([], TFun([TEXT, TEXT], BOOL)),
406
+ "ends_with": Scheme([], TFun([TEXT, TEXT], BOOL)),
407
+ "replace": Scheme([], TFun([TEXT, TEXT, TEXT], TEXT)),
408
+ "trim": Scheme([], TFun([TEXT], TEXT)),
409
+ "upper": Scheme([], TFun([TEXT], TEXT)),
410
+ "lower": Scheme([], TFun([TEXT], TEXT)),
411
+ "slice": Scheme([], TFun([TEXT, INT, INT], TEXT)),
412
+ "parse_int": Scheme([], TFun([TEXT], _option(INT))),
413
+ "parse_float": Scheme([], TFun([TEXT], _option(FLOAT))),
414
+ "sort": Scheme([s1], TFun([tlist(s1)], tlist(s1))),
415
+ "abs": Scheme([n1], TFun([n1], n1)),
416
+ "min": Scheme([n2], TFun([n2, n2], n2)),
417
+ "max": Scheme([n3], TFun([n3, n3], n3)),
418
+ "empty_map": Scheme([mk, mv], _map_t(mk, mv)),
419
+ "put": Scheme([mk2, mv2], TFun([_map_t(mk2, mv2), mk2, mv2], _map_t(mk2, mv2))),
420
+ "get": Scheme([mk3, mv3], TFun([_map_t(mk3, mv3), mk3], _option(mv3))),
421
+ "remove": Scheme([mk4, mv4], TFun([_map_t(mk4, mv4), mk4], _map_t(mk4, mv4))),
422
+ "has": Scheme([mk5, mv5], TFun([_map_t(mk5, mv5), mk5], BOOL)),
423
+ "keys": Scheme([mk6, mv6], TFun([_map_t(mk6, mv6)], tlist(mk6))),
424
+ "size": Scheme([mk7, mv7], TFun([_map_t(mk7, mv7)], INT)),
425
+ "print": Scheme([], TFun([IO, TEXT], UNIT)),
426
+ "read_line": Scheme([], TFun([IO], TEXT)),
427
+ "read_file": Scheme([], TFun([FS, TEXT], _result(TEXT, TEXT))),
428
+ "write_file": Scheme([], TFun([FS, TEXT, TEXT], _result(UNIT, TEXT))),
429
+ "list_dir": Scheme([], TFun([FS, TEXT], _result(tlist(TEXT), TEXT))),
430
+ "exists": Scheme([], TFun([FS, TEXT], BOOL)),
431
+ "get_env": Scheme([], TFun([ENV, TEXT], _option(TEXT))),
432
+ "http_get": Scheme([], TFun([NET, TEXT], _result(TEXT, TEXT))),
433
+ "rand_int": Scheme([], TFun([RAND, INT, INT], INT)),
434
+ "rand_float": Scheme([], TFun([RAND], FLOAT)),
435
+ "now_ms": Scheme([], TFun([CLOCK], INT)),
436
+ }
437
+ return values, types