pyscriptc 0.1.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.
- pyscriptc/__init__.py +3 -0
- pyscriptc/__main__.py +3 -0
- pyscriptc/_posthog_token.py +1 -0
- pyscriptc/builtins.py +156 -0
- pyscriptc/canonical.py +167 -0
- pyscriptc/check.py +49 -0
- pyscriptc/cli.py +271 -0
- pyscriptc/coverage.py +388 -0
- pyscriptc/debug.py +120 -0
- pyscriptc/diagnostics.py +179 -0
- pyscriptc/errors.py +56 -0
- pyscriptc/ir.py +365 -0
- pyscriptc/llvm.py +2129 -0
- pyscriptc/log.py +127 -0
- pyscriptc/lower/__init__.py +1071 -0
- pyscriptc/lower/_calls.py +1499 -0
- pyscriptc/lower/_classes.py +969 -0
- pyscriptc/lower/_core.py +301 -0
- pyscriptc/lower/_expr.py +886 -0
- pyscriptc/lower/_stdlib.py +491 -0
- pyscriptc/lower/_stdlib_shared.py +93 -0
- pyscriptc/lower/_stmt.py +1173 -0
- pyscriptc/package.py +249 -0
- pyscriptc/restlower.py +1008 -0
- pyscriptc/runtime/ps_argv.c +28 -0
- pyscriptc/runtime/ps_b64.c +168 -0
- pyscriptc/runtime/ps_coll.c +133 -0
- pyscriptc/runtime/ps_dt.c +388 -0
- pyscriptc/runtime/ps_hash.c +411 -0
- pyscriptc/runtime/ps_help.c +219 -0
- pyscriptc/runtime/ps_hmac.c +120 -0
- pyscriptc/runtime/ps_io.c +198 -0
- pyscriptc/runtime/ps_island.c +272 -0
- pyscriptc/runtime/ps_json.c +402 -0
- pyscriptc/runtime/ps_mix.c +47 -0
- pyscriptc/runtime/ps_more.c +1089 -0
- pyscriptc/runtime/ps_osmore.c +447 -0
- pyscriptc/runtime/ps_path.c +547 -0
- pyscriptc/runtime/ps_re.c +1102 -0
- pyscriptc/runtime/ps_rt.c +1175 -0
- pyscriptc/runtime/ps_rt.h +189 -0
- pyscriptc/runtime/ps_struct.c +291 -0
- pyscriptc/runtime/ps_time.c +207 -0
- pyscriptc/runtime/ps_unicode.c +1521 -0
- pyscriptc/runtime/ps_url.c +327 -0
- pyscriptc/stdlib_queue.py +191 -0
- pyscriptc/stdmore.py +1596 -0
- pyscriptc/telemetry.py +219 -0
- pyscriptc-0.1.0.dist-info/METADATA +187 -0
- pyscriptc-0.1.0.dist-info/RECORD +54 -0
- pyscriptc-0.1.0.dist-info/WHEEL +5 -0
- pyscriptc-0.1.0.dist-info/entry_points.txt +2 -0
- pyscriptc-0.1.0.dist-info/licenses/LICENSE +201 -0
- pyscriptc-0.1.0.dist-info/top_level.txt +1 -0
pyscriptc/lower/_core.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Leaf definitions shared by every module in the `lower` package.
|
|
2
|
+
|
|
3
|
+
`Fence`, `Binding`, and the small pure helpers live here so sibling modules
|
|
4
|
+
(`_stdlib.py`, `__init__.py`) can import them without a cycle. Moved verbatim
|
|
5
|
+
from `lower.py`; no signature or behavior change.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from pyscriptc.ir import types_equal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Fence(Exception):
|
|
17
|
+
def __init__(self, code: str, message: str, span: dict[str, Any], function: str | None) -> None:
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.code = code
|
|
20
|
+
self.message = message
|
|
21
|
+
self.span = span
|
|
22
|
+
self.function = function
|
|
23
|
+
|
|
24
|
+
def as_dict(self) -> dict[str, Any]:
|
|
25
|
+
return {
|
|
26
|
+
"code": self.code,
|
|
27
|
+
"message": self.message,
|
|
28
|
+
"function": self.function,
|
|
29
|
+
"span": self.span,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Binding:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
kind: str,
|
|
37
|
+
module: str,
|
|
38
|
+
attr: str,
|
|
39
|
+
queued: bool,
|
|
40
|
+
typ: dict[str, Any] | None = None,
|
|
41
|
+
rooted: bool = False,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.kind = kind
|
|
44
|
+
self.module = module
|
|
45
|
+
self.attr = attr
|
|
46
|
+
self.queued = queued
|
|
47
|
+
self.type = typ
|
|
48
|
+
# True when `import urllib.parse` binds `urllib`, not the whole module.
|
|
49
|
+
self.rooted = rooted
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _assign(target: str, value: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
53
|
+
return {"kind": "assign", "target": target, "value": value, "span": span}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _const_str_join(parts: list[dict[str, Any]], span: dict[str, Any]) -> dict[str, Any] | None:
|
|
57
|
+
"""Module-level f-string pieces that are already str constants are one str constant."""
|
|
58
|
+
|
|
59
|
+
if any(part.get("kind") != "const" or part.get("type", {}).get("kind") != "str" for part in parts):
|
|
60
|
+
return None
|
|
61
|
+
text = "".join(str(part["value"]) for part in parts)
|
|
62
|
+
return {"kind": "const", "value": text, "type": {"kind": "str"}, "span": span}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _concat_str(parts: list[dict[str, Any]], span: dict[str, Any]) -> dict[str, Any]:
|
|
66
|
+
if not parts:
|
|
67
|
+
return {"kind": "const", "value": "", "type": {"kind": "str"}, "span": span}
|
|
68
|
+
acc = parts[0]
|
|
69
|
+
for part in parts[1:]:
|
|
70
|
+
acc = {"kind": "binary", "op": "+", "left": acc, "right": part, "type": {"kind": "str"}, "span": span}
|
|
71
|
+
return acc
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _percent_pieces(fmt: str) -> list[tuple[str, str]] | None:
|
|
75
|
+
pieces: list[tuple[str, str]] = []
|
|
76
|
+
buf: list[str] = []
|
|
77
|
+
index = 0
|
|
78
|
+
size = len(fmt)
|
|
79
|
+
while index < size:
|
|
80
|
+
if fmt[index] != "%":
|
|
81
|
+
buf.append(fmt[index])
|
|
82
|
+
index += 1
|
|
83
|
+
continue
|
|
84
|
+
if index + 1 >= size:
|
|
85
|
+
return None
|
|
86
|
+
nxt = fmt[index + 1]
|
|
87
|
+
if nxt == "%":
|
|
88
|
+
buf.append("%")
|
|
89
|
+
index += 2
|
|
90
|
+
continue
|
|
91
|
+
if nxt not in {"s", "d"}:
|
|
92
|
+
return None
|
|
93
|
+
pieces.append(("lit", "".join(buf)))
|
|
94
|
+
buf = []
|
|
95
|
+
pieces.append((nxt, ""))
|
|
96
|
+
index += 2
|
|
97
|
+
pieces.append(("lit", "".join(buf)))
|
|
98
|
+
return pieces
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _const_list_concat(left: dict[str, Any], right: dict[str, Any], span: dict[str, Any]) -> dict[str, Any] | None:
|
|
102
|
+
"""Two constant lists of one element type are one list. A runtime list stays fenced."""
|
|
103
|
+
|
|
104
|
+
if left.get("kind") != "list" or right.get("kind") != "list":
|
|
105
|
+
return None
|
|
106
|
+
if left["type"]["kind"] != "list" or right["type"]["kind"] != "list":
|
|
107
|
+
return None
|
|
108
|
+
if left["type"].get("set") or right["type"].get("set"):
|
|
109
|
+
return None
|
|
110
|
+
if not types_equal(left["type"]["elem"], right["type"]["elem"]):
|
|
111
|
+
return None
|
|
112
|
+
return {
|
|
113
|
+
"kind": "list",
|
|
114
|
+
"elems": [*left["elems"], *right["elems"]],
|
|
115
|
+
"type": {"kind": "list", "elem": left["type"]["elem"]},
|
|
116
|
+
"span": span,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _name(name: str, typ: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
121
|
+
return {"kind": "name", "id": name, "type": typ, "span": span}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _const_int(value: int, span: dict[str, Any]) -> dict[str, Any]:
|
|
125
|
+
return {"kind": "const", "value": str(value), "type": {"kind": "int"}, "span": span}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _int_text(value: str) -> bool:
|
|
129
|
+
body = value[1:] if value.startswith("-") else value
|
|
130
|
+
return bool(body) and body.isdigit()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _static_int(expr: dict[str, Any]) -> int | None:
|
|
134
|
+
if expr.get("kind") == "const" and expr.get("type", {}).get("kind") == "int":
|
|
135
|
+
value = expr.get("value")
|
|
136
|
+
if isinstance(value, str) and _int_text(value):
|
|
137
|
+
return int(value)
|
|
138
|
+
if expr.get("kind") == "unary" and expr.get("op") == "-" and isinstance(expr.get("operand"), dict):
|
|
139
|
+
inner = _static_int(expr["operand"])
|
|
140
|
+
if inner is not None:
|
|
141
|
+
return -inner
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _const_bool(value: bool, span: dict[str, Any]) -> dict[str, Any]:
|
|
146
|
+
return {"kind": "const", "value": value, "type": {"kind": "bool"}, "span": span}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _const_none(span: dict[str, Any]) -> dict[str, Any]:
|
|
150
|
+
return {"kind": "const", "value": None, "type": {"kind": "none"}, "span": span}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _len_call(seq: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
154
|
+
return {
|
|
155
|
+
"kind": "call",
|
|
156
|
+
"callee": {"kind": "builtin", "name": "len"},
|
|
157
|
+
"args": [seq],
|
|
158
|
+
"type": {"kind": "int"},
|
|
159
|
+
"span": span,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _binary_int(op: str, left: dict[str, Any], right: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
164
|
+
return {"kind": "binary", "op": op, "left": left, "right": right, "type": {"kind": "int"}, "span": span}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _compare(op: str, left: dict[str, Any], right: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
168
|
+
return {"kind": "compare", "op": op, "left": left, "right": right, "type": {"kind": "bool"}, "span": span}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _show(typ: dict[str, Any]) -> str:
|
|
172
|
+
if typ["kind"] == "list":
|
|
173
|
+
head = "set" if typ.get("set") else "list"
|
|
174
|
+
return f"{head}[{_show(typ['elem'])}]"
|
|
175
|
+
if typ["kind"] == "dict":
|
|
176
|
+
return f"dict[{_show(typ['key'])}, {_show(typ['value'])}]"
|
|
177
|
+
if typ["kind"] == "class":
|
|
178
|
+
return str(typ.get("label", typ["name"]))
|
|
179
|
+
return str(typ["kind"])
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _plain_const_kind(node: ast.expr) -> str | None:
|
|
183
|
+
"""A str constant or an int constant. Bool is not an int here."""
|
|
184
|
+
if not isinstance(node, ast.Constant) or isinstance(node.value, bool):
|
|
185
|
+
return None
|
|
186
|
+
if isinstance(node.value, str):
|
|
187
|
+
return "str"
|
|
188
|
+
if isinstance(node.value, int):
|
|
189
|
+
return "int"
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _dotted(node: ast.Attribute) -> str:
|
|
194
|
+
parts: list[str] = [node.attr]
|
|
195
|
+
cur: ast.AST = node.value
|
|
196
|
+
while isinstance(cur, ast.Attribute):
|
|
197
|
+
parts.append(cur.attr)
|
|
198
|
+
cur = cur.value
|
|
199
|
+
if isinstance(cur, ast.Name):
|
|
200
|
+
parts.append(cur.id)
|
|
201
|
+
parts.reverse()
|
|
202
|
+
return ".".join(parts)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
_CMP = {
|
|
206
|
+
ast.Lt: "<",
|
|
207
|
+
ast.LtE: "<=",
|
|
208
|
+
ast.Gt: ">",
|
|
209
|
+
ast.GtE: ">=",
|
|
210
|
+
ast.Eq: "==",
|
|
211
|
+
ast.NotEq: "!=",
|
|
212
|
+
}
|
|
213
|
+
_SCALAR = {"int", "float", "str", "bytes"}
|
|
214
|
+
_TRUTHY = frozenset({"str", "bytes", "int", "list", "dict"})
|
|
215
|
+
_LIST_FORMS = frozenset({"List"})
|
|
216
|
+
_DICT_FORMS = frozenset({"Dict"})
|
|
217
|
+
_SET_FORMS = frozenset({"Set"})
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
_FUTURE_OK = frozenset(
|
|
221
|
+
{
|
|
222
|
+
"annotations",
|
|
223
|
+
"generator_stop",
|
|
224
|
+
"nested_scopes",
|
|
225
|
+
"generators",
|
|
226
|
+
"division",
|
|
227
|
+
"absolute_import",
|
|
228
|
+
"with_statement",
|
|
229
|
+
"print_function",
|
|
230
|
+
"unicode_literals",
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _package_base(module_name: str, is_package: bool, level: int) -> list[str] | None:
|
|
236
|
+
if level < 1 or not module_name:
|
|
237
|
+
return None
|
|
238
|
+
bits = module_name.split(".")
|
|
239
|
+
if not is_package:
|
|
240
|
+
bits = bits[:-1]
|
|
241
|
+
drop = level - 1
|
|
242
|
+
if drop > len(bits):
|
|
243
|
+
return None
|
|
244
|
+
return bits[: len(bits) - drop]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _tuple_assign(
|
|
248
|
+
dest: str, typ: dict[str, Any], elems: list[dict[str, Any]], span: dict[str, Any]
|
|
249
|
+
) -> list[dict[str, Any]]:
|
|
250
|
+
made = {
|
|
251
|
+
"kind": "call",
|
|
252
|
+
"callee": {"kind": "class", "name": typ["name"]},
|
|
253
|
+
"args": [],
|
|
254
|
+
"type": typ,
|
|
255
|
+
"span": span,
|
|
256
|
+
}
|
|
257
|
+
stmts = [_assign(dest, made, span)]
|
|
258
|
+
for index, elem in enumerate(elems):
|
|
259
|
+
stmts.append(
|
|
260
|
+
{
|
|
261
|
+
"kind": "setattr",
|
|
262
|
+
"object": _name(dest, typ, span),
|
|
263
|
+
"name": f"_{index}",
|
|
264
|
+
"value": elem,
|
|
265
|
+
"span": span,
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
return stmts
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _zero(typ: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
272
|
+
if typ["kind"] == "int":
|
|
273
|
+
return _const_int(0, span)
|
|
274
|
+
if typ["kind"] == "bool":
|
|
275
|
+
return _const_bool(False, span)
|
|
276
|
+
if typ["kind"] == "float":
|
|
277
|
+
return {"kind": "const", "value": "0.0", "type": typ, "span": span}
|
|
278
|
+
if typ["kind"] == "class":
|
|
279
|
+
# Fresh for-targets of a synthetic tuple class start as a zeroed
|
|
280
|
+
# instance (ps_alloc calloc); the loop step then rebinding every field.
|
|
281
|
+
return {
|
|
282
|
+
"kind": "call",
|
|
283
|
+
"callee": {"kind": "class", "name": typ["name"]},
|
|
284
|
+
"args": [],
|
|
285
|
+
"type": typ,
|
|
286
|
+
"span": span,
|
|
287
|
+
}
|
|
288
|
+
return {"kind": "const", "value": "", "type": typ, "span": span}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _is_none_expr(node: ast.expr) -> bool:
|
|
292
|
+
return isinstance(node, ast.Constant) and node.value is None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _declared_globals(body: list[ast.stmt]) -> set[str]:
|
|
296
|
+
names: set[str] = set()
|
|
297
|
+
for stmt in body:
|
|
298
|
+
for node in ast.walk(stmt):
|
|
299
|
+
if isinstance(node, ast.Global):
|
|
300
|
+
names.update(node.names)
|
|
301
|
+
return names
|