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/restlower.py
ADDED
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
"""Call lowering for the modules in `stdmore`.
|
|
2
|
+
|
|
3
|
+
The lowerer evaluates arguments and reports fences. This module decides which
|
|
4
|
+
calls are in the closed surface and which symbol implements them.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pyscriptc.stdmore import (
|
|
13
|
+
CALL_FENCES,
|
|
14
|
+
CLASS_METHODS,
|
|
15
|
+
CTYPES,
|
|
16
|
+
FOR_ONLY,
|
|
17
|
+
OPERATORS,
|
|
18
|
+
PATH_ATTRS,
|
|
19
|
+
SIZEOF,
|
|
20
|
+
WITH_ONLY,
|
|
21
|
+
fixed_spec,
|
|
22
|
+
slot_size,
|
|
23
|
+
type_alias,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_CMP = {"eq": ast.Eq, "ne": ast.NotEq, "lt": ast.Lt, "le": ast.LtE, "gt": ast.Gt, "ge": ast.GtE}
|
|
27
|
+
_BIN = {"add": ast.Add, "sub": ast.Sub, "mul": ast.Mult, "floordiv": ast.FloorDiv}
|
|
28
|
+
_OPEN = {"r": 1, "w": 2, "rb": 3, "wb": 4, "a": 5, "ab": 6}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def rest_call(
|
|
32
|
+
lo: Any,
|
|
33
|
+
node: ast.Call,
|
|
34
|
+
span: dict[str, Any],
|
|
35
|
+
module: str,
|
|
36
|
+
attr: str,
|
|
37
|
+
expect: dict[str, Any] | None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
if module == "sys" and attr == "exit":
|
|
40
|
+
return _exit(lo, node, span)
|
|
41
|
+
if module == "importlib.resources" and attr == "as_file":
|
|
42
|
+
return _as_file_value(lo, node, span)
|
|
43
|
+
message = CALL_FENCES.get((module, attr))
|
|
44
|
+
if message:
|
|
45
|
+
lo.fail(node, "PS2001", message)
|
|
46
|
+
if (module, attr) in FOR_ONLY:
|
|
47
|
+
lo.fail(node, "PS2002", f"{module}.{attr} is only lowered as the iterator of a for")
|
|
48
|
+
if (module, attr) in WITH_ONLY:
|
|
49
|
+
lo.fail(node, "PS2002", f"{module}.{attr} is only lowered as a with context")
|
|
50
|
+
if module == "ctypes" and attr in CTYPES:
|
|
51
|
+
lo.fail(node, "PS2001", f"ctypes.{attr} is not callable in this subset")
|
|
52
|
+
spec = fixed_spec(module, attr)
|
|
53
|
+
if spec is not None:
|
|
54
|
+
return lo._fixed_call(node, span, module, attr, spec)
|
|
55
|
+
if module == "datetime" and attr == "date":
|
|
56
|
+
return _ints(lo, node, span, "datetime", "date", "ps_date_ymd", "Date", 3, ["year", "month", "day"])
|
|
57
|
+
if module == "datetime" and attr == "datetime":
|
|
58
|
+
return _ints(
|
|
59
|
+
lo,
|
|
60
|
+
node,
|
|
61
|
+
span,
|
|
62
|
+
"datetime",
|
|
63
|
+
"datetime",
|
|
64
|
+
"ps_dt_new",
|
|
65
|
+
"DateTime",
|
|
66
|
+
3,
|
|
67
|
+
["year", "month", "day", "hour", "minute", "second", "microsecond"],
|
|
68
|
+
)
|
|
69
|
+
if module == "datetime" and attr == "time":
|
|
70
|
+
return _ints(
|
|
71
|
+
lo, node, span, "datetime", "time", "ps_tod_new", "Time", 0, ["hour", "minute", "second", "microsecond"]
|
|
72
|
+
)
|
|
73
|
+
if module == "datetime" and attr == "timedelta":
|
|
74
|
+
return _ints(
|
|
75
|
+
lo,
|
|
76
|
+
node,
|
|
77
|
+
span,
|
|
78
|
+
"datetime",
|
|
79
|
+
"timedelta",
|
|
80
|
+
"ps_delta_new",
|
|
81
|
+
"TimeDelta",
|
|
82
|
+
0,
|
|
83
|
+
["days", "seconds", "microseconds"],
|
|
84
|
+
)
|
|
85
|
+
if module == "collections" and attr == "OrderedDict":
|
|
86
|
+
return _ordered(lo, node, span, expect)
|
|
87
|
+
lo._reject_keywords(node)
|
|
88
|
+
if module == "operator" and attr in OPERATORS:
|
|
89
|
+
return _operator(lo, node, span, attr)
|
|
90
|
+
if module == "bisect" and attr in {"bisect_left", "bisect_right"}:
|
|
91
|
+
return _bisect(lo, node, span, attr)
|
|
92
|
+
if module == "time" and attr == "sleep":
|
|
93
|
+
return _sleep(lo, node, span)
|
|
94
|
+
if module == "time" and attr in {"gmtime", "localtime"}:
|
|
95
|
+
return _clock(lo, node, span, attr)
|
|
96
|
+
if module == "collections" and attr == "deque":
|
|
97
|
+
return _deque(lo, node, span, expect)
|
|
98
|
+
if module == "functools" and attr == "reduce":
|
|
99
|
+
return _reduce(lo, node, span)
|
|
100
|
+
if module == "posixpath" and attr == "join":
|
|
101
|
+
return _join(lo, node, span)
|
|
102
|
+
if module == "pathlib" and attr == "Path":
|
|
103
|
+
return _one(
|
|
104
|
+
lo, node, span, "pathlib", "Path", "ps_path_new", {"kind": "str"}, {"kind": "class", "name": "Path"}
|
|
105
|
+
)
|
|
106
|
+
if module == "io" and attr == "StringIO":
|
|
107
|
+
return _buffer(lo, node, span, "io", "StringIO", "ps_io_string", "str", "TextIO")
|
|
108
|
+
if module == "io" and attr == "BytesIO":
|
|
109
|
+
return _buffer(lo, node, span, "io", "BytesIO", "ps_io_bytes", "bytes", "BytesIO")
|
|
110
|
+
if module == "ctypes" and attr == "sizeof":
|
|
111
|
+
return _sizeof(lo, node, span)
|
|
112
|
+
if module == "hmac" and attr in {"digest", "new"}:
|
|
113
|
+
return _hmac(lo, node, span, attr)
|
|
114
|
+
if module == "hmac" and attr == "compare_digest":
|
|
115
|
+
return _hmac_compare(lo, node, span)
|
|
116
|
+
if module == "unicodedata" and attr == "name":
|
|
117
|
+
return _unicode_name(lo, node, span)
|
|
118
|
+
if module == "importlib.resources" and attr == "files":
|
|
119
|
+
return _resource_files(lo, node, span)
|
|
120
|
+
if module == "atexit" and attr == "register":
|
|
121
|
+
return _atexit_register(lo, node, span)
|
|
122
|
+
lo.fail(node, "PS2001", f"{module}.{attr} is not in the lowered subset of {module}")
|
|
123
|
+
raise AssertionError("unreachable")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def builtin_call(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any] | None:
|
|
127
|
+
assert isinstance(node.func, ast.Name)
|
|
128
|
+
name = node.func.id
|
|
129
|
+
if name == "open":
|
|
130
|
+
return _open(lo, node, span)
|
|
131
|
+
if name == "print":
|
|
132
|
+
return _print(lo, node, span)
|
|
133
|
+
if node.keywords and name in {
|
|
134
|
+
"isinstance",
|
|
135
|
+
"getattr",
|
|
136
|
+
"hasattr",
|
|
137
|
+
"abs",
|
|
138
|
+
"min",
|
|
139
|
+
"max",
|
|
140
|
+
"sum",
|
|
141
|
+
"ord",
|
|
142
|
+
"chr",
|
|
143
|
+
"round",
|
|
144
|
+
}:
|
|
145
|
+
lo._reject_keywords(node)
|
|
146
|
+
if name == "isinstance":
|
|
147
|
+
return _isinstance(lo, node, span)
|
|
148
|
+
if name == "getattr":
|
|
149
|
+
return _getattr(lo, node, span)
|
|
150
|
+
if name == "hasattr":
|
|
151
|
+
return _hasattr(lo, node, span)
|
|
152
|
+
if name == "abs":
|
|
153
|
+
return _abs(lo, node, span)
|
|
154
|
+
if name in {"min", "max"}:
|
|
155
|
+
return _minmax(lo, node, span, name)
|
|
156
|
+
if name == "sum":
|
|
157
|
+
return _sum(lo, node, span)
|
|
158
|
+
if name == "sorted":
|
|
159
|
+
return _sorted(lo, node, span)
|
|
160
|
+
if name == "ord":
|
|
161
|
+
return _ord(lo, node, span)
|
|
162
|
+
if name == "chr":
|
|
163
|
+
return _chr(lo, node, span)
|
|
164
|
+
if name == "round":
|
|
165
|
+
return _round(lo, node, span)
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _const_int(value: int, span: dict[str, Any]) -> dict[str, Any]:
|
|
170
|
+
return {"kind": "const", "value": str(value), "type": {"kind": "int"}, "span": span}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_PRINT_KINDS = {"str", "int", "float", "bool", "bytes"}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _print(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
177
|
+
"""`print` lowers to the builtin of the same name; the emitter writes stdout.
|
|
178
|
+
|
|
179
|
+
Subset: positional values of the compiler's scalar set (str, int, float,
|
|
180
|
+
bool, bytes), `sep=`/`end=` str keywords; `file=`/`flush=` stay fenced.
|
|
181
|
+
The keyword exprs ride on the call dict so the emitter can splice them.
|
|
182
|
+
"""
|
|
183
|
+
extras: dict[str, Any] = {}
|
|
184
|
+
for kw in node.keywords:
|
|
185
|
+
if kw.arg is None:
|
|
186
|
+
lo.fail(kw, "PS2002", "**kwargs are not lowered")
|
|
187
|
+
if kw.arg not in {"sep", "end"}:
|
|
188
|
+
lo.fail(
|
|
189
|
+
kw,
|
|
190
|
+
"PS2003",
|
|
191
|
+
f"print keyword argument {kw.arg} is not lowered; print supports sep= and end= only",
|
|
192
|
+
)
|
|
193
|
+
value = lo._expr(kw.value)
|
|
194
|
+
if value["type"]["kind"] != "str":
|
|
195
|
+
lo.fail(kw, "PS1003", f"print {kw.arg}= takes a str")
|
|
196
|
+
extras[kw.arg] = value
|
|
197
|
+
if any(isinstance(arg, ast.Starred) for arg in node.args):
|
|
198
|
+
lo.fail(node, "PS2002", "starred calls are not lowered")
|
|
199
|
+
args = [lo._expr(arg) for arg in node.args]
|
|
200
|
+
for arg in args:
|
|
201
|
+
if arg["type"]["kind"] not in _PRINT_KINDS:
|
|
202
|
+
lo.fail(
|
|
203
|
+
node,
|
|
204
|
+
"PS2003",
|
|
205
|
+
f"print is lowered for str, int, float, bool, and bytes values, not {arg['type']['kind']};"
|
|
206
|
+
" wrap it in str(...)",
|
|
207
|
+
)
|
|
208
|
+
expr = {
|
|
209
|
+
"kind": "call",
|
|
210
|
+
"callee": {"kind": "builtin", "name": "print"},
|
|
211
|
+
"args": args,
|
|
212
|
+
"type": {"kind": "none"},
|
|
213
|
+
"span": span,
|
|
214
|
+
}
|
|
215
|
+
expr.update(extras)
|
|
216
|
+
return expr
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _const_text(value: str, kind: str, span: dict[str, Any]) -> dict[str, Any]:
|
|
220
|
+
return {"kind": "const", "value": value, "type": {"kind": kind}, "span": span}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _class(name: str) -> dict[str, Any]:
|
|
224
|
+
return {"kind": "class", "name": name}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _ints(
|
|
228
|
+
lo: Any,
|
|
229
|
+
node: ast.Call,
|
|
230
|
+
span: dict[str, Any],
|
|
231
|
+
module: str,
|
|
232
|
+
attr: str,
|
|
233
|
+
symbol: str,
|
|
234
|
+
cls: str,
|
|
235
|
+
minimum: int,
|
|
236
|
+
names: list[str],
|
|
237
|
+
) -> dict[str, Any]:
|
|
238
|
+
params: list[dict[str, Any]] = []
|
|
239
|
+
for index, name in enumerate(names):
|
|
240
|
+
param: dict[str, Any] = {"name": name, "type": {"kind": "int"}}
|
|
241
|
+
if index >= minimum:
|
|
242
|
+
param["default"] = _const_int(0, span)
|
|
243
|
+
params.append(param)
|
|
244
|
+
args = lo._fill_args(node, params, f"{module}.{attr}")
|
|
245
|
+
return lo._abi_call(module, attr, args, _class(cls), symbol, span)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _bisect(lo: Any, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
249
|
+
lo._reject_keywords(node)
|
|
250
|
+
if len(node.args) != 2:
|
|
251
|
+
lo.fail(node, "PS2003", f"bisect.{attr} expects 2 argument(s)")
|
|
252
|
+
seq = lo._expr(node.args[0])
|
|
253
|
+
key = lo._expr(node.args[1])
|
|
254
|
+
if seq["type"]["kind"] != "list" or seq["type"]["elem"]["kind"] != "int":
|
|
255
|
+
lo.fail(node, "PS1003", f"bisect.{attr} takes a list[int]")
|
|
256
|
+
if key["type"]["kind"] != "int":
|
|
257
|
+
lo.fail(node, "PS1003", f"bisect.{attr} key is int")
|
|
258
|
+
symbol = "ps_bisect_left" if attr == "bisect_left" else "ps_bisect_right"
|
|
259
|
+
return lo._abi_call("bisect", attr, [seq, key], {"kind": "int"}, symbol, span)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _as_float(lo: Any, node: ast.AST, arg: dict[str, Any], span: dict[str, Any], what: str) -> dict[str, Any]:
|
|
263
|
+
if arg["type"]["kind"] == "int":
|
|
264
|
+
return {"kind": "cast", "op": "int_to_float", "value": arg, "type": {"kind": "float"}, "span": span}
|
|
265
|
+
if arg["type"]["kind"] != "float":
|
|
266
|
+
lo.fail(node, "PS1003", f"{what} argument is {arg['type']['kind']}, expected float")
|
|
267
|
+
return arg
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _exit(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
271
|
+
if len(node.args) != 1:
|
|
272
|
+
lo.fail(node, "PS2003", "sys.exit expects 1 argument(s)")
|
|
273
|
+
arg = lo._expr(node.args[0])
|
|
274
|
+
if arg["type"]["kind"] != "int":
|
|
275
|
+
lo.fail(node, "PS1003", "sys.exit argument is an int")
|
|
276
|
+
return lo._abi_call("sys", "exit", [arg], {"kind": "none"}, "exit", span)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _sleep(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
280
|
+
if len(node.args) != 1:
|
|
281
|
+
lo.fail(node, "PS2003", "time.sleep expects 1 argument(s)")
|
|
282
|
+
arg = _as_float(lo, node, lo._expr(node.args[0]), span, "time.sleep")
|
|
283
|
+
return lo._abi_call("time", "sleep", [arg], {"kind": "none"}, "ps_time_sleep", span)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _clock(lo: Any, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
287
|
+
if len(node.args) > 1:
|
|
288
|
+
lo.fail(node, "PS2003", f"time.{attr} expects 0 or 1 argument(s)")
|
|
289
|
+
if node.args:
|
|
290
|
+
arg = _as_float(lo, node, lo._expr(node.args[0]), span, f"time.{attr}")
|
|
291
|
+
else:
|
|
292
|
+
arg = lo._abi_call("time", "time", [], {"kind": "float"}, "ps_time_time", span)
|
|
293
|
+
symbol = "ps_time_gm" if attr == "gmtime" else "ps_time_local"
|
|
294
|
+
return lo._abi_call("time", attr, [arg], _class("StructTime"), symbol, span)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _deque(lo: Any, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
298
|
+
if len(node.args) > 1:
|
|
299
|
+
lo.fail(node, "PS2003", "deque expects 0 or 1 argument(s)")
|
|
300
|
+
if not node.args:
|
|
301
|
+
if expect is None or expect.get("name") != "Deque" or "elem" not in expect:
|
|
302
|
+
lo.fail(node, "PS1001", "deque() needs a deque annotation")
|
|
303
|
+
elem = expect["elem"]
|
|
304
|
+
if elem["kind"] == "bool":
|
|
305
|
+
lo.fail(node, "PS2002", "list[bool] is not lowered")
|
|
306
|
+
size = _const_int(slot_size(elem), span)
|
|
307
|
+
return lo._abi_call("collections", "deque", [size], expect, "ps_deque_empty", span)
|
|
308
|
+
arg = lo._expr(node.args[0])
|
|
309
|
+
if arg["type"]["kind"] != "list":
|
|
310
|
+
lo.fail(node, "PS1003", "deque() takes a list")
|
|
311
|
+
elem = arg["type"]["elem"]
|
|
312
|
+
if elem["kind"] == "bool":
|
|
313
|
+
lo.fail(node, "PS2002", "list[bool] is not lowered")
|
|
314
|
+
typ = {"kind": "class", "name": "Deque", "elem": elem}
|
|
315
|
+
if expect is not None and expect.get("name") == "Deque":
|
|
316
|
+
lo._expect(node, typ, expect, "deque")
|
|
317
|
+
size = _const_int(slot_size(elem), span)
|
|
318
|
+
return lo._abi_call("collections", "deque", [size, arg], typ, "ps_deque_from", span)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _ordered(lo: Any, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
322
|
+
"""`OrderedDict()` with no arguments is the empty dict the compiler already builds.
|
|
323
|
+
|
|
324
|
+
A positional iterable and keyword arguments stay fenced. The `ordered` mark
|
|
325
|
+
is not part of the type key, so an annotated `dict` assignment still matches.
|
|
326
|
+
"""
|
|
327
|
+
if node.args:
|
|
328
|
+
lo.fail(node, "PS2001", "an OrderedDict iterable is not lowered")
|
|
329
|
+
if node.keywords:
|
|
330
|
+
lo.fail(node, "PS2001", "OrderedDict keyword arguments are not lowered")
|
|
331
|
+
if expect is not None and expect["kind"] != "dict":
|
|
332
|
+
lo.fail(node, "PS1001", "OrderedDict needs a dict annotation")
|
|
333
|
+
base = expect if expect is not None else {"kind": "dict", "key": {"kind": "str"}, "value": {"kind": "int"}}
|
|
334
|
+
return {"kind": "dict", "pairs": [], "type": {**base, "ordered": True}, "span": span}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _reduce(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
338
|
+
"""`functools.reduce(operator.add, xs)` is a loop of the existing int `+`.
|
|
339
|
+
|
|
340
|
+
A direct function name stays the existing call. `operator.add` has no
|
|
341
|
+
function value, so that spelling stores the list once and adds each later
|
|
342
|
+
element. An initializer on `operator.add` stays fenced. An empty list
|
|
343
|
+
raises ValueError; this subset has no TypeError.
|
|
344
|
+
"""
|
|
345
|
+
lo._reject_keywords(node)
|
|
346
|
+
if len(node.args) not in {2, 3}:
|
|
347
|
+
lo.fail(node, "PS2003", "functools.reduce expects 2 or 3 argument(s)")
|
|
348
|
+
fn = node.args[0]
|
|
349
|
+
if _is_operator_add(lo, fn):
|
|
350
|
+
if len(node.args) == 3:
|
|
351
|
+
lo.fail(node, "PS2002", "functools.reduce initializer is not lowered")
|
|
352
|
+
return _reduce_add(lo, node, span)
|
|
353
|
+
if not isinstance(fn, ast.Name) or fn.id not in lo.sigs:
|
|
354
|
+
lo.fail(node, "PS2001", "functools.reduce takes a direct function name")
|
|
355
|
+
params, ret = lo.sigs[fn.id]
|
|
356
|
+
if len(params) != 2 or not _same(params[0]["type"], params[1]["type"]) or not _same(params[0]["type"], ret):
|
|
357
|
+
lo.fail(node, "PS2001", "functools.reduce function is (T, T) -> T")
|
|
358
|
+
seq = lo._expr(node.args[1])
|
|
359
|
+
if seq["type"]["kind"] != "list" or not _same(seq["type"]["elem"], ret):
|
|
360
|
+
lo.fail(node, "PS1003", "functools.reduce takes a list of the function's type")
|
|
361
|
+
args = [seq]
|
|
362
|
+
if len(node.args) == 3:
|
|
363
|
+
init = lo._expr(node.args[2], ret)
|
|
364
|
+
lo._expect(node, init["type"], ret, "functools.reduce initializer")
|
|
365
|
+
args.append(init)
|
|
366
|
+
expr = lo._abi_call("functools", "reduce", args, ret, "", span)
|
|
367
|
+
expr.pop("symbol", None)
|
|
368
|
+
expr["fn"] = fn.id
|
|
369
|
+
return expr
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _reduce_add(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
373
|
+
seq = lo._expr(node.args[1])
|
|
374
|
+
if seq["type"]["kind"] != "list" or seq["type"].get("set") or seq["type"]["elem"]["kind"] != "int":
|
|
375
|
+
lo.fail(node, "PS1003", "functools.reduce takes a list of int")
|
|
376
|
+
int_t = {"kind": "int"}
|
|
377
|
+
list_t = seq["type"]
|
|
378
|
+
src = lo._temp("reds")
|
|
379
|
+
acc = lo._temp("reda")
|
|
380
|
+
idx = lo._temp("redi")
|
|
381
|
+
length = lo._temp("redn")
|
|
382
|
+
lo.locals[src] = list_t
|
|
383
|
+
lo.locals[acc] = int_t
|
|
384
|
+
lo.locals[idx] = int_t
|
|
385
|
+
lo.locals[length] = int_t
|
|
386
|
+
src_n = _nm(src, list_t, span)
|
|
387
|
+
acc_n = _nm(acc, int_t, span)
|
|
388
|
+
idx_n = _nm(idx, int_t, span)
|
|
389
|
+
length_n = _nm(length, int_t, span)
|
|
390
|
+
item = {
|
|
391
|
+
"kind": "subscript",
|
|
392
|
+
"value": src_n,
|
|
393
|
+
"index": idx_n,
|
|
394
|
+
"type": int_t,
|
|
395
|
+
"span": span,
|
|
396
|
+
}
|
|
397
|
+
lo.prelude.extend(
|
|
398
|
+
[
|
|
399
|
+
_asg(src, seq, span),
|
|
400
|
+
_asg(
|
|
401
|
+
length,
|
|
402
|
+
{
|
|
403
|
+
"kind": "call",
|
|
404
|
+
"callee": {"kind": "builtin", "name": "len"},
|
|
405
|
+
"args": [src_n],
|
|
406
|
+
"type": int_t,
|
|
407
|
+
"span": span,
|
|
408
|
+
},
|
|
409
|
+
span,
|
|
410
|
+
),
|
|
411
|
+
{
|
|
412
|
+
"kind": "if",
|
|
413
|
+
"cond": _cmp("==", length_n, _int(0, span), span),
|
|
414
|
+
"then": [{"kind": "raise", "exc": "ValueError", "message": None, "span": span}],
|
|
415
|
+
"else": [],
|
|
416
|
+
"span": span,
|
|
417
|
+
},
|
|
418
|
+
_asg(
|
|
419
|
+
acc,
|
|
420
|
+
{"kind": "subscript", "value": src_n, "index": _int(0, span), "type": int_t, "span": span},
|
|
421
|
+
span,
|
|
422
|
+
),
|
|
423
|
+
_asg(idx, _int(1, span), span),
|
|
424
|
+
{
|
|
425
|
+
"kind": "while",
|
|
426
|
+
"cond": _cmp("<", idx_n, length_n, span),
|
|
427
|
+
"body": [
|
|
428
|
+
_asg(
|
|
429
|
+
acc,
|
|
430
|
+
{
|
|
431
|
+
"kind": "binary",
|
|
432
|
+
"op": "+",
|
|
433
|
+
"left": acc_n,
|
|
434
|
+
"right": item,
|
|
435
|
+
"type": int_t,
|
|
436
|
+
"span": span,
|
|
437
|
+
},
|
|
438
|
+
span,
|
|
439
|
+
),
|
|
440
|
+
_asg(
|
|
441
|
+
idx,
|
|
442
|
+
{
|
|
443
|
+
"kind": "binary",
|
|
444
|
+
"op": "+",
|
|
445
|
+
"left": idx_n,
|
|
446
|
+
"right": _int(1, span),
|
|
447
|
+
"type": int_t,
|
|
448
|
+
"span": span,
|
|
449
|
+
},
|
|
450
|
+
span,
|
|
451
|
+
),
|
|
452
|
+
],
|
|
453
|
+
"span": span,
|
|
454
|
+
},
|
|
455
|
+
]
|
|
456
|
+
)
|
|
457
|
+
return _nm(acc, int_t, span)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _is_operator_add(lo: Any, node: ast.expr) -> bool:
|
|
461
|
+
if isinstance(node, ast.Attribute):
|
|
462
|
+
resolved = lo._resolve_module(node)
|
|
463
|
+
return resolved is not None and resolved[1] == "operator" and resolved[2] == "add"
|
|
464
|
+
if isinstance(node, ast.Name):
|
|
465
|
+
binding = lo.bindings.get(node.id)
|
|
466
|
+
return binding is not None and binding.module == "operator" and binding.attr == "add"
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _nm(name: str, typ: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
471
|
+
return {"kind": "name", "id": name, "type": typ, "span": span}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _int(value: int, span: dict[str, Any]) -> dict[str, Any]:
|
|
475
|
+
return {"kind": "const", "value": str(value), "type": {"kind": "int"}, "span": span}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _asg(target: str, value: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
479
|
+
return {"kind": "assign", "target": target, "value": value, "span": span}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _cmp(op: str, left: dict[str, Any], right: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
483
|
+
return {"kind": "compare", "op": op, "left": left, "right": right, "type": {"kind": "bool"}, "span": span}
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _join(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
487
|
+
if not 1 <= len(node.args) <= 8:
|
|
488
|
+
lo.fail(node, "PS2003", "posixpath.join expects 1 to 8 argument(s)")
|
|
489
|
+
args = []
|
|
490
|
+
for arg_node in node.args:
|
|
491
|
+
arg = lo._expr(arg_node)
|
|
492
|
+
if arg["type"]["kind"] != "str":
|
|
493
|
+
lo.fail(node, "PS1003", "posixpath.join arguments are str")
|
|
494
|
+
args.append(arg)
|
|
495
|
+
return lo._abi_call("posixpath", "join", args, {"kind": "str"}, "ps_path_join", span)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _one(
|
|
499
|
+
lo: Any,
|
|
500
|
+
node: ast.Call,
|
|
501
|
+
span: dict[str, Any],
|
|
502
|
+
module: str,
|
|
503
|
+
attr: str,
|
|
504
|
+
symbol: str,
|
|
505
|
+
want: dict[str, Any],
|
|
506
|
+
ret: dict[str, Any],
|
|
507
|
+
) -> dict[str, Any]:
|
|
508
|
+
if len(node.args) != 1:
|
|
509
|
+
lo.fail(node, "PS2003", f"{module}.{attr} expects 1 argument(s)")
|
|
510
|
+
arg = lo._expr(node.args[0])
|
|
511
|
+
lo._expect(node, arg["type"], want, f"{module}.{attr}")
|
|
512
|
+
return lo._abi_call(module, attr, [arg], ret, symbol, span)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _buffer(
|
|
516
|
+
lo: Any, node: ast.Call, span: dict[str, Any], module: str, attr: str, symbol: str, kind: str, cls: str
|
|
517
|
+
) -> dict[str, Any]:
|
|
518
|
+
if len(node.args) > 1:
|
|
519
|
+
lo.fail(node, "PS2003", f"{module}.{attr} expects 0 or 1 argument(s)")
|
|
520
|
+
if node.args:
|
|
521
|
+
arg = lo._expr(node.args[0])
|
|
522
|
+
if arg["type"]["kind"] != kind:
|
|
523
|
+
lo.fail(node, "PS1003", f"{module}.{attr} argument is {kind}")
|
|
524
|
+
else:
|
|
525
|
+
arg = _const_text("", kind, span)
|
|
526
|
+
return lo._abi_call(module, attr, [arg], _class(cls), symbol, span)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _sizeof(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
530
|
+
if len(node.args) != 1:
|
|
531
|
+
lo.fail(node, "PS2003", "ctypes.sizeof expects 1 argument(s)")
|
|
532
|
+
name = _ctype(lo, node.args[0])
|
|
533
|
+
if name is None:
|
|
534
|
+
lo.fail(node, "PS2001", "ctypes.sizeof argument is not a lowered ctype")
|
|
535
|
+
return {"kind": "const", "value": str(SIZEOF[name]), "type": {"kind": "int"}, "span": span}
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _ctype(lo: Any, node: ast.expr) -> str | None:
|
|
539
|
+
if isinstance(node, ast.Attribute):
|
|
540
|
+
resolved = lo._resolve_module(node)
|
|
541
|
+
if resolved is not None and resolved[1] == "ctypes" and resolved[2] in SIZEOF:
|
|
542
|
+
return str(resolved[2])
|
|
543
|
+
if isinstance(node, ast.Name):
|
|
544
|
+
binding = lo.bindings.get(node.id)
|
|
545
|
+
if binding is not None and binding.module == "ctypes" and binding.attr in SIZEOF:
|
|
546
|
+
return str(binding.attr)
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _operator(lo: Any, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
551
|
+
if attr == "neg":
|
|
552
|
+
if len(node.args) != 1:
|
|
553
|
+
lo.fail(node, "PS2003", "operator.neg expects 1 argument(s)")
|
|
554
|
+
fake = ast.UnaryOp(op=ast.USub(), operand=node.args[0])
|
|
555
|
+
ast.copy_location(fake, node)
|
|
556
|
+
return lo._unary(fake, span)
|
|
557
|
+
if attr in _CMP:
|
|
558
|
+
if len(node.args) != 2:
|
|
559
|
+
lo.fail(node, "PS2003", f"operator.{attr} expects 2 argument(s)")
|
|
560
|
+
fake = ast.Compare(left=node.args[0], ops=[_CMP[attr]()], comparators=[node.args[1]])
|
|
561
|
+
ast.copy_location(fake, node)
|
|
562
|
+
return lo._compare(fake, span)
|
|
563
|
+
if attr in _BIN:
|
|
564
|
+
if len(node.args) != 2:
|
|
565
|
+
lo.fail(node, "PS2003", f"operator.{attr} expects 2 argument(s)")
|
|
566
|
+
fake = ast.BinOp(left=node.args[0], op=_BIN[attr](), right=node.args[1])
|
|
567
|
+
ast.copy_location(fake, node)
|
|
568
|
+
return lo._binary(fake, span)
|
|
569
|
+
if attr == "concat":
|
|
570
|
+
if len(node.args) != 2:
|
|
571
|
+
lo.fail(node, "PS2003", "operator.concat expects 2 argument(s)")
|
|
572
|
+
left = lo._expr(node.args[0])
|
|
573
|
+
right = lo._expr(node.args[1])
|
|
574
|
+
if left["type"]["kind"] == right["type"]["kind"] and left["type"]["kind"] in {"str", "bytes"}:
|
|
575
|
+
return {
|
|
576
|
+
"kind": "binary",
|
|
577
|
+
"op": "+",
|
|
578
|
+
"left": left,
|
|
579
|
+
"right": right,
|
|
580
|
+
"type": left["type"],
|
|
581
|
+
"span": span,
|
|
582
|
+
}
|
|
583
|
+
lo.fail(node, "PS2004", "operator.concat takes str or bytes")
|
|
584
|
+
lo.fail(node, "PS2001", f"operator.{attr} is not in the lowered subset of operator")
|
|
585
|
+
raise AssertionError("unreachable")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _open(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
589
|
+
slots = lo._place_nodes(node, ["file", "mode"])
|
|
590
|
+
if slots[0] is None:
|
|
591
|
+
lo.fail(node, "PS2003", "open expects a path and an optional mode literal")
|
|
592
|
+
path = lo._expr(slots[0])
|
|
593
|
+
if path["type"]["kind"] != "str":
|
|
594
|
+
lo.fail(node, "PS1003", "open path is str")
|
|
595
|
+
mode = "r"
|
|
596
|
+
if slots[1] is not None:
|
|
597
|
+
mode_node = slots[1]
|
|
598
|
+
if not isinstance(mode_node, ast.Constant) or not isinstance(mode_node.value, str):
|
|
599
|
+
lo.fail(node, "PS2001", "open mode must be a string literal")
|
|
600
|
+
mode = mode_node.value
|
|
601
|
+
if mode not in _OPEN:
|
|
602
|
+
lo.fail(node, "PS2001", "open mode is not lowered")
|
|
603
|
+
cls = "BytesIO" if "b" in mode else "TextIO"
|
|
604
|
+
code = _const_int(_OPEN[mode], span)
|
|
605
|
+
return lo._abi_call("io", "open", [path, code], _class(cls), "ps_io_open", span)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _isinstance(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
609
|
+
if len(node.args) != 2:
|
|
610
|
+
lo.fail(node, "PS2003", "isinstance expects 2 argument(s)")
|
|
611
|
+
obj = lo._expr(node.args[0])
|
|
612
|
+
want = lo._isinstance_type(node.args[1])
|
|
613
|
+
flag = lo._isinstance_holds(obj["type"], want)
|
|
614
|
+
return {"kind": "const", "value": flag, "type": {"kind": "bool"}, "span": span}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _getattr(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
618
|
+
if len(node.args) != 2:
|
|
619
|
+
lo.fail(node, "PS2003", "getattr expects 2 argument(s)")
|
|
620
|
+
obj = lo._expr(node.args[0])
|
|
621
|
+
name = _literal_name(lo, node)
|
|
622
|
+
return lo._lookup_attr(obj, name, span, node)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _hasattr(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
626
|
+
if len(node.args) != 2:
|
|
627
|
+
lo.fail(node, "PS2003", "hasattr expects 2 argument(s)")
|
|
628
|
+
obj = lo._expr(node.args[0])
|
|
629
|
+
name = _literal_name(lo, node)
|
|
630
|
+
return {"kind": "const", "value": lo._has_attr(obj, name), "type": {"kind": "bool"}, "span": span}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _literal_name(lo: Any, node: ast.Call) -> str:
|
|
634
|
+
arg = node.args[1]
|
|
635
|
+
if isinstance(arg, ast.Constant) and isinstance(arg.value, str) and arg.value.isidentifier():
|
|
636
|
+
if any(ord(ch) > 127 for ch in arg.value):
|
|
637
|
+
lo.fail(node, "PS2005", "non-ASCII string literals are not lowered")
|
|
638
|
+
return arg.value
|
|
639
|
+
lo.fail(node, "PS2001", "the attribute name is a string literal")
|
|
640
|
+
raise AssertionError("unreachable")
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _abs(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
644
|
+
if len(node.args) != 1:
|
|
645
|
+
lo.fail(node, "PS2003", "abs expects 1 argument(s)")
|
|
646
|
+
arg = lo._expr(node.args[0])
|
|
647
|
+
kind = arg["type"]["kind"]
|
|
648
|
+
if kind == "int":
|
|
649
|
+
return lo._abi_call("builtins", "abs", [arg], {"kind": "int"}, "ps_abs_i64", span)
|
|
650
|
+
if kind == "float":
|
|
651
|
+
return lo._abi_call("builtins", "abs", [arg], {"kind": "float"}, "ps_abs_f64", span)
|
|
652
|
+
lo.fail(node, "PS2003", "abs is lowered for int and float")
|
|
653
|
+
raise AssertionError("unreachable")
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _minmax(lo: Any, node: ast.Call, span: dict[str, Any], name: str) -> dict[str, Any]:
|
|
657
|
+
if len(node.args) != 2:
|
|
658
|
+
lo.fail(node, "PS2003", f"{name} expects 2 argument(s)")
|
|
659
|
+
left = lo._expr(node.args[0])
|
|
660
|
+
right = lo._expr(node.args[1])
|
|
661
|
+
if left["type"]["kind"] != right["type"]["kind"] or left["type"]["kind"] not in {"int", "float", "str", "bytes"}:
|
|
662
|
+
lo.fail(node, "PS2003", f"{name} takes two values of the same scalar type")
|
|
663
|
+
return {
|
|
664
|
+
"kind": "call",
|
|
665
|
+
"callee": {"kind": "builtin", "name": name},
|
|
666
|
+
"args": [left, right],
|
|
667
|
+
"type": left["type"],
|
|
668
|
+
"span": span,
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _sum(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
673
|
+
if len(node.args) != 1:
|
|
674
|
+
lo.fail(node, "PS2003", "sum expects 1 argument(s)")
|
|
675
|
+
arg = lo._expr(node.args[0])
|
|
676
|
+
if arg["type"]["kind"] != "list" or arg["type"]["elem"]["kind"] not in {"int", "float"}:
|
|
677
|
+
lo.fail(node, "PS2003", "sum takes a list of int or float")
|
|
678
|
+
kind = arg["type"]["elem"]["kind"]
|
|
679
|
+
symbol = "ps_sum_i64" if kind == "int" else "ps_sum_f64"
|
|
680
|
+
return lo._abi_call("builtins", "sum", [arg], {"kind": kind}, symbol, span)
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
_SORT_ELEM = {"int", "float", "str", "bytes"}
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _sorted(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
687
|
+
if len(node.args) != 1:
|
|
688
|
+
lo.fail(node, "PS2003", "sorted expects 1 argument(s)")
|
|
689
|
+
if node.keywords:
|
|
690
|
+
if any(kw.arg is None for kw in node.keywords):
|
|
691
|
+
lo.fail(node, "PS2002", "**kwargs are not lowered")
|
|
692
|
+
if len(node.keywords) == 1 and node.keywords[0].arg == "key":
|
|
693
|
+
lam = node.keywords[0].value
|
|
694
|
+
if not isinstance(lam, ast.Lambda):
|
|
695
|
+
lo.fail(lam, "PS2003", "sorted key= takes an inline lambda")
|
|
696
|
+
args = lam.args
|
|
697
|
+
params = args.posonlyargs + args.args
|
|
698
|
+
variadic = args.vararg or args.kwarg or args.kwonlyargs or args.defaults or any(args.kw_defaults)
|
|
699
|
+
if len(params) != 1 or variadic:
|
|
700
|
+
lo.fail(lam, "PS2003", "a sorted key= lambda takes one positional parameter")
|
|
701
|
+
for sub in ast.walk(lam.body):
|
|
702
|
+
if isinstance(sub, (ast.Lambda, ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
|
|
703
|
+
lo.fail(lam, "PS2003", "a sorted key= lambda is a plain expression")
|
|
704
|
+
|
|
705
|
+
arg = lo._expr(node.args[0])
|
|
706
|
+
kind = arg["type"]["kind"]
|
|
707
|
+
if kind == "dict":
|
|
708
|
+
elem = arg["type"]["key"]
|
|
709
|
+
elems_expr = "{s}.keys()"
|
|
710
|
+
elif kind == "list":
|
|
711
|
+
elem = arg["type"]["elem"]
|
|
712
|
+
elems_expr = "{s}"
|
|
713
|
+
else:
|
|
714
|
+
lo.fail(node.args[0], "PS2003", "sorted key= takes a list or dict")
|
|
715
|
+
raise AssertionError("unreachable")
|
|
716
|
+
if elem["kind"] not in _SORT_ELEM:
|
|
717
|
+
lo.fail(node.args[0], "PS2003", "sorted key= iterates int, float, str, or bytes elements")
|
|
718
|
+
|
|
719
|
+
w = params[0].arg
|
|
720
|
+
fields = lam.body.elts if isinstance(lam.body, ast.Tuple) else [lam.body]
|
|
721
|
+
|
|
722
|
+
saved_w = lo.locals.get(w)
|
|
723
|
+
lo.locals[w] = elem
|
|
724
|
+
probes = [lo._expr(field) for field in fields]
|
|
725
|
+
if saved_w is None:
|
|
726
|
+
del lo.locals[w]
|
|
727
|
+
else:
|
|
728
|
+
lo.locals[w] = saved_w
|
|
729
|
+
if any(p["type"]["kind"] not in _SORT_ELEM for p in probes):
|
|
730
|
+
lo.fail(lam, "PS2003", "a sorted key= lambda returns scalars or a tuple of scalars")
|
|
731
|
+
|
|
732
|
+
def temp(hint: str) -> str:
|
|
733
|
+
return lo._temp(hint).replace(".", "_")
|
|
734
|
+
|
|
735
|
+
s = temp("seq")
|
|
736
|
+
e = temp("elems")
|
|
737
|
+
keys = [temp(f"k{i}") for i in range(len(fields))]
|
|
738
|
+
n = temp("n")
|
|
739
|
+
idx = temp("idx")
|
|
740
|
+
i = temp("i")
|
|
741
|
+
j = temp("j")
|
|
742
|
+
v = temp("v")
|
|
743
|
+
out = temp("out")
|
|
744
|
+
|
|
745
|
+
lo.locals[s] = arg["type"]
|
|
746
|
+
lo.prelude.append({"kind": "assign", "target": s, "value": arg, "span": span})
|
|
747
|
+
lo.locals[e] = {"kind": "list", "elem": elem}
|
|
748
|
+
for p, k in zip(probes, keys, strict=True):
|
|
749
|
+
lo.locals[k] = {"kind": "list", "elem": p["type"]}
|
|
750
|
+
for t in (n, i, j, v):
|
|
751
|
+
lo.locals[t] = {"kind": "int"}
|
|
752
|
+
lo.locals[idx] = {"kind": "list", "elem": {"kind": "int"}}
|
|
753
|
+
lo.locals[out] = {"kind": "list", "elem": elem}
|
|
754
|
+
|
|
755
|
+
def less(field: int) -> str:
|
|
756
|
+
jb = f"{idx}[{j} - 1]"
|
|
757
|
+
lt = f"{keys[field]}[{v}] < {keys[field]}[{jb}]"
|
|
758
|
+
if field == len(keys) - 1:
|
|
759
|
+
return lt
|
|
760
|
+
return f"{lt} or ({keys[field]}[{v}] == {keys[field]}[{jb}] and ({less(field + 1)}))"
|
|
761
|
+
|
|
762
|
+
# The block reuses the lambda's own parameter name as the loop
|
|
763
|
+
# variable; a pre-existing local is pinned and restored after.
|
|
764
|
+
lines = [f"{e} = {elems_expr.format(s=s)}"]
|
|
765
|
+
if saved_w is not None:
|
|
766
|
+
save_w = temp("save")
|
|
767
|
+
lo.locals[save_w] = elem
|
|
768
|
+
lines.append(f"{save_w} = {w}")
|
|
769
|
+
for k in keys:
|
|
770
|
+
lines.append(f"{k} = []")
|
|
771
|
+
lines.append(f"for {w} in {e}:")
|
|
772
|
+
for pos, k in enumerate(keys):
|
|
773
|
+
lines.append(f" {k}.append({ast.unparse(fields[pos])})")
|
|
774
|
+
if saved_w is not None:
|
|
775
|
+
lines.append(f"{w} = {save_w}")
|
|
776
|
+
lines += [
|
|
777
|
+
f"{n} = len({keys[0]})",
|
|
778
|
+
f"{idx} = []",
|
|
779
|
+
f"{i} = 0",
|
|
780
|
+
f"while {i} < {n}:",
|
|
781
|
+
f" {idx}.append({i})",
|
|
782
|
+
f" {i} = {i} + 1",
|
|
783
|
+
f"{i} = 1",
|
|
784
|
+
f"while {i} < {n}:",
|
|
785
|
+
f" {v} = {idx}[{i}]",
|
|
786
|
+
f" {j} = {i}",
|
|
787
|
+
f" while {j} > 0 and ({less(0)}):",
|
|
788
|
+
f" {idx}[{j}] = {idx}[{j} - 1]",
|
|
789
|
+
f" {j} = {j} - 1",
|
|
790
|
+
f" {idx}[{j}] = {v}",
|
|
791
|
+
f" {i} = {i} + 1",
|
|
792
|
+
f"{out} = []",
|
|
793
|
+
f"{i} = 0",
|
|
794
|
+
f"while {i} < {n}:",
|
|
795
|
+
f" {out}.append({e}[{idx}[{i}]])",
|
|
796
|
+
f" {i} = {i} + 1",
|
|
797
|
+
]
|
|
798
|
+
for stmt in ast.parse("\n".join(lines)).body:
|
|
799
|
+
for sub in ast.walk(stmt):
|
|
800
|
+
sub.lineno, sub.col_offset = node.lineno, node.col_offset
|
|
801
|
+
sub.end_lineno, sub.end_col_offset = node.end_lineno, node.end_col_offset
|
|
802
|
+
lo.prelude.extend(lo._body([stmt]))
|
|
803
|
+
return {"kind": "name", "id": out, "type": {"kind": "list", "elem": elem}, "span": span}
|
|
804
|
+
lo._reject_keywords(node)
|
|
805
|
+
arg = lo._expr(node.args[0])
|
|
806
|
+
if arg["type"]["kind"] != "list" or arg["type"]["elem"]["kind"] not in _SORT_ELEM:
|
|
807
|
+
lo.fail(node, "PS2003", "sorted takes a list of int, float, str, or bytes")
|
|
808
|
+
folded = _const_sorted(arg, span)
|
|
809
|
+
if folded is not None:
|
|
810
|
+
return folded
|
|
811
|
+
kind = arg["type"]["elem"]["kind"]
|
|
812
|
+
symbol = {"int": "ps_sorted_i64", "float": "ps_sorted_f64", "str": "ps_sorted_buf", "bytes": "ps_sorted_buf"}[kind]
|
|
813
|
+
return lo._abi_call("builtins", "sorted", [arg], arg["type"], symbol, span)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _const_sorted(arg: dict[str, Any], span: dict[str, Any]) -> dict[str, Any] | None:
|
|
817
|
+
"""sorted() of a constant list is that list. Int constants sort as integers, not their digits."""
|
|
818
|
+
|
|
819
|
+
if arg.get("kind") != "list":
|
|
820
|
+
return None
|
|
821
|
+
elems = arg["elems"]
|
|
822
|
+
if any(item.get("kind") != "const" for item in elems):
|
|
823
|
+
return None
|
|
824
|
+
kind = arg["type"]["elem"]["kind"]
|
|
825
|
+
if kind == "int":
|
|
826
|
+
ordered = sorted(elems, key=lambda item: int(item["value"]))
|
|
827
|
+
elif kind in {"str", "bytes"}:
|
|
828
|
+
ordered = sorted(elems, key=lambda item: item["value"])
|
|
829
|
+
else:
|
|
830
|
+
return None
|
|
831
|
+
return {"kind": "list", "elems": ordered, "type": arg["type"], "span": span}
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _ord(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
835
|
+
if len(node.args) != 1:
|
|
836
|
+
lo.fail(node, "PS2003", "ord expects 1 argument(s)")
|
|
837
|
+
arg = lo._expr(node.args[0])
|
|
838
|
+
if arg["type"]["kind"] != "str":
|
|
839
|
+
lo.fail(node, "PS1003", "ord argument is str")
|
|
840
|
+
return lo._abi_call("builtins", "ord", [arg], {"kind": "int"}, "ps_ord", span)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def _chr(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
844
|
+
if len(node.args) != 1:
|
|
845
|
+
lo.fail(node, "PS2003", "chr expects 1 argument(s)")
|
|
846
|
+
arg = lo._expr(node.args[0])
|
|
847
|
+
if arg["type"]["kind"] != "int":
|
|
848
|
+
lo.fail(node, "PS1003", "chr argument is int")
|
|
849
|
+
return lo._abi_call("builtins", "chr", [arg], {"kind": "str"}, "ps_chr", span)
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def _round(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
853
|
+
if len(node.args) not in {1, 2}:
|
|
854
|
+
lo.fail(node, "PS2003", "round expects 1 or 2 argument(s)")
|
|
855
|
+
arg = lo._expr(node.args[0])
|
|
856
|
+
if len(node.args) == 1 and arg["type"]["kind"] == "int":
|
|
857
|
+
return arg
|
|
858
|
+
if arg["type"]["kind"] != "float":
|
|
859
|
+
lo.fail(node, "PS1003", "round argument is float")
|
|
860
|
+
if len(node.args) == 1:
|
|
861
|
+
return lo._abi_call("builtins", "round", [arg], {"kind": "int"}, "ps_round_i64", span)
|
|
862
|
+
digits = lo._expr(node.args[1])
|
|
863
|
+
if digits["type"]["kind"] != "int":
|
|
864
|
+
lo.fail(node, "PS1003", "round ndigits is int")
|
|
865
|
+
return lo._abi_call("builtins", "round", [arg, digits], {"kind": "float"}, "ps_round_nd", span)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _same(a: dict[str, Any], b: dict[str, Any]) -> bool:
|
|
869
|
+
from pyscriptc.ir import types_equal
|
|
870
|
+
|
|
871
|
+
return types_equal(a, b)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def method_spec(class_name: str, name: str) -> dict[str, Any] | None:
|
|
875
|
+
return CLASS_METHODS.get(class_name, {}).get(name)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def path_attr(name: str) -> tuple[dict[str, Any], str] | None:
|
|
879
|
+
return PATH_ATTRS.get(name)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
_HMAC_ALGS = {"md5": 1, "sha1": 2, "sha256": 3, "sha384": 4, "sha512": 5}
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def _hmac_alg(lo: Any, node: ast.expr) -> int | None:
|
|
886
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
887
|
+
return _HMAC_ALGS.get(node.value.lower())
|
|
888
|
+
member = ""
|
|
889
|
+
if isinstance(node, ast.Attribute):
|
|
890
|
+
resolved = lo._resolve_module(node)
|
|
891
|
+
if resolved is not None and resolved[1] == "hashlib":
|
|
892
|
+
member = resolved[2]
|
|
893
|
+
elif isinstance(node, ast.Name):
|
|
894
|
+
binding = lo.bindings.get(node.id)
|
|
895
|
+
if binding is not None and binding.kind == "func" and binding.module == "hashlib":
|
|
896
|
+
member = binding.attr
|
|
897
|
+
if not member:
|
|
898
|
+
return None
|
|
899
|
+
return _HMAC_ALGS.get(member)
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def _hmac(lo: Any, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
903
|
+
if len(node.args) != 3:
|
|
904
|
+
lo.fail(node, "PS2003", f"hmac.{attr} expects 3 argument(s)")
|
|
905
|
+
key = lo._expr(node.args[0])
|
|
906
|
+
msg = lo._expr(node.args[1])
|
|
907
|
+
if key["type"]["kind"] != "bytes" or msg["type"]["kind"] != "bytes":
|
|
908
|
+
lo.fail(node, "PS1003", f"hmac.{attr} key and message are bytes")
|
|
909
|
+
alg = _hmac_alg(lo, node.args[2])
|
|
910
|
+
if alg is None:
|
|
911
|
+
lo.fail(node, "PS2001", "hmac algorithm is a literal md5, sha1, sha256, sha384, or sha512")
|
|
912
|
+
alg_expr = _const_int(alg, span)
|
|
913
|
+
if attr == "digest":
|
|
914
|
+
return lo._abi_call("hmac", "digest", [key, msg, alg_expr], {"kind": "bytes"}, "ps_hmac_digest", span)
|
|
915
|
+
return lo._abi_call("hmac", "new", [key, msg, alg_expr], {"kind": "class", "name": "Hmac"}, "ps_hmac_new", span)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def _hmac_compare(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
919
|
+
if len(node.args) != 2:
|
|
920
|
+
lo.fail(node, "PS2003", "hmac.compare_digest expects 2 argument(s)")
|
|
921
|
+
left = lo._expr(node.args[0])
|
|
922
|
+
right = lo._expr(node.args[1])
|
|
923
|
+
kind = left["type"]["kind"]
|
|
924
|
+
if kind not in {"str", "bytes"} or right["type"]["kind"] != kind:
|
|
925
|
+
lo.fail(node, "PS1003", "hmac.compare_digest takes two str or two bytes")
|
|
926
|
+
return lo._abi_call("hmac", "compare_digest", [left, right], {"kind": "bool"}, "ps_hmac_compare", span)
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def _unicode_name(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
930
|
+
if not 1 <= len(node.args) <= 2:
|
|
931
|
+
lo.fail(node, "PS2003", "unicodedata.name expects 1 or 2 argument(s)")
|
|
932
|
+
char = lo._expr(node.args[0])
|
|
933
|
+
if char["type"]["kind"] != "str":
|
|
934
|
+
lo.fail(node, "PS1003", "unicodedata.name argument is str")
|
|
935
|
+
if len(node.args) == 2:
|
|
936
|
+
default = lo._expr(node.args[1])
|
|
937
|
+
if default["type"]["kind"] != "str":
|
|
938
|
+
lo.fail(node, "PS1003", "unicodedata.name default is str")
|
|
939
|
+
has = _const_int(1, span)
|
|
940
|
+
else:
|
|
941
|
+
default = _const_text("", "str", span)
|
|
942
|
+
has = _const_int(0, span)
|
|
943
|
+
return lo._abi_call("unicodedata", "name", [char, has, default], {"kind": "str"}, "ps_unicode_name", span)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def _resource_search() -> str:
|
|
947
|
+
import os
|
|
948
|
+
import sys
|
|
949
|
+
|
|
950
|
+
found: list[str] = []
|
|
951
|
+
seen: set[str] = set()
|
|
952
|
+
for entry in sys.path:
|
|
953
|
+
if not entry:
|
|
954
|
+
entry = os.getcwd()
|
|
955
|
+
if not os.path.isdir(entry):
|
|
956
|
+
continue
|
|
957
|
+
path = os.path.abspath(entry)
|
|
958
|
+
if path in seen:
|
|
959
|
+
continue
|
|
960
|
+
seen.add(path)
|
|
961
|
+
found.append(path)
|
|
962
|
+
return "\n".join(found)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def _as_file_value(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
966
|
+
"""as_file of a directory resource is that path. __enter__ returns it; __exit__ is a no-op."""
|
|
967
|
+
|
|
968
|
+
if len(node.args) != 1 or node.keywords:
|
|
969
|
+
lo.fail(node, "PS2003", "as_file expects 1 argument(s)")
|
|
970
|
+
opened = lo._expr(node.args[0])
|
|
971
|
+
if opened["type"]["kind"] != "class" or opened["type"].get("name") != "Path":
|
|
972
|
+
lo.fail(node, "PS1003", "as_file takes a resource path")
|
|
973
|
+
return opened
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _resource_files(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
977
|
+
if len(node.args) != 1:
|
|
978
|
+
lo.fail(node, "PS2003", "importlib.resources.files expects 1 argument(s)")
|
|
979
|
+
package = lo._expr(node.args[0])
|
|
980
|
+
if package["type"]["kind"] != "str":
|
|
981
|
+
lo.fail(node, "PS1003", "importlib.resources.files package is str")
|
|
982
|
+
search = _const_text(_resource_search(), "str", span)
|
|
983
|
+
return lo._abi_call(
|
|
984
|
+
"importlib.resources",
|
|
985
|
+
"files",
|
|
986
|
+
[package, search],
|
|
987
|
+
{"kind": "class", "name": "Path"},
|
|
988
|
+
"ps_res_files",
|
|
989
|
+
span,
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def _atexit_register(lo: Any, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
994
|
+
if len(node.args) != 1 or not isinstance(node.args[0], ast.Name):
|
|
995
|
+
lo.fail(node, "PS2001", "atexit.register takes a no-argument function")
|
|
996
|
+
name = node.args[0].id
|
|
997
|
+
sig = lo.sigs.get(name)
|
|
998
|
+
if sig is None:
|
|
999
|
+
lo.fail(node, "PS2001", "atexit.register takes a no-argument function")
|
|
1000
|
+
params, _ret = sig
|
|
1001
|
+
if params:
|
|
1002
|
+
lo.fail(node, "PS2001", "atexit.register takes a no-argument function")
|
|
1003
|
+
target = _const_text(lo._ir_name(name), "str", span)
|
|
1004
|
+
return lo._abi_call("atexit", "register", [target], {"kind": "none"}, "", span)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def alias_name(module: str, member: str) -> str | None:
|
|
1008
|
+
return type_alias(module, member)
|