From f3b9939aab4554753ce385d4aa3bf12f5b5a04d8 Mon Sep 17 00:00:00 2001 From: Dong-hee Na Date: Sun, 6 Jan 2019 16:11:21 +0900 Subject: [PATCH] builtin: Implement min/max builtin function This CL implementation is not 100% accurate since, keyfunc and default value parameter is not supported. We can support it later CL. --- builtin/builtin.go | 133 ++++++++++++++++++++++++++++++++++++++- builtin/tests/builtin.py | 57 +++++++++++++++++ py/args.go | 9 ++- 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/builtin/builtin.go b/builtin/builtin.go index 0aea4767..218f8744 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -47,8 +47,8 @@ func init() { // py.MustNewMethod("iter", builtin_iter, 0, iter_doc), py.MustNewMethod("len", builtin_len, 0, len_doc), py.MustNewMethod("locals", py.InternalMethodLocals, 0, locals_doc), - // py.MustNewMethod("max", builtin_max, 0, max_doc), - // py.MustNewMethod("min", builtin_min, 0, min_doc), + py.MustNewMethod("max", builtin_max, 0, max_doc), + py.MustNewMethod("min", builtin_min, 0, min_doc), py.MustNewMethod("next", builtin_next, 0, next_doc), py.MustNewMethod("open", builtin_open, 0, open_doc), // py.MustNewMethod("oct", builtin_oct, 0, oct_doc), @@ -772,6 +772,135 @@ func builtin_len(self, v py.Object) (py.Object, error) { return py.Len(v) } +const max_doc = ` +max(iterable, *[, default=obj, key=func]) -> value +max(arg1, arg2, *args, *[, key=func]) -> value + +With a single iterable argument, return its biggest item. The +default keyword-only argument specifies an object to return if +the provided iterable is empty. +With two or more arguments, return the largest argument.` + +func builtin_max(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + return min_max(args, kwargs, "max") +} + +const min_doc = ` +min(iterable, *[, default=obj, key=func]) -> value +min(arg1, arg2, *args, *[, key=func]) -> value + +With a single iterable argument, return its smallest item. The +default keyword-only argument specifies an object to return if +the provided iterable is empty. +With two or more arguments, return the smallest argument.` + +func builtin_min(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + return min_max(args, kwargs, "min") +} + +func min_max(args py.Tuple, kwargs py.StringDict, name string) (py.Object, error) { + kwlist := []string{"key", "default"} + positional := len(args) + var format string + var values py.Object + var cmp func(a py.Object, b py.Object) (py.Object, error) + if name == "min" { + format = "|$OO:min" + cmp = py.Le + } else if name == "max" { + format = "|$OO:max" + cmp = py.Ge + } + var defaultValue py.Object + var keyFunc py.Object + var maxVal, maxItem py.Object + var kf *py.Function + + if positional > 1 { + values = args + } else { + err := py.UnpackTuple(args, nil, name, 1, 1, &values) + if err != nil { + return nil, err + } + } + err := py.ParseTupleAndKeywords(nil, kwargs, format, kwlist, &keyFunc, &defaultValue) + if err != nil { + return nil, err + } + if keyFunc == py.None { + keyFunc = nil + } + if keyFunc != nil { + var ok bool + kf, ok = keyFunc.(*py.Function) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "'%s' object is not callable", keyFunc.Type()) + } + } + if defaultValue != nil { + maxItem = defaultValue + if keyFunc != nil { + maxVal, err = py.Call(kf, py.Tuple{defaultValue}, nil) + if err != nil { + return nil, err + } + } else { + maxVal = defaultValue + } + } + iter, err := py.Iter(values) + if err != nil { + return nil, err + } + + for { + item, err := py.Next(iter) + if err != nil { + if py.IsException(py.StopIteration, err) { + break + } + return nil, err + } + if maxVal == nil { + if keyFunc != nil { + maxVal, err = py.Call(kf, py.Tuple{item}, nil) + if err != nil { + return nil, err + } + } else { + maxVal = item + } + maxItem = item + } else { + var compareVal py.Object + if keyFunc != nil { + compareVal, err = py.Call(kf, py.Tuple{item}, nil) + if err != nil { + return nil, err + } + } else { + compareVal = item + } + changed, err := cmp(compareVal, maxVal) + if err != nil { + return nil, err + } + if changed == py.True { + maxVal = compareVal + maxItem = item + } + } + + } + + if maxItem == nil { + return nil, py.ExceptionNewf(py.ValueError, "%s() arg is an empty sequence", name) + } + + return maxItem, nil +} + const chr_doc = `chr(i) -> Unicode character Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.` diff --git a/builtin/tests/builtin.py b/builtin/tests/builtin.py index d7140d68..4bb01106 100644 --- a/builtin/tests/builtin.py +++ b/builtin/tests/builtin.py @@ -75,6 +75,63 @@ def fn(x): assert locals()["x"] == 1 fn(1) +def func(p): + return p[1] + +doc="min" +values = (1,2,3) +v = min(values) +assert v == 1 +v = min(4,5,6) +assert v == 4 +v = min((), default=-1) +assert v == -1 +v = min([], default=-1) +assert v == -1 +v = min([], key=func, default=(1,3)) +assert v == (1,3) +v = min([(1,3), (2,1)], key=func) +assert v == (2,1) +ok = False +try: + min([(1,3), (2,1)], key=3) +except TypeError: + ok = True +assert ok, "TypeError not raised" +ok = False +try: + min([]) +except ValueError: + ok = True +assert ok, "ValueError not raised" + +doc="max" +values = (1,2,3) +v = max(values) +assert v == 3 +v = max(4,5,6) +assert v == 6 +v = max((), default=-1) +assert v == -1 +v = max([], default=-1) +assert v == -1 +v = max([], key=func, default=(1,3)) +assert v == (1,3) +v = max([(1,3), (2,1)], key=func) +assert v == (1,3) +ok = False +try: + max([(1,3), (2,1)], key=3) +except TypeError: + ok = True +assert ok, "TypeError not raised" +ok = False +try: + max([]) +except ValueError: + ok = True +assert ok, "ValueError not raised" + doc="next no default" def gen(): yield 1 diff --git a/py/args.go b/py/args.go index dcdfc392..f38fdfde 100644 --- a/py/args.go +++ b/py/args.go @@ -417,11 +417,16 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist return ExceptionNewf(TypeError, "Internal error: supply the same number of results and kwlist") } min, max, name, ops := parseFormat(format) + keywordOnly := false err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, max) if err != nil { return err } + if len(ops) > 0 && ops[0] == "$" { + keywordOnly = true + ops = ops[1:] + } // Check all the kwargs are in kwlist // O(N^2) Slow but kwlist is usually short for kwargName := range kwargs { @@ -442,10 +447,10 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist return ExceptionNewf(TypeError, "%s() got multiple values for argument '%s'", name, kw) } args = append(args, value) + } else if keywordOnly { + args = append(args, nil) } - } - for i, arg := range args { op := ops[i] result := results[i]