Add default argument values.

Unlike in Python, I'm taking the approach of evaluating these at function
call time rather than definition time. Assigning things like empty lists/dicts
to default arguments has always been a ridiculous thing in Python, and I don't
want to make that mistake. I'm pretty sure Python only continues to do that
because it was something they didn't want to break for backwards compatibility
reasons even in Python 3.
This commit is contained in:
K. Lange 2021-01-03 12:32:04 +09:00
parent e46d753999
commit 76e70b79d0
5 changed files with 65 additions and 28 deletions

View File

@ -102,7 +102,7 @@ greet("user")
# → Hello, user!
```
Optional arguments are supported, though as of writing they must default to `None`:
Default arguments can be specified as follows:
```py
def greet(name=None):
@ -116,6 +116,8 @@ gree("user")
# Hello, user!
```
If a default argument value is not provided, the expression assigned to it will be evaluated as if it were at the top of the body of the function, like in Ruby (and _not like in Python_).
Blocks, including function `def` blocks and control flow structures like `if` and `for`, must be indented with spaces to a level greater than the enclosing block.
You may indent blocks to whatever level you desire, so long as ordering remains consistent, though the recommendtation indentation size is 4 spaces.

View File

@ -621,6 +621,22 @@ static void endScope() {
}
}
static int emitJump(uint8_t opcode) {
emitByte(opcode);
emitBytes(0xFF, 0xFF);
return currentChunk()->count - 2;
}
static void patchJump(int offset) {
int jump = currentChunk()->count - offset - 2;
if (jump > 0xFFFF) {
error("Unsupported far jump (we'll get there)");
}
currentChunk()->code[offset] = (jump >> 8) & 0xFF;
currentChunk()->code[offset + 1] = (jump) & 0xFF;
}
static void block(size_t indentation, const char * blockName) {
if (match(TOKEN_EOL)) {
if (check(TOKEN_INDENTATION)) {
@ -683,7 +699,25 @@ static void function(FunctionType type, size_t blockWidth) {
ssize_t paramConstant = parseVariable("Expect parameter name.");
defineVariable(paramConstant);
if (match(TOKEN_EQUAL)) {
consume(TOKEN_NONE,"Optional arguments can only be assigned the default value of None.");
/*
* We inline default arguments by checking if they are equal
* to a sentinel value and replacing them with the requested
* argument. This allows us to send None (useful) to override
* defaults that are something else. This essentially ends
* up as the following at the top of the function:
* if param == KWARGS_SENTINEL:
* param = EXPRESSION
*/
size_t myLocal = current->localCount - 1;
EMIT_CONSTANT_OP(OP_GET_LOCAL, myLocal);
emitConstant(KWARGS_VAL(0));
emitByte(OP_EQUAL);
int jumpIndex = emitJump(OP_JUMP_IF_FALSE);
beginScope();
expression(); /* Read expression */
EMIT_CONSTANT_OP(OP_SET_LOCAL, myLocal);
endScope();
patchJump(jumpIndex);
current->function->keywordArgs++;
} else {
current->function->requiredArgs++;
@ -875,22 +909,6 @@ static KrkToken decorator(size_t level, FunctionType type) {
return funcName;
}
static int emitJump(uint8_t opcode) {
emitByte(opcode);
emitBytes(0xFF, 0xFF);
return currentChunk()->count - 2;
}
static void patchJump(int offset) {
int jump = currentChunk()->count - offset - 2;
if (jump > 0xFFFF) {
error("Unsupported far jump (we'll get there)");
}
currentChunk()->code[offset] = (jump >> 8) & 0xFF;
currentChunk()->code[offset + 1] = (jump) & 0xFF;
}
static void emitLoop(int loopStart) {
/* Patch continue statements to point to here, before the loop operation (yes that's silly) */

23
test/testDefaultArgs.krk Normal file
View File

@ -0,0 +1,23 @@
def foo(default="bacon"):
print "You like",default,"right?"
foo()
foo("sports")
def fillValues(a=1,b=2,c="c",d=None,e=2.71828):
print a,b,c,d,e
fillValues(b=True)
fillValues(c="test",a="one",e=object)
# Not like in Python! This is absolutely an anti-feature in Python.
def alwaysAFreshList(l=[]):
print "l=",l
l.append(1)
print "l*=",l
alwaysAFreshList()
alwaysAFreshList()
alwaysAFreshList([1,2,3])
alwaysAFreshList([1,2,3])
alwaysAFreshList()

View File

@ -34,6 +34,7 @@ void krk_printValue(FILE * f, KrkValue printable) {
case VAL_FLOATING: fprintf(f, "%g", AS_FLOATING(printable)); break;
case VAL_NONE: fprintf(f, "None"); break;
case VAL_HANDLER: fprintf(f, "{try->%ld}", AS_HANDLER(printable)); break;
case VAL_KWARGS: fprintf(f, "{sentinel=%ld}", AS_INTEGER(printable)); break;
default: break;
}
return;
@ -79,6 +80,7 @@ int krk_valuesEqual(KrkValue a, KrkValue b) {
switch (a.type) {
case VAL_BOOLEAN: return AS_BOOLEAN(a) == AS_BOOLEAN(b);
case VAL_NONE: return 1; /* None always equals None */
case VAL_KWARGS: /* Equal if same number of args; may be useful for comparing sentinels (0) to arg lists. */
case VAL_INTEGER: return AS_INTEGER(a) == AS_INTEGER(b);
case VAL_FLOATING: return AS_FLOATING(a) == AS_FLOATING(b);
case VAL_HANDLER: {

12
vm.c
View File

@ -708,10 +708,7 @@ static int call(KrkClosure * closure, int argCount, int extra) {
*
* Finally, we do one last pass to see if any of the sentinel values
* indicating missing positional arguments is still there and raise
* another TypeError to indicate missing required arguments, and fill
* out the default values for missing keyword arguments.
* (TODO: Support other default values for kwargs; need to put them
* somewhere, probably close them as upvalues?)
* another TypeError to indicate missing required arguments.
*
* At this point we can reset the stack head and continue to the actual
* call with all of the arguments, including the defaults, in the right
@ -780,11 +777,6 @@ _finishArg:
return 0;
}
}
for (; clearSlots < closure->function->requiredArgs + closure->function->keywordArgs; ++clearSlots) {
if (IS_KWARGS(startOfPositionals[clearSlots])) {
startOfPositionals[clearSlots] = NONE_VAL();/* TODO: Default value for kwarg */
}
}
argCount = closure->function->requiredArgs + closure->function->keywordArgs;
while (vm.stackTop > startOfPositionals + argCount) krk_pop();
}
@ -792,7 +784,7 @@ _finishArg:
return 0;
}
while (argCount < (closure->function->requiredArgs + closure->function->keywordArgs)) {
krk_push(NONE_VAL());
krk_push(KWARGS_VAL(0));
argCount++;
}
if (vm.frameCount == FRAMES_MAX) {