python-cc 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pcc/cli_bootstrap.py +3 -3
- pcc/cli_core.py +15 -0
- pcc/llvm_capi/ir.py +10 -20
- pcc/parse/py_lex.py +163 -67
- pcc/parse/py_lift.py +40 -2
- pcc/parse/py_parse.py +16 -7
- pcc/py_frontend/codegen/class_gen.py +5 -0
- pcc/py_frontend/codegen/layer1.py +908 -243
- pcc/py_frontend/codegen/marshal.py +22 -6
- pcc/py_frontend/codegen/runtime_abi.py +4 -0
- pcc/py_frontend/export_meta.py +4 -1
- pcc/py_frontend/pipeline.py +8 -1
- pcc/py_frontend/type_infer.py +116 -28
- pcc/py_frontend/types.py +3 -0
- pcc/py_runtime/include/py_runtime.h +4 -0
- pcc/py_runtime/py/py_obj_ops_compare.py +17 -0
- pcc/py_runtime/py/py_set.py +24 -0
- pcc/py_runtime/py/py_str_accessors.py +66 -5
- pcc/py_runtime/src/py_obj_ops_compare.c +12 -0
- pcc/py_runtime/src/py_set.c +12 -0
- pcc/py_runtime/src/py_str_accessors.c +56 -8
- python_cc-0.1.2.dist-info/METADATA +448 -0
- {python_cc-0.1.1.dist-info → python_cc-0.1.2.dist-info}/RECORD +26 -26
- python_cc-0.1.2.dist-info/entry_points.txt +3 -0
- python_cc-0.1.1.dist-info/METADATA +0 -985
- python_cc-0.1.1.dist-info/entry_points.txt +0 -2
- {python_cc-0.1.1.dist-info → python_cc-0.1.2.dist-info}/WHEEL +0 -0
- {python_cc-0.1.1.dist-info → python_cc-0.1.2.dist-info}/licenses/LICENSE +0 -0
pcc/cli_bootstrap.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
-
from .py_frontend import
|
|
4
|
+
from .py_frontend.pipeline import compile_python as _compile_python
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
_DEFAULT_EMIT_LL = "__PCC_DEFAULT_LL__"
|
|
@@ -280,7 +280,7 @@ def bootstrap_cli_main(argv=None) -> int:
|
|
|
280
280
|
ll_out = output_path if output_path else path[:-3] + ".ll"
|
|
281
281
|
else:
|
|
282
282
|
ll_out = output_path if output_path else emit_llvm
|
|
283
|
-
|
|
283
|
+
_compile_python(
|
|
284
284
|
path,
|
|
285
285
|
ll_out,
|
|
286
286
|
verbose=verbose,
|
|
@@ -291,7 +291,7 @@ def bootstrap_cli_main(argv=None) -> int:
|
|
|
291
291
|
recursive_stdlib=False,
|
|
292
292
|
)
|
|
293
293
|
else:
|
|
294
|
-
|
|
294
|
+
_compile_python(
|
|
295
295
|
path,
|
|
296
296
|
output_path,
|
|
297
297
|
verbose=verbose,
|
pcc/cli_core.py
CHANGED
|
@@ -1166,3 +1166,18 @@ def cli_main_sys_argv_exit() -> None:
|
|
|
1166
1166
|
from sys import exit as _exit
|
|
1167
1167
|
|
|
1168
1168
|
_exit(code)
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
def cli_main_strict_sys_argv_exit() -> None:
|
|
1172
|
+
"""Entry point for the ``pcc-static`` console script.
|
|
1173
|
+
|
|
1174
|
+
Same as ``pcc`` but with the no-libpython flag combination
|
|
1175
|
+
(``--python-libpython=off`` + ``--ir-scaffold=on``) wired in as
|
|
1176
|
+
the default. An explicitly-passed ``--python-libpython`` /
|
|
1177
|
+
``--ir-scaffold`` CLI flag, or a pre-set ``PCC_PYTHON_LIBPYTHON``
|
|
1178
|
+
/ ``PCC_IR_SCAFFOLD`` env var, still takes precedence.
|
|
1179
|
+
"""
|
|
1180
|
+
import os
|
|
1181
|
+
os.environ.setdefault("PCC_PYTHON_LIBPYTHON", "off")
|
|
1182
|
+
os.environ.setdefault("PCC_IR_SCAFFOLD", "on")
|
|
1183
|
+
cli_main_sys_argv_exit()
|
pcc/llvm_capi/ir.py
CHANGED
|
@@ -799,11 +799,11 @@ class Function:
|
|
|
799
799
|
# stay clean.
|
|
800
800
|
self._name_counter = len(self.args)
|
|
801
801
|
self._block_counter = 0
|
|
802
|
-
# Track
|
|
803
|
-
#
|
|
804
|
-
#
|
|
805
|
-
#
|
|
806
|
-
#
|
|
802
|
+
# Track explicit names used by blocks and the small number of
|
|
803
|
+
# callers that require llvmlite-style stable deduplication. SSA
|
|
804
|
+
# instruction names are allocated from ``_name_counter`` instead;
|
|
805
|
+
# pcc-Python's dict is too expensive for per-instruction use in
|
|
806
|
+
# self-hosted codegen.
|
|
807
807
|
self._name_registry: dict[str, int] = {}
|
|
808
808
|
self.linkage = ""
|
|
809
809
|
self.attributes = FunctionAttributes()
|
|
@@ -1209,25 +1209,15 @@ class IRBuilder:
|
|
|
1209
1209
|
fn = self._fn
|
|
1210
1210
|
if fn is None:
|
|
1211
1211
|
raise ValueError("IRBuilder has no current function")
|
|
1212
|
+
fn._name_counter += 1
|
|
1212
1213
|
if name == "":
|
|
1213
|
-
fn._name_counter += 1
|
|
1214
1214
|
return f".{fn._name_counter}"
|
|
1215
|
-
|
|
1216
|
-
fn._name_registry[name] = n + 1
|
|
1217
|
-
if n == 0:
|
|
1218
|
-
return name
|
|
1219
|
-
candidate = f"{name}.{n}"
|
|
1220
|
-
while candidate in fn._name_registry:
|
|
1221
|
-
n += 1
|
|
1222
|
-
candidate = f"{name}.{n}"
|
|
1223
|
-
fn._name_registry[candidate] = 1
|
|
1224
|
-
return candidate
|
|
1215
|
+
return f"{name}.{fn._name_counter}"
|
|
1225
1216
|
|
|
1226
1217
|
def _next(self, name: str, ty: Type) -> Value:
|
|
1227
1218
|
"""Produce a new local SSA value with ``name`` (or a fresh temp
|
|
1228
|
-
if empty).
|
|
1229
|
-
|
|
1230
|
-
— same as llvmlite."""
|
|
1219
|
+
if empty). Named temporaries use a function-wide serial suffix,
|
|
1220
|
+
trading llvmlite text parity for O(1) self-hosted codegen."""
|
|
1231
1221
|
return Value(ty, f"%{self._next_name(name)}")
|
|
1232
1222
|
|
|
1233
1223
|
# ------------- return / branch / terminator -------------
|
|
@@ -1924,7 +1914,7 @@ def Module___init___named(name):
|
|
|
1924
1914
|
return Module(name=name)
|
|
1925
1915
|
|
|
1926
1916
|
|
|
1927
|
-
def
|
|
1917
|
+
def scaffold_Module___init__():
|
|
1928
1918
|
return Module(name="")
|
|
1929
1919
|
|
|
1930
1920
|
|
pcc/parse/py_lex.py
CHANGED
|
@@ -43,6 +43,65 @@ _OPS_MULTI = (
|
|
|
43
43
|
_OPS_SINGLE = "+-*/%@&|^~<>=()[]{},:.;"
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def _is_digit_code(c: int) -> bool:
|
|
47
|
+
return c >= 48 and c <= 57
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_alpha_code(c: int) -> bool:
|
|
51
|
+
return (c >= 65 and c <= 90) or (c >= 97 and c <= 122)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_alnum_code(c: int) -> bool:
|
|
55
|
+
return _is_alpha_code(c) or _is_digit_code(c)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _lower_code(c: int) -> int:
|
|
59
|
+
if c >= 65 and c <= 90:
|
|
60
|
+
return c + 32
|
|
61
|
+
return c
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_prefix_code(c: int) -> bool:
|
|
65
|
+
lc = _lower_code(c)
|
|
66
|
+
return lc == 98 or lc == 102 or lc == 114 or lc == 117
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_quote_code(c: int) -> bool:
|
|
70
|
+
return c == 34 or c == 39
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _is_single_op_code(c: int) -> bool:
|
|
74
|
+
return (
|
|
75
|
+
c == 37 or c == 38 or c == 40 or c == 41 or c == 42
|
|
76
|
+
or c == 43 or c == 44 or c == 45 or c == 46 or c == 47
|
|
77
|
+
or c == 58 or c == 59 or c == 60 or c == 61 or c == 62
|
|
78
|
+
or c == 64 or c == 91 or c == 93 or c == 94 or c == 123
|
|
79
|
+
or c == 124 or c == 125 or c == 126
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _is_open_paren_code(c: int) -> bool:
|
|
84
|
+
return c == 40 or c == 91 or c == 123
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _is_close_paren_code(c: int) -> bool:
|
|
88
|
+
return c == 41 or c == 93 or c == 125
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _pcc_str_byte_len(s: str) -> int:
|
|
92
|
+
return len(s)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _pcc_str_byte_at(s: str, pos: int) -> int:
|
|
96
|
+
if pos < len(s):
|
|
97
|
+
return ord(s[pos])
|
|
98
|
+
return -1
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _pcc_str_byte_slice(s: str, lo: int, hi: int) -> str:
|
|
102
|
+
return s[lo:hi]
|
|
103
|
+
|
|
104
|
+
|
|
46
105
|
@dataclass(frozen=True)
|
|
47
106
|
class Token:
|
|
48
107
|
kind: str
|
|
@@ -70,6 +129,7 @@ class Lexer:
|
|
|
70
129
|
|
|
71
130
|
def __init__(self, src: str, filename: str = "<input>") -> None:
|
|
72
131
|
self.src = src
|
|
132
|
+
self._src_len = _pcc_str_byte_len(src)
|
|
73
133
|
self.filename = filename
|
|
74
134
|
self.pos = 0
|
|
75
135
|
self.line = 1
|
|
@@ -84,51 +144,51 @@ class Lexer:
|
|
|
84
144
|
"""Run the lexer to completion, returning all tokens."""
|
|
85
145
|
out: list[Token] = []
|
|
86
146
|
indent_stack: list[int] = self._indent_stack
|
|
87
|
-
while self.pos <
|
|
147
|
+
while self.pos < self._src_len:
|
|
88
148
|
if self._at_line_start and self._paren_depth == 0:
|
|
89
149
|
self._emit_indent(out)
|
|
90
150
|
self._at_line_start = False
|
|
91
|
-
if self.pos >=
|
|
151
|
+
if self.pos >= self._src_len:
|
|
92
152
|
break
|
|
93
|
-
ch = self.
|
|
94
|
-
if ch ==
|
|
153
|
+
ch = self._peek_code(0)
|
|
154
|
+
if ch == 10:
|
|
95
155
|
self._emit_newline(out)
|
|
96
156
|
continue
|
|
97
|
-
if ch
|
|
157
|
+
if ch == 32 or ch == 9:
|
|
98
158
|
self.pos += 1
|
|
99
159
|
self.col += 1
|
|
100
160
|
continue
|
|
101
|
-
if ch ==
|
|
102
|
-
while self.pos <
|
|
161
|
+
if ch == 35:
|
|
162
|
+
while self.pos < self._src_len and self._peek_code(0) != 10:
|
|
103
163
|
self.pos += 1
|
|
104
164
|
continue
|
|
105
|
-
if ch ==
|
|
165
|
+
if ch == 92 and self._peek_code(1) == 10:
|
|
106
166
|
self.pos += 2
|
|
107
167
|
self.line += 1
|
|
108
168
|
self.col = 1
|
|
109
169
|
continue
|
|
110
170
|
if (
|
|
111
|
-
ch
|
|
112
|
-
and self.
|
|
171
|
+
_is_prefix_code(ch)
|
|
172
|
+
and _is_quote_code(self._peek_code(1))
|
|
113
173
|
):
|
|
114
174
|
out.append(self._read_string())
|
|
115
175
|
continue
|
|
116
176
|
if (
|
|
117
|
-
ch
|
|
118
|
-
and self.
|
|
119
|
-
and self.
|
|
177
|
+
_is_prefix_code(ch)
|
|
178
|
+
and _is_prefix_code(self._peek_code(1))
|
|
179
|
+
and _is_quote_code(self._peek_code(2))
|
|
120
180
|
):
|
|
121
181
|
out.append(self._read_string())
|
|
122
182
|
continue
|
|
123
|
-
if ch
|
|
183
|
+
if _is_alpha_code(ch) or ch == 95:
|
|
124
184
|
out.append(self._read_name())
|
|
125
185
|
continue
|
|
126
|
-
if ch
|
|
127
|
-
ch ==
|
|
186
|
+
if _is_digit_code(ch) or (
|
|
187
|
+
ch == 46 and _is_digit_code(self._peek_code(1))
|
|
128
188
|
):
|
|
129
189
|
out.append(self._read_number())
|
|
130
190
|
continue
|
|
131
|
-
if ch
|
|
191
|
+
if _is_quote_code(ch):
|
|
132
192
|
out.append(self._read_string())
|
|
133
193
|
continue
|
|
134
194
|
out.append(self._read_op())
|
|
@@ -142,18 +202,31 @@ class Lexer:
|
|
|
142
202
|
|
|
143
203
|
# ------------------------------------------------------ helpers
|
|
144
204
|
|
|
205
|
+
def _code_at(self, pos: int) -> int:
|
|
206
|
+
if pos < self._src_len:
|
|
207
|
+
return _pcc_str_byte_at(self.src, pos)
|
|
208
|
+
return -1
|
|
209
|
+
|
|
210
|
+
def _slice(self, lo: int, hi: int) -> str:
|
|
211
|
+
return _pcc_str_byte_slice(self.src, lo, hi)
|
|
212
|
+
|
|
213
|
+
def _peek_code(self, off: int) -> int:
|
|
214
|
+
return self._code_at(self.pos + off)
|
|
215
|
+
|
|
145
216
|
def _peek(self, off: int) -> str:
|
|
146
217
|
p = self.pos + off
|
|
147
|
-
return self.
|
|
218
|
+
return self._slice(p, p + 1) if p < self._src_len else ""
|
|
148
219
|
|
|
149
220
|
def _emit_indent(self, out: list[Token]) -> None:
|
|
150
221
|
indent_stack: list[int] = self._indent_stack
|
|
151
222
|
depth = 0
|
|
152
223
|
p = self.pos
|
|
153
|
-
|
|
154
|
-
|
|
224
|
+
pc = self._code_at(p)
|
|
225
|
+
while p < self._src_len and (pc == 32 or pc == 9):
|
|
226
|
+
depth += 1 if pc == 32 else 8
|
|
155
227
|
p += 1
|
|
156
|
-
|
|
228
|
+
pc = self._code_at(p)
|
|
229
|
+
if p >= self._src_len or pc == 10 or pc == 35:
|
|
157
230
|
self.pos = p
|
|
158
231
|
self.col = depth + 1
|
|
159
232
|
return
|
|
@@ -183,12 +256,15 @@ class Lexer:
|
|
|
183
256
|
start = self.pos
|
|
184
257
|
start_col = self.col
|
|
185
258
|
while (
|
|
186
|
-
self.pos <
|
|
187
|
-
and (
|
|
259
|
+
self.pos < self._src_len
|
|
260
|
+
and (
|
|
261
|
+
_is_alnum_code(self._peek_code(0))
|
|
262
|
+
or self._peek_code(0) == 95
|
|
263
|
+
)
|
|
188
264
|
):
|
|
189
265
|
self.pos += 1
|
|
190
266
|
self.col += 1
|
|
191
|
-
text = self.
|
|
267
|
+
text = self._slice(start, self.pos)
|
|
192
268
|
kind = TK_KEYWORD if text in KEYWORDS else TK_NAME
|
|
193
269
|
return Token(kind, text, self.line, start_col)
|
|
194
270
|
|
|
@@ -197,47 +273,62 @@ class Lexer:
|
|
|
197
273
|
start_col = self.col
|
|
198
274
|
# Hex / octal / binary: ``0x…`` / ``0o…`` / ``0b…`` (PEP 3127).
|
|
199
275
|
if (
|
|
200
|
-
self.
|
|
201
|
-
and self.pos + 1 <
|
|
202
|
-
and
|
|
276
|
+
self._peek_code(0) == 48
|
|
277
|
+
and self.pos + 1 < self._src_len
|
|
278
|
+
and (
|
|
279
|
+
_lower_code(self._code_at(self.pos + 1)) == 98
|
|
280
|
+
or _lower_code(self._code_at(self.pos + 1)) == 111
|
|
281
|
+
or _lower_code(self._code_at(self.pos + 1)) == 120
|
|
282
|
+
)
|
|
203
283
|
):
|
|
204
|
-
base = self.
|
|
284
|
+
base = _lower_code(self._code_at(self.pos + 1))
|
|
205
285
|
self.pos += 2
|
|
206
286
|
self.col += 2
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
287
|
+
while self.pos < self._src_len:
|
|
288
|
+
c = self._peek_code(0)
|
|
289
|
+
valid = False
|
|
290
|
+
if c == 95:
|
|
291
|
+
valid = True
|
|
292
|
+
elif base == 120:
|
|
293
|
+
valid = (
|
|
294
|
+
_is_digit_code(c)
|
|
295
|
+
or (c >= 65 and c <= 70)
|
|
296
|
+
or (c >= 97 and c <= 102)
|
|
297
|
+
)
|
|
298
|
+
elif base == 111:
|
|
299
|
+
valid = c >= 48 and c <= 55
|
|
300
|
+
else:
|
|
301
|
+
valid = c == 48 or c == 49
|
|
302
|
+
if not valid:
|
|
303
|
+
break
|
|
214
304
|
self.pos += 1
|
|
215
305
|
self.col += 1
|
|
216
306
|
return Token(
|
|
217
|
-
TK_NUMBER, self.
|
|
307
|
+
TK_NUMBER, self._slice(start, self.pos), self.line, start_col,
|
|
218
308
|
)
|
|
219
309
|
has_dot = False
|
|
220
310
|
has_exp = False
|
|
221
|
-
while self.pos <
|
|
222
|
-
ch = self.
|
|
223
|
-
if ch
|
|
311
|
+
while self.pos < self._src_len:
|
|
312
|
+
ch = self._peek_code(0)
|
|
313
|
+
if _is_digit_code(ch):
|
|
224
314
|
self.pos += 1
|
|
225
315
|
self.col += 1
|
|
226
|
-
elif ch ==
|
|
316
|
+
elif ch == 46 and not has_dot and not has_exp:
|
|
227
317
|
has_dot = True
|
|
228
318
|
self.pos += 1
|
|
229
319
|
self.col += 1
|
|
230
|
-
elif ch
|
|
320
|
+
elif (ch == 101 or ch == 69) and not has_exp:
|
|
231
321
|
has_exp = True
|
|
232
322
|
self.pos += 1
|
|
233
323
|
self.col += 1
|
|
234
|
-
|
|
324
|
+
nxt = self._peek_code(0)
|
|
325
|
+
if self.pos < self._src_len and (nxt == 43 or nxt == 45):
|
|
235
326
|
self.pos += 1
|
|
236
327
|
self.col += 1
|
|
237
|
-
elif ch
|
|
328
|
+
elif ch == 95:
|
|
238
329
|
self.pos += 1
|
|
239
330
|
self.col += 1
|
|
240
|
-
elif ch
|
|
331
|
+
elif ch == 106 or ch == 74:
|
|
241
332
|
# Imaginary literal suffix — we accept the char so the
|
|
242
333
|
# tokenizer doesn't choke, the frontend doesn't use them.
|
|
243
334
|
self.pos += 1
|
|
@@ -245,7 +336,7 @@ class Lexer:
|
|
|
245
336
|
break
|
|
246
337
|
else:
|
|
247
338
|
break
|
|
248
|
-
return Token(TK_NUMBER, self.
|
|
339
|
+
return Token(TK_NUMBER, self._slice(start, self.pos), self.line, start_col)
|
|
249
340
|
|
|
250
341
|
def _read_string(self) -> Token:
|
|
251
342
|
start = self.pos
|
|
@@ -253,31 +344,34 @@ class Lexer:
|
|
|
253
344
|
# Consume the optional prefix chars (``b``, ``r``, ``f``, ``u``
|
|
254
345
|
# and their 2-char combinations).
|
|
255
346
|
while (
|
|
256
|
-
self.pos <
|
|
257
|
-
and self.
|
|
258
|
-
and self.
|
|
347
|
+
self.pos < self._src_len
|
|
348
|
+
and _is_prefix_code(self._peek_code(0))
|
|
349
|
+
and not _is_quote_code(self._peek_code(0))
|
|
259
350
|
):
|
|
260
351
|
self.pos += 1
|
|
261
352
|
self.col += 1
|
|
262
|
-
|
|
353
|
+
quote_code = self._peek_code(0)
|
|
263
354
|
# Handle triple-quoted.
|
|
264
|
-
triple =
|
|
355
|
+
triple = (
|
|
356
|
+
self._peek_code(1) == quote_code
|
|
357
|
+
and self._peek_code(2) == quote_code
|
|
358
|
+
)
|
|
265
359
|
if triple:
|
|
266
360
|
self.pos += 3
|
|
267
361
|
self.col += 3
|
|
268
|
-
while self.pos <
|
|
362
|
+
while self.pos < self._src_len:
|
|
269
363
|
if (
|
|
270
|
-
self.
|
|
271
|
-
and self.
|
|
272
|
-
and self.
|
|
364
|
+
self._peek_code(0) == quote_code
|
|
365
|
+
and self._peek_code(1) == quote_code
|
|
366
|
+
and self._peek_code(2) == quote_code
|
|
273
367
|
):
|
|
274
368
|
self.pos += 3
|
|
275
369
|
self.col += 3
|
|
276
370
|
return Token(
|
|
277
|
-
TK_STRING, self.
|
|
371
|
+
TK_STRING, self._slice(start, self.pos),
|
|
278
372
|
self.line, start_col,
|
|
279
373
|
)
|
|
280
|
-
if self.
|
|
374
|
+
if self._peek_code(0) == 10:
|
|
281
375
|
self.line += 1
|
|
282
376
|
self.col = 1
|
|
283
377
|
else:
|
|
@@ -289,20 +383,20 @@ class Lexer:
|
|
|
289
383
|
# Single-line.
|
|
290
384
|
self.pos += 1
|
|
291
385
|
self.col += 1
|
|
292
|
-
while self.pos <
|
|
293
|
-
ch = self.
|
|
294
|
-
if ch ==
|
|
386
|
+
while self.pos < self._src_len:
|
|
387
|
+
ch = self._peek_code(0)
|
|
388
|
+
if ch == 92:
|
|
295
389
|
self.pos += 2
|
|
296
390
|
self.col += 2
|
|
297
391
|
continue
|
|
298
|
-
if ch ==
|
|
392
|
+
if ch == quote_code:
|
|
299
393
|
self.pos += 1
|
|
300
394
|
self.col += 1
|
|
301
395
|
return Token(
|
|
302
|
-
TK_STRING, self.
|
|
396
|
+
TK_STRING, self._slice(start, self.pos),
|
|
303
397
|
self.line, start_col,
|
|
304
398
|
)
|
|
305
|
-
if ch ==
|
|
399
|
+
if ch == 10:
|
|
306
400
|
raise LexError(
|
|
307
401
|
f"{self.filename}:{self.line}: unterminated string"
|
|
308
402
|
)
|
|
@@ -317,19 +411,21 @@ class Lexer:
|
|
|
317
411
|
# Try 3-, 2-, then 1-char operators.
|
|
318
412
|
for op in _OPS_MULTI:
|
|
319
413
|
op_len = len(op)
|
|
320
|
-
if
|
|
414
|
+
if self._slice(pos, pos + op_len) == op:
|
|
321
415
|
self.pos = pos + op_len
|
|
322
416
|
self.col += op_len
|
|
323
417
|
return Token(TK_OP, op, self.line, start_col)
|
|
324
|
-
|
|
325
|
-
if
|
|
326
|
-
|
|
418
|
+
ch_code = self._peek_code(0)
|
|
419
|
+
if _is_single_op_code(ch_code):
|
|
420
|
+
ch = self._slice(self.pos, self.pos + 1)
|
|
421
|
+
if _is_open_paren_code(ch_code):
|
|
327
422
|
self._paren_depth += 1
|
|
328
|
-
elif
|
|
423
|
+
elif _is_close_paren_code(ch_code):
|
|
329
424
|
self._paren_depth = max(0, self._paren_depth - 1)
|
|
330
425
|
self.pos += 1
|
|
331
426
|
self.col += 1
|
|
332
427
|
return Token(TK_OP, ch, self.line, start_col)
|
|
428
|
+
ch = self._slice(self.pos, self.pos + 1)
|
|
333
429
|
raise LexError(
|
|
334
430
|
f"{self.filename}:{self.line}:{self.col}: stray character {ch!r}"
|
|
335
431
|
)
|
pcc/parse/py_lift.py
CHANGED
|
@@ -34,6 +34,42 @@ class LiftError(Exception):
|
|
|
34
34
|
"""Raised when a parser node has no py_ast equivalent yet."""
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def _pow10f_lift(exp: int) -> float:
|
|
38
|
+
out = float(int("1", 10))
|
|
39
|
+
ten = float(int("10", 10))
|
|
40
|
+
i = 0
|
|
41
|
+
while i < exp:
|
|
42
|
+
out = out * ten
|
|
43
|
+
i += 1
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_float_literal_lift(text: str) -> float:
|
|
48
|
+
exp = 0
|
|
49
|
+
mantissa = text
|
|
50
|
+
lower = text.lower()
|
|
51
|
+
e_idx = lower.find("e")
|
|
52
|
+
if e_idx >= 0:
|
|
53
|
+
mantissa = text[:e_idx]
|
|
54
|
+
exp = int(text[e_idx + 1:], 10)
|
|
55
|
+
dot_idx = mantissa.find(".")
|
|
56
|
+
frac_len = 0
|
|
57
|
+
digits = mantissa
|
|
58
|
+
if dot_idx >= 0:
|
|
59
|
+
frac_len = len(mantissa) - dot_idx - 1
|
|
60
|
+
digits = mantissa[:dot_idx] + mantissa[dot_idx + 1:]
|
|
61
|
+
if not digits:
|
|
62
|
+
digits = "0"
|
|
63
|
+
value = float(int(digits, 10))
|
|
64
|
+
if frac_len > 0:
|
|
65
|
+
value = value / _pow10f_lift(frac_len)
|
|
66
|
+
if exp > 0:
|
|
67
|
+
value = value * _pow10f_lift(exp)
|
|
68
|
+
elif exp < 0:
|
|
69
|
+
value = value / _pow10f_lift(-exp)
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
37
73
|
def lift_module(mod: pp._Module, filename: str, module_name: str) -> pa.Module:
|
|
38
74
|
L = _Lifter(filename)
|
|
39
75
|
body = tuple(L.lift_stmt(s) for s in mod.body)
|
|
@@ -355,12 +391,12 @@ class _Lifter:
|
|
|
355
391
|
return pa.IntLit(
|
|
356
392
|
span=span,
|
|
357
393
|
ty=pa.IntType(name="int", width=64, signed=True),
|
|
358
|
-
value=e.
|
|
394
|
+
value=int(e.text, 0),
|
|
359
395
|
)
|
|
360
396
|
return pa.FloatLit(
|
|
361
397
|
span=span,
|
|
362
398
|
ty=pa.FloatType(name="float", width=64),
|
|
363
|
-
value=e.
|
|
399
|
+
value=_parse_float_literal_lift(e.text),
|
|
364
400
|
)
|
|
365
401
|
|
|
366
402
|
def _e_Str(self, e: pp._Str) -> pa.StrLit:
|
|
@@ -686,6 +722,8 @@ def _lift_type(node) -> pa.Type:
|
|
|
686
722
|
key=_lift_type(inner.elems[0]),
|
|
687
723
|
value=_lift_type(inner.elems[1]),
|
|
688
724
|
)
|
|
725
|
+
if base.ident in ("set", "frozenset"):
|
|
726
|
+
return pa.DynType(name="set")
|
|
689
727
|
return _DYN
|
|
690
728
|
if isinstance(node, pp._BinOp) and node.op == "|":
|
|
691
729
|
# PEP 604 union — pcc has no Union type, fall back to Dyn.
|
pcc/parse/py_parse.py
CHANGED
|
@@ -49,10 +49,11 @@ def _join_strings(parts: list[str], sep: str) -> str:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def _pow10f(exp: int) -> float:
|
|
52
|
-
out = 1
|
|
52
|
+
out = float(int("1", 10))
|
|
53
|
+
ten = float(int("10", 10))
|
|
53
54
|
i = 0
|
|
54
55
|
while i < exp:
|
|
55
|
-
out = out *
|
|
56
|
+
out = out * ten
|
|
56
57
|
i += 1
|
|
57
58
|
return out
|
|
58
59
|
|
|
@@ -107,7 +108,7 @@ class _Expr:
|
|
|
107
108
|
|
|
108
109
|
@dataclass
|
|
109
110
|
class _Num:
|
|
110
|
-
|
|
111
|
+
text: str
|
|
111
112
|
line: int
|
|
112
113
|
is_int: bool
|
|
113
114
|
|
|
@@ -1193,8 +1194,11 @@ class Parser:
|
|
|
1193
1194
|
|
|
1194
1195
|
def _parse_compare(self):
|
|
1195
1196
|
lhs = self._parse_bitor()
|
|
1197
|
+
comparisons = []
|
|
1198
|
+
prev = lhs
|
|
1196
1199
|
while True:
|
|
1197
1200
|
t = self._peek()
|
|
1201
|
+
line = t.line
|
|
1198
1202
|
if t.kind == TK_OP and t.text in (
|
|
1199
1203
|
"<", ">", "<=", ">=", "==", "!=",
|
|
1200
1204
|
):
|
|
@@ -1220,8 +1224,13 @@ class Parser:
|
|
|
1220
1224
|
else:
|
|
1221
1225
|
break
|
|
1222
1226
|
rhs = self._parse_bitor()
|
|
1223
|
-
|
|
1224
|
-
|
|
1227
|
+
comparisons.append(_Compare(op=op, lhs=prev, rhs=rhs, line=line))
|
|
1228
|
+
prev = rhs
|
|
1229
|
+
if not comparisons:
|
|
1230
|
+
return lhs
|
|
1231
|
+
if len(comparisons) == 1:
|
|
1232
|
+
return comparisons[0]
|
|
1233
|
+
return _BoolOp(op="and", values=comparisons, line=comparisons[0].line)
|
|
1225
1234
|
|
|
1226
1235
|
def _parse_bitor(self):
|
|
1227
1236
|
lhs = self._parse_bitxor()
|
|
@@ -1596,8 +1605,8 @@ class Parser:
|
|
|
1596
1605
|
self._advance()
|
|
1597
1606
|
clean = t.text.replace("_", "")
|
|
1598
1607
|
if "." in clean or "e" in clean.lower():
|
|
1599
|
-
return _Num(
|
|
1600
|
-
return _Num(
|
|
1608
|
+
return _Num(clean, t.line, False)
|
|
1609
|
+
return _Num(clean, t.line, True)
|
|
1601
1610
|
if t.kind == TK_STRING:
|
|
1602
1611
|
self._advance()
|
|
1603
1612
|
strings = [t]
|
|
@@ -138,6 +138,11 @@ class ClassInfo:
|
|
|
138
138
|
# call-site lookups (e.g. ``_find_method_def``) see synthetic
|
|
139
139
|
# methods, not just what the user wrote.
|
|
140
140
|
self.expanded_cd: "ClassDef | None" = None
|
|
141
|
+
# Cross-module extern classes synthesize FuncDef stubs from export
|
|
142
|
+
# metadata so kwargs/default resolution can use normal method
|
|
143
|
+
# lowering. Keep this as a declared pcc field; dynamic post-init
|
|
144
|
+
# attributes are not reliable in the native object layout.
|
|
145
|
+
self.extern_method_defs: dict[str, FuncDef] = {}
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
class ClassLowering:
|