Skip to content

Commit 230fec7

Browse files
committed
py: Implement positional and keyword args via * and **.
Extends previous implementation with * for function calls to * and ** for both function and method calls.
1 parent f6a8209 commit 230fec7

6 files changed

Lines changed: 258 additions & 19 deletions

File tree

py/runtime.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "mpconfig.h"
88
#include "qstr.h"
99
#include "obj.h"
10+
#include "objtuple.h"
1011
#include "objmodule.h"
1112
#include "parsenum.h"
1213
#include "runtime0.h"
@@ -503,6 +504,148 @@ mp_obj_t mp_call_method_n_kw(uint n_args, uint n_kw, const mp_obj_t *args) {
503504
return mp_call_function_n_kw(args[0], n_args + adjust, n_kw, args + 2 - adjust);
504505
}
505506

507+
mp_obj_t mp_call_method_n_kw_var(bool have_self, uint n_args_n_kw, const mp_obj_t *args, mp_obj_t pos_seq, mp_obj_t kw_dict) {
508+
mp_obj_t fun = *args++;
509+
mp_obj_t self = MP_OBJ_NULL;
510+
if (have_self) {
511+
self = *args++; // may be MP_OBJ_NULL
512+
}
513+
uint n_args = n_args_n_kw & 0xff;
514+
uint n_kw = (n_args_n_kw >> 8) & 0xff;
515+
516+
DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, seq=%p, dict=%p)\n", fun, self, n_args, n_kw, args, pos_seq, kw_dict);
517+
518+
// We need to create the following array of objects:
519+
// args[0 .. n_args] unpacked(pos_seq) args[n_args .. n_args + 2 * n_kw] unpacked(kw_dict)
520+
// TODO: optimize one day to avoid constructing new arg array? Will be hard.
521+
522+
// The new args array
523+
mp_obj_t *args2;
524+
uint args2_alloc;
525+
uint args2_len = 0;
526+
527+
// Try to get a hint for the size of the kw_dict
528+
uint kw_dict_len = 0;
529+
if (kw_dict != MP_OBJ_NULL && MP_OBJ_IS_TYPE(kw_dict, &mp_type_dict)) {
530+
kw_dict_len = mp_obj_dict_len(kw_dict);
531+
}
532+
533+
// Extract the pos_seq sequence to the new args array.
534+
// Note that it can be arbitrary iterator.
535+
if (pos_seq == MP_OBJ_NULL) {
536+
// no sequence
537+
538+
// allocate memory for the new array of args
539+
args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len);
540+
args2 = m_new(mp_obj_t, args2_alloc);
541+
542+
// copy the self
543+
if (self != MP_OBJ_NULL) {
544+
args2[args2_len++] = self;
545+
}
546+
547+
// copy the fixed pos args
548+
m_seq_copy(args2 + args2_len, args, n_args, mp_obj_t);
549+
args2_len += n_args;
550+
551+
} else if (MP_OBJ_IS_TYPE(pos_seq, &mp_type_tuple) || MP_OBJ_IS_TYPE(pos_seq, &mp_type_list)) {
552+
// optimise the case of a tuple and list
553+
554+
// get the items
555+
uint len;
556+
mp_obj_t *items;
557+
mp_obj_get_array(pos_seq, &len, &items);
558+
559+
// allocate memory for the new array of args
560+
args2_alloc = 1 + n_args + len + 2 * (n_kw + kw_dict_len);
561+
args2 = m_new(mp_obj_t, args2_alloc);
562+
563+
// copy the self
564+
if (self != MP_OBJ_NULL) {
565+
args2[args2_len++] = self;
566+
}
567+
568+
// copy the fixed and variable position args
569+
m_seq_cat(args2 + args2_len, args, n_args, items, len, mp_obj_t);
570+
args2_len += n_args + len;
571+
572+
} else {
573+
// generic iterator
574+
575+
// allocate memory for the new array of args
576+
args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len) + 3;
577+
args2 = m_new(mp_obj_t, args2_alloc);
578+
579+
// copy the self
580+
if (self != MP_OBJ_NULL) {
581+
args2[args2_len++] = self;
582+
}
583+
584+
// copy the fixed position args
585+
m_seq_copy(args2 + args2_len, args, n_args, mp_obj_t);
586+
587+
// extract the variable position args from the iterator
588+
mp_obj_t iterable = mp_getiter(pos_seq);
589+
mp_obj_t item;
590+
while ((item = mp_iternext(iterable)) != MP_OBJ_NULL) {
591+
if (args2_len >= args2_alloc) {
592+
args2 = m_renew(mp_obj_t, args2, args2_alloc, args2_alloc * 2);
593+
args2_alloc *= 2;
594+
}
595+
args2[args2_len++] = item;
596+
}
597+
}
598+
599+
// The size of the args2 array now is the number of positional args.
600+
uint pos_args_len = args2_len;
601+
602+
// Copy the fixed kw args.
603+
m_seq_copy(args2 + args2_len, args + n_args, 2 * n_kw, mp_obj_t);
604+
args2_len += 2 * n_kw;
605+
606+
// Extract (key,value) pairs from kw_dict dictionary and append to args2.
607+
// Note that it can be arbitrary iterator.
608+
if (kw_dict == MP_OBJ_NULL) {
609+
// pass
610+
} else if (MP_OBJ_IS_TYPE(kw_dict, &mp_type_dict)) {
611+
// dictionary
612+
mp_map_t *map = mp_obj_dict_get_map(kw_dict);
613+
assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above
614+
for (uint i = 0; i < map->alloc; i++) {
615+
if (map->table[i].key != MP_OBJ_NULL) {
616+
args2[args2_len++] = map->table[i].key;
617+
args2[args2_len++] = map->table[i].value;
618+
}
619+
}
620+
} else {
621+
// generic mapping
622+
// TODO is calling 'items' on the mapping the correct thing to do here?
623+
mp_obj_t dest[2];
624+
mp_load_method(kw_dict, MP_QSTR_items, dest);
625+
mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest));
626+
mp_obj_t item;
627+
while ((item = mp_iternext(iterable)) != MP_OBJ_NULL) {
628+
if (args2_len + 1 >= args2_alloc) {
629+
uint new_alloc = args2_alloc * 2;
630+
if (new_alloc < 4) {
631+
new_alloc = 4;
632+
}
633+
args2 = m_renew(mp_obj_t, args2, args2_alloc, new_alloc);
634+
args2_alloc = new_alloc;
635+
}
636+
mp_obj_t *items;
637+
mp_obj_get_array_fixed_n(item, 2, &items);
638+
args2[args2_len++] = items[0];
639+
args2[args2_len++] = items[1];
640+
}
641+
}
642+
643+
mp_obj_t res = mp_call_function_n_kw(fun, pos_args_len, (args2_len - pos_args_len) / 2, args2);
644+
m_del(mp_obj_t, args2, args2_alloc);
645+
646+
return res;
647+
}
648+
506649
mp_obj_t mp_build_tuple(int n_args, mp_obj_t *items) {
507650
return mp_obj_new_tuple(n_args, items);
508651
}

py/runtime.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2);
3737
mp_obj_t mp_call_function_n_kw_for_native(mp_obj_t fun_in, uint n_args_kw, const mp_obj_t *args);
3838
mp_obj_t mp_call_function_n_kw(mp_obj_t fun, uint n_args, uint n_kw, const mp_obj_t *args);
3939
mp_obj_t mp_call_method_n_kw(uint n_args, uint n_kw, const mp_obj_t *args);
40+
mp_obj_t mp_call_method_n_kw_var(bool have_self, uint n_args_n_kw, const mp_obj_t *args, mp_obj_t pos_seq, mp_obj_t kw_dict);
4041

4142
mp_obj_t mp_build_tuple(int n_args, mp_obj_t *items);
4243
mp_obj_t mp_build_list(int n_args, mp_obj_t *items);

py/vm.c

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include "bc0.h"
1212
#include "bc.h"
1313
#include "objgenerator.h"
14-
#include "objtuple.h"
1514

1615
// Value stack grows up (this makes it incompatible with native C stack, but
1716
// makes sure that arguments to functions are in natural order arg1..argN
@@ -672,31 +671,39 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
672671
SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1));
673672
break;
674673

675-
case MP_BC_CALL_FUNCTION_VAR: {
674+
case MP_BC_CALL_FUNCTION_VAR:
676675
DECODE_UINT;
677676
// unum & 0xff == n_positional
678677
// (unum >> 8) & 0xff == n_keyword
679678
// We have folowing stack layout here:
680-
// arg0 arg1 ... kw0 val0 kw1 val1 ... seq <- TOS
681-
// We need to splice seq after all positional args and before kwargs
682-
// TODO: optimize one day to avoid constructing new arg array? Will be hard.
683-
mp_obj_t seq = POP();
684-
int total_stack_args = (unum & 0xff) + ((unum >> 7) & 0x1fe);
685-
sp -= total_stack_args;
686-
687-
// Convert vararg sequence to tuple. Note that it can be arbitrary iterator.
688-
// This is null call for tuple, and TODO: we actually could optimize case of list.
689-
mp_obj_tuple_t *varargs = mp_obj_tuple_make_new(MP_OBJ_NULL, 1, 0, &seq);
679+
// fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq <- TOS
680+
obj1 = POP();
681+
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe);
682+
SET_TOP(mp_call_method_n_kw_var(false, unum, sp, obj1, MP_OBJ_NULL));
683+
break;
690684

691-
int pos_args_len = (unum & 0xff) + varargs->len;
692-
mp_obj_t *args = m_new(mp_obj_t, total_stack_args + varargs->len);
693-
m_seq_cat(args, sp + 1, unum & 0xff, varargs->items, varargs->len, mp_obj_t);
694-
m_seq_copy(args + pos_args_len, sp + (unum & 0xff) + 1, ((unum >> 7) & 0x1fe), mp_obj_t);
685+
case MP_BC_CALL_FUNCTION_KW:
686+
DECODE_UINT;
687+
// unum & 0xff == n_positional
688+
// (unum >> 8) & 0xff == n_keyword
689+
// We have folowing stack layout here:
690+
// fun arg0 arg1 ... kw0 val0 kw1 val1 ... dict <- TOS
691+
obj1 = POP();
692+
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe);
693+
SET_TOP(mp_call_method_n_kw_var(false, unum, sp, MP_OBJ_NULL, obj1));
694+
break;
695695

696-
SET_TOP(mp_call_function_n_kw(*sp, pos_args_len, (unum >> 8) & 0xff, args));
697-
m_del(mp_obj_t, args, total_stack_args + varargs->len);
696+
case MP_BC_CALL_FUNCTION_VAR_KW:
697+
DECODE_UINT;
698+
// unum & 0xff == n_positional
699+
// (unum >> 8) & 0xff == n_keyword
700+
// We have folowing stack layout here:
701+
// fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS
702+
obj2 = POP();
703+
obj1 = POP();
704+
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe);
705+
SET_TOP(mp_call_method_n_kw_var(false, unum, sp, obj1, obj2));
698706
break;
699-
}
700707

701708
case MP_BC_CALL_METHOD:
702709
DECODE_UINT;
@@ -706,6 +713,40 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
706713
SET_TOP(mp_call_method_n_kw(unum & 0xff, (unum >> 8) & 0xff, sp));
707714
break;
708715

716+
case MP_BC_CALL_METHOD_VAR:
717+
DECODE_UINT;
718+
// unum & 0xff == n_positional
719+
// (unum >> 8) & 0xff == n_keyword
720+
// We have folowing stack layout here:
721+
// fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq <- TOS
722+
obj1 = POP();
723+
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1;
724+
SET_TOP(mp_call_method_n_kw_var(true, unum, sp, obj1, MP_OBJ_NULL));
725+
break;
726+
727+
case MP_BC_CALL_METHOD_KW:
728+
DECODE_UINT;
729+
// unum & 0xff == n_positional
730+
// (unum >> 8) & 0xff == n_keyword
731+
// We have folowing stack layout here:
732+
// fun self arg0 arg1 ... kw0 val0 kw1 val1 ... dict <- TOS
733+
obj1 = POP();
734+
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1;
735+
SET_TOP(mp_call_method_n_kw_var(true, unum, sp, MP_OBJ_NULL, obj1));
736+
break;
737+
738+
case MP_BC_CALL_METHOD_VAR_KW:
739+
DECODE_UINT;
740+
// unum & 0xff == n_positional
741+
// (unum >> 8) & 0xff == n_keyword
742+
// We have folowing stack layout here:
743+
// fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS
744+
obj2 = POP();
745+
obj1 = POP();
746+
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1;
747+
SET_TOP(mp_call_method_n_kw_var(true, unum, sp, obj1, obj2));
748+
break;
749+
709750
case MP_BC_RETURN_VALUE:
710751
unwind_return:
711752
while (exc_sp >= exc_stack) {

tests/basics/fun-calldblstar.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# test calling a function with keywords given by **dict
2+
3+
def f(a, b):
4+
print(a, b)
5+
6+
f(1, **{'b':2})
7+
f(1, **{'b':val for val in range(1)})
8+
9+
# test calling a method with keywords given by **dict
10+
11+
class A:
12+
def f(self, a, b):
13+
print(a, b)
14+
15+
a = A()
16+
a.f(1, **{'b':2})
17+
a.f(1, **{'b':val for val in range(1)})

tests/basics/fun-callstar.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# function calls with *pos
2+
13
def foo(a, b, c):
24
print(a, b, c)
35

@@ -11,3 +13,21 @@ def foo(a, b, c):
1113

1214
# Iterator
1315
foo(*range(3))
16+
17+
# method calls with *pos
18+
19+
class A:
20+
def foo(self, a, b, c):
21+
print(a, b, c)
22+
23+
a = A()
24+
a.foo(*(1, 2, 3))
25+
a.foo(1, *(2, 3))
26+
a.foo(1, 2, *(3,))
27+
a.foo(1, 2, 3, *())
28+
29+
# Another sequence type
30+
a.foo(1, 2, *[100])
31+
32+
# Iterator
33+
a.foo(*range(3))
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# test calling a function with *tuple and **dict
2+
3+
def f(a, b, c, d):
4+
print(a, b, c, d)
5+
6+
f(*(1, 2), **{'c':3, 'd':4})
7+
f(*(1, 2), **{['c', 'd'][i]:(3 + i) for i in range(2)})
8+
9+
# test calling a method with *tuple and **dict
10+
11+
class A:
12+
def f(self, a, b, c, d):
13+
print(a, b, c, d)
14+
15+
a = A()
16+
a.f(*(1, 2), **{'c':3, 'd':4})
17+
a.f(*(1, 2), **{['c', 'd'][i]:(3 + i) for i in range(2)})

0 commit comments

Comments
 (0)