Lambda should be able to accept *args and **kwargs
This commit is contained in:
parent
65f7ae3f22
commit
bbfe948b86
215
src/compiler.c
215
src/compiler.c
@ -1309,6 +1309,109 @@ static void argumentDefinition(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void functionPrologue(Compiler * compiler) {
|
||||
KrkCodeObject * func = endCompiler();
|
||||
if (compiler->annotationCount) {
|
||||
EMIT_OPERAND_OP(OP_MAKE_DICT, compiler->annotationCount * 2);
|
||||
}
|
||||
size_t ind = krk_addConstant(currentChunk(), OBJECT_VAL(func));
|
||||
EMIT_OPERAND_OP(OP_CLOSURE, ind);
|
||||
doUpvalues(compiler, func);
|
||||
if (compiler->annotationCount) {
|
||||
emitByte(OP_ANNOTATE);
|
||||
}
|
||||
freeCompiler(compiler);
|
||||
}
|
||||
|
||||
static int argumentList(FunctionType type) {
|
||||
int hasCollectors = 0;
|
||||
KrkToken self = syntheticToken("self");
|
||||
|
||||
do {
|
||||
if (isMethod(type) && check(TOKEN_IDENTIFIER) &&
|
||||
identifiersEqual(&parser.current, &self)) {
|
||||
if (hasCollectors || current->codeobject->requiredArgs != 1) {
|
||||
errorAtCurrent("Argument name 'self' in a method signature is reserved for the implicit first argument.");
|
||||
return 1;
|
||||
}
|
||||
advance();
|
||||
if (type != TYPE_LAMBDA && check(TOKEN_COLON)) {
|
||||
KrkToken name = parser.previous;
|
||||
match(TOKEN_COLON);
|
||||
typeHint(name);
|
||||
}
|
||||
if (check(TOKEN_EQUAL)) {
|
||||
errorAtCurrent("'self' can not be a keyword argument.");
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (match(TOKEN_ASTERISK) || check(TOKEN_POW)) {
|
||||
if (match(TOKEN_POW)) {
|
||||
if (hasCollectors == 2) {
|
||||
error("Duplicate ** in parameter list.");
|
||||
return 1;
|
||||
}
|
||||
hasCollectors = 2;
|
||||
current->codeobject->flags |= KRK_CODEOBJECT_FLAGS_COLLECTS_KWS;
|
||||
} else {
|
||||
if (hasCollectors) {
|
||||
error("Syntax error.");
|
||||
return 1;
|
||||
}
|
||||
hasCollectors = 1;
|
||||
current->codeobject->flags |= KRK_CODEOBJECT_FLAGS_COLLECTS_ARGS;
|
||||
}
|
||||
/* Collect a name, specifically "args" or "kwargs" are commont */
|
||||
ssize_t paramConstant = parseVariable(
|
||||
(hasCollectors == 1) ? "Expected parameter name after '*'." : "Expected parameter name after '**'.");
|
||||
if (parser.hadError) return 1;
|
||||
defineVariable(paramConstant);
|
||||
KrkToken name = parser.previous;
|
||||
if (isMethod(type) && identifiersEqual(&name,&self)) {
|
||||
errorAtCurrent("Argument name 'self' in a method signature is reserved for the implicit first argument.");
|
||||
return 1;
|
||||
}
|
||||
if (type != TYPE_LAMBDA && check(TOKEN_COLON)) {
|
||||
match(TOKEN_COLON);
|
||||
typeHint(name);
|
||||
}
|
||||
/* Make that a valid local for this function */
|
||||
size_t myLocal = current->localCount - 1;
|
||||
EMIT_OPERAND_OP(OP_GET_LOCAL, myLocal);
|
||||
/* Check if it's equal to the unset-kwarg-sentinel value */
|
||||
emitBytes(OP_UNSET, OP_IS);
|
||||
int jumpIndex = emitJump(OP_JUMP_IF_FALSE_OR_POP);
|
||||
/* And if it is, set it to the appropriate type */
|
||||
beginScope();
|
||||
if (hasCollectors == 1) EMIT_OPERAND_OP(OP_MAKE_LIST,0);
|
||||
else EMIT_OPERAND_OP(OP_MAKE_DICT,0);
|
||||
EMIT_OPERAND_OP(OP_SET_LOCAL, myLocal);
|
||||
endScope();
|
||||
/* Otherwise pop the comparison. */
|
||||
patchJump(jumpIndex);
|
||||
emitByte(OP_POP); /* comparison value or expression */
|
||||
continue;
|
||||
}
|
||||
if (hasCollectors) {
|
||||
error("arguments follow catch-all collector");
|
||||
break;
|
||||
}
|
||||
ssize_t paramConstant = parseVariable("Expected parameter name.");
|
||||
if (parser.hadError) return 1;
|
||||
hideLocal();
|
||||
if (type != TYPE_LAMBDA && check(TOKEN_COLON)) {
|
||||
KrkToken name = parser.previous;
|
||||
match(TOKEN_COLON);
|
||||
typeHint(name);
|
||||
}
|
||||
argumentDefinition();
|
||||
defineVariable(paramConstant);
|
||||
} while (match(TOKEN_COMMA));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void function(FunctionType type, size_t blockWidth) {
|
||||
Compiler compiler;
|
||||
initCompiler(&compiler, type);
|
||||
@ -1319,89 +1422,10 @@ static void function(FunctionType type, size_t blockWidth) {
|
||||
if (isMethod(type)) current->codeobject->requiredArgs = 1;
|
||||
if (isCoroutine(type)) current->codeobject->flags |= KRK_CODEOBJECT_FLAGS_IS_COROUTINE;
|
||||
|
||||
int hasCollectors = 0;
|
||||
KrkToken self = syntheticToken("self");
|
||||
|
||||
consume(TOKEN_LEFT_PAREN, "Expected start of parameter list after function name.");
|
||||
startEatingWhitespace();
|
||||
if (!check(TOKEN_RIGHT_PAREN)) {
|
||||
do {
|
||||
if (isMethod(type) && check(TOKEN_IDENTIFIER) &&
|
||||
identifiersEqual(&parser.current, &self)) {
|
||||
if (hasCollectors || current->codeobject->requiredArgs != 1) {
|
||||
errorAtCurrent("Argument name 'self' in a method signature is reserved for the implicit first argument.");
|
||||
goto _bail;
|
||||
}
|
||||
advance();
|
||||
if (check(TOKEN_COLON)) {
|
||||
KrkToken name = parser.previous;
|
||||
match(TOKEN_COLON);
|
||||
typeHint(name);
|
||||
}
|
||||
if (check(TOKEN_EQUAL)) {
|
||||
errorAtCurrent("'self' can not be a keyword argument.");
|
||||
goto _bail;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (match(TOKEN_ASTERISK) || check(TOKEN_POW)) {
|
||||
if (match(TOKEN_POW)) {
|
||||
if (hasCollectors == 2) {
|
||||
error("Duplicate ** in parameter list.");
|
||||
goto _bail;
|
||||
}
|
||||
hasCollectors = 2;
|
||||
current->codeobject->flags |= KRK_CODEOBJECT_FLAGS_COLLECTS_KWS;
|
||||
} else {
|
||||
if (hasCollectors) {
|
||||
error("Syntax error.");
|
||||
goto _bail;
|
||||
}
|
||||
hasCollectors = 1;
|
||||
current->codeobject->flags |= KRK_CODEOBJECT_FLAGS_COLLECTS_ARGS;
|
||||
}
|
||||
/* Collect a name, specifically "args" or "kwargs" are commont */
|
||||
ssize_t paramConstant = parseVariable(
|
||||
(hasCollectors == 1) ? "Expected parameter name after '*'." : "Expected parameter name after '**'.");
|
||||
if (parser.hadError) goto _bail;
|
||||
defineVariable(paramConstant);
|
||||
if (check(TOKEN_COLON)) {
|
||||
KrkToken name = parser.previous;
|
||||
match(TOKEN_COLON);
|
||||
typeHint(name);
|
||||
}
|
||||
/* Make that a valid local for this function */
|
||||
size_t myLocal = current->localCount - 1;
|
||||
EMIT_OPERAND_OP(OP_GET_LOCAL, myLocal);
|
||||
/* Check if it's equal to the unset-kwarg-sentinel value */
|
||||
emitBytes(OP_UNSET, OP_IS);
|
||||
int jumpIndex = emitJump(OP_JUMP_IF_FALSE_OR_POP);
|
||||
/* And if it is, set it to the appropriate type */
|
||||
beginScope();
|
||||
if (hasCollectors == 1) EMIT_OPERAND_OP(OP_MAKE_LIST,0);
|
||||
else EMIT_OPERAND_OP(OP_MAKE_DICT,0);
|
||||
EMIT_OPERAND_OP(OP_SET_LOCAL, myLocal);
|
||||
endScope();
|
||||
/* Otherwise pop the comparison. */
|
||||
patchJump(jumpIndex);
|
||||
emitByte(OP_POP); /* comparison value or expression */
|
||||
continue;
|
||||
}
|
||||
if (hasCollectors) {
|
||||
error("arguments follow catch-all collector");
|
||||
break;
|
||||
}
|
||||
ssize_t paramConstant = parseVariable("Expected parameter name.");
|
||||
if (parser.hadError) goto _bail;
|
||||
hideLocal();
|
||||
if (check(TOKEN_COLON)) {
|
||||
KrkToken name = parser.previous;
|
||||
match(TOKEN_COLON);
|
||||
typeHint(name);
|
||||
}
|
||||
argumentDefinition();
|
||||
defineVariable(paramConstant);
|
||||
} while (match(TOKEN_COMMA));
|
||||
if (argumentList(type)) goto _bail;
|
||||
}
|
||||
stopEatingWhitespace();
|
||||
consume(TOKEN_RIGHT_PAREN, "Expected end of parameter list.");
|
||||
@ -1413,19 +1437,7 @@ static void function(FunctionType type, size_t blockWidth) {
|
||||
consume(TOKEN_COLON, "Expected colon after function signature.");
|
||||
block(blockWidth,"def");
|
||||
_bail: (void)0;
|
||||
KrkCodeObject * function = endCompiler();
|
||||
if (compiler.annotationCount) {
|
||||
EMIT_OPERAND_OP(OP_MAKE_DICT, compiler.annotationCount * 2);
|
||||
}
|
||||
size_t ind = krk_addConstant(currentChunk(), OBJECT_VAL(function));
|
||||
EMIT_OPERAND_OP(OP_CLOSURE, ind);
|
||||
doUpvalues(&compiler, function);
|
||||
|
||||
if (compiler.annotationCount) {
|
||||
emitByte(OP_ANNOTATE);
|
||||
}
|
||||
|
||||
freeCompiler(&compiler);
|
||||
functionPrologue(&compiler);
|
||||
}
|
||||
|
||||
static void classBody(size_t blockWidth) {
|
||||
@ -1610,24 +1622,15 @@ static void lambda(int exprType) {
|
||||
beginScope();
|
||||
|
||||
if (!check(TOKEN_COLON)) {
|
||||
do {
|
||||
ssize_t paramConstant = parseVariable("Expected parameter name.");
|
||||
if (parser.hadError) goto _bail;
|
||||
hideLocal();
|
||||
argumentDefinition();
|
||||
defineVariable(paramConstant);
|
||||
} while (match(TOKEN_COMMA));
|
||||
if (argumentList(TYPE_LAMBDA)) goto _bail;
|
||||
}
|
||||
|
||||
consume(TOKEN_COLON, "Expected ':' after lambda arguments");
|
||||
expression();
|
||||
|
||||
_bail: (void)0;
|
||||
KrkCodeObject * lambda = endCompiler();
|
||||
size_t ind = krk_addConstant(currentChunk(), OBJECT_VAL(lambda));
|
||||
EMIT_OPERAND_OP(OP_CLOSURE, ind);
|
||||
doUpvalues(&lambdaCompiler, lambda);
|
||||
freeCompiler(&lambdaCompiler);
|
||||
_bail:
|
||||
functionPrologue(&lambdaCompiler);
|
||||
|
||||
invalidTarget(exprType, "lambda");
|
||||
}
|
||||
|
||||
|
48
test/testBadSelfAsMethodArg.krk
Normal file
48
test/testBadSelfAsMethodArg.krk
Normal file
@ -0,0 +1,48 @@
|
||||
import dis
|
||||
|
||||
let template = '''
|
||||
class Foo:
|
||||
def method(__args__):
|
||||
pass
|
||||
'''
|
||||
|
||||
# 'self' can only be the first argument
|
||||
try:
|
||||
dis.build(template.replace('__args__','a,self'))
|
||||
print('fail')
|
||||
except SyntaxError as e:
|
||||
print('pass' if 'implicit' in str(e) else 'fail')
|
||||
|
||||
# 'self' can not be a star arg
|
||||
try:
|
||||
dis.build(template.replace('__args__','a,*self'))
|
||||
print('fail')
|
||||
except SyntaxError as e:
|
||||
print('pass' if 'implicit' in str(e) else 'fail')
|
||||
|
||||
# 'self' can not be a star-star arg
|
||||
try:
|
||||
dis.build(template.replace('__args__','a,**self'))
|
||||
print('fail')
|
||||
except SyntaxError as e:
|
||||
print('pass' if 'implicit' in str(e) else 'fail')
|
||||
|
||||
# 'self' can not take a default value
|
||||
try:
|
||||
dis.build(template.replace('__args__','self=42'))
|
||||
print('fail')
|
||||
except SyntaxError as e:
|
||||
print('pass' if 'keyword argument' in str(e) else 'fail')
|
||||
|
||||
# for that matter, neither can star args
|
||||
try:
|
||||
dis.build(template.replace('__args__','*args=[]'))
|
||||
print('fail')
|
||||
except SyntaxError as e:
|
||||
print('pass' if 'end of' in str(e) else 'fail')
|
||||
|
||||
try:
|
||||
dis.build(template.replace('__args__','**args=[]'))
|
||||
print('fail')
|
||||
except SyntaxError as e:
|
||||
print('pass' if 'end of' in str(e) else 'fail')
|
6
test/testBadSelfAsMethodArg.krk.expect
Normal file
6
test/testBadSelfAsMethodArg.krk.expect
Normal file
@ -0,0 +1,6 @@
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
8
test/testLambdaTakesStars.krk
Normal file
8
test/testLambdaTakesStars.krk
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
let star = lambda *args: ', '.join(str(x) for x in args)
|
||||
|
||||
print(star(1,2,3,'a','b','c'))
|
||||
|
||||
let kws = lambda **kwargs: ', '.join(sorted(f'{x}: {y}' for x, y in kwargs.items()))
|
||||
|
||||
print(kws(a=42,b='hi',c=None))
|
2
test/testLambdaTakesStars.krk.expect
Normal file
2
test/testLambdaTakesStars.krk.expect
Normal file
@ -0,0 +1,2 @@
|
||||
1, 2, 3, a, b, c
|
||||
a: 42, b: hi, c: None
|
Loading…
Reference in New Issue
Block a user