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/_expr.py
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
"""Expression lowering for the lowerer.
|
|
2
|
+
|
|
3
|
+
Split out of `__init__.py` as E0 move 2. `Lowerer` composes this mixin in.
|
|
4
|
+
The area owns the `_expr` dispatch and its expression-form methods: names,
|
|
5
|
+
constants, unary/binary/compare (including chains and membership), list and
|
|
6
|
+
dict displays, subscripts and slices, attributes, calls, `and`/`or`,
|
|
7
|
+
conditionals, f-strings and `%` formatting.
|
|
8
|
+
|
|
9
|
+
Tables shared with the statement, call, and typing paths (`_CMP`, `_SCALAR`,
|
|
10
|
+
`_TRUTHY`, `_LIST_FORMS`, `_DICT_FORMS`, `_SET_FORMS`) live in `_core.py`;
|
|
11
|
+
`_INT_BIN`/`_FLOAT_BIN` are expression-only and stay here. The boundary calls
|
|
12
|
+
in both directions stay `self._method()` so the move is behavior-identical.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from pyscriptc.builtins import EXCEPTIONS
|
|
21
|
+
from pyscriptc.errors import span_of
|
|
22
|
+
from pyscriptc.ir import types_equal
|
|
23
|
+
from pyscriptc.lower._core import (
|
|
24
|
+
_CMP,
|
|
25
|
+
_SCALAR,
|
|
26
|
+
_TRUTHY,
|
|
27
|
+
_assign,
|
|
28
|
+
_binary_int,
|
|
29
|
+
_compare,
|
|
30
|
+
_concat_str,
|
|
31
|
+
_const_bool,
|
|
32
|
+
_const_int,
|
|
33
|
+
_const_list_concat,
|
|
34
|
+
_const_none,
|
|
35
|
+
_const_str_join,
|
|
36
|
+
_len_call,
|
|
37
|
+
_name,
|
|
38
|
+
_percent_pieces,
|
|
39
|
+
_plain_const_kind,
|
|
40
|
+
_show,
|
|
41
|
+
_static_int,
|
|
42
|
+
)
|
|
43
|
+
from pyscriptc.stdlib_queue import (
|
|
44
|
+
MATH_CONSTANTS,
|
|
45
|
+
MATH_FUNCTIONS,
|
|
46
|
+
SPECIAL_FUNCTIONS,
|
|
47
|
+
wave_function,
|
|
48
|
+
)
|
|
49
|
+
from pyscriptc.stdmore import COMPARE_CLASSES, member_callable, module_constant
|
|
50
|
+
|
|
51
|
+
_INT_BIN = {ast.Add: "+", ast.Sub: "-", ast.Mult: "*", ast.FloorDiv: "//"}
|
|
52
|
+
_FLOAT_BIN = {ast.Add: "+", ast.Sub: "-", ast.Mult: "*"}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ExprMixin:
|
|
56
|
+
def _expr(self, node: ast.expr, expect: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
57
|
+
span = span_of(node, self.filename)
|
|
58
|
+
if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load):
|
|
59
|
+
return self._name(node, span)
|
|
60
|
+
if isinstance(node, ast.Constant):
|
|
61
|
+
return self._const(node, span)
|
|
62
|
+
if isinstance(node, ast.UnaryOp):
|
|
63
|
+
return self._unary(node, span)
|
|
64
|
+
if isinstance(node, ast.BinOp):
|
|
65
|
+
return self._binary(node, span)
|
|
66
|
+
if isinstance(node, ast.Compare):
|
|
67
|
+
return self._compare(node, span)
|
|
68
|
+
if isinstance(node, ast.Call):
|
|
69
|
+
return self._call(node, span, expect)
|
|
70
|
+
if isinstance(node, ast.Attribute):
|
|
71
|
+
return self._attribute(node, span)
|
|
72
|
+
if isinstance(node, ast.List):
|
|
73
|
+
return self._list(node, span, expect)
|
|
74
|
+
if isinstance(node, ast.Set):
|
|
75
|
+
return self._set_display(node, span, expect)
|
|
76
|
+
if isinstance(node, ast.Dict):
|
|
77
|
+
return self._dict(node, span, expect)
|
|
78
|
+
if isinstance(node, ast.Tuple):
|
|
79
|
+
return self._tuple_expr(node, span)
|
|
80
|
+
if isinstance(node, ast.Subscript):
|
|
81
|
+
return self._subscript(node, span)
|
|
82
|
+
if isinstance(node, ast.BoolOp):
|
|
83
|
+
return self._boolop(node, span)
|
|
84
|
+
if isinstance(node, ast.JoinedStr):
|
|
85
|
+
return self._joined(node, span)
|
|
86
|
+
if isinstance(node, ast.ListComp):
|
|
87
|
+
return self._listcomp(node, span, expect)
|
|
88
|
+
if isinstance(node, ast.IfExp):
|
|
89
|
+
return self._ifexp(node, span)
|
|
90
|
+
self.fail(node, "PS2002", f"{type(node).__name__} is not lowered")
|
|
91
|
+
raise AssertionError("unreachable")
|
|
92
|
+
|
|
93
|
+
def _name(self, node: ast.Name, span: dict[str, Any]) -> dict[str, Any]:
|
|
94
|
+
if node.id in self.locals:
|
|
95
|
+
return _name(node.id, self.locals[node.id], span)
|
|
96
|
+
slot = self.global_slots.get(node.id)
|
|
97
|
+
if slot is not None and slot.get("active"):
|
|
98
|
+
return self._global_get(node.id, span)
|
|
99
|
+
if node.id in self.constants:
|
|
100
|
+
found = self.constants[node.id]
|
|
101
|
+
if found.get("kind") == "tuple-const":
|
|
102
|
+
return self._materialize_tuple(found["elems"], span)
|
|
103
|
+
return {**found, "span": span}
|
|
104
|
+
if node.id in self.type_aliases:
|
|
105
|
+
self.fail(node, "PS2002", f"{node.id} is a typing name and is not a value")
|
|
106
|
+
binding = self.bindings.get(node.id)
|
|
107
|
+
if binding is not None:
|
|
108
|
+
if binding.queued:
|
|
109
|
+
self._queued(node, f"{binding.module}.{binding.attr}" if binding.attr else binding.module)
|
|
110
|
+
if binding.kind == "erased":
|
|
111
|
+
self.fail(node, "PS2002", f"{node.id} is a typing name and is not a value")
|
|
112
|
+
if binding.kind == "const":
|
|
113
|
+
if binding.module == "math":
|
|
114
|
+
value: Any = MATH_CONSTANTS[binding.attr]
|
|
115
|
+
typ: dict[str, Any] = {"kind": "float"}
|
|
116
|
+
else:
|
|
117
|
+
found = module_constant(binding.module, binding.attr)
|
|
118
|
+
if found is None:
|
|
119
|
+
self.fail(node, "PS2001", f"{binding.module}.{binding.attr} is not a constant")
|
|
120
|
+
value, typ = found
|
|
121
|
+
return {"kind": "const", "value": value, "type": typ, "span": span}
|
|
122
|
+
if binding.kind in {"module", "pkg"}:
|
|
123
|
+
self.fail(node, "PS2002", f"module {node.id} is not a value in the static subset")
|
|
124
|
+
self.fail(node, "PS2002", f"{node.id} is a function and is not a value")
|
|
125
|
+
if node.id in self.classes:
|
|
126
|
+
self.fail(node, "PS2002", f"class {node.id} is not a value")
|
|
127
|
+
if node.id in EXCEPTIONS:
|
|
128
|
+
self.fail(node, "PS2002", f"{node.id} is only lowered in raise and except")
|
|
129
|
+
self.fail(node, "PS2002", f"{node.id} is not a local or parameter")
|
|
130
|
+
raise AssertionError("unreachable")
|
|
131
|
+
|
|
132
|
+
def _const(self, node: ast.Constant, span: dict[str, Any]) -> dict[str, Any]:
|
|
133
|
+
value = node.value
|
|
134
|
+
if isinstance(value, bool):
|
|
135
|
+
return {"kind": "const", "value": value, "type": {"kind": "bool"}, "span": span}
|
|
136
|
+
if isinstance(value, int):
|
|
137
|
+
return {"kind": "const", "value": str(value), "type": {"kind": "int"}, "span": span}
|
|
138
|
+
if isinstance(value, float):
|
|
139
|
+
return {"kind": "const", "value": repr(value), "type": {"kind": "float"}, "span": span}
|
|
140
|
+
if isinstance(value, str):
|
|
141
|
+
if any(ord(ch) > 255 for ch in value):
|
|
142
|
+
self.fail(node, "PS2005", "string literals outside Latin-1 are not lowered")
|
|
143
|
+
return {"kind": "const", "value": value, "type": {"kind": "str"}, "span": span}
|
|
144
|
+
if isinstance(value, bytes):
|
|
145
|
+
return {"kind": "const", "value": value.decode("latin1"), "type": {"kind": "bytes"}, "span": span}
|
|
146
|
+
if value is None:
|
|
147
|
+
return _const_none(span)
|
|
148
|
+
self.fail(node, "PS2002", f"literal {type(value).__name__} is not lowered")
|
|
149
|
+
raise AssertionError("unreachable")
|
|
150
|
+
|
|
151
|
+
def _unary(self, node: ast.UnaryOp, span: dict[str, Any]) -> dict[str, Any]:
|
|
152
|
+
operand = self._expr(node.operand)
|
|
153
|
+
kind = operand["type"]["kind"]
|
|
154
|
+
if isinstance(node.op, ast.USub) and kind == "int":
|
|
155
|
+
return {"kind": "unary", "op": "-", "operand": operand, "type": {"kind": "int"}, "span": span}
|
|
156
|
+
if isinstance(node.op, ast.USub) and kind == "float":
|
|
157
|
+
return {"kind": "unary", "op": "-", "operand": operand, "type": {"kind": "float"}, "span": span}
|
|
158
|
+
if isinstance(node.op, ast.Not) and kind == "bool":
|
|
159
|
+
return {"kind": "unary", "op": "not", "operand": operand, "type": {"kind": "bool"}, "span": span}
|
|
160
|
+
if isinstance(node.op, ast.Not) and kind in _TRUTHY:
|
|
161
|
+
inner = self._truthy(operand, span)
|
|
162
|
+
return {"kind": "unary", "op": "not", "operand": inner, "type": {"kind": "bool"}, "span": span}
|
|
163
|
+
if isinstance(node.op, ast.USub) and kind == "class" and operand["type"].get("name") == "TimeDelta":
|
|
164
|
+
return {"kind": "unary", "op": "-", "operand": operand, "type": operand["type"], "span": span}
|
|
165
|
+
self.fail(node, "PS2004", f"unary {type(node.op).__name__} on {kind} is not lowered")
|
|
166
|
+
raise AssertionError("unreachable")
|
|
167
|
+
|
|
168
|
+
def _binary(self, node: ast.BinOp, span: dict[str, Any]) -> dict[str, Any]:
|
|
169
|
+
if isinstance(node.op, ast.Mod):
|
|
170
|
+
return self._percent(node, span)
|
|
171
|
+
left = self._expr(node.left)
|
|
172
|
+
right = self._expr(node.right)
|
|
173
|
+
left_kind = left["type"]["kind"]
|
|
174
|
+
right_kind = right["type"]["kind"]
|
|
175
|
+
if left_kind == "bool" or right_kind == "bool":
|
|
176
|
+
self.fail(node, "PS2004", "bool arithmetic is not lowered (bool is a subclass of int in Python)")
|
|
177
|
+
if isinstance(node.op, ast.Pow):
|
|
178
|
+
return self._pow(node, left, right, span)
|
|
179
|
+
op_type = type(node.op)
|
|
180
|
+
if left_kind == "int" and right_kind == "int" and op_type in _INT_BIN:
|
|
181
|
+
return _binary_int(_INT_BIN[op_type], left, right, span)
|
|
182
|
+
if left_kind == "float" and right_kind == "float" and op_type in _FLOAT_BIN:
|
|
183
|
+
return {
|
|
184
|
+
"kind": "binary",
|
|
185
|
+
"op": _FLOAT_BIN[op_type],
|
|
186
|
+
"left": left,
|
|
187
|
+
"right": right,
|
|
188
|
+
"type": {"kind": "float"},
|
|
189
|
+
"span": span,
|
|
190
|
+
}
|
|
191
|
+
if left_kind == right_kind and left_kind in {"str", "bytes"} and isinstance(node.op, ast.Add):
|
|
192
|
+
return {
|
|
193
|
+
"kind": "binary",
|
|
194
|
+
"op": "+",
|
|
195
|
+
"left": left,
|
|
196
|
+
"right": right,
|
|
197
|
+
"type": {"kind": left_kind},
|
|
198
|
+
"span": span,
|
|
199
|
+
}
|
|
200
|
+
if left_kind == "class" or right_kind == "class":
|
|
201
|
+
return self._class_binary(node, left, right, span)
|
|
202
|
+
joined = _const_list_concat(left, right, span)
|
|
203
|
+
if joined is not None and isinstance(node.op, ast.Add):
|
|
204
|
+
return joined
|
|
205
|
+
if (
|
|
206
|
+
isinstance(node.op, ast.Add)
|
|
207
|
+
and left_kind == "list"
|
|
208
|
+
and right_kind == "list"
|
|
209
|
+
and not left["type"].get("set")
|
|
210
|
+
and not right["type"].get("set")
|
|
211
|
+
and types_equal(left["type"]["elem"], right["type"]["elem"])
|
|
212
|
+
):
|
|
213
|
+
# `xs + ys` builds a fresh list; `xs += ys` reaches the same node
|
|
214
|
+
# through the aug-assign rewrite, which makes += copy, not mutate.
|
|
215
|
+
return self._abi_call(
|
|
216
|
+
"builtins",
|
|
217
|
+
"concat",
|
|
218
|
+
[left, right],
|
|
219
|
+
{"kind": "list", "elem": left["type"]["elem"]},
|
|
220
|
+
"ps_seq_concat",
|
|
221
|
+
span,
|
|
222
|
+
)
|
|
223
|
+
if isinstance(node.op, ast.Mult):
|
|
224
|
+
if left_kind == "list" and right_kind == "int" and not left["type"].get("set"):
|
|
225
|
+
return self._abi_call("builtins", "repeat", [left, right], left["type"], "ps_seq_repeat", span)
|
|
226
|
+
if left_kind == "int" and right_kind == "list" and not right["type"].get("set"):
|
|
227
|
+
return self._abi_call("builtins", "repeat", [right, left], right["type"], "ps_seq_repeat", span)
|
|
228
|
+
self.fail(node, "PS2004", f"{op_type.__name__} on {left_kind} and {right_kind} is not lowered")
|
|
229
|
+
raise AssertionError("unreachable")
|
|
230
|
+
|
|
231
|
+
def _pow(
|
|
232
|
+
self, node: ast.BinOp, left: dict[str, Any], right: dict[str, Any], span: dict[str, Any]
|
|
233
|
+
) -> dict[str, Any]:
|
|
234
|
+
"""`base ** exp` is a loop of the existing int `*`.
|
|
235
|
+
|
|
236
|
+
Both operands are ints. The base and the exponent are stored once, then
|
|
237
|
+
the accumulator starts at 1 and is multiplied `exp` times. `0 ** 0` is
|
|
238
|
+
that 1. A negative exponent stays fenced: a constant is refused here,
|
|
239
|
+
and a computed negative raises ValueError instead of becoming a float.
|
|
240
|
+
"""
|
|
241
|
+
if left["type"]["kind"] != "int" or right["type"]["kind"] != "int":
|
|
242
|
+
self.fail(node, "PS2004", "** is only lowered for int")
|
|
243
|
+
static = _static_int(right)
|
|
244
|
+
if static is not None and static < 0:
|
|
245
|
+
self.fail(node, "PS2004", "a negative exponent is not lowered")
|
|
246
|
+
int_t = {"kind": "int"}
|
|
247
|
+
base = self._temp("powb")
|
|
248
|
+
exp = self._temp("powe")
|
|
249
|
+
acc = self._temp("powa")
|
|
250
|
+
idx = self._temp("powi")
|
|
251
|
+
for name in (base, exp, acc, idx):
|
|
252
|
+
self.locals[name] = int_t
|
|
253
|
+
base_n = _name(base, int_t, span)
|
|
254
|
+
exp_n = _name(exp, int_t, span)
|
|
255
|
+
acc_n = _name(acc, int_t, span)
|
|
256
|
+
idx_n = _name(idx, int_t, span)
|
|
257
|
+
self.prelude.extend(
|
|
258
|
+
[
|
|
259
|
+
_assign(base, left, span),
|
|
260
|
+
_assign(exp, right, span),
|
|
261
|
+
{
|
|
262
|
+
"kind": "if",
|
|
263
|
+
"cond": _compare("<", exp_n, _const_int(0, span), span),
|
|
264
|
+
"then": [{"kind": "raise", "exc": "ValueError", "message": None, "span": span}],
|
|
265
|
+
"else": [],
|
|
266
|
+
"span": span,
|
|
267
|
+
},
|
|
268
|
+
_assign(acc, _const_int(1, span), span),
|
|
269
|
+
_assign(idx, _const_int(0, span), span),
|
|
270
|
+
{
|
|
271
|
+
"kind": "while",
|
|
272
|
+
"cond": _compare("<", idx_n, exp_n, span),
|
|
273
|
+
"body": [
|
|
274
|
+
_assign(acc, _binary_int("*", acc_n, base_n, span), span),
|
|
275
|
+
_assign(idx, _binary_int("+", idx_n, _const_int(1, span), span), span),
|
|
276
|
+
],
|
|
277
|
+
"span": span,
|
|
278
|
+
},
|
|
279
|
+
]
|
|
280
|
+
)
|
|
281
|
+
return _name(acc, int_t, span)
|
|
282
|
+
|
|
283
|
+
def _compare(self, node: ast.Compare, span: dict[str, Any]) -> dict[str, Any]:
|
|
284
|
+
if len(node.ops) != 1 or len(node.comparators) != 1:
|
|
285
|
+
return self._compare_chain(node, span)
|
|
286
|
+
if isinstance(node.ops[0], (ast.In, ast.NotIn)):
|
|
287
|
+
return self._contains(node, span, isinstance(node.ops[0], ast.NotIn))
|
|
288
|
+
if isinstance(node.ops[0], (ast.Is, ast.IsNot)):
|
|
289
|
+
return self._is_none(node, span, isinstance(node.ops[0], ast.IsNot))
|
|
290
|
+
op = _CMP.get(type(node.ops[0]))
|
|
291
|
+
if op is None:
|
|
292
|
+
self.fail(node, "PS2004", f"{type(node.ops[0]).__name__} is not lowered")
|
|
293
|
+
left = self._expr(node.left)
|
|
294
|
+
right = self._expr(node.comparators[0])
|
|
295
|
+
if left["type"].get("ordered") or right["type"].get("ordered"):
|
|
296
|
+
self.fail(node, "PS2004", "OrderedDict equality is not lowered")
|
|
297
|
+
if (
|
|
298
|
+
left["type"]["kind"] == "class"
|
|
299
|
+
and right["type"]["kind"] == "class"
|
|
300
|
+
and left["type"].get("name") == right["type"].get("name")
|
|
301
|
+
and left["type"].get("name") in COMPARE_CLASSES
|
|
302
|
+
):
|
|
303
|
+
return _compare(op, left, right, span)
|
|
304
|
+
if left["type"]["kind"] != right["type"]["kind"] or left["type"]["kind"] not in {
|
|
305
|
+
"int",
|
|
306
|
+
"float",
|
|
307
|
+
"bool",
|
|
308
|
+
"str",
|
|
309
|
+
"bytes",
|
|
310
|
+
}:
|
|
311
|
+
self.fail(node, "PS2004", "comparisons need two values of the same scalar type")
|
|
312
|
+
if left["type"]["kind"] == "bool" and op not in {"==", "!="}:
|
|
313
|
+
self.fail(node, "PS2004", "bool ordering is not lowered")
|
|
314
|
+
return _compare(op, left, right, span)
|
|
315
|
+
|
|
316
|
+
def _compare_chain(self, node: ast.Compare, span: dict[str, Any]) -> dict[str, Any]:
|
|
317
|
+
"""`a < b < c` is the existing compares joined by the existing `and`.
|
|
318
|
+
|
|
319
|
+
Evaluate the left, then the first middle, and compare. Each later
|
|
320
|
+
middle is stored once, inside the `and` branch, so a false link does
|
|
321
|
+
not evaluate the rest. `is` stays fenced. A link the one-op compare
|
|
322
|
+
rejects still fences.
|
|
323
|
+
"""
|
|
324
|
+
if len(node.ops) != len(node.comparators) or len(node.ops) < 2:
|
|
325
|
+
self.fail(node, "PS2004", "chained comparisons are not lowered")
|
|
326
|
+
if any(isinstance(op, (ast.Is, ast.IsNot)) for op in node.ops):
|
|
327
|
+
self.fail(node, "PS2004", "is is not lowered")
|
|
328
|
+
bool_t = {"kind": "bool"}
|
|
329
|
+
result = self._temp("chain")
|
|
330
|
+
self.locals[result] = bool_t
|
|
331
|
+
left_ref = self._chain_pin(node.left)
|
|
332
|
+
prev_ref: ast.expr = self._chain_pin(node.comparators[0])
|
|
333
|
+
first = self._one_link(node, node.ops[0], left_ref, prev_ref)
|
|
334
|
+
self.prelude.append(_assign(result, first, span))
|
|
335
|
+
for index in range(1, len(node.ops)):
|
|
336
|
+
saved = self.prelude
|
|
337
|
+
self.prelude = []
|
|
338
|
+
try:
|
|
339
|
+
operand = node.comparators[index]
|
|
340
|
+
if index + 1 < len(node.ops):
|
|
341
|
+
right_ref: ast.expr = self._chain_pin(operand)
|
|
342
|
+
else:
|
|
343
|
+
right_ref = operand
|
|
344
|
+
link = self._one_link(node, node.ops[index], prev_ref, right_ref)
|
|
345
|
+
if index + 1 < len(node.ops):
|
|
346
|
+
prev_ref = right_ref
|
|
347
|
+
finally:
|
|
348
|
+
inner = self.prelude
|
|
349
|
+
self.prelude = saved
|
|
350
|
+
inner.append(_assign(result, link, span))
|
|
351
|
+
cond = self._truthy(_name(result, bool_t, span), span)
|
|
352
|
+
self.prelude.append({"kind": "if", "cond": cond, "then": inner, "else": [], "span": span})
|
|
353
|
+
return _name(result, bool_t, span)
|
|
354
|
+
|
|
355
|
+
def _chain_pin(self, expr: ast.expr) -> ast.Name:
|
|
356
|
+
value = self._expr(expr)
|
|
357
|
+
temp = self._temp("cmp")
|
|
358
|
+
self.locals[temp] = value["type"]
|
|
359
|
+
pinned = ast.Name(id=temp, ctx=ast.Load())
|
|
360
|
+
ast.copy_location(pinned, expr)
|
|
361
|
+
self.prelude.append(_assign(temp, value, span_of(expr, self.filename)))
|
|
362
|
+
return pinned
|
|
363
|
+
|
|
364
|
+
def _one_link(self, origin: ast.Compare, op: ast.cmpop, left: ast.expr, right: ast.expr) -> dict[str, Any]:
|
|
365
|
+
compare = ast.Compare(left=left, ops=[op], comparators=[right])
|
|
366
|
+
ast.copy_location(compare, origin)
|
|
367
|
+
return self._compare(compare, span_of(origin, self.filename))
|
|
368
|
+
|
|
369
|
+
def _contains(self, node: ast.Compare, span: dict[str, Any], negated: bool) -> dict[str, Any]:
|
|
370
|
+
folded = self._const_tuple_membership(node, span, negated)
|
|
371
|
+
if folded is not None:
|
|
372
|
+
return folded
|
|
373
|
+
needle = self._expr(node.left)
|
|
374
|
+
hay = self._expr(node.comparators[0])
|
|
375
|
+
hay_kind = hay["type"]["kind"]
|
|
376
|
+
if hay_kind in {"str", "bytes"}:
|
|
377
|
+
if needle["type"]["kind"] != hay_kind:
|
|
378
|
+
self.fail(node, "PS1003", f"in on {hay_kind} takes {hay_kind}")
|
|
379
|
+
elif hay_kind == "list":
|
|
380
|
+
if hay["type"]["elem"]["kind"] not in _SCALAR or not types_equal(needle["type"], hay["type"]["elem"]):
|
|
381
|
+
self.fail(node, "PS1003", "in on a list takes an element of that list")
|
|
382
|
+
elif hay_kind == "dict":
|
|
383
|
+
if not types_equal(needle["type"], hay["type"]["key"]):
|
|
384
|
+
self.fail(node, "PS1003", "in on a dict takes a key")
|
|
385
|
+
else:
|
|
386
|
+
self.fail(node, "PS2002", "in is lowered for str, bytes, list, and dict")
|
|
387
|
+
if hay_kind == "list" and not hay["type"].get("set") and hay["type"]["elem"]["kind"] == "float":
|
|
388
|
+
return self._float_list_membership(needle, hay, span, negated)
|
|
389
|
+
return {
|
|
390
|
+
"kind": "contains",
|
|
391
|
+
"hay": hay,
|
|
392
|
+
"needle": needle,
|
|
393
|
+
"negated": negated,
|
|
394
|
+
"type": {"kind": "bool"},
|
|
395
|
+
"span": span,
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
def _float_list_membership(
|
|
399
|
+
self, needle: dict[str, Any], hay: dict[str, Any], span: dict[str, Any], negated: bool
|
|
400
|
+
) -> dict[str, Any]:
|
|
401
|
+
"""Float list membership is ==, so NaN is not a member of a list of NaNs.
|
|
402
|
+
|
|
403
|
+
Int and str lists stay on the contains helper. A set stays there too.
|
|
404
|
+
"""
|
|
405
|
+
int_t = {"kind": "int"}
|
|
406
|
+
float_t = {"kind": "float"}
|
|
407
|
+
bool_t = {"kind": "bool"}
|
|
408
|
+
held = self._temp("fneedle")
|
|
409
|
+
src = self._temp("fseq")
|
|
410
|
+
idx = self._temp("fidx")
|
|
411
|
+
length = self._temp("flen")
|
|
412
|
+
found = self._temp("fhas")
|
|
413
|
+
item = self._temp("felt")
|
|
414
|
+
self.locals[held] = float_t
|
|
415
|
+
self.locals[src] = hay["type"]
|
|
416
|
+
self.locals[idx] = int_t
|
|
417
|
+
self.locals[length] = int_t
|
|
418
|
+
self.locals[found] = bool_t
|
|
419
|
+
self.locals[item] = float_t
|
|
420
|
+
self.prelude.extend(
|
|
421
|
+
[
|
|
422
|
+
_assign(held, needle, span),
|
|
423
|
+
_assign(src, hay, span),
|
|
424
|
+
_assign(length, _len_call(_name(src, hay["type"], span), span), span),
|
|
425
|
+
_assign(idx, _const_int(0, span), span),
|
|
426
|
+
_assign(found, _const_bool(False, span), span),
|
|
427
|
+
{
|
|
428
|
+
"kind": "while",
|
|
429
|
+
"cond": _compare("<", _name(idx, int_t, span), _name(length, int_t, span), span),
|
|
430
|
+
"body": [
|
|
431
|
+
_assign(
|
|
432
|
+
item,
|
|
433
|
+
{
|
|
434
|
+
"kind": "subscript",
|
|
435
|
+
"value": _name(src, hay["type"], span),
|
|
436
|
+
"index": _name(idx, int_t, span),
|
|
437
|
+
"type": float_t,
|
|
438
|
+
"span": span,
|
|
439
|
+
},
|
|
440
|
+
span,
|
|
441
|
+
),
|
|
442
|
+
{
|
|
443
|
+
"kind": "if",
|
|
444
|
+
"cond": _compare("==", _name(item, float_t, span), _name(held, float_t, span), span),
|
|
445
|
+
"then": [_assign(found, _const_bool(True, span), span)],
|
|
446
|
+
"else": [],
|
|
447
|
+
"span": span,
|
|
448
|
+
},
|
|
449
|
+
_assign(idx, _binary_int("+", _name(idx, int_t, span), _const_int(1, span), span), span),
|
|
450
|
+
],
|
|
451
|
+
"span": span,
|
|
452
|
+
},
|
|
453
|
+
]
|
|
454
|
+
)
|
|
455
|
+
result = _name(found, bool_t, span)
|
|
456
|
+
if negated:
|
|
457
|
+
return {"kind": "unary", "op": "not", "operand": result, "type": bool_t, "span": span}
|
|
458
|
+
return result
|
|
459
|
+
|
|
460
|
+
def _const_tuple_membership(self, node: ast.Compare, span: dict[str, Any], negated: bool) -> dict[str, Any] | None:
|
|
461
|
+
"""`value in (c0, c1, ...)` of one const type is `==` joined by `or`.
|
|
462
|
+
|
|
463
|
+
The tuple is not a class. Empty is false. One element is one `==`.
|
|
464
|
+
`not in` is `not` of that test. A name, a list, a mix, or a non-constant
|
|
465
|
+
element stays on the ordinary path.
|
|
466
|
+
"""
|
|
467
|
+
hay = node.comparators[0]
|
|
468
|
+
if not isinstance(hay, ast.Tuple) or any(isinstance(elt, ast.Starred) for elt in hay.elts):
|
|
469
|
+
return None
|
|
470
|
+
kinds = [_plain_const_kind(elt) for elt in hay.elts]
|
|
471
|
+
if any(kind is None for kind in kinds) or len(set(kinds)) > 1:
|
|
472
|
+
return None
|
|
473
|
+
needle = self._expr(node.left)
|
|
474
|
+
if not hay.elts:
|
|
475
|
+
self.prelude.append({"kind": "expr", "expr": needle, "span": span})
|
|
476
|
+
return _const_bool(negated, span)
|
|
477
|
+
elem = kinds[0]
|
|
478
|
+
if needle["type"]["kind"] != elem:
|
|
479
|
+
if len(hay.elts) < 2:
|
|
480
|
+
self.fail(hay, "PS2002", "tuples need at least two fields")
|
|
481
|
+
self.fail(node, "PS2002", "in is lowered for str, bytes, list, and dict")
|
|
482
|
+
temp = self._temp("in")
|
|
483
|
+
self.locals[temp] = needle["type"]
|
|
484
|
+
self.prelude.append(_assign(temp, needle, span))
|
|
485
|
+
compares: list[ast.expr] = []
|
|
486
|
+
for elt in hay.elts:
|
|
487
|
+
left = ast.Name(id=temp, ctx=ast.Load())
|
|
488
|
+
ast.copy_location(left, node.left)
|
|
489
|
+
compare = ast.Compare(left=left, ops=[ast.Eq()], comparators=[elt])
|
|
490
|
+
ast.copy_location(compare, node)
|
|
491
|
+
compares.append(compare)
|
|
492
|
+
if len(compares) == 1:
|
|
493
|
+
test = self._expr(compares[0])
|
|
494
|
+
else:
|
|
495
|
+
chain = ast.BoolOp(op=ast.Or(), values=compares)
|
|
496
|
+
ast.copy_location(chain, node)
|
|
497
|
+
test = self._boolop(chain, span)
|
|
498
|
+
if negated:
|
|
499
|
+
return {"kind": "unary", "op": "not", "operand": test, "type": {"kind": "bool"}, "span": span}
|
|
500
|
+
return test
|
|
501
|
+
|
|
502
|
+
def _list(self, node: ast.List, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
503
|
+
if not node.elts:
|
|
504
|
+
if expect is None or expect["kind"] != "list":
|
|
505
|
+
self.fail(node, "PS1001", "an empty list needs a list annotation")
|
|
506
|
+
return {"kind": "list", "elems": [], "type": expect, "span": span}
|
|
507
|
+
elem_expect = expect["elem"] if expect and expect["kind"] == "list" else None
|
|
508
|
+
elems = [self._expr(elt, elem_expect) for elt in node.elts]
|
|
509
|
+
elem = elems[0]["type"]
|
|
510
|
+
if elem["kind"] == "bool":
|
|
511
|
+
self.fail(node, "PS2002", "list[bool] is not lowered")
|
|
512
|
+
for item in elems[1:]:
|
|
513
|
+
if not types_equal(item["type"], elem):
|
|
514
|
+
self.fail(node, "PS1003", "list elements have one type")
|
|
515
|
+
typ = {"kind": "list", "elem": elem}
|
|
516
|
+
if expect and expect["kind"] == "list":
|
|
517
|
+
self._expect(node, typ, expect, "list")
|
|
518
|
+
return {"kind": "list", "elems": elems, "type": typ, "span": span}
|
|
519
|
+
|
|
520
|
+
def _dict(self, node: ast.Dict, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
521
|
+
if not node.keys or any(key is None for key in node.keys):
|
|
522
|
+
if expect is None or expect["kind"] != "dict":
|
|
523
|
+
self.fail(node, "PS1001", "an empty dict needs a dict annotation")
|
|
524
|
+
if any(key is None for key in node.keys):
|
|
525
|
+
self.fail(node, "PS2002", "dict unpacking is not lowered")
|
|
526
|
+
return {"kind": "dict", "pairs": [], "type": expect, "span": span}
|
|
527
|
+
key_expect = expect["key"] if expect and expect["kind"] == "dict" else None
|
|
528
|
+
val_expect = expect["value"] if expect and expect["kind"] == "dict" else None
|
|
529
|
+
pairs = []
|
|
530
|
+
for key_node, val_node in zip(node.keys, node.values, strict=True):
|
|
531
|
+
assert key_node is not None
|
|
532
|
+
key = self._expr(key_node, key_expect)
|
|
533
|
+
value = self._expr(val_node, val_expect)
|
|
534
|
+
pairs.append({"key": key, "value": value})
|
|
535
|
+
key_t = pairs[0]["key"]["type"]
|
|
536
|
+
val_t = pairs[0]["value"]["type"]
|
|
537
|
+
if key_t["kind"] not in {"int", "str"}:
|
|
538
|
+
self.fail(node, "PS2002", "dict keys are int or str")
|
|
539
|
+
if val_t["kind"] == "bool":
|
|
540
|
+
self.fail(node, "PS2002", "dict values of bool are not lowered")
|
|
541
|
+
for pair in pairs[1:]:
|
|
542
|
+
if not types_equal(pair["key"]["type"], key_t) or not types_equal(pair["value"]["type"], val_t):
|
|
543
|
+
self.fail(node, "PS1003", "dict entries have one key type and one value type")
|
|
544
|
+
typ = {"kind": "dict", "key": key_t, "value": val_t}
|
|
545
|
+
if expect and expect["kind"] == "dict":
|
|
546
|
+
self._expect(node, typ, expect, "dict")
|
|
547
|
+
return {"kind": "dict", "pairs": pairs, "type": typ, "span": span}
|
|
548
|
+
|
|
549
|
+
def _subscript(self, node: ast.Subscript, span: dict[str, Any]) -> dict[str, Any]:
|
|
550
|
+
if isinstance(node.slice, ast.Slice):
|
|
551
|
+
return self._slice(node, span)
|
|
552
|
+
value = self._expr(node.value)
|
|
553
|
+
index = self._expr(node.slice)
|
|
554
|
+
kind = value["type"]["kind"]
|
|
555
|
+
if kind == "class" and self.classes.get(value["type"]["name"], {}).get("tuple"):
|
|
556
|
+
return self._tuple_subscript(node, value, span)
|
|
557
|
+
if kind == "list" and value["type"].get("set"):
|
|
558
|
+
self.fail(node, "PS2002", "a set has no subscript")
|
|
559
|
+
if kind == "list":
|
|
560
|
+
if index["type"]["kind"] != "int":
|
|
561
|
+
self.fail(node, "PS1003", "a list index is int")
|
|
562
|
+
result = value["type"]["elem"]
|
|
563
|
+
elif kind == "dict":
|
|
564
|
+
self._expect(node, index["type"], value["type"]["key"], "dict key")
|
|
565
|
+
result = value["type"]["value"]
|
|
566
|
+
elif kind == "str":
|
|
567
|
+
if index["type"]["kind"] != "int":
|
|
568
|
+
self.fail(node, "PS1003", "a str index is int")
|
|
569
|
+
result = {"kind": "str"}
|
|
570
|
+
elif kind == "bytes":
|
|
571
|
+
if index["type"]["kind"] != "int":
|
|
572
|
+
self.fail(node, "PS1003", "a bytes index is int")
|
|
573
|
+
result = {"kind": "int"}
|
|
574
|
+
elif kind == "class" and value["type"].get("name") == "Deque":
|
|
575
|
+
if index["type"]["kind"] != "int":
|
|
576
|
+
self.fail(node, "PS1003", "a deque index is int")
|
|
577
|
+
if "elem" not in value["type"]:
|
|
578
|
+
self.fail(node, "PS2002", "this deque has no element type")
|
|
579
|
+
result = value["type"]["elem"]
|
|
580
|
+
else:
|
|
581
|
+
self.fail(node, "PS2002", "subscript is lowered for list, dict, str, and bytes")
|
|
582
|
+
return {"kind": "subscript", "value": value, "index": index, "type": result, "span": span}
|
|
583
|
+
|
|
584
|
+
def _attribute(self, node: ast.Attribute, span: dict[str, Any]) -> dict[str, Any]:
|
|
585
|
+
if (
|
|
586
|
+
isinstance(node.value, ast.Call)
|
|
587
|
+
and node.attr == "name"
|
|
588
|
+
and self._is_mod(node.value, "threading", "current_thread")
|
|
589
|
+
):
|
|
590
|
+
return self._abi_call(
|
|
591
|
+
"threading", "current_thread.name", [], {"kind": "str"}, "ps_island_thread_name", span
|
|
592
|
+
)
|
|
593
|
+
resolved = self._resolve_module(node)
|
|
594
|
+
if resolved is not None:
|
|
595
|
+
dotted, module, member, status = resolved
|
|
596
|
+
if status == "erased":
|
|
597
|
+
self.fail(node, "PS2002", f"{dotted} is a typing name and is not a value")
|
|
598
|
+
if status == "pkg":
|
|
599
|
+
item = self._pkg_item(node, module, member)
|
|
600
|
+
if item["kind"] == "const":
|
|
601
|
+
value = item["value"]
|
|
602
|
+
if value.get("kind") == "tuple-const":
|
|
603
|
+
return self._materialize_tuple(value["elems"], span)
|
|
604
|
+
return {**value, "span": span}
|
|
605
|
+
if item["kind"] == "class":
|
|
606
|
+
self.fail(node, "PS2002", f"class {dotted} is not a value")
|
|
607
|
+
self.fail(node, "PS2002", f"{dotted} is a function and is not a value")
|
|
608
|
+
if status == "queued":
|
|
609
|
+
self._queued(node, dotted)
|
|
610
|
+
if status == "value":
|
|
611
|
+
self.fail(node, "PS2002", f"module {dotted} is not a value in the static subset")
|
|
612
|
+
if module == "math" and member in MATH_CONSTANTS:
|
|
613
|
+
return {"kind": "const", "value": MATH_CONSTANTS[member], "type": {"kind": "float"}, "span": span}
|
|
614
|
+
constant = module_constant(module, member)
|
|
615
|
+
if constant is not None:
|
|
616
|
+
value, typ = constant
|
|
617
|
+
return {"kind": "const", "value": value, "type": typ, "span": span}
|
|
618
|
+
if module == "sys" and member == "argv":
|
|
619
|
+
return self._abi_call("sys", "argv", [], {"kind": "list", "elem": {"kind": "str"}}, "ps_argv_get", span)
|
|
620
|
+
if (module == "math" and member in MATH_FUNCTIONS) or wave_function(module, member) is not None:
|
|
621
|
+
self.fail(node, "PS2002", f"{dotted} is a function and is not a value")
|
|
622
|
+
if member in SPECIAL_FUNCTIONS.get(module, frozenset()) or member_callable(module, member):
|
|
623
|
+
self.fail(node, "PS2002", f"{dotted} is a function and is not a value")
|
|
624
|
+
self.fail(node, "PS2001", f"{dotted} is not in the lowered subset of {module}")
|
|
625
|
+
class_name = self.class_aliases.get(node.value.id, node.value.id) if isinstance(node.value, ast.Name) else None
|
|
626
|
+
if class_name is not None and class_name in self.classes:
|
|
627
|
+
info = self.classes[class_name]
|
|
628
|
+
if info.get("enum"):
|
|
629
|
+
member = info["members"].get(node.attr)
|
|
630
|
+
if member is None:
|
|
631
|
+
self.fail(node, "PS2001", f"{node.value.id}.{node.attr} is not an enum member")
|
|
632
|
+
return {"kind": "const", "value": str(member), "type": {"kind": "int"}, "span": span}
|
|
633
|
+
value = self._expr(node.value)
|
|
634
|
+
return self._lookup_attr(value, node.attr, span, node)
|
|
635
|
+
|
|
636
|
+
def _call(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
637
|
+
if isinstance(node.func, ast.Name):
|
|
638
|
+
return self._call_name(node, span, expect)
|
|
639
|
+
if isinstance(node.func, ast.Attribute):
|
|
640
|
+
return self._call_attr(node, span, expect)
|
|
641
|
+
self.fail(node, "PS2002", "this call is not lowered")
|
|
642
|
+
raise AssertionError("unreachable")
|
|
643
|
+
|
|
644
|
+
def _truthy(self, expr: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
645
|
+
kind = expr["type"]["kind"]
|
|
646
|
+
if kind == "bool":
|
|
647
|
+
return expr
|
|
648
|
+
if kind == "int":
|
|
649
|
+
return _compare("!=", expr, _const_int(0, span), span)
|
|
650
|
+
if kind == "float":
|
|
651
|
+
zero = {"kind": "const", "value": "0.0", "type": {"kind": "float"}, "span": span}
|
|
652
|
+
return _compare("!=", expr, zero, span)
|
|
653
|
+
if kind in {"str", "bytes", "list", "dict"}:
|
|
654
|
+
length = {
|
|
655
|
+
"kind": "call",
|
|
656
|
+
"callee": {"kind": "builtin", "name": "len"},
|
|
657
|
+
"args": [expr],
|
|
658
|
+
"type": {"kind": "int"},
|
|
659
|
+
"span": span,
|
|
660
|
+
}
|
|
661
|
+
return _compare("!=", length, _const_int(0, span), span)
|
|
662
|
+
self.fail(ast.Pass(), "PS2004", f"and/or on {kind} is not lowered")
|
|
663
|
+
raise AssertionError("unreachable")
|
|
664
|
+
|
|
665
|
+
def _mixed_cond_kind(self, kind: str) -> bool:
|
|
666
|
+
return kind == "bool" or kind in _TRUTHY
|
|
667
|
+
|
|
668
|
+
def _bool_operand(self, node: ast.expr, as_cond: bool) -> dict[str, Any]:
|
|
669
|
+
if as_cond and isinstance(node, ast.BoolOp):
|
|
670
|
+
return self._boolop(node, span_of(node, self.filename), as_cond=True)
|
|
671
|
+
return self._expr(node)
|
|
672
|
+
|
|
673
|
+
def _boolop(self, node: ast.BoolOp, span: dict[str, Any], *, as_cond: bool = False) -> dict[str, Any]:
|
|
674
|
+
"""Same-type `and`/`or` still yields the operand.
|
|
675
|
+
|
|
676
|
+
A mixed `and`/`or` lowers as the truthiness of each side joined by bool
|
|
677
|
+
`and`/`or`, so the result is always a bool. That diverges from CPython,
|
|
678
|
+
where `a or b` returns the deciding operand with its own type; the IR
|
|
679
|
+
has no tagged union for `T_a | T_b` (epic E3c). The right side is
|
|
680
|
+
evaluated only when the left does not decide it.
|
|
681
|
+
"""
|
|
682
|
+
op_and = isinstance(node.op, ast.And)
|
|
683
|
+
first = self._bool_operand(node.values[0], as_cond)
|
|
684
|
+
typ = first["type"]
|
|
685
|
+
if typ["kind"] not in _TRUTHY | {"bool", "float"}:
|
|
686
|
+
self.fail(node, "PS2004", f"and/or on {typ['kind']} is not lowered")
|
|
687
|
+
temp = self._temp("boolop")
|
|
688
|
+
self.locals[temp] = typ
|
|
689
|
+
self.prelude.append(_assign(temp, first, span))
|
|
690
|
+
for value_node in node.values[1:]:
|
|
691
|
+
saved = self.prelude
|
|
692
|
+
self.prelude = []
|
|
693
|
+
right = self._bool_operand(value_node, as_cond)
|
|
694
|
+
inner = self.prelude
|
|
695
|
+
self.prelude = saved
|
|
696
|
+
if not types_equal(right["type"], typ):
|
|
697
|
+
left_ok = self._mixed_cond_kind(typ["kind"])
|
|
698
|
+
right_ok = self._mixed_cond_kind(right["type"]["kind"])
|
|
699
|
+
if not left_ok or not right_ok:
|
|
700
|
+
self.fail(node, "PS2004", "and/or across two types is not lowered")
|
|
701
|
+
if typ["kind"] != "bool":
|
|
702
|
+
bool_temp = self._temp("cond")
|
|
703
|
+
self.locals[bool_temp] = {"kind": "bool"}
|
|
704
|
+
self.prelude.append(_assign(bool_temp, self._truthy(_name(temp, typ, span), span), span))
|
|
705
|
+
temp = bool_temp
|
|
706
|
+
typ = {"kind": "bool"}
|
|
707
|
+
inner.append(_assign(temp, self._truthy(right, span), span))
|
|
708
|
+
else:
|
|
709
|
+
inner.append(_assign(temp, right, span))
|
|
710
|
+
cond = self._truthy(_name(temp, typ, span), span)
|
|
711
|
+
if not op_and:
|
|
712
|
+
cond = {"kind": "unary", "op": "not", "operand": cond, "type": {"kind": "bool"}, "span": span}
|
|
713
|
+
self.prelude.append({"kind": "if", "cond": cond, "then": inner, "else": [], "span": span})
|
|
714
|
+
return _name(temp, typ, span)
|
|
715
|
+
|
|
716
|
+
def _ifexp(self, node: ast.IfExp, span: dict[str, Any]) -> dict[str, Any]:
|
|
717
|
+
"""`a if cond else b` is the if statement BoolOp already uses.
|
|
718
|
+
|
|
719
|
+
The condition is whatever `if` already accepts. Both operands lower to
|
|
720
|
+
one type. Each operand's statements sit in its own branch, so the
|
|
721
|
+
untaken call does not run. A nested conditional is the same desugar.
|
|
722
|
+
"""
|
|
723
|
+
cond = self._bool_cond(node.test)
|
|
724
|
+
saved = self.prelude
|
|
725
|
+
self.prelude = []
|
|
726
|
+
try:
|
|
727
|
+
body = self._expr(node.body)
|
|
728
|
+
body_pre = self.prelude
|
|
729
|
+
self.prelude = []
|
|
730
|
+
orelse = self._expr(node.orelse)
|
|
731
|
+
orelse_pre = self.prelude
|
|
732
|
+
finally:
|
|
733
|
+
self.prelude = saved
|
|
734
|
+
if not types_equal(body["type"], orelse["type"]):
|
|
735
|
+
self.fail(node, "PS2004", "conditional expression across two types is not lowered")
|
|
736
|
+
typ = body["type"]
|
|
737
|
+
then_body = self._ifexp_branch(body_pre, body, span)
|
|
738
|
+
else_body = self._ifexp_branch(orelse_pre, orelse, span)
|
|
739
|
+
if typ["kind"] == "none":
|
|
740
|
+
# None is not a register. The if still runs the taken operand.
|
|
741
|
+
self.prelude.append({"kind": "if", "cond": cond, "then": then_body, "else": else_body, "span": span})
|
|
742
|
+
return _const_none(span)
|
|
743
|
+
temp = self._temp("ifexp")
|
|
744
|
+
self.locals[temp] = typ
|
|
745
|
+
then_body.append(_assign(temp, body, span))
|
|
746
|
+
else_body.append(_assign(temp, orelse, span))
|
|
747
|
+
self.prelude.append({"kind": "if", "cond": cond, "then": then_body, "else": else_body, "span": span})
|
|
748
|
+
return _name(temp, typ, span)
|
|
749
|
+
|
|
750
|
+
def _ifexp_branch(
|
|
751
|
+
self, prelude: list[dict[str, Any]], value: dict[str, Any], span: dict[str, Any]
|
|
752
|
+
) -> list[dict[str, Any]]:
|
|
753
|
+
if value["type"]["kind"] != "none":
|
|
754
|
+
return list(prelude)
|
|
755
|
+
body = list(prelude)
|
|
756
|
+
if value["kind"] != "const":
|
|
757
|
+
body.append({"kind": "expr", "expr": value, "span": span})
|
|
758
|
+
return body
|
|
759
|
+
|
|
760
|
+
def _joined(self, node: ast.JoinedStr, span: dict[str, Any]) -> dict[str, Any]:
|
|
761
|
+
parts: list[dict[str, Any]] = []
|
|
762
|
+
for value in node.values:
|
|
763
|
+
if isinstance(value, ast.Constant) and isinstance(value.value, str):
|
|
764
|
+
parts.append(self._const(value, span_of(value, self.filename)))
|
|
765
|
+
continue
|
|
766
|
+
if isinstance(value, ast.FormattedValue):
|
|
767
|
+
if value.conversion != -1 or value.format_spec is not None:
|
|
768
|
+
self.fail(value, "PS2002", "f-string format specs are not lowered")
|
|
769
|
+
parts.append(self._as_str(self._expr(value.value), value))
|
|
770
|
+
continue
|
|
771
|
+
self.fail(value, "PS2002", "f-strings are not lowered")
|
|
772
|
+
if self.function is None:
|
|
773
|
+
folded = _const_str_join(parts, span)
|
|
774
|
+
if folded is not None:
|
|
775
|
+
return folded
|
|
776
|
+
return _concat_str(parts, span)
|
|
777
|
+
|
|
778
|
+
def _as_str(self, expr: dict[str, Any], node: ast.AST) -> dict[str, Any]:
|
|
779
|
+
kind = expr["type"]["kind"]
|
|
780
|
+
span = expr["span"]
|
|
781
|
+
if kind == "str":
|
|
782
|
+
return expr
|
|
783
|
+
if kind in {"int", "float"}:
|
|
784
|
+
return {
|
|
785
|
+
"kind": "call",
|
|
786
|
+
"callee": {"kind": "builtin", "name": "str"},
|
|
787
|
+
"args": [expr],
|
|
788
|
+
"type": {"kind": "str"},
|
|
789
|
+
"span": span,
|
|
790
|
+
}
|
|
791
|
+
self.fail(node, "PS2002", f"formatting {_show(expr['type'])} is not lowered")
|
|
792
|
+
raise AssertionError("unreachable")
|
|
793
|
+
|
|
794
|
+
def _percent(self, node: ast.BinOp, span: dict[str, Any]) -> dict[str, Any]:
|
|
795
|
+
if not isinstance(node.left, ast.Constant) or not isinstance(node.left.value, str):
|
|
796
|
+
self.fail(node, "PS2004", "a format string must be a literal")
|
|
797
|
+
if any(ord(ch) > 255 for ch in node.left.value):
|
|
798
|
+
self.fail(node, "PS2005", "string literals outside Latin-1 are not lowered")
|
|
799
|
+
pieces = _percent_pieces(node.left.value)
|
|
800
|
+
if pieces is None:
|
|
801
|
+
self.fail(node, "PS2004", "% formatting is lowered for %s and %d")
|
|
802
|
+
specs = [kind for kind, _text in pieces if kind != "lit"]
|
|
803
|
+
if isinstance(node.right, ast.Tuple):
|
|
804
|
+
values = list(node.right.elts)
|
|
805
|
+
else:
|
|
806
|
+
values = [node.right]
|
|
807
|
+
if len(values) != len(specs):
|
|
808
|
+
self.fail(node, "PS2003", "format string and values disagree")
|
|
809
|
+
rendered: list[dict[str, Any]] = []
|
|
810
|
+
index = 0
|
|
811
|
+
for kind, text in pieces:
|
|
812
|
+
if kind == "lit":
|
|
813
|
+
if text:
|
|
814
|
+
rendered.append({"kind": "const", "value": text, "type": {"kind": "str"}, "span": span})
|
|
815
|
+
continue
|
|
816
|
+
expr = self._expr(values[index])
|
|
817
|
+
index += 1
|
|
818
|
+
if kind == "s":
|
|
819
|
+
if expr["type"]["kind"] != "str":
|
|
820
|
+
self.fail(node, "PS1003", "%s takes a str")
|
|
821
|
+
rendered.append(expr)
|
|
822
|
+
else:
|
|
823
|
+
if expr["type"]["kind"] != "int":
|
|
824
|
+
self.fail(node, "PS1003", "%d takes an int")
|
|
825
|
+
rendered.append(self._as_str(expr, node))
|
|
826
|
+
return _concat_str(rendered, span)
|
|
827
|
+
|
|
828
|
+
def _slice(self, node: ast.Subscript, span: dict[str, Any]) -> dict[str, Any]:
|
|
829
|
+
slc = node.slice
|
|
830
|
+
assert isinstance(slc, ast.Slice)
|
|
831
|
+
value = self._expr(node.value)
|
|
832
|
+
kind = value["type"]["kind"]
|
|
833
|
+
if kind == "list" and value["type"].get("set"):
|
|
834
|
+
self.fail(node, "PS2002", "a set has no subscript")
|
|
835
|
+
if kind not in {"str", "bytes", "list"}:
|
|
836
|
+
self.fail(node, "PS2002", "slices are lowered for str, bytes, and list")
|
|
837
|
+
start, has_start = self._slice_bound(slc.lower, span)
|
|
838
|
+
stop, has_stop = self._slice_bound(slc.upper, span)
|
|
839
|
+
step = self._slice_step(slc.step, span)
|
|
840
|
+
start = self._pin_slice_int(start, span)
|
|
841
|
+
stop = self._pin_slice_int(stop, span)
|
|
842
|
+
step = self._pin_slice_int(step, span)
|
|
843
|
+
symbol = "ps_seq_slice" if kind == "list" else "ps_str_slice"
|
|
844
|
+
result = value["type"] if kind != "list" else value["type"]
|
|
845
|
+
if kind == "str":
|
|
846
|
+
result = {"kind": "str"}
|
|
847
|
+
elif kind == "bytes":
|
|
848
|
+
result = {"kind": "bytes"}
|
|
849
|
+
return self._abi_call(
|
|
850
|
+
"builtins",
|
|
851
|
+
"slice",
|
|
852
|
+
[value, start, stop, _const_int(has_start, span), _const_int(has_stop, span), step],
|
|
853
|
+
result,
|
|
854
|
+
symbol,
|
|
855
|
+
span,
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
def _slice_bound(self, node: ast.expr | None, span: dict[str, Any]) -> tuple[dict[str, Any], int]:
|
|
859
|
+
if node is None:
|
|
860
|
+
return _const_int(0, span), 0
|
|
861
|
+
expr = self._expr(node, {"kind": "int"})
|
|
862
|
+
if expr["type"]["kind"] != "int":
|
|
863
|
+
self.fail(node, "PS1003", "a slice bound is int")
|
|
864
|
+
return expr, 1
|
|
865
|
+
|
|
866
|
+
def _slice_step(self, node: ast.expr | None, span: dict[str, Any]) -> dict[str, Any]:
|
|
867
|
+
"""Any nonzero step lowers; the runtime walks backwards for step < 0.
|
|
868
|
+
|
|
869
|
+
Step 0 is the runtime's ValueError. The step expression is one operand,
|
|
870
|
+
so it runs once.
|
|
871
|
+
"""
|
|
872
|
+
if node is None:
|
|
873
|
+
return _const_int(1, span)
|
|
874
|
+
expr = self._expr(node, {"kind": "int"})
|
|
875
|
+
if expr["type"]["kind"] != "int":
|
|
876
|
+
self.fail(node, "PS1003", "a slice step is int")
|
|
877
|
+
return expr
|
|
878
|
+
|
|
879
|
+
def _pin_slice_int(self, expr: dict[str, Any], span: dict[str, Any]) -> dict[str, Any]:
|
|
880
|
+
"""Store a slice operand once. A module call emits its arguments twice."""
|
|
881
|
+
if expr["kind"] in {"const", "name"}:
|
|
882
|
+
return expr
|
|
883
|
+
temp = self._temp("sl")
|
|
884
|
+
self.locals[temp] = {"kind": "int"}
|
|
885
|
+
self.prelude.append(_assign(temp, expr, span))
|
|
886
|
+
return _name(temp, {"kind": "int"}, span)
|