py: Implement default keyword only args.

Should finish addressing issue #524.
This commit is contained in:
Damien George 2014-06-07 22:01:00 +01:00
parent aabd83ea20
commit f0778a7ccb
6 changed files with 56 additions and 27 deletions

View File

@ -1035,7 +1035,10 @@ void compile_funcdef_param(compiler_t *comp, mp_parse_node_t pn) {
if (comp->have_star) { if (comp->have_star) {
comp->num_dict_params += 1; comp->num_dict_params += 1;
#if !MICROPY_EMIT_CPYTHON #if MICROPY_EMIT_CPYTHON
EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pn_id), false);
compile_node(comp, pn_equal);
#else
// in Micro Python we put the default dict parameters into a dictionary using the bytecode // in Micro Python we put the default dict parameters into a dictionary using the bytecode
if (comp->num_dict_params == 1) { if (comp->num_dict_params == 1) {
// in Micro Python we put the default positional parameters into a tuple using the bytecode // in Micro Python we put the default positional parameters into a tuple using the bytecode
@ -1048,11 +1051,10 @@ void compile_funcdef_param(compiler_t *comp, mp_parse_node_t pn) {
// first default dict param, so make the map // first default dict param, so make the map
EMIT_ARG(build_map, 0); EMIT_ARG(build_map, 0);
} }
#endif
EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pn_id), false); // compile value then key, then store it to the dict
compile_node(comp, pn_equal); compile_node(comp, pn_equal);
#if !MICROPY_EMIT_CPYTHON EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pn_id), false);
// in Micro Python we put the default dict parameters into a dictionary using the bytecode
EMIT(store_map); EMIT(store_map);
#endif #endif
} else { } else {

View File

@ -119,14 +119,14 @@ mp_obj_t mp_make_function_from_raw_code(mp_raw_code_t *rc, mp_obj_t def_args, mp
// def_args must be MP_OBJ_NULL or a tuple // def_args must be MP_OBJ_NULL or a tuple
assert(def_args == MP_OBJ_NULL || MP_OBJ_IS_TYPE(def_args, &mp_type_tuple)); assert(def_args == MP_OBJ_NULL || MP_OBJ_IS_TYPE(def_args, &mp_type_tuple));
// TODO implement default kw args // def_kw_args must be MP_OBJ_NULL or a dict
assert(def_kw_args == MP_OBJ_NULL); assert(def_kw_args == MP_OBJ_NULL || MP_OBJ_IS_TYPE(def_kw_args, &mp_type_dict));
// make the function, depending on the raw code kind // make the function, depending on the raw code kind
mp_obj_t fun; mp_obj_t fun;
switch (rc->kind) { switch (rc->kind) {
case MP_CODE_BYTECODE: case MP_CODE_BYTECODE:
fun = mp_obj_new_fun_bc(rc->scope_flags, rc->arg_names, rc->n_pos_args, rc->n_kwonly_args, def_args, rc->u_byte.code); fun = mp_obj_new_fun_bc(rc->scope_flags, rc->arg_names, rc->n_pos_args, rc->n_kwonly_args, def_args, def_kw_args, rc->u_byte.code);
break; break;
case MP_CODE_NATIVE_PY: case MP_CODE_NATIVE_PY:
fun = mp_make_function_n(rc->n_pos_args, rc->u_native.fun); fun = mp_make_function_n(rc->n_pos_args, rc->u_native.fun);

View File

@ -380,7 +380,7 @@ mp_obj_t mp_obj_new_exception_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg);
mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, uint n_args, const mp_obj_t *args); mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, uint n_args, const mp_obj_t *args);
mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg); mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg);
mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!) mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n_kwonly_args, mp_obj_t def_args, const byte *code); mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n_kwonly_args, mp_obj_t def_args, mp_obj_t def_kw_args, const byte *code);
mp_obj_t mp_obj_new_fun_asm(uint n_args, void *fun); mp_obj_t mp_obj_new_fun_asm(uint n_args, void *fun);
mp_obj_t mp_obj_new_gen_wrap(mp_obj_t fun); mp_obj_t mp_obj_new_gen_wrap(mp_obj_t fun);
mp_obj_t mp_obj_new_closure(mp_obj_t fun, uint n_closed, const mp_obj_t *closed); mp_obj_t mp_obj_new_closure(mp_obj_t fun, uint n_closed, const mp_obj_t *closed);

View File

@ -249,6 +249,10 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
// This function is pretty complicated. It's main aim is to be efficient in speed and RAM // This function is pretty complicated. It's main aim is to be efficient in speed and RAM
// usage for the common case of positional only args. // usage for the common case of positional only args.
// //
// TODO Now that we allocate the state for the bytecode execution, we probably don't
// need extra_args anymore, and definitely don't need flat_args.
//
// TODO The following comment is obsolete and probably wrong:
// extra_args layout: def_args, var_arg tuple, kwonly args, var_kw dict // extra_args layout: def_args, var_arg tuple, kwonly args, var_kw dict
DEBUG_printf("Input n_args: %d, n_kw: %d\n", n_args, n_kw); DEBUG_printf("Input n_args: %d, n_kw: %d\n", n_args, n_kw);
@ -260,7 +264,7 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
DEBUG_printf("Func n_def_args: %d\n", self->n_def_args); DEBUG_printf("Func n_def_args: %d\n", self->n_def_args);
const mp_obj_t *kwargs = args + n_args; const mp_obj_t *kwargs = args + n_args;
mp_obj_t *extra_args = self->extra_args + self->n_def_args; mp_obj_t *extra_args = self->extra_args + self->n_def_args + self->has_def_kw_args;
uint n_extra_args = 0; uint n_extra_args = 0;
// check positional arguments // check positional arguments
@ -295,7 +299,7 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
// check keyword arguments // check keyword arguments
if (n_kw != 0) { if (n_kw != 0 || self->has_def_kw_args) {
// We cannot use dynamically-sized array here, because GCC indeed // We cannot use dynamically-sized array here, because GCC indeed
// deallocates it on leaving defining scope (unlike most static stack allocs). // deallocates it on leaving defining scope (unlike most static stack allocs).
// So, we have 2 choices: allocate it unconditionally at the top of function // So, we have 2 choices: allocate it unconditionally at the top of function
@ -355,10 +359,19 @@ continue2:;
} }
// Check that all mandatory keyword args are specified // Check that all mandatory keyword args are specified
// Fill in default kw args if we have them
for (int i = 0; i < self->n_kwonly_args; i++) { for (int i = 0; i < self->n_kwonly_args; i++) {
if (flat_args[self->n_pos_args + i] == MP_OBJ_NULL) { if (flat_args[self->n_pos_args + i] == MP_OBJ_NULL) {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, mp_map_elem_t *elem = NULL;
"function missing required keyword argument '%s'", qstr_str(self->args[self->n_pos_args + i]))); if (self->has_def_kw_args) {
elem = mp_map_lookup(&((mp_obj_dict_t*)self->extra_args[self->n_def_args])->map, MP_OBJ_NEW_QSTR(self->args[self->n_pos_args + i]), MP_MAP_LOOKUP);
}
if (elem != NULL) {
flat_args[self->n_pos_args + i] = elem->value;
} else {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
"function missing required keyword argument '%s'", qstr_str(self->args[self->n_pos_args + i])));
}
} }
} }
@ -513,7 +526,7 @@ const mp_obj_type_t mp_type_fun_bc = {
.binary_op = fun_binary_op, .binary_op = fun_binary_op,
}; };
mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n_kwonly_args, mp_obj_t def_args_in, const byte *code) { mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n_kwonly_args, mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code) {
uint n_def_args = 0; uint n_def_args = 0;
uint n_extra_args = 0; uint n_extra_args = 0;
mp_obj_tuple_t *def_args = def_args_in; mp_obj_tuple_t *def_args = def_args_in;
@ -522,6 +535,9 @@ mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n
n_def_args = def_args->len; n_def_args = def_args->len;
n_extra_args = def_args->len; n_extra_args = def_args->len;
} }
if (def_kw_args != MP_OBJ_NULL) {
n_extra_args += 1;
}
if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) { if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
n_extra_args += 1; n_extra_args += 1;
} }
@ -535,18 +551,24 @@ mp_obj_t mp_obj_new_fun_bc(uint scope_flags, qstr *args, uint n_pos_args, uint n
o->n_pos_args = n_pos_args; o->n_pos_args = n_pos_args;
o->n_kwonly_args = n_kwonly_args; o->n_kwonly_args = n_kwonly_args;
o->n_def_args = n_def_args; o->n_def_args = n_def_args;
o->has_def_kw_args = def_kw_args != MP_OBJ_NULL;
o->takes_var_args = (scope_flags & MP_SCOPE_FLAG_VARARGS) != 0; o->takes_var_args = (scope_flags & MP_SCOPE_FLAG_VARARGS) != 0;
o->takes_kw_args = (scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0; o->takes_kw_args = (scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0;
o->bytecode = code; o->bytecode = code;
memset(o->extra_args, 0, n_extra_args * sizeof(mp_obj_t)); mp_obj_t *extra_args = o->extra_args;
memset(extra_args, 0, n_extra_args * sizeof(mp_obj_t));
if (def_args != MP_OBJ_NULL) { if (def_args != MP_OBJ_NULL) {
memcpy(o->extra_args, def_args->items, n_def_args * sizeof(mp_obj_t)); memcpy(extra_args, def_args->items, n_def_args * sizeof(mp_obj_t));
extra_args += n_def_args;
}
if (def_kw_args != MP_OBJ_NULL) {
*extra_args++ = def_kw_args;
} }
if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) { if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
o->extra_args[n_def_args] = MP_OBJ_NULL; *extra_args++ = MP_OBJ_NULL;
} }
if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) { if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
o->extra_args[n_extra_args - 1] = MP_OBJ_NULL; *extra_args = MP_OBJ_NULL;
} }
return o; return o;
} }

View File

@ -30,10 +30,15 @@ typedef struct _mp_obj_fun_bc_t {
machine_uint_t n_pos_args : 16; // number of arguments this function takes machine_uint_t n_pos_args : 16; // number of arguments this function takes
machine_uint_t n_kwonly_args : 16; // number of arguments this function takes machine_uint_t n_kwonly_args : 16; // number of arguments this function takes
machine_uint_t n_def_args : 16; // number of default arguments machine_uint_t n_def_args : 16; // number of default arguments
machine_uint_t has_def_kw_args : 1; // set if this function has default keyword args
machine_uint_t takes_var_args : 1; // set if this function takes variable args machine_uint_t takes_var_args : 1; // set if this function takes variable args
machine_uint_t takes_kw_args : 1; // set if this function takes keyword args machine_uint_t takes_kw_args : 1; // set if this function takes keyword args
const byte *bytecode; // bytecode for the function const byte *bytecode; // bytecode for the function
qstr *args; // argument names (needed to resolve positional args passed as keywords) qstr *args; // argument names (needed to resolve positional args passed as keywords)
// values of default args (if any), plus a slot at the end for var args and/or kw args (if it takes them) // the following extra_args array is allocated space to take (in order):
// - values of positional default args (if any)
// - a single slot for default kw args dict (if it has them)
// - a single slot for var args tuple (if it takes them)
// - a single slot for kw args dict (if it takes them)
mp_obj_t extra_args[]; mp_obj_t extra_args[];
} mp_obj_fun_bc_t; } mp_obj_fun_bc_t;

View File

@ -43,14 +43,14 @@ def f(a, *, b, **kw):
f(1, b=2) f(1, b=2)
f(1, b=2, c=3) f(1, b=2, c=3)
## with a default value; not currently working # with a default value
#def g(a, *, b=2, c): def g(a, *, b=2, c):
# print(a, b, c) print(a, b, c)
#
#g(1, c=3) g(1, c=3)
#g(1, b=3, c=4) g(1, b=3, c=4)
#g(1, **{'c':3}) g(1, **{'c':3})
#g(1, **{'b':'3', 'c':4}) g(1, **{'b':'3', 'c':4})
# with named star # with named star
def f(*a, b, c): def f(*a, b, c):