Skip to content

Commit a3f94e0

Browse files
committed
py: Add arg checking helper functions.
These are to assist in writing native C functions that take positional and keyword arguments. mp_arg_check_num is for just checking the number of arguments is correct. mp_arg_parse_all is for parsing positional and keyword arguments with default values.
1 parent 27dd471 commit a3f94e0

6 files changed

Lines changed: 106 additions & 35 deletions

File tree

py/argcheck.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include <stdlib.h>
2+
#include <assert.h>
3+
4+
#include "nlr.h"
5+
#include "misc.h"
6+
#include "mpconfig.h"
7+
#include "qstr.h"
8+
#include "obj.h"
9+
#include "runtime.h"
10+
11+
void mp_arg_check_num(uint n_args, uint n_kw, uint n_args_min, uint n_args_max, bool takes_kw) {
12+
// TODO maybe take the function name as an argument so we can print nicer error messages
13+
14+
if (n_kw && !takes_kw) {
15+
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
16+
}
17+
18+
if (n_args_min == n_args_max) {
19+
if (n_args != n_args_min) {
20+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
21+
"function takes %d positional arguments but %d were given",
22+
n_args_min, n_args));
23+
}
24+
} else {
25+
if (n_args < n_args_min) {
26+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
27+
"function missing %d required positional arguments",
28+
n_args_min - n_args));
29+
} else if (n_args > n_args_max) {
30+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
31+
"function expected at most %d arguments, got %d",
32+
n_args_max, n_args));
33+
}
34+
}
35+
}
36+
37+
void mp_arg_parse_all(uint n_pos, const mp_obj_t *pos, mp_map_t *kws, uint n_allowed, const mp_arg_parse_t *allowed, mp_arg_parse_val_t *out_vals) {
38+
uint pos_found = 0, kws_found = 0;
39+
for (uint i = 0; i < n_allowed; i++) {
40+
mp_obj_t given_arg;
41+
if (i < n_pos) {
42+
if (allowed[i].flags & MP_ARG_PARSE_KW_ONLY) {
43+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' argument must be given by a keyword", qstr_str(allowed[i].qstr)));
44+
}
45+
pos_found++;
46+
given_arg = pos[i];
47+
} else {
48+
mp_map_elem_t *kw = mp_map_lookup(kws, MP_OBJ_NEW_QSTR(allowed[i].qstr), MP_MAP_LOOKUP);
49+
if (kw == NULL) {
50+
if (allowed[i].flags & MP_ARG_PARSE_REQUIRED) {
51+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' argument required", qstr_str(allowed[i].qstr)));
52+
}
53+
out_vals[i] = allowed[i].defval;
54+
continue;
55+
} else {
56+
kws_found++;
57+
given_arg = kw->value;
58+
}
59+
}
60+
if (allowed[i].flags == MP_ARG_PARSE_BOOL) {
61+
out_vals[i].u_bool = mp_obj_is_true(given_arg);
62+
} else if (allowed[i].flags == MP_ARG_PARSE_INT) {
63+
out_vals[i].u_int = mp_obj_get_int(given_arg);
64+
} else if (allowed[i].flags == MP_ARG_PARSE_OBJ) {
65+
out_vals[i].u_obj = given_arg;
66+
} else {
67+
assert(0);
68+
}
69+
}
70+
if (pos_found < n_pos) {
71+
// TODO better error message
72+
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "extra positional arguments given"));
73+
}
74+
if (kws_found < kws->used) {
75+
// TODO better error message
76+
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "extra keyword arguments given"));
77+
}
78+
}

py/objarray.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ STATIC mp_obj_t array_construct(char typecode, mp_obj_t initializer) {
7575
}
7676

7777
STATIC mp_obj_t array_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
78-
mp_check_nargs(n_args, 1, 2, n_kw, false);
78+
mp_arg_check_num(n_args, n_kw, 1, 2, false);
7979

8080
// get typecode
8181
uint l;
@@ -91,7 +91,7 @@ STATIC mp_obj_t array_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const m
9191
}
9292

9393
STATIC mp_obj_t bytearray_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
94-
mp_check_nargs(n_args, 0, 1, n_kw, false);
94+
mp_arg_check_num(n_args, n_kw, 0, 1, false);
9595

9696
if (n_args == 0) {
9797
// no args: construct an empty bytearray

py/objfun.c

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,6 @@
2525

2626
// mp_obj_fun_native_t defined in obj.h
2727

28-
STATIC void check_nargs(mp_obj_fun_native_t *self, int n_args, int n_kw) {
29-
mp_check_nargs(n_args, self->n_args_min, self->n_args_max, n_kw, self->is_kw);
30-
}
31-
32-
void mp_check_nargs(int n_args, machine_uint_t n_args_min, machine_uint_t n_args_max, int n_kw, bool is_kw) {
33-
// TODO maybe take the function name as an argument so we can print nicer error messages
34-
35-
if (n_kw && !is_kw) {
36-
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
37-
}
38-
39-
if (n_args_min == n_args_max) {
40-
if (n_args != n_args_min) {
41-
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
42-
"function takes %d positional arguments but %d were given",
43-
n_args_min, n_args));
44-
}
45-
} else {
46-
if (n_args < n_args_min) {
47-
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
48-
"function missing %d required positional arguments",
49-
n_args_min - n_args));
50-
} else if (n_args > n_args_max) {
51-
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
52-
"function expected at most %d arguments, got %d",
53-
n_args_max, n_args));
54-
}
55-
}
56-
}
57-
5828
STATIC mp_obj_t fun_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
5929
switch (op) {
6030
case MP_BINARY_OP_EQUAL:
@@ -70,7 +40,7 @@ STATIC mp_obj_t fun_native_call(mp_obj_t self_in, uint n_args, uint n_kw, const
7040
mp_obj_fun_native_t *self = self_in;
7141

7242
// check number of arguments
73-
check_nargs(self, n_args, n_kw);
43+
mp_arg_check_num(n_args, n_kw, self->n_args_min, self->n_args_max, self->is_kw);
7444

7545
if (self->is_kw) {
7646
// function allows keywords

py/objrange.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ typedef struct _mp_obj_range_t {
5757
} mp_obj_range_t;
5858

5959
STATIC mp_obj_t range_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
60-
mp_check_nargs(n_args, 1, 3, n_kw, false);
60+
mp_arg_check_num(n_args, n_kw, 1, 3, false);
6161

6262
mp_obj_range_t *o = m_new_obj(mp_obj_range_t);
6363
o->base.type = &mp_type_range;

py/py.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ PY_O_BASENAME = \
4040
parsenum.o \
4141
emitglue.o \
4242
runtime.o \
43+
argcheck.o \
4344
map.o \
4445
obj.o \
4546
objarray.o \

py/runtime.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,32 @@ typedef enum {
44
MP_VM_RETURN_EXCEPTION,
55
} mp_vm_return_kind_t;
66

7+
typedef enum {
8+
MP_ARG_PARSE_BOOL = 0x001,
9+
MP_ARG_PARSE_INT = 0x002,
10+
MP_ARG_PARSE_OBJ = 0x003,
11+
MP_ARG_PARSE_KIND_MASK = 0x0ff,
12+
MP_ARG_PARSE_REQUIRED = 0x100,
13+
MP_ARG_PARSE_KW_ONLY = 0x200,
14+
} mp_arg_parse_flag_t;
15+
16+
typedef union _mp_arg_parse_val_t {
17+
bool u_bool;
18+
machine_int_t u_int;
19+
mp_obj_t u_obj;
20+
} mp_arg_parse_val_t;
21+
22+
typedef struct _mp_arg_parse_t {
23+
qstr qstr;
24+
machine_uint_t flags;
25+
mp_arg_parse_val_t defval;
26+
} mp_arg_parse_t;
27+
728
void mp_init(void);
829
void mp_deinit(void);
930

10-
void mp_check_nargs(int n_args, machine_uint_t n_args_min, machine_uint_t n_args_max, int n_kw, bool is_kw);
31+
void mp_arg_check_num(uint n_args, uint n_kw, uint n_args_min, uint n_args_max, bool takes_kw);
32+
void mp_arg_parse_all(uint n_pos, const mp_obj_t *pos, mp_map_t *kws, uint n_allowed, const mp_arg_parse_t *allowed, mp_arg_parse_val_t *out_vals);
1133

1234
mp_obj_dict_t *mp_locals_get(void);
1335
void mp_locals_set(mp_obj_dict_t *d);

0 commit comments

Comments
 (0)