diff --git a/py/arithmetic.go b/py/arithmetic.go index 199fceee..768764b8 100644 --- a/py/arithmetic.go +++ b/py/arithmetic.go @@ -147,24 +147,6 @@ func MakeFloat(a Object) (Object, error) { return nil, ExceptionNewf(TypeError, "unsupported operand type(s) for float: '%s'", a.Type().Name) } -// Iter the python Object returning an Object -// -// Will raise TypeError if Iter can't be run on this object -func Iter(a Object) (Object, error) { - - if A, ok := a.(I__iter__); ok { - res, err := A.M__iter__() - if err != nil { - return nil, err - } - if res != NotImplemented { - return res, nil - } - } - - return nil, ExceptionNewf(TypeError, "unsupported operand type(s) for iter: '%s'", a.Type().Name) -} - // Add two python objects together returning an Object // // Will raise TypeError if can't be add can't be run on these objects diff --git a/py/dict.go b/py/dict.go index 9aac7631..497da3a7 100644 --- a/py/dict.go +++ b/py/dict.go @@ -36,7 +36,7 @@ func init() { return nil, err } sMap := self.(StringDict) - o := make([]Object, 0, len(sMap)) + o := make(Tuple, 0, len(sMap)) for k, v := range sMap { o = append(o, Tuple{String(k), v}) } @@ -49,7 +49,7 @@ func init() { return nil, err } sMap := self.(StringDict) - o := make([]Object, 0, len(sMap)) + o := make(Tuple, 0, len(sMap)) for k := range sMap { o = append(o, String(k)) } @@ -62,7 +62,7 @@ func init() { return nil, err } sMap := self.(StringDict) - o := make([]Object, 0, len(sMap)) + o := make(Tuple, 0, len(sMap)) for _, v := range sMap { o = append(o, v) } @@ -204,7 +204,7 @@ func (a StringDict) M__repr__() (Object, error) { // Returns a list of keys from the dict func (d StringDict) M__iter__() (Object, error) { - o := make([]Object, 0, len(d)) + o := make(Tuple, 0, len(d)) for k := range d { o = append(o, String(k)) } diff --git a/py/exception.go b/py/exception.go index 2e8f91a2..73f92747 100644 --- a/py/exception.go +++ b/py/exception.go @@ -336,6 +336,8 @@ func ExceptionGivenMatches(err, exc Object) bool { func IsException(exception *Type, r interface{}) bool { var t *Type switch ex := r.(type) { + case ExceptionInfo: + t = ex.Type case *Exception: t = ex.Type() case *Type: diff --git a/py/filter.go b/py/filter.go new file mode 100644 index 00000000..447c4829 --- /dev/null +++ b/py/filter.go @@ -0,0 +1,72 @@ +// Copyright 2023 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package py + +// A python Filter object +type Filter struct { + it Object + fun Object +} + +var FilterType = NewTypeX("filter", `filter(function or None, iterable) --> filter object + +Return an iterator yielding those items of iterable for which function(item) +is true. If function is None, return the items that are true.`, + FilterTypeNew, nil) + +// Type of this object +func (f *Filter) Type() *Type { + return FilterType +} + +// FilterTypeNew +func FilterTypeNew(metatype *Type, args Tuple, kwargs StringDict) (res Object, err error) { + var fun, seq Object + var it Object + err = UnpackTuple(args, kwargs, "filter", 2, 2, &fun, &seq) + if err != nil { + return nil, err + } + it, err = Iter(seq) + if err != nil { + return nil, err + } + return &Filter{it: it, fun: fun}, nil +} + +func (f *Filter) M__iter__() (Object, error) { + return f, nil +} + +func (f *Filter) M__next__() (Object, error) { + var ok bool + for { + item, err := Next(f.it) + if err != nil { + return nil, err + } + // if (lz->func == Py_None || lz->func == (PyObject *)&PyBool_Type) + if _, _ok := f.fun.(Bool); _ok || f.fun == None { + ok, err = ObjectIsTrue(item) + } else { + var good Object + good, err = Call(f.fun, Tuple{item}, nil) + if err != nil { + return nil, err + } + ok, err = ObjectIsTrue(good) + } + if ok { + return item, nil + } + if err != nil { + return nil, err + } + } +} + +// Check interface is satisfied +var _ I__iter__ = (*Filter)(nil) +var _ I__next__ = (*Filter)(nil) diff --git a/py/gen.go b/py/gen.go index 16db6ad8..671b0831 100644 --- a/py/gen.go +++ b/py/gen.go @@ -45,7 +45,6 @@ var data = Data{ {Name: "complex", Title: "MakeComplex", Operator: "complex", Unary: true, Conversion: "Complex"}, {Name: "int", Title: "MakeInt", Operator: "int", Unary: true, Conversion: "Int"}, {Name: "float", Title: "MakeFloat", Operator: "float", Unary: true, Conversion: "Float"}, - {Name: "iter", Title: "Iter", Operator: "iter", Unary: true}, }, BinaryOps: Ops{ {Name: "add", Title: "Add", Operator: "+", Binary: true}, diff --git a/py/internal.go b/py/internal.go index df0e285c..e649b299 100644 --- a/py/internal.go +++ b/py/internal.go @@ -430,3 +430,20 @@ func ReprAsString(self Object) (string, error) { } return string(str), nil } + +// Returns an iterator object +// +// Call __Iter__ Returns an iterator object +// +// If object is sequence object, create an iterator +func Iter(self Object) (res Object, err error) { + if I, ok := self.(I__iter__); ok { + return I.M__iter__() + } else if res, ok, err = TypeCall0(self, "__iter__"); ok { + return res, err + } + if ObjectIsSequence(self) { + return NewIterator(self), nil + } + return nil, ExceptionNewf(TypeError, "'%s' object is not iterable", self.Type().Name) +} diff --git a/py/iterator.go b/py/iterator.go index a2368da8..350700a9 100644 --- a/py/iterator.go +++ b/py/iterator.go @@ -8,8 +8,8 @@ package py // A python Iterator object type Iterator struct { - Pos int - Objs []Object + Pos int + Seq Object } var IteratorType = NewType("iterator", "iterator type") @@ -20,10 +20,10 @@ func (o *Iterator) Type() *Type { } // Define a new iterator -func NewIterator(Objs []Object) *Iterator { +func NewIterator(Seq Object) *Iterator { m := &Iterator{ - Pos: 0, - Objs: Objs, + Pos: 0, + Seq: Seq, } return m } @@ -33,13 +33,29 @@ func (it *Iterator) M__iter__() (Object, error) { } // Get next one from the iteration -func (it *Iterator) M__next__() (Object, error) { - if it.Pos >= len(it.Objs) { - return nil, StopIteration +func (it *Iterator) M__next__() (res Object, err error) { + if tuple, ok := it.Seq.(Tuple); ok { + if it.Pos >= len(tuple) { + return nil, StopIteration + } + res = tuple[it.Pos] + it.Pos++ + return res, nil + } + index := Int(it.Pos) + if I, ok := it.Seq.(I__getitem__); ok { + res, err = I.M__getitem__(index) + } else if res, ok, err = TypeCall1(it.Seq, "__getitem__", index); !ok { + return nil, ExceptionNewf(TypeError, "'%s' object is not iterable", it.Type().Name) + } + if err != nil { + if IsException(IndexError, err) { + return nil, StopIteration + } + return nil, err } - r := it.Objs[it.Pos] it.Pos++ - return r, nil + return res, nil } // Check interface is satisfied diff --git a/py/list.go b/py/list.go index 28a118a1..9f6f62b0 100644 --- a/py/list.go +++ b/py/list.go @@ -186,7 +186,7 @@ func (l *List) M__bool__() (Object, error) { } func (l *List) M__iter__() (Object, error) { - return NewIterator(l.Items), nil + return NewIterator(Tuple(l.Items)), nil } func (l *List) M__getitem__(key Object) (Object, error) { @@ -496,7 +496,11 @@ func SortInPlace(l *List, kwargs StringDict, funcName string) error { reverse = False } // FIXME: requires the same bool-check like CPython (or better "|$Op" that doesn't panic on nil). - s := ptrSortable{&sortable{l, keyFunc, ObjectIsTrue(reverse), nil}} + ok, err := ObjectIsTrue(reverse) + if err != nil { + return err + } + s := ptrSortable{&sortable{l, keyFunc, ok, nil}} sort.Stable(s) return s.s.firstErr } diff --git a/py/map.go b/py/map.go new file mode 100644 index 00000000..1c343538 --- /dev/null +++ b/py/map.go @@ -0,0 +1,60 @@ +// Copyright 2023 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package py + +// A python Map object +type Map struct { + iters Tuple + fun Object +} + +var MapType = NewTypeX("filter", `map(func, *iterables) --> map object + +Make an iterator that computes the function using arguments from +each of the iterables. Stops when the shortest iterable is exhausted.`, + MapTypeNew, nil) + +// Type of this object +func (m *Map) Type() *Type { + return FilterType +} + +// MapType +func MapTypeNew(metatype *Type, args Tuple, kwargs StringDict) (res Object, err error) { + numargs := len(args) + if numargs < 2 { + return nil, ExceptionNewf(TypeError, "map() must have at least two arguments.") + } + iters := make(Tuple, numargs-1) + for i := 1; i < numargs; i++ { + iters[i-1], err = Iter(args[i]) + if err != nil { + return nil, err + } + } + return &Map{iters: iters, fun: args[0]}, nil +} + +func (m *Map) M__iter__() (Object, error) { + return m, nil +} + +func (m *Map) M__next__() (Object, error) { + numargs := len(m.iters) + argtuple := make(Tuple, numargs) + + for i := 0; i < numargs; i++ { + val, err := Next(m.iters[i]) + if err != nil { + return nil, err + } + argtuple[i] = val + } + return Call(m.fun, argtuple, nil) +} + +// Check interface is satisfied +var _ I__iter__ = (*Map)(nil) +var _ I__next__ = (*Map)(nil) diff --git a/py/object.go b/py/object.go index 55141cec..540ea0b6 100644 --- a/py/object.go +++ b/py/object.go @@ -23,24 +23,53 @@ func ObjectRepr(o Object) Object { } // Return whether the object is True or not -func ObjectIsTrue(o Object) bool { - if o == True { - return true +func ObjectIsTrue(o Object) (cmp bool, err error) { + switch o { + case True: + return true, nil + case False: + return false, nil + case None: + return false, nil } - if o == False { - return false + + var res Object + switch t := o.(type) { + case I__bool__: + res, err = t.M__bool__() + case I__len__: + res, err = t.M__len__() + case *Type: + var ok bool + if res, ok, err = TypeCall0(o, "__bool__"); ok { + break + } + if res, ok, err = TypeCall0(o, "__len__"); ok { + break + } + _ = ok // pass static-check + } + if err != nil { + return false, err } - if o == None { - return false + switch t := res.(type) { + case Bool: + return t == True, nil + case Int: + return t > 0, nil } + return true, nil +} - if I, ok := o.(I__bool__); ok { - cmp, err := I.M__bool__() - if err == nil && cmp == True { +// Return whether the object is a sequence +func ObjectIsSequence(o Object) bool { + switch t := o.(type) { + case I__getitem__: + return true + case *Type: + if t.GetAttrOrNil("__getitem__") != nil { return true - } else if err == nil && cmp == False { - return false } } return false diff --git a/py/tests/filter.py b/py/tests/filter.py new file mode 100644 index 00000000..c42b2b63 --- /dev/null +++ b/py/tests/filter.py @@ -0,0 +1,65 @@ +# test_builtin.py:BuiltinTest.test_filter() +from libtest import assertRaises + +doc="filter" +class T0: + def __bool__(self): + return True +class T1: + def __len__(self): + return 1 +class T2: + def __bool__(self): + return False +class T3: + pass +t0, t1, t2, t3 = T0(), T1(), T2(), T3() +assert list(filter(None, [t0, t1, t2, t3])) == [t0, t1, t3] +assert list(filter(None, [1, [], 2, ''])) == [1, 2] + +class T3: + def __len__(self): + raise ValueError +t3 = T3() +assertRaises(ValueError, list, filter(None, [t3])) + +class Squares: + def __init__(self, max): + self.max = max + self.sofar = [] + + def __len__(self): return len(self.sofar) + + def __getitem__(self, i): + if not 0 <= i < self.max: raise IndexError + n = len(self.sofar) + while n <= i: + self.sofar.append(n*n) + n += 1 + return self.sofar[i] + +assert list(filter(lambda c: 'a' <= c <= 'z', 'Hello World')) == list('elloorld') +assert list(filter(None, [1, 'hello', [], [3], '', None, 9, 0])) == [1, 'hello', [3], 9] +assert list(filter(lambda x: x > 0, [1, -3, 9, 0, 2])) == [1, 9, 2] +assert list(filter(None, Squares(10))) == [1, 4, 9, 16, 25, 36, 49, 64, 81] +assert list(filter(lambda x: x%2, Squares(10))) == [1, 9, 25, 49, 81] +def identity(item): + return 1 +filter(identity, Squares(5)) +assertRaises(TypeError, filter) +class BadSeq(object): + def __getitem__(self, index): + if index<4: + return 42 + raise ValueError +assertRaises(ValueError, list, filter(lambda x: x, BadSeq())) +def badfunc(): + pass +assertRaises(TypeError, list, filter(badfunc, range(5))) + +# test bltinmodule.c::filtertuple() +assert list(filter(None, (1, 2))) == [1, 2] +assert list(filter(lambda x: x>=3, (1, 2, 3, 4))) == [3, 4] +assertRaises(TypeError, list, filter(42, (1, 2))) + +doc="finished" diff --git a/py/tests/iter.py b/py/tests/iter.py index 53422b79..4eda19c9 100644 --- a/py/tests/iter.py +++ b/py/tests/iter.py @@ -18,4 +18,16 @@ def f(): words2 = list(iter(words1)) for w1, w2 in zip(words1, words2): assert w1 == w2 + +class SequenceClass: + def __init__(self, n): + self.n = n + def __getitem__(self, i): + if 0 <= i < self.n: + return i + else: + raise IndexError + +assert list(iter(SequenceClass(5))) == [0, 1, 2, 3, 4] + doc="finished" \ No newline at end of file diff --git a/py/tests/map.py b/py/tests/map.py new file mode 100644 index 00000000..3e5a4e1e --- /dev/null +++ b/py/tests/map.py @@ -0,0 +1,54 @@ +# test_builtin.py:BuiltinTest.test_map() +from libtest import assertRaises + +doc="map" +class Squares: + def __init__(self, max): + self.max = max + self.sofar = [] + + def __len__(self): return len(self.sofar) + + def __getitem__(self, i): + if not 0 <= i < self.max: raise IndexError + n = len(self.sofar) + while n <= i: + self.sofar.append(n*n) + n += 1 + return self.sofar[i] + +assert list(map(lambda x: x*x, range(1,4))) == [1, 4, 9] +try: + from math import sqrt +except ImportError: + def sqrt(x): + return pow(x, 0.5) +assert list(map(lambda x: list(map(sqrt, x)), [[16, 4], [81, 9]])) == [[4.0, 2.0], [9.0, 3.0]] +assert list(map(lambda x, y: x+y, [1,3,2], [9,1,4])) == [10, 4, 6] + +def plus(*v): + accu = 0 + for i in v: accu = accu + i + return accu +assert list(map(plus, [1, 3, 7])) == [1, 3, 7] +assert list(map(plus, [1, 3, 7], [4, 9, 2])) == [1+4, 3+9, 7+2] +assert list(map(plus, [1, 3, 7], [4, 9, 2], [1, 1, 0])) == [1+4+1, 3+9+1, 7+2+0] +assert list(map(int, Squares(10))) == [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +def Max(a, b): + if a is None: + return b + if b is None: + return a + return max(a, b) +assert list(map(Max, Squares(3), Squares(2))) == [0, 1] +assertRaises(TypeError, map) +assertRaises(TypeError, map, lambda x: x, 42) +class BadSeq: + def __iter__(self): + raise ValueError + yield None +assertRaises(ValueError, list, map(lambda x: x, BadSeq())) +def badfunc(x): + raise RuntimeError +assertRaises(RuntimeError, list, map(badfunc, range(5))) +doc="finished" diff --git a/py/type.go b/py/type.go index be6d19af..509a7628 100644 --- a/py/type.go +++ b/py/type.go @@ -306,7 +306,7 @@ func (t *Type) NewTypeFlags(Name string, Doc string, New NewFunc, Init InitFunc, Dict: StringDict{}, Bases: Tuple{t}, } - TypeDelayReady(t) + TypeDelayReady(tt) return tt } diff --git a/stdlib/builtin/builtin.go b/stdlib/builtin/builtin.go index 83502849..290cb939 100644 --- a/stdlib/builtin/builtin.go +++ b/stdlib/builtin/builtin.go @@ -8,6 +8,7 @@ package builtin import ( "fmt" "math/big" + "strconv" "unicode/utf8" "github.com/go-python/gpython/compile" @@ -53,7 +54,7 @@ func init() { 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), + py.MustNewMethod("oct", builtin_oct, 0, oct_doc), py.MustNewMethod("ord", builtin_ord, 0, ord_doc), py.MustNewMethod("pow", builtin_pow, 0, pow_doc), py.MustNewMethod("print", builtin_print, 0, print_doc), @@ -77,13 +78,13 @@ func init() { "complex": py.ComplexType, "dict": py.StringDictType, // FIXME "enumerate": py.EnumerateType, - // "filter": py.FilterType, - "float": py.FloatType, - "frozenset": py.FrozenSetType, + "filter": py.FilterType, + "float": py.FloatType, + "frozenset": py.FrozenSetType, // "property": py.PropertyType, - "int": py.IntType, // FIXME LongType? - "list": py.ListType, - // "map": py.MapType, + "int": py.IntType, // FIXME LongType? + "list": py.ListType, + "map": py.MapType, "object": py.ObjectType, "range": py.RangeType, // "reversed": py.ReversedType, @@ -292,7 +293,11 @@ func builtin_all(self, seq py.Object) (py.Object, error) { } return nil, err } - if !py.ObjectIsTrue(item) { + ok, err := py.ObjectIsTrue(item) + if err != nil { + return nil, err + } + if !ok { return py.False, nil } } @@ -317,7 +322,11 @@ func builtin_any(self, seq py.Object) (py.Object, error) { } return nil, err } - if py.ObjectIsTrue(item) { + ok, err := py.ObjectIsTrue(item) + if err != nil { + return nil, err + } + if ok { return py.True, nil } } @@ -584,6 +593,56 @@ func builtin_open(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Objec int(buffering.(py.Int))) } +const oct_doc = `oct(number) -> string + +Return the octal representation of an integer. + + >>> oct(342391) + '0o1234567' +` + +func builtin_oct(self, v py.Object) (py.Object, error) { + var ( + i int64 + err error + ) + switch v := v.(type) { + case *py.BigInt: + vv := (*big.Int)(v) + neg := false + if vv.Cmp(big.NewInt(0)) == -1 { + neg = true + } + str := vv.Text(8) + if neg { + str = "-0o" + str[1:] + } else { + str = "0o" + str + } + return py.String(str), nil + case py.IGoInt64: + i, err = v.GoInt64() + case py.IGoInt: + var vv int + vv, err = v.GoInt() + i = int64(vv) + default: + return nil, py.ExceptionNewf(py.TypeError, "'%s' object cannot be interpreted as an integer", v.Type().Name) + } + + if err != nil { + return nil, err + } + + str := strconv.FormatInt(i, 8) + if i < 0 { + str = "-0o" + str[1:] + } else { + str = "0o" + str + } + return py.String(str), nil +} + const ord_doc = `ord(c) -> integer Return the integer ordinal of a one-character string.` @@ -857,11 +916,16 @@ func builtin_hex(self, v py.Object) (py.Object, error) { // test bigint first to make sure we correctly handle the case // where int64 isn't large enough. vv := (*big.Int)(v) - format := "%#x" + neg := false if vv.Cmp(big.NewInt(0)) == -1 { - format = "%+#x" + neg = true + } + str := vv.Text(16) + if neg { + str = "-0x" + str[1:] + } else { + str = "0x" + str } - str := fmt.Sprintf(format, vv) return py.String(str), nil case py.IGoInt64: i, err = v.GoInt64() @@ -877,11 +941,12 @@ func builtin_hex(self, v py.Object) (py.Object, error) { return nil, err } - format := "%#x" + str := strconv.FormatInt(i, 16) if i < 0 { - format = "%+#x" + str = "-0x" + str[1:] + } else { + str = "0x" + str } - str := fmt.Sprintf(format, i) return py.String(str), nil } diff --git a/stdlib/builtin/tests/builtin.py b/stdlib/builtin/tests/builtin.py index 07f1704a..ae4e8a5f 100644 --- a/stdlib/builtin/tests/builtin.py +++ b/stdlib/builtin/tests/builtin.py @@ -275,6 +275,12 @@ def gen2(): ok = True assert ok, "ValueError not raised" +doc="oct" +assert oct(0) == '0o0' +assert oct(100) == '0o144' +assert oct(-100) == '-0o144' +assertRaises(TypeError, oct, ()) + doc="ord" assert 65 == ord("A") assert 163 == ord("£")