Support * and ** in function signatures for collections of additional arguments and keyword args

This commit is contained in:
K. Lange 2021-01-03 16:02:50 +09:00
parent 3f848dba19
commit 36716e3508
5 changed files with 152 additions and 12 deletions

View File

@ -310,6 +310,23 @@ static KrkFunction * endCompiler() {
krk_writeValueArray(&function->keywordArgNames, value);
krk_pop();
}
size_t args = current->function->requiredArgs + current->function->keywordArgs;
if (current->function->collectsArguments) {
KrkValue value = OBJECT_VAL(krk_copyString(current->locals[args].name.start,
current->locals[args].name.length));
krk_push(value);
krk_writeValueArray(&function->keywordArgNames, value);
krk_pop();
args++;
}
if (current->function->collectsKeywords) {
KrkValue value = OBJECT_VAL(krk_copyString(current->locals[args].name.start,
current->locals[args].name.length));
krk_push(value);
krk_writeValueArray(&function->keywordArgNames, value);
krk_pop();
args++;
}
#ifdef ENABLE_DISASSEMBLY
if ((vm.flags & KRK_ENABLE_DISASSEMBLY) && !parser.hadError) {
@ -687,6 +704,8 @@ static void function(FunctionType type, size_t blockWidth) {
if (type == TYPE_METHOD || type == TYPE_INIT) current->function->requiredArgs = 1;
int hasCollectors = 0;
consume(TOKEN_LEFT_PAREN, "Expected start of parameter list after function name.");
if (!check(TOKEN_RIGHT_PAREN)) {
do {
@ -696,6 +715,44 @@ static void function(FunctionType type, size_t blockWidth) {
}
continue;
}
if (match(TOKEN_ASTERISK)) {
if (match(TOKEN_ASTERISK)) {
if (hasCollectors == 2) {
error("Duplicate ** in parameter list.");
return;
}
hasCollectors = 2;
current->function->collectsKeywords = 1;
} else {
if (hasCollectors) {
error("Syntax error.");
return;
}
hasCollectors = 1;
current->function->collectsArguments = 1;
}
/* Collect a name, specifically "args" or "kwargs" are commont */
ssize_t paramConstant = parseVariable("Expect parameter name.");
defineVariable(paramConstant);
/* Make that a valid local for this function */
size_t myLocal = current->localCount - 1;
EMIT_CONSTANT_OP(OP_GET_LOCAL, myLocal);
/* Check if it's equal to the unset-kwarg-sentinel value */
emitConstant(KWARGS_VAL(0));
emitByte(OP_EQUAL);
int jumpIndex = emitJump(OP_JUMP_IF_FALSE);
/* And if it is, set it to the appropriate type */
beginScope();
KrkToken synth = syntheticToken(hasCollectors == 1 ? "listOf" : "dictOf");
namedVariable(synth, 0);
emitBytes(OP_CALL, 0);
EMIT_CONSTANT_OP(OP_SET_LOCAL, myLocal);
endScope();
/* Otherwise pop the comparison. */
patchJump(jumpIndex);
emitByte(OP_POP); /* comparison value */
continue;
}
ssize_t paramConstant = parseVariable("Expect parameter name.");
defineVariable(paramConstant);
if (match(TOKEN_EQUAL)) {
@ -718,6 +775,7 @@ static void function(FunctionType type, size_t blockWidth) {
EMIT_CONSTANT_OP(OP_SET_LOCAL, myLocal);
endScope();
patchJump(jumpIndex);
emitByte(OP_POP);
current->function->keywordArgs++;
} else {
current->function->requiredArgs++;

View File

@ -68,6 +68,8 @@ KrkFunction * krk_newFunction() {
function->upvalueCount = 0;
function->name = NULL;
function->docstring = NULL;
function->collectsArguments = 0;
function->collectsKeywords = 0;
krk_initValueArray(&function->requiredArgNames);
krk_initValueArray(&function->keywordArgNames);
krk_initChunk(&function->chunk);

View File

@ -65,6 +65,8 @@ typedef struct {
KrkString * docstring;
KrkValueArray requiredArgNames;
KrkValueArray keywordArgNames;
unsigned char collectsArguments:1;
unsigned char collectsKeywords:1;
} KrkFunction;
typedef struct {

24
test/testArgsKwargs.krk Normal file
View File

@ -0,0 +1,24 @@
def anything(*args, **kwargs):
print "Positionals:", args
print "Keywords:", kwargs
anything(1,2,3,"a","b",foo="bar",biz=42)
def func(a,b,*args,**kwargs):
print a, b, args
let x = 'hello'
print x
func(1,2,3,4,5,6,foo="bar")
def last(a,b,c=1,d=2,**kwargs):
print "Main:", a, b, c, d
print "Keyword:", kwargs
last(1,2)
last(1,2,7,3)
last(1,2,test="thing")
try:
last(1,2,'c','d',7,8,extra='foo')
except:
print exception.arg

78
vm.c
View File

@ -687,6 +687,13 @@ static int checkArgumentCount(KrkClosure * closure, int argCount) {
* where we need to restore the stack to when we return from this call.
*/
static int call(KrkClosure * closure, int argCount, int extra) {
KrkValue * startOfPositionals = &vm.stackTop[-argCount];
size_t potentialPositionalArgs = closure->function->requiredArgs + closure->function->keywordArgs;
size_t totalArguments = closure->function->requiredArgs + closure->function->keywordArgs + closure->function->collectsArguments + closure->function->collectsKeywords;
size_t offsetOfExtraArgs = closure->function->requiredArgs + closure->function->keywordArgs;
size_t offsetOfExtraKeys = offsetOfExtraArgs + closure->function->collectsArguments;
size_t argCountX = argCount;
if (argCount && IS_KWARGS(vm.stackTop[-1])) {
/**
* Process keyword arguments.
@ -717,11 +724,23 @@ static int call(KrkClosure * closure, int argCount, int extra) {
long kwargsCount = AS_INTEGER(vm.stackTop[-1]);
krk_pop(); /* Pop the arg counter */
argCount--;
long existingPositionalArgs = argCount - kwargsCount * 2;
size_t existingPositionalArgs = argCount - kwargsCount * 2;
int found = 0;
KrkValue * startOfPositionals = &vm.stackTop[-argCount];
int extraKwargs = 0;
if (existingPositionalArgs > potentialPositionalArgs) {
if (!closure->function->collectsArguments) {
checkArgumentCount(closure,existingPositionalArgs);
return 0;
}
krk_push(NONE_VAL()); krk_push(NONE_VAL()); krk_pop(); krk_pop();
startOfPositionals[offsetOfExtraArgs] = krk_list_of(existingPositionalArgs - potentialPositionalArgs,
&startOfPositionals[potentialPositionalArgs]);
existingPositionalArgs = potentialPositionalArgs + 1;
}
KrkValue * endOfPositionals = &vm.stackTop[-kwargsCount * 2];
for (long availableSlots = argCount; availableSlots < (closure->function->requiredArgs + closure->function->keywordArgs); ++availableSlots) {
for (size_t availableSlots = argCount; availableSlots < (totalArguments); ++availableSlots) {
krk_push(KWARGS_VAL(0)); /* Make sure we definitely have enough space */
}
KrkValue * startOfExtras = vm.stackTop;
@ -747,6 +766,13 @@ static int call(KrkClosure * closure, int argCount, int extra) {
}
}
/* If we got to this point, it's not a recognized argument for this function. */
if (closure->function->collectsKeywords) {
krk_push(name);
krk_push(value);
found++;
extraKwargs++;
continue;
}
krk_runtimeError(vm.exceptions.typeError, "%s() got an unexpected keyword argument '%s'",
closure->function->name ? closure->function->name->chars : "<unnamed function>",
AS_CSTRING(name));
@ -758,15 +784,32 @@ _finishArg:
startOfPositionals[clearSlots] = KWARGS_VAL(0);
}
for (int i = 0; i < found; ++i) {
int destination = AS_INTEGER(startOfExtras[i*2]);
if (!IS_KWARGS(startOfPositionals[destination])) {
krk_runtimeError(vm.exceptions.typeError, "%s() got multiple values for argument '%s'",
closure->function->name ? closure->function->name->chars : "<unnamed function>",
(destination < closure->function->requiredArgs ? AS_CSTRING(closure->function->requiredArgNames.values[destination]) :
AS_CSTRING(closure->function->keywordArgNames.values[destination - closure->function->requiredArgs])));
if (IS_INTEGER(startOfExtras[i*2])) {
int destination = AS_INTEGER(startOfExtras[i*2]);
if (!IS_KWARGS(startOfPositionals[destination])) {
krk_runtimeError(vm.exceptions.typeError, "%s() got multiple values for argument '%s'",
closure->function->name ? closure->function->name->chars : "<unnamed function>",
(destination < closure->function->requiredArgs ? AS_CSTRING(closure->function->requiredArgNames.values[destination]) :
AS_CSTRING(closure->function->keywordArgNames.values[destination - closure->function->requiredArgs])));
return 0;
}
startOfPositionals[destination] = startOfExtras[i*2+1];
} else if (IS_STRING(startOfExtras[i*2])) {
krk_push(startOfExtras[i*2]);
krk_push(startOfExtras[i*2+1]);
} else {
krk_runtimeError(vm.exceptions.typeError, "Internal error?");
return 0;
}
startOfPositionals[destination] = startOfExtras[i*2+1];
}
if (extraKwargs) {
krk_push(NONE_VAL()); krk_push(NONE_VAL()); krk_pop(); krk_pop();
startOfPositionals[offsetOfExtraKeys] = krk_dict_of(extraKwargs*2,&startOfExtras[found*2]);
while (extraKwargs) {
krk_pop();
krk_pop();
extraKwargs--;
}
}
long clearSlots;
for (clearSlots = existingPositionalArgs; clearSlots < closure->function->requiredArgs; ++clearSlots) {
@ -777,10 +820,21 @@ _finishArg:
return 0;
}
}
argCount = closure->function->requiredArgs + closure->function->keywordArgs;
argCount = totalArguments;
argCountX = argCount - (closure->function->collectsArguments + closure->function->collectsKeywords);
while (vm.stackTop > startOfPositionals + argCount) krk_pop();
} else {
/* We can't have had any kwargs. */
if ((size_t)argCount > potentialPositionalArgs && closure->function->collectsArguments) {
krk_push(NONE_VAL()); krk_push(NONE_VAL()); krk_pop(); krk_pop();
startOfPositionals[offsetOfExtraArgs] = krk_list_of(argCount - potentialPositionalArgs,
&startOfPositionals[potentialPositionalArgs]);
argCount = closure->function->requiredArgs + 1;
argCountX = argCount - 1;
while (vm.stackTop > startOfPositionals + argCount) krk_pop();
}
}
if (!checkArgumentCount(closure, argCount)) {
if (!checkArgumentCount(closure, argCountX)) {
return 0;
}
while (argCount < (closure->function->requiredArgs + closure->function->keywordArgs)) {