Skip to content

Commit af5c88f

Browse files
author
Drew O'Meara
committed
fix+enhanced py.ParseTupleAndKeywords()
1 parent 965bc08 commit af5c88f

File tree

1 file changed

+62
-42
lines changed

1 file changed

+62
-42
lines changed

py/args.go

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,7 @@
368368
// $
369369
//
370370
// PyArg_ParseTupleAndKeywords() only: Indicates that the remaining
371-
// arguments in the Python argument list are keyword-only. Currently,
372-
// all keyword-only arguments must also be optional arguments, so |
373-
// must always be specified before $ in the format string.
371+
// arguments in the Python argument list are keyword-only.
374372
//
375373
// New in version 3.3.
376374
//
@@ -416,17 +414,13 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
416414
if kwlist != nil && len(results) != len(kwlist) {
417415
return ExceptionNewf(TypeError, "Internal error: supply the same number of results and kwlist")
418416
}
419-
min, max, name, ops := parseFormat(format)
420-
keywordOnly := false
421-
err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, max)
417+
var opsBuf [16]formatOp
418+
min, name, kwOnly_i, ops := parseFormat(format, opsBuf[:0])
419+
err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, len(ops))
422420
if err != nil {
423421
return err
424422
}
425423

426-
if len(ops) > 0 && ops[0] == "$" {
427-
keywordOnly = true
428-
ops = ops[1:]
429-
}
430424
// Check all the kwargs are in kwlist
431425
// O(N^2) Slow but kwlist is usually short
432426
for kwargName := range kwargs {
@@ -439,46 +433,60 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
439433
found:
440434
}
441435

442-
// Create args tuple with all the arguments we have in
443-
args = args.Copy()
444-
for i, kw := range kwlist {
445-
if value, ok := kwargs[kw]; ok {
446-
if len(args) > i {
436+
// Walk through all the results we want
437+
for i, op := range ops {
438+
439+
var (
440+
arg Object
441+
kw string
442+
)
443+
if i < len(kwlist) {
444+
kw = kwlist[i]
445+
arg = kwargs[kw]
446+
}
447+
448+
// Consume ordered args first -- they should not require keyword only or also be specified via keyword
449+
if i < len(args) {
450+
if i >= kwOnly_i {
451+
return ExceptionNewf(TypeError, "%s() specifies argument '%s' that is keyword only", name, kw)
452+
}
453+
if arg != nil {
447454
return ExceptionNewf(TypeError, "%s() got multiple values for argument '%s'", name, kw)
448455
}
449-
args = append(args, value)
450-
} else if keywordOnly {
451-
args = append(args, nil)
456+
arg = args[i]
452457
}
453-
}
454-
for i, arg := range args {
455-
op := ops[i]
458+
459+
// Unspecified args retain their default value
460+
if arg == nil {
461+
continue
462+
}
463+
456464
result := results[i]
457-
switch op {
458-
case "O":
465+
switch op.code {
466+
case 'O':
459467
*result = arg
460-
case "Z", "z":
468+
case 'Z', 'z':
461469
if _, ok := arg.(NoneType); ok {
462470
*result = arg
463471
break
464472
}
465473
fallthrough
466-
case "U", "s":
474+
case 'U', 's':
467475
if _, ok := arg.(String); !ok {
468476
return ExceptionNewf(TypeError, "%s() argument %d must be str, not %s", name, i+1, arg.Type().Name)
469477
}
470478
*result = arg
471-
case "i":
479+
case 'i':
472480
if _, ok := arg.(Int); !ok {
473481
return ExceptionNewf(TypeError, "%s() argument %d must be int, not %s", name, i+1, arg.Type().Name)
474482
}
475483
*result = arg
476-
case "p":
484+
case 'p':
477485
if _, ok := arg.(Bool); !ok {
478486
return ExceptionNewf(TypeError, "%s() argument %d must be bool, not %s", name, i+1, arg.Type().Name)
479487
}
480488
*result = arg
481-
case "d":
489+
case 'd':
482490
switch x := arg.(type) {
483491
case Int:
484492
*result = Float(x)
@@ -500,30 +508,42 @@ func ParseTuple(args Tuple, format string, results ...*Object) error {
500508
return ParseTupleAndKeywords(args, nil, format, nil, results...)
501509
}
502510

511+
type formatOp struct {
512+
code byte
513+
modifier byte
514+
}
515+
503516
// Parse the format
504-
func parseFormat(format string) (min, max int, name string, ops []string) {
517+
func parseFormat(format string, in []formatOp) (min int, name string, kwOnly_i int, ops []formatOp) {
505518
name = "function"
506519
min = -1
507-
for format != "" {
508-
op := string(format[0])
509-
format = format[1:]
510-
if len(format) > 1 && (format[1] == '*' || format[1] == '#') {
511-
op += string(format[0])
512-
format = format[1:]
520+
kwOnly_i = 0xFFFF
521+
ops = in[:0]
522+
523+
N := len(format)
524+
for i := 0; i < N; {
525+
op := formatOp{code: format[i]}
526+
i++
527+
if i < N {
528+
if mod := format[i]; mod == '*' || mod == '#' {
529+
op.modifier = mod
530+
i++
531+
}
513532
}
514-
switch op {
515-
case ":", ";":
516-
name = format
517-
format = ""
518-
case "|":
533+
switch op.code {
534+
case ':', ';':
535+
name = format[i:]
536+
i = N
537+
case '$':
538+
kwOnly_i = len(ops)
539+
case '|':
519540
min = len(ops)
520541
default:
521542
ops = append(ops, op)
522543
}
523544
}
524-
max = len(ops)
525545
if min < 0 {
526-
min = max
546+
min = len(ops)
527547
}
528548
return
529549
}

0 commit comments

Comments
 (0)