Skip to content

Commit 912b417

Browse files
jgarzikclaude
andcommitted
Add method arg validation, functools/itertools/pickle stubs
Infrastructure for importing real CPython tests: - Add min_args/max_args to PyBuiltinObject, validate in builtin_func_call Methods like list.append() now raise TypeError when called with wrong number of args instead of segfaulting. - Add builtin_func_new_checked and add_method_to_dict_checked helpers - Register list methods with proper arg count bounds - Create lib/functools.py (cmp_to_key, reduce, partial) - Create lib/itertools.py (chain, islice, count, repeat, cycle, accumulate, starmap, product) - Create lib/pickle.py (stub with HIGHEST_PROTOCOL constant) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9082da2 commit 912b417

6 files changed

Lines changed: 312 additions & 11 deletions

File tree

include/builtins.inc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ struc PyBuiltinObject
6060
.func_id: resq 1 ; +16: builtin function ID
6161
.func_name: resq 1 ; +24: ptr to name string
6262
.func_ptr: resq 1 ; +32: ptr to C implementation
63+
.min_args: resq 1 ; +40: minimum nargs (including self), 0 = no check
64+
.max_args: resq 1 ; +48: maximum nargs, -1 = no max check
6365
endstruc
6466

6567
%endif ; BUILTINS_INC

lib/functools.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# functools - adapted from CPython for apython
2+
3+
def cmp_to_key(mycmp):
4+
"""Convert a cmp= function into a key= function"""
5+
class K(object):
6+
__slots__ = ['obj']
7+
def __init__(self, obj):
8+
self.obj = obj
9+
def __lt__(self, other):
10+
return mycmp(self.obj, other.obj) < 0
11+
def __gt__(self, other):
12+
return mycmp(self.obj, other.obj) > 0
13+
def __eq__(self, other):
14+
return mycmp(self.obj, other.obj) == 0
15+
def __le__(self, other):
16+
return mycmp(self.obj, other.obj) <= 0
17+
def __ge__(self, other):
18+
return mycmp(self.obj, other.obj) >= 0
19+
__hash__ = None
20+
return K
21+
22+
23+
def reduce(function, iterable, initial=None):
24+
"""Apply function of two arguments cumulatively to iterable items."""
25+
it = iter(iterable)
26+
if initial is None:
27+
try:
28+
value = next(it)
29+
except StopIteration:
30+
raise TypeError("reduce() of empty iterable with no initial value")
31+
else:
32+
value = initial
33+
for element in it:
34+
value = function(value, element)
35+
return value
36+
37+
38+
def partial(func, *args, **kwargs):
39+
"""Create a partial function application."""
40+
def wrapper(*more_args, **more_kwargs):
41+
all_kwargs = dict(kwargs)
42+
all_kwargs.update(more_kwargs)
43+
return func(*args, *more_args, **all_kwargs)
44+
return wrapper

lib/itertools.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# itertools - adapted from CPython for apython
2+
3+
def chain(*iterables):
4+
"""chain(*iterables) --> chain object
5+
Return a chain object whose .__next__() method returns elements from the
6+
first iterable until it is exhausted, then elements from the next
7+
iterable, until all of the iterables are exhausted."""
8+
for it in iterables:
9+
yield from it
10+
11+
12+
def islice(iterable, *args):
13+
"""islice(iterable, stop) or islice(iterable, start, stop[, step])"""
14+
if len(args) == 1:
15+
start, stop, step = 0, args[0], 1
16+
elif len(args) == 2:
17+
start, stop, step = args[0], args[1], 1
18+
elif len(args) == 3:
19+
start, stop, step = args
20+
else:
21+
raise TypeError("islice expected 2-4 arguments")
22+
23+
it = iter(iterable)
24+
# Skip start elements
25+
for i in range(start):
26+
try:
27+
next(it)
28+
except StopIteration:
29+
return
30+
31+
count = 0
32+
for i, item in enumerate(it):
33+
if stop is not None and start + i >= stop:
34+
return
35+
if i % step == 0:
36+
yield item
37+
38+
39+
def count(start=0, step=1):
40+
"""count(start=0, step=1) --> count object
41+
Return a count object whose .__next__() method returns consecutive values."""
42+
n = start
43+
while True:
44+
yield n
45+
n += step
46+
47+
48+
def repeat(obj, times=None):
49+
"""repeat(object [,times]) -> create an iterator which returns the object
50+
for the specified number of times."""
51+
if times is None:
52+
while True:
53+
yield obj
54+
else:
55+
for i in range(times):
56+
yield obj
57+
58+
59+
def cycle(iterable):
60+
"""cycle(iterable) --> cycle object
61+
Return elements from the iterable until it is exhausted.
62+
Then repeat the sequence indefinitely."""
63+
saved = []
64+
for element in iterable:
65+
yield element
66+
saved.append(element)
67+
while saved:
68+
for element in saved:
69+
yield element
70+
71+
72+
def accumulate(iterable, func=None, initial=None):
73+
"""accumulate(iterable[, func, initial]) --> accumulate object"""
74+
it = iter(iterable)
75+
if initial is not None:
76+
total = initial
77+
else:
78+
try:
79+
total = next(it)
80+
except StopIteration:
81+
return
82+
yield total
83+
for element in it:
84+
if func is None:
85+
total = total + element
86+
else:
87+
total = func(total, element)
88+
yield total
89+
90+
91+
def starmap(function, iterable):
92+
"""starmap(function, iterable) --> starmap object"""
93+
for args in iterable:
94+
yield function(*args)
95+
96+
97+
def product(*iterables, repeat=1):
98+
"""product(*iterables, repeat=1) --> product object"""
99+
pools = [list(pool) for pool in iterables] * repeat
100+
result = [[]]
101+
for pool in pools:
102+
result = [x + [y] for x in result for y in pool]
103+
for prod in result:
104+
yield tuple(prod)

lib/pickle.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# pickle - minimal stub for apython
2+
# Serialization is not yet implemented. This stub provides
3+
# constants so tests can import pickle and check HIGHEST_PROTOCOL.
4+
5+
HIGHEST_PROTOCOL = 5
6+
DEFAULT_PROTOCOL = 5
7+
8+
9+
class PicklingError(Exception):
10+
pass
11+
12+
13+
class UnpicklingError(Exception):
14+
pass
15+
16+
17+
def dumps(obj, protocol=None):
18+
raise NotImplementedError("not implemented in apython")
19+
20+
21+
def loads(data):
22+
raise NotImplementedError("not implemented in apython")
23+
24+
25+
def dump(obj, file, protocol=None):
26+
raise NotImplementedError("not implemented in apython")
27+
28+
29+
def load(file):
30+
raise NotImplementedError("not implemented in apython")

src/builtins.asm

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ DEF_FUNC builtin_func_new
161161
mov qword [rax + PyBuiltinObject.func_id], 0 ; not used for func_ptr dispatch
162162
mov [rax + PyBuiltinObject.func_name], r13
163163
mov [rax + PyBuiltinObject.func_ptr], rbx
164+
mov qword [rax + PyBuiltinObject.min_args], 0 ; 0 = no check
165+
mov qword [rax + PyBuiltinObject.max_args], -1 ; -1 = no max check
164166

165167
pop r13
166168
pop r12
@@ -169,18 +171,65 @@ DEF_FUNC builtin_func_new
169171
ret
170172
END_FUNC builtin_func_new
171173

174+
;; ============================================================================
175+
;; builtin_func_new_checked(void *func_ptr, const char *name_cstr,
176+
;; int64_t min_args, int64_t max_args)
177+
;; Like builtin_func_new but sets arg count bounds for validation.
178+
;; rdx = min_args (including self), rcx = max_args (-1 = no max)
179+
;; ============================================================================
180+
global builtin_func_new_checked
181+
DEF_FUNC builtin_func_new_checked
182+
push r14
183+
push r15
184+
mov r14, rdx ; min_args
185+
mov r15, rcx ; max_args
186+
call builtin_func_new
187+
mov [rax + PyBuiltinObject.min_args], r14
188+
mov [rax + PyBuiltinObject.max_args], r15
189+
pop r15
190+
pop r14
191+
leave
192+
ret
193+
END_FUNC builtin_func_new_checked
194+
172195
;; ============================================================================
173196
;; builtin_func_call(PyObject *self, PyObject **args, int64_t nargs) -> PyObject*
174197
;; Dispatch to the underlying C function: func_ptr(args, nargs)
198+
;; Validates nargs against min_args/max_args if set.
175199
;; ============================================================================
176200
DEF_FUNC_BARE builtin_func_call
177201
; self = rdi, args = rsi, nargs = rdx
202+
; Check min_args (0 = no check)
203+
mov rcx, [rdi + PyBuiltinObject.min_args]
204+
test rcx, rcx
205+
jz .bfc_no_min_check
206+
cmp rdx, rcx
207+
jl .bfc_too_few
208+
.bfc_no_min_check:
209+
; Check max_args (-1 = no check)
210+
mov rcx, [rdi + PyBuiltinObject.max_args]
211+
cmp rcx, -1
212+
je .bfc_no_max_check
213+
cmp rdx, rcx
214+
jg .bfc_too_many
215+
.bfc_no_max_check:
178216
; Extract func_ptr from self
179217
mov rax, [rdi + PyBuiltinObject.func_ptr]
180218
; Call func_ptr(args, nargs)
181219
mov rdi, rsi ; args
182220
mov rsi, rdx ; nargs
183221
jmp rax ; tail call
222+
223+
.bfc_too_few:
224+
extern exc_TypeError_type
225+
extern raise_exception
226+
lea rdi, [rel exc_TypeError_type]
227+
CSTRING rsi, "function takes at least 1 argument"
228+
call raise_exception
229+
.bfc_too_many:
230+
lea rdi, [rel exc_TypeError_type]
231+
CSTRING rsi, "function takes at most N arguments"
232+
call raise_exception
184233
END_FUNC builtin_func_call
185234

186235
;; ============================================================================

0 commit comments

Comments
 (0)