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) {
|
static void function(FunctionType type, size_t blockWidth) {
|
||||||
Compiler compiler;
|
Compiler compiler;
|
||||||
initCompiler(&compiler, type);
|
initCompiler(&compiler, type);
|
||||||
@ -1319,89 +1422,10 @@ static void function(FunctionType type, size_t blockWidth) {
|
|||||||
if (isMethod(type)) current->codeobject->requiredArgs = 1;
|
if (isMethod(type)) current->codeobject->requiredArgs = 1;
|
||||||
if (isCoroutine(type)) current->codeobject->flags |= KRK_CODEOBJECT_FLAGS_IS_COROUTINE;
|
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.");
|
consume(TOKEN_LEFT_PAREN, "Expected start of parameter list after function name.");
|
||||||
startEatingWhitespace();
|
startEatingWhitespace();
|
||||||
if (!check(TOKEN_RIGHT_PAREN)) {
|
if (!check(TOKEN_RIGHT_PAREN)) {
|
||||||
do {
|
if (argumentList(type)) goto _bail;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
stopEatingWhitespace();
|
stopEatingWhitespace();
|
||||||
consume(TOKEN_RIGHT_PAREN, "Expected end of parameter list.");
|
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.");
|
consume(TOKEN_COLON, "Expected colon after function signature.");
|
||||||
block(blockWidth,"def");
|
block(blockWidth,"def");
|
||||||
_bail: (void)0;
|
_bail: (void)0;
|
||||||
KrkCodeObject * function = endCompiler();
|
functionPrologue(&compiler);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void classBody(size_t blockWidth) {
|
static void classBody(size_t blockWidth) {
|
||||||
@ -1610,24 +1622,15 @@ static void lambda(int exprType) {
|
|||||||
beginScope();
|
beginScope();
|
||||||
|
|
||||||
if (!check(TOKEN_COLON)) {
|
if (!check(TOKEN_COLON)) {
|
||||||
do {
|
if (argumentList(TYPE_LAMBDA)) goto _bail;
|
||||||
ssize_t paramConstant = parseVariable("Expected parameter name.");
|
|
||||||
if (parser.hadError) goto _bail;
|
|
||||||
hideLocal();
|
|
||||||
argumentDefinition();
|
|
||||||
defineVariable(paramConstant);
|
|
||||||
} while (match(TOKEN_COMMA));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(TOKEN_COLON, "Expected ':' after lambda arguments");
|
consume(TOKEN_COLON, "Expected ':' after lambda arguments");
|
||||||
expression();
|
expression();
|
||||||
|
|
||||||
_bail: (void)0;
|
_bail:
|
||||||
KrkCodeObject * lambda = endCompiler();
|
functionPrologue(&lambdaCompiler);
|
||||||
size_t ind = krk_addConstant(currentChunk(), OBJECT_VAL(lambda));
|
|
||||||
EMIT_OPERAND_OP(OP_CLOSURE, ind);
|
|
||||||
doUpvalues(&lambdaCompiler, lambda);
|
|
||||||
freeCompiler(&lambdaCompiler);
|
|
||||||
invalidTarget(exprType, "lambda");
|
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