diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49657da8..86a5f633 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} @@ -34,12 +34,12 @@ jobs: git config --global core.eol lf - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Cache-Go - uses: actions/cache@v1 + uses: actions/cache@v4 with: # In order: # * Module download cache @@ -93,11 +93,11 @@ jobs: run: | go run ./ci/run-tests.go $TAGS -race - name: static-check - uses: dominikh/staticcheck-action@v1.2.0 + uses: dominikh/staticcheck-action@v1 with: install-go: false cache-key: ${{ matrix.platform }} version: "2022.1" - name: Upload-Coverage if: matrix.platform == 'ubuntu-latest' - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 diff --git a/compile/compile.go b/compile/compile.go index 76d46c1a..fd8d7a2b 100644 --- a/compile/compile.go +++ b/compile/compile.go @@ -213,6 +213,8 @@ func (c *compiler) compileAst(Ast ast.Ast, filename string, futureFlags int, don case *ast.Suite: panic("suite should not be possible") case *ast.Lambda: + code.Argcount = int32(len(node.Args.Args)) + code.Kwonlyargcount = int32(len(node.Args.Kwonlyargs)) // Make None the first constant as lambda can't have a docstring c.Const(py.None) code.Name = "" @@ -220,6 +222,8 @@ func (c *compiler) compileAst(Ast ast.Ast, filename string, futureFlags int, don c.Expr(node.Body) valueOnStack = true case *ast.FunctionDef: + code.Argcount = int32(len(node.Args.Args)) + code.Kwonlyargcount = int32(len(node.Args.Kwonlyargs)) code.Name = string(node.Name) c.setQualname() c.Stmts(c.docString(node.Body, true)) @@ -299,6 +303,7 @@ func (c *compiler) compileAst(Ast ast.Ast, filename string, futureFlags int, don code.Stacksize = int32(c.OpCodes.StackDepth()) code.Nlocals = int32(len(code.Varnames)) code.Lnotab = string(c.OpCodes.Lnotab()) + code.InitCell2arg() return nil } @@ -479,7 +484,8 @@ func (c *compiler) makeClosure(code *py.Code, args uint32, child *compiler, qual if reftype == symtable.ScopeCell { arg = c.FindId(name, c.Code.Cellvars) } else { /* (reftype == FREE) */ - arg = c.FindId(name, c.Code.Freevars) + // using CellAndFreeVars in closures requires skipping Cellvars + arg = len(c.Code.Cellvars) + c.FindId(name, c.Code.Freevars) } if arg < 0 { panic(fmt.Sprintf("compile: makeClosure: lookup %q in %q %v %v\nfreevars of %q: %v\n", name, c.SymTable.Name, reftype, arg, code.Name, code.Freevars)) @@ -1363,7 +1369,12 @@ func (c *compiler) NameOp(name string, ctx ast.ExprContext) { if op == 0 { panic("NameOp: Op not set") } - c.OpArg(op, c.Index(mangled, dict)) + i := c.Index(mangled, dict) + // using CellAndFreeVars in closures requires skipping Cellvars + if scope == symtable.ScopeFree { + i += uint32(len(c.Code.Cellvars)) + } + c.OpArg(op, i) } // Call a function which is already on the stack with n arguments already on the stack diff --git a/py/code.go b/py/code.go index 09027497..ad9a42af 100644 --- a/py/code.go +++ b/py/code.go @@ -112,8 +112,6 @@ func NewCode(argcount int32, kwonlyargcount int32, filename_ Object, name_ Object, firstlineno int32, lnotab_ Object) *Code { - var cell2arg []byte - // Type assert the objects consts := consts_.(Tuple) namesTuple := names_.(Tuple) @@ -154,7 +152,6 @@ func NewCode(argcount int32, kwonlyargcount int32, // return nil; // } - n_cellvars := len(cellvars) intern_strings(namesTuple) intern_strings(varnamesTuple) intern_strings(freevarsTuple) @@ -167,13 +164,40 @@ func NewCode(argcount int32, kwonlyargcount int32, } } } + + co := &Code{ + Argcount: argcount, + Kwonlyargcount: kwonlyargcount, + Nlocals: nlocals, + Stacksize: stacksize, + Flags: flags, + Code: code, + Consts: consts, + Names: names, + Varnames: varnames, + Freevars: freevars, + Cellvars: cellvars, + Filename: filename, + Name: name, + Firstlineno: firstlineno, + Lnotab: lnotab, + Weakreflist: nil, + } + co.InitCell2arg() + return co +} + +// Create mapping between cells and arguments if needed. +func (co *Code) InitCell2arg() { + var cell2arg []byte + n_cellvars := len(co.Cellvars) /* Create mapping between cells and arguments if needed. */ if n_cellvars != 0 { - total_args := argcount + kwonlyargcount - if flags&CO_VARARGS != 0 { + total_args := co.Argcount + co.Kwonlyargcount + if co.Flags&CO_VARARGS != 0 { total_args++ } - if flags&CO_VARKEYWORDS != 0 { + if co.Flags&CO_VARKEYWORDS != 0 { total_args++ } used_cell2arg := false @@ -182,9 +206,9 @@ func NewCode(argcount int32, kwonlyargcount int32, cell2arg[i] = CO_CELL_NOT_AN_ARG } // Find cells which are also arguments. - for i, cell := range cellvars { + for i, cell := range co.Cellvars { for j := int32(0); j < total_args; j++ { - arg := varnames[j] + arg := co.Varnames[j] if cell == arg { cell2arg[i] = byte(j) used_cell2arg = true @@ -196,26 +220,7 @@ func NewCode(argcount int32, kwonlyargcount int32, cell2arg = nil } } - - return &Code{ - Argcount: argcount, - Kwonlyargcount: kwonlyargcount, - Nlocals: nlocals, - Stacksize: stacksize, - Flags: flags, - Code: code, - Consts: consts, - Names: names, - Varnames: varnames, - Freevars: freevars, - Cellvars: cellvars, - Cell2arg: cell2arg, - Filename: filename, - Name: name, - Firstlineno: firstlineno, - Lnotab: lnotab, - Weakreflist: nil, - } + co.Cell2arg = cell2arg } // Return number of free variables diff --git a/py/int.go b/py/int.go index 919c28aa..511f1f88 100644 --- a/py/int.go +++ b/py/int.go @@ -411,9 +411,6 @@ func (a Int) M__truediv__(other Object) (Object, error) { return nil, err } fa := Float(a) - if err != nil { - return nil, err - } fb := b.(Float) if fb == 0 { return nil, divisionByZero @@ -427,9 +424,6 @@ func (a Int) M__rtruediv__(other Object) (Object, error) { return nil, err } fa := Float(a) - if err != nil { - return nil, err - } fb := b.(Float) if fa == 0 { return nil, divisionByZero diff --git a/py/string.go b/py/string.go index a28e6e74..a987a11a 100644 --- a/py/string.go +++ b/py/string.go @@ -218,6 +218,17 @@ replaced.`) return self.(String).LStrip(args) }, 0, "lstrip(chars) -> replace chars from begining of string") + StringType.Dict["upper"] = MustNewMethod("upper", func(self Object, args Tuple, kwargs StringDict) (Object, error) { + return self.(String).Upper() + }, 0, "upper() -> a copy of the string converted to uppercase") + + StringType.Dict["lower"] = MustNewMethod("lower", func(self Object, args Tuple, kwargs StringDict) (Object, error) { + return self.(String).Lower() + }, 0, "lower() -> a copy of the string converted to lowercase") + + StringType.Dict["join"] = MustNewMethod("join", func(self Object, args Tuple) (Object, error) { + return self.(String).Join(args) + }, 0, "join(iterable) -> return a string which is the concatenation of the strings in iterable") } // Type of this object @@ -739,6 +750,38 @@ func (s String) RStrip(args Tuple) (Object, error) { return String(strings.TrimRightFunc(string(s), f)), nil } +func (s String) Upper() (Object, error) { + return String(strings.ToUpper(string(s))), nil +} + +func (s String) Lower() (Object, error) { + return String(strings.ToLower(string(s))), nil +} + +func (s String) Join(args Tuple) (Object, error) { + if len(args) != 1 { + return nil, ExceptionNewf(TypeError, "join() takes exactly one argument (%d given)", len(args)) + } + var parts []string + iterable, err := Iter(args[0]) + if err != nil { + return nil, err + } + item, err := Next(iterable) + for err == nil { + str, ok := item.(String) + if !ok { + return nil, ExceptionNewf(TypeError, "sequence item %d: expected str instance, %s found", len(parts), item.Type().Name) + } + parts = append(parts, string(str)) + item, err = Next(iterable) + } + if err != StopIteration { + return nil, err + } + return String(strings.Join(parts, string(s))), nil +} + // Check stringerface is satisfied var ( _ richComparison = String("") diff --git a/py/string_test.go b/py/string_test.go index 7f6e0c34..053cd781 100644 --- a/py/string_test.go +++ b/py/string_test.go @@ -98,3 +98,49 @@ func TestStringFind(t *testing.T) { }) } } + +func TestStringUpper(t *testing.T) { + tests := []struct { + name string + s String + want Object + }{{ + name: "abc", + s: String("abc"), + want: String("ABC")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Upper() + if err != nil { + t.Fatalf("Upper() error = %v", err) + } + if got.(String) != tt.want.(String) { + t.Fatalf("Upper() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStringLower(t *testing.T) { + tests := []struct { + name string + s String + want Object + }{{ + name: "ABC", + s: String("ABC"), + want: String("abc")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Lower() + if err != nil { + t.Fatalf("Lower() error = %v", err) + } + if got.(String) != tt.want.(String) { + t.Fatalf("Lower() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/py/tests/string.py b/py/tests/string.py index 8af36ca6..d1d16cfd 100644 --- a/py/tests/string.py +++ b/py/tests/string.py @@ -897,6 +897,23 @@ def index(s, i): assert a.lstrip("a ") == "bada a" assert a.strip("a ") == "bad" +doc="upper" +a = "abc" +assert a.upper() == "ABC" + +doc="lower" +a = "ABC" +assert a.lower() == "abc" + +doc="join" +assert ",".join(['a', 'b', 'c']) == "a,b,c" +assert " ".join(('a', 'b', 'c')) == "a b c" +assert " ".join("abc") == "a b c" +assert "".join(['a', 'b', 'c']) == "abc" +assert ",".join([]) == "" +assert ",".join(()) == "" +assertRaises(TypeError, lambda: ",".join([1, 2, 3])) + class Index: def __index__(self): return 1 diff --git a/py/type.go b/py/type.go index 509a7628..db31ba61 100644 --- a/py/type.go +++ b/py/type.go @@ -1093,7 +1093,7 @@ func (t *Type) Ready() error { // if the type dictionary doesn't contain a __doc__, set it from // the tp_doc slot. - if _, ok := t.Dict["__doc__"]; ok { + if _, ok := t.Dict["__doc__"]; !ok { if t.Doc != "" { t.Dict["__doc__"] = String(t.Doc) } else { diff --git a/stdlib/array/array.go b/stdlib/array/array.go new file mode 100644 index 00000000..b9e682ae --- /dev/null +++ b/stdlib/array/array.go @@ -0,0 +1,762 @@ +// 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 array provides the implementation of the python's 'array' module. +package array + +import ( + "fmt" + "reflect" + "strings" + + "github.com/go-python/gpython/py" +) + +// FIXME(sbinet): consider creating an "array handler" type for each of the typecodes +// and make the handler a field of the "array" type. +// or make "array" an interface ? + +// array provides efficient manipulation of C-arrays (as Go slices). +type array struct { + descr byte // typecode of elements + esize int // element size in bytes + data any + + append func(v py.Object) (py.Object, error) + extend func(seq py.Object) (py.Object, error) +} + +// Type of this StringDict object +func (*array) Type() *py.Type { + return ArrayType +} + +var ( + _ py.Object = (*array)(nil) + _ py.I__getitem__ = (*array)(nil) + _ py.I__setitem__ = (*array)(nil) + _ py.I__len__ = (*array)(nil) + _ py.I__repr__ = (*array)(nil) + _ py.I__str__ = (*array)(nil) +) + +var ( + typecodes = py.String("bBuhHiIlLqQfd") + ArrayType = py.ObjectType.NewType("array.array", array_doc, array_new, nil) + + descr2esize = map[byte]int{ + 'b': 1, + 'B': 1, + 'u': 2, + 'h': 2, + 'H': 2, + 'i': 2, + 'I': 2, + 'l': 8, + 'L': 8, + 'q': 8, + 'Q': 8, + 'f': 4, + 'd': 8, + } +) + +func init() { + py.RegisterModule(&py.ModuleImpl{ + Info: py.ModuleInfo{ + Name: "array", + Doc: "This module defines an object type which can efficiently represent\n" + + "an array of basic values: characters, integers, floating point\n" + + "numbers. Arrays are sequence types and behave very much like lists,\n" + + "except that the type of objects stored in them is constrained.\n", + }, + Methods: []*py.Method{}, + Globals: py.StringDict{ + "typecodes": typecodes, + "array": ArrayType, + "ArrayType": ArrayType, + }, + }) + + ArrayType.Dict["itemsize"] = &py.Property{ + Fget: func(self py.Object) (py.Object, error) { + arr := self.(*array) + return py.Int(arr.esize), nil + }, + Doc: "the size, in bytes, of one array item", + } + + ArrayType.Dict["typecode"] = &py.Property{ + Fget: func(self py.Object) (py.Object, error) { + arr := self.(*array) + return py.String(arr.descr), nil + }, + Doc: "the typecode character used to create the array", + } + + ArrayType.Dict["append"] = py.MustNewMethod("append", array_append, 0, array_append_doc) + ArrayType.Dict["extend"] = py.MustNewMethod("extend", array_extend, 0, array_extend_doc) +} + +const array_doc = `array(typecode [, initializer]) -> array + +Return a new array whose items are restricted by typecode, and +initialized from the optional initializer value, which must be a list, +string or iterable over elements of the appropriate type. + +Arrays represent basic values and behave very much like lists, except +the type of objects stored in them is constrained. The type is specified +at object creation time by using a type code, which is a single character. +The following type codes are defined: + + Type code C Type Minimum size in bytes + 'b' signed integer 1 + 'B' unsigned integer 1 + 'u' Unicode character 2 (see note) + 'h' signed integer 2 + 'H' unsigned integer 2 + 'i' signed integer 2 + 'I' unsigned integer 2 + 'l' signed integer 4 + 'L' unsigned integer 4 + 'q' signed integer 8 (see note) + 'Q' unsigned integer 8 (see note) + 'f' floating point 4 + 'd' floating point 8 + +NOTE: The 'u' typecode corresponds to Python's unicode character. On +narrow builds this is 2-bytes on wide builds this is 4-bytes. + +NOTE: The 'q' and 'Q' type codes are only available if the platform +C compiler used to build Python supports 'long long', or, on Windows, +'__int64'. + +Methods: + +append() -- append a new item to the end of the array +buffer_info() -- return information giving the current memory info +byteswap() -- byteswap all the items of the array +count() -- return number of occurrences of an object +extend() -- extend array by appending multiple elements from an iterable +fromfile() -- read items from a file object +fromlist() -- append items from the list +frombytes() -- append items from the string +index() -- return index of first occurrence of an object +insert() -- insert a new item into the array at a provided position +pop() -- remove and return item (default last) +remove() -- remove first occurrence of an object +reverse() -- reverse the order of the items in the array +tofile() -- write all items to a file object +tolist() -- return the array converted to an ordinary list +tobytes() -- return the array converted to a string + +Attributes: + +typecode -- the typecode character used to create the array +itemsize -- the length in bytes of one array item + +` + +func array_new(metatype *py.Type, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + switch n := len(args); n { + case 0: + return nil, py.ExceptionNewf(py.TypeError, "array() takes at least 1 argument (0 given)") + case 1, 2: + // ok + default: + return nil, py.ExceptionNewf(py.TypeError, "array() takes at most 2 arguments (%d given)", n) + } + + if len(kwargs) != 0 { + return nil, py.ExceptionNewf(py.TypeError, "array.array() takes no keyword arguments") + } + + descr, ok := args[0].(py.String) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "array() argument 1 must be a unicode character, not %s", args[0].Type().Name) + } + + if len(descr) != 1 { + return nil, py.ExceptionNewf(py.TypeError, "array() argument 1 must be a unicode character, not str") + } + + if !strings.ContainsAny(string(descr), string(typecodes)) { + ts := new(strings.Builder) + for i, v := range typecodes { + if i > 0 { + switch { + case i == len(typecodes)-1: + ts.WriteString(" or ") + default: + ts.WriteString(", ") + } + } + ts.WriteString(string(v)) + } + return nil, py.ExceptionNewf(py.ValueError, "bad typecode (must be %s)", ts) + } + + arr := &array{ + descr: descr[0], + esize: descr2esize[descr[0]], + } + + switch descr[0] { + case 'u': + var data []rune + arr.data = data + arr.append = arr.appendRune + arr.extend = arr.extendRune + case 'b': + var data []int8 + arr.data = data + arr.append = arr.appendI8 + arr.extend = arr.extendI8 + case 'h': + var data []int16 + arr.data = data + arr.append = arr.appendI16 + arr.extend = arr.extendI16 + case 'i': + var data []int32 + arr.data = data + arr.append = arr.appendI32 + arr.extend = arr.extendI32 + case 'l', 'q': + var data []int64 + arr.data = data + arr.append = arr.appendI64 + arr.extend = arr.extendI64 + case 'B': + var data []uint8 + arr.data = data + arr.append = arr.appendU8 + arr.extend = arr.extendU8 + case 'H': + var data []uint16 + arr.data = data + arr.append = arr.appendU16 + arr.extend = arr.extendU16 + case 'I': + var data []uint32 + arr.data = data + arr.append = arr.appendU32 + arr.extend = arr.extendU32 + case 'L', 'Q': + var data []uint64 + arr.data = data + arr.append = arr.appendU64 + arr.extend = arr.extendU64 + case 'f': + var data []float32 + arr.data = data + arr.append = arr.appendF32 + arr.extend = arr.extendF32 + case 'd': + var data []float64 + arr.data = data + arr.append = arr.appendF64 + arr.extend = arr.extendF64 + } + + if len(args) == 2 { + _, err := arr.extend(args[1]) + if err != nil { + return nil, err + } + } + + return arr, nil +} + +const array_append_doc = `Append new value v to the end of the array.` + +func array_append(self py.Object, args py.Tuple) (py.Object, error) { + arr, ok := self.(*array) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "expected an array, got '%s'", self.Type().Name) + } + if len(args) != 1 { + return nil, py.ExceptionNewf(py.TypeError, "array.append() takes exactly one argument (%d given)", len(args)) + } + + return arr.append(args[0]) +} + +const array_extend_doc = `Append items to the end of the array.` + +func array_extend(self py.Object, args py.Tuple) (py.Object, error) { + arr, ok := self.(*array) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "expected an array, got '%s'", self.Type().Name) + } + if len(args) == 0 { + return nil, py.ExceptionNewf(py.TypeError, "extend() takes exactly 1 positional argument (%d given)", len(args)) + } + if len(args) != 1 { + return nil, py.ExceptionNewf(py.TypeError, "extend() takes at most 1 argument (%d given)", len(args)) + } + + return arr.extend(args[0]) +} + +func (arr *array) M__repr__() (py.Object, error) { + o := new(strings.Builder) + o.WriteString("array('" + string(arr.descr) + "'") + if data := reflect.ValueOf(arr.data); arr.data != nil && data.Len() > 0 { + switch arr.descr { + case 'u': + o.WriteString(", '") + o.WriteString(string(arr.data.([]rune))) + o.WriteString("'") + default: + o.WriteString(", [") + for i := 0; i < data.Len(); i++ { + if i > 0 { + o.WriteString(", ") + } + // FIXME(sbinet): we don't get exactly the same display wrt CPython for float32 + fmt.Fprintf(o, "%v", data.Index(i)) + } + o.WriteString("]") + } + } + o.WriteString(")") + return py.String(o.String()), nil +} + +func (arr *array) M__str__() (py.Object, error) { + return arr.M__repr__() +} + +func (arr *array) M__len__() (py.Object, error) { + if arr.data == nil { + return py.Int(0), nil + } + sli := reflect.ValueOf(arr.data) + return py.Int(sli.Len()), nil +} + +func (arr *array) M__getitem__(k py.Object) (py.Object, error) { + switch k := k.(type) { + case py.Int: + var ( + sli = reflect.ValueOf(arr.data) + i = int(k) + ) + if i < 0 { + i = sli.Len() + i + } + if i < 0 || sli.Len() <= i { + return nil, py.ExceptionNewf(py.IndexError, "array index out of range") + } + switch arr.descr { + case 'b', 'h', 'i', 'l', 'q': + return py.Int(sli.Index(i).Int()), nil + case 'B', 'H', 'I', 'L', 'Q': + return py.Int(sli.Index(i).Uint()), nil + case 'u': + return py.String([]rune{rune(sli.Index(i).Int())}), nil + case 'f', 'd': + return py.Float(sli.Index(i).Float()), nil + } + case *py.Slice: + return nil, py.NotImplementedError + default: + return nil, py.ExceptionNewf(py.TypeError, "array indices must be integers") + } + panic("impossible") +} + +func (arr *array) M__setitem__(k, v py.Object) (py.Object, error) { + switch k := k.(type) { + case py.Int: + var ( + sli = reflect.ValueOf(arr.data) + i = int(k) + ) + if i < 0 { + i = sli.Len() + i + } + if i < 0 || sli.Len() <= i { + return nil, py.ExceptionNewf(py.IndexError, "array index out of range") + } + switch arr.descr { + case 'b', 'h', 'i', 'l', 'q': + vv, ok := v.(py.Int) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "'%s' object cannot be interpreted as an integer", v.Type().Name) + } + sli.Index(i).SetInt(int64(vv)) + case 'B', 'H', 'I', 'L', 'Q': + vv, ok := v.(py.Int) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "'%s' object cannot be interpreted as an integer", v.Type().Name) + } + sli.Index(i).SetUint(uint64(vv)) + case 'u': + vv, ok := v.(py.Int) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "array item must be unicode character") + } + sli.Index(i).SetInt(int64(vv)) + case 'f', 'd': + var vv float64 + switch v := v.(type) { + case py.Int: + vv = float64(v) + case py.Float: + vv = float64(v) + default: + return nil, py.ExceptionNewf(py.TypeError, "must be real number, not %s", v.Type().Name) + } + sli.Index(i).SetFloat(vv) + } + return py.None, nil + case *py.Slice: + return nil, py.NotImplementedError + default: + return nil, py.ExceptionNewf(py.TypeError, "array indices must be integers") + } + panic("impossible") +} + +func (arr *array) appendRune(v py.Object) (py.Object, error) { + str, ok := v.(py.String) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "array item must be unicode character") + } + + arr.data = append(arr.data.([]rune), []rune(str)...) + return py.None, nil +} + +func (arr *array) appendI8(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]int8), int8(vv)) + return py.None, nil +} + +func (arr *array) appendI16(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]int16), int16(vv)) + return py.None, nil +} + +func (arr *array) appendI32(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]int32), int32(vv)) + return py.None, nil +} + +func (arr *array) appendI64(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]int64), int64(vv)) + return py.None, nil +} + +func (arr *array) appendU8(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]uint8), uint8(vv)) + return py.None, nil +} + +func (arr *array) appendU16(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]uint16), uint16(vv)) + return py.None, nil +} + +func (arr *array) appendU32(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]uint32), uint32(vv)) + return py.None, nil +} + +func (arr *array) appendU64(v py.Object) (py.Object, error) { + vv, err := asInt(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]uint64), uint64(vv)) + return py.None, nil +} + +func (arr *array) appendF32(v py.Object) (py.Object, error) { + vv, err := py.FloatAsFloat64(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]float32), float32(vv)) + return py.None, nil +} + +func (arr *array) appendF64(v py.Object) (py.Object, error) { + vv, err := py.FloatAsFloat64(v) + if err != nil { + return nil, err + } + arr.data = append(arr.data.([]float64), float64(vv)) + return py.None, nil +} + +func (arr *array) extendRune(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendRune(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendI8(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendI8(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendI16(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendI16(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendI32(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendI32(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendI64(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendI64(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendU8(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendU8(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendU16(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendU16(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendU32(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendU32(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendU64(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendU64(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendF32(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendF32(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func (arr *array) extendF64(arg py.Object) (py.Object, error) { + itr, err := py.Iter(arg) + if err != nil { + return nil, err + } + + nxt := itr.(py.I__next__) + + for { + o, err := nxt.M__next__() + if err == py.StopIteration { + break + } + _, err = arr.appendF64(o) + if err != nil { + return nil, err + } + } + return py.None, nil +} + +func asInt(o py.Object) (int64, error) { + v, ok := o.(py.Int) + if !ok { + return 0, py.ExceptionNewf(py.TypeError, "unsupported operand type(s) for int: '%s'", o.Type().Name) + } + return int64(v), nil +} diff --git a/stdlib/array/array_test.go b/stdlib/array/array_test.go new file mode 100644 index 00000000..a34ed842 --- /dev/null +++ b/stdlib/array/array_test.go @@ -0,0 +1,15 @@ +// 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 array_test + +import ( + "testing" + + "github.com/go-python/gpython/pytest" +) + +func TestArray(t *testing.T) { + pytest.RunScript(t, "./testdata/test.py") +} diff --git a/stdlib/array/testdata/test.py b/stdlib/array/testdata/test.py new file mode 100644 index 00000000..906ed898 --- /dev/null +++ b/stdlib/array/testdata/test.py @@ -0,0 +1,173 @@ +# 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. + +import array + +print("globals:") +for name in ("typecodes", "array"): + v = getattr(array, name) + print("\narray.%s:\n%s" % (name,repr(v))) + pass + +def assertEqual(x, y): + assert x == y, "got: %s, want: %s" % (repr(x), repr(y)) + +assertEqual(array.typecodes, 'bBuhHiIlLqQfd') + +for i, typ in enumerate(array.typecodes): + print("") + print("typecode '%s'" % (typ,)) + if typ == 'u': + arr = array.array(typ, "?世界!") + if typ in "bhilq": + arr = array.array(typ, [-1, -2, -3, -4]) + if typ in "BHILQ": + arr = array.array(typ, [+1, +2, +3, +4]) + if typ in "fd": + arr = array.array(typ, [-1.0, -2.0, -3.0, -4.0]) + print(" array: %s ## repr" % (repr(arr),)) + print(" array: %s ## str" % (str(arr),)) + print(" itemsize: %s" % (arr.itemsize,)) + print(" typecode: %s" % (arr.typecode,)) + print(" len: %s" % (len(arr),)) + print(" arr[0]: %s" % (arr[0],)) + print(" arr[-1]: %s" % (arr[-1],)) + try: + arr[-10] + print(" ERROR1: expected an exception") + except: + print(" caught an exception [ok]") + + try: + arr[10] + print(" ERROR2: expected an exception") + except: + print(" caught an exception [ok]") + arr[-2] = 33 + if typ in "fd": + arr[-2] = 0.3 + print(" arr[-2]: %s" % (arr[-2],)) + + try: + arr[-10] = 2 + print(" ERROR3: expected an exception") + except: + print(" caught an exception [ok]") + + if typ in "bhilqfd": + arr.extend([-5,-6]) + if typ in "BHILQ": + arr.extend([5,6]) + if typ == 'u': + arr.extend("he") + print(" array: %s" % (repr(arr),)) + print(" len: %s" % (len(arr),)) + + if typ in "bhilqfd": + arr.append(-7) + if typ in "BHILQ": + arr.append(7) + if typ == 'u': + arr.append("l") + print(" array: %s" % (repr(arr),)) + print(" len: %s" % (len(arr),)) + + try: + arr.append() + print(" ERROR4: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.append([]) + print(" ERROR5: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.append(1, 2) + print(" ERROR6: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.append(None) + print(" ERROR7: expected an exception") + except: + print(" caught an exception [ok]") + + try: + arr.extend() + print(" ERROR8: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.extend(None) + print(" ERROR9: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.extend([1,None]) + print(" ERROR10: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.extend(1,None) + print(" ERROR11: expected an exception") + except: + print(" caught an exception [ok]") + + try: + arr[0] = object() + print(" ERROR12: expected an exception") + except: + print(" caught an exception [ok]") + pass + +print("\n") +print("## testing array.array(...)") +try: + arr = array.array() + print("ERROR1: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array(b"d") + print("ERROR2: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("?") + print("ERROR3: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("dd") + print("ERROR4: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", initializer=[1,2]) + print("ERROR5: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", [1], []) + print("ERROR6: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", 1) + print("ERROR7: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", ["a","b"]) + print("ERROR8: expected an exception") +except: + print("caught an exception [ok]") diff --git a/stdlib/array/testdata/test_golden.txt b/stdlib/array/testdata/test_golden.txt new file mode 100644 index 00000000..09e9db3d --- /dev/null +++ b/stdlib/array/testdata/test_golden.txt @@ -0,0 +1,356 @@ +globals: + +array.typecodes: +'bBuhHiIlLqQfd' + +array.array: + + +typecode 'b' + array: array('b', [-1, -2, -3, -4]) ## repr + array: array('b', [-1, -2, -3, -4]) ## str + itemsize: 1 + typecode: b + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('b', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('b', [-1, -2, 33, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'B' + array: array('B', [1, 2, 3, 4]) ## repr + array: array('B', [1, 2, 3, 4]) ## str + itemsize: 1 + typecode: B + len: 4 + arr[0]: 1 + arr[-1]: 4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('B', [1, 2, 33, 4, 5, 6]) + len: 6 + array: array('B', [1, 2, 33, 4, 5, 6, 7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'u' + array: array('u', '?世界!') ## repr + array: array('u', '?世界!') ## str + itemsize: 2 + typecode: u + len: 4 + arr[0]: ? + arr[-1]: ! + caught an exception [ok] + caught an exception [ok] + arr[-2]: ! + caught an exception [ok] + array: array('u', '?世!!he') + len: 6 + array: array('u', '?世!!hel') + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'h' + array: array('h', [-1, -2, -3, -4]) ## repr + array: array('h', [-1, -2, -3, -4]) ## str + itemsize: 2 + typecode: h + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('h', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('h', [-1, -2, 33, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'H' + array: array('H', [1, 2, 3, 4]) ## repr + array: array('H', [1, 2, 3, 4]) ## str + itemsize: 2 + typecode: H + len: 4 + arr[0]: 1 + arr[-1]: 4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('H', [1, 2, 33, 4, 5, 6]) + len: 6 + array: array('H', [1, 2, 33, 4, 5, 6, 7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'i' + array: array('i', [-1, -2, -3, -4]) ## repr + array: array('i', [-1, -2, -3, -4]) ## str + itemsize: 2 + typecode: i + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('i', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('i', [-1, -2, 33, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'I' + array: array('I', [1, 2, 3, 4]) ## repr + array: array('I', [1, 2, 3, 4]) ## str + itemsize: 2 + typecode: I + len: 4 + arr[0]: 1 + arr[-1]: 4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('I', [1, 2, 33, 4, 5, 6]) + len: 6 + array: array('I', [1, 2, 33, 4, 5, 6, 7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'l' + array: array('l', [-1, -2, -3, -4]) ## repr + array: array('l', [-1, -2, -3, -4]) ## str + itemsize: 8 + typecode: l + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('l', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('l', [-1, -2, 33, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'L' + array: array('L', [1, 2, 3, 4]) ## repr + array: array('L', [1, 2, 3, 4]) ## str + itemsize: 8 + typecode: L + len: 4 + arr[0]: 1 + arr[-1]: 4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('L', [1, 2, 33, 4, 5, 6]) + len: 6 + array: array('L', [1, 2, 33, 4, 5, 6, 7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'q' + array: array('q', [-1, -2, -3, -4]) ## repr + array: array('q', [-1, -2, -3, -4]) ## str + itemsize: 8 + typecode: q + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('q', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('q', [-1, -2, 33, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'Q' + array: array('Q', [1, 2, 3, 4]) ## repr + array: array('Q', [1, 2, 3, 4]) ## str + itemsize: 8 + typecode: Q + len: 4 + arr[0]: 1 + arr[-1]: 4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 33 + caught an exception [ok] + array: array('Q', [1, 2, 33, 4, 5, 6]) + len: 6 + array: array('Q', [1, 2, 33, 4, 5, 6, 7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'f' + array: array('f', [-1, -2, -3, -4]) ## repr + array: array('f', [-1, -2, -3, -4]) ## str + itemsize: 4 + typecode: f + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 0.30000001192092896 + caught an exception [ok] + array: array('f', [-1, -2, 0.3, -4, -5, -6]) + len: 6 + array: array('f', [-1, -2, 0.3, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + +typecode 'd' + array: array('d', [-1, -2, -3, -4]) ## repr + array: array('d', [-1, -2, -3, -4]) ## str + itemsize: 8 + typecode: d + len: 4 + arr[0]: -1 + arr[-1]: -4 + caught an exception [ok] + caught an exception [ok] + arr[-2]: 0.3 + caught an exception [ok] + array: array('d', [-1, -2, 0.3, -4, -5, -6]) + len: 6 + array: array('d', [-1, -2, 0.3, -4, -5, -6, -7]) + len: 7 + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + caught an exception [ok] + + +## testing array.array(...) +caught an exception [ok] +caught an exception [ok] +caught an exception [ok] +caught an exception [ok] +caught an exception [ok] +caught an exception [ok] +caught an exception [ok] +caught an exception [ok] diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index d945c382..7d1fb811 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -18,6 +18,7 @@ import ( "github.com/go-python/gpython/stdlib/marshal" "github.com/go-python/gpython/vm" + _ "github.com/go-python/gpython/stdlib/array" _ "github.com/go-python/gpython/stdlib/binascii" _ "github.com/go-python/gpython/stdlib/builtin" _ "github.com/go-python/gpython/stdlib/glob" diff --git a/stdlib/sys/sys.go b/stdlib/sys/sys.go index 52d733b6..3a2318eb 100644 --- a/stdlib/sys/sys.go +++ b/stdlib/sys/sys.go @@ -19,6 +19,7 @@ package sys import ( "os" + "runtime" "github.com/go-python/gpython/py" ) @@ -659,7 +660,14 @@ func init() { executable, err := os.Executable() if err != nil { - panic(err) + switch runtime.GOOS { + case "js", "wasip1": + // These platforms don't implement os.Executable (at least as of Go + // 1.21), see https://github.com/tailscale/tailscale/pull/8325 + executable = "gpython" + default: + panic(err) + } } globals := py.StringDict{ diff --git a/vm/tests/class.py b/vm/tests/class.py index 2c8fd70b..f9781cd7 100644 --- a/vm/tests/class.py +++ b/vm/tests/class.py @@ -47,17 +47,16 @@ def method1(self, x): c = x() assert c.method1(1) == 2 -# FIXME doesn't work -# doc="CLASS_DEREF2" -# def classderef2(x): -# class DeRefTest: -# VAR = x -# def method1(self, x): -# "method1" -# return self.VAR+x -# return DeRefTest -# x = classderef2(1) -# c = x() -# assert c.method1(1) == 2 +doc="CLASS_DEREF2" +def classderef2(x): + class DeRefTest: + VAR = x + def method1(self, x): + "method1" + return self.VAR+x + return DeRefTest +x = classderef2(1) +c = x() +assert c.method1(1) == 2 doc="finished" diff --git a/vm/tests/decorators.py b/vm/tests/decorators.py new file mode 100644 index 00000000..b7e2d703 --- /dev/null +++ b/vm/tests/decorators.py @@ -0,0 +1,327 @@ +# 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. + +# Copied from Python-3.4.9\Lib\test\test_decorators.py + +import libtest as self + +def funcattrs(**kwds): + def decorate(func): + # FIXME func.__dict__.update(kwds) + for k, v in kwds.items(): + func.__dict__[k] = v + return func + return decorate + +class MiscDecorators (object): + @staticmethod + def author(name): + def decorate(func): + func.__dict__['author'] = name + return func + return decorate + +# ----------------------------------------------- + +class DbcheckError (Exception): + def __init__(self, exprstr, func, args, kwds): + # A real version of this would set attributes here + Exception.__init__(self, "dbcheck %r failed (func=%s args=%s kwds=%s)" % + (exprstr, func, args, kwds)) + + +def dbcheck(exprstr, globals=None, locals=None): + "Decorator to implement debugging assertions" + def decorate(func): + expr = compile(exprstr, "dbcheck-%s" % func.__name__, "eval") + def check(*args, **kwds): + if not eval(expr, globals, locals): + raise DbcheckError(exprstr, func, args, kwds) + return func(*args, **kwds) + return check + return decorate + +# ----------------------------------------------- + +def countcalls(counts): + "Decorator to count calls to a function" + def decorate(func): + func_name = func.__name__ + counts[func_name] = 0 + def call(*args, **kwds): + counts[func_name] += 1 + return func(*args, **kwds) + call.__name__ = func_name + return call + return decorate + +# ----------------------------------------------- + +# FIXME: dict can only have string keys +# def memoize(func): +# saved = {} +# def call(*args): +# try: +# return saved[args] +# except KeyError: +# res = func(*args) +# saved[args] = res +# return res +# except TypeError: +# # Unhashable argument +# return func(*args) +# call.__name__ = func.__name__ +# return call +def memoize(func): + saved = {} + def call(*args): + try: + if isinstance(args[0], list): + raise TypeError + return saved[str(args)] + except KeyError: + res = func(*args) + saved[str(args)] = res + return res + except TypeError: + # Unhashable argument + return func(*args) + call.__name__ = func.__name__ + return call + +# ----------------------------------------------- + +doc="test_single" +# FIXME staticmethod +# class C(object): +# @staticmethod +# def foo(): return 42 +# self.assertEqual(C.foo(), 42) +# self.assertEqual(C().foo(), 42) + +doc="test_staticmethod_function" +@staticmethod +def notamethod(x): + return x +self.assertRaises(TypeError, notamethod, 1) + +doc="test_dotted" +# FIXME class decorator +# decorators = MiscDecorators() +# @decorators.author('Cleese') +# def foo(): return 42 +# self.assertEqual(foo(), 42) +# self.assertEqual(foo.author, 'Cleese') + +doc="test_argforms" +def noteargs(*args, **kwds): + def decorate(func): + setattr(func, 'dbval', (args, kwds)) + return func + return decorate + +args = ( 'Now', 'is', 'the', 'time' ) +kwds = dict(one=1, two=2) +@noteargs(*args, **kwds) +def f1(): return 42 +self.assertEqual(f1(), 42) +self.assertEqual(f1.dbval, (args, kwds)) + +@noteargs('terry', 'gilliam', eric='idle', john='cleese') +def f2(): return 84 +self.assertEqual(f2(), 84) +self.assertEqual(f2.dbval, (('terry', 'gilliam'), + dict(eric='idle', john='cleese'))) + +@noteargs(1, 2,) +def f3(): pass +self.assertEqual(f3.dbval, ((1, 2), {})) + +doc="test_dbcheck" +# FIXME TypeError: "catching 'BaseException' that does not inherit from BaseException is not allowed" +# @dbcheck('args[1] is not None') +# def f(a, b): +# return a + b +# self.assertEqual(f(1, 2), 3) +# self.assertRaises(DbcheckError, f, 1, None) + +doc="test_memoize" +counts = {} + +@memoize +@countcalls(counts) +def double(x): + return x * 2 +self.assertEqual(double.__name__, 'double') + +self.assertEqual(counts, dict(double=0)) + +# Only the first call with a given argument bumps the call count: +# +# Only the first call with a given argument bumps the call count: +# +self.assertEqual(double(2), 4) +self.assertEqual(counts['double'], 1) +self.assertEqual(double(2), 4) +self.assertEqual(counts['double'], 1) +self.assertEqual(double(3), 6) +self.assertEqual(counts['double'], 2) + +# Unhashable arguments do not get memoized: +# +self.assertEqual(double([10]), [10, 10]) +self.assertEqual(counts['double'], 3) +self.assertEqual(double([10]), [10, 10]) +self.assertEqual(counts['double'], 4) + +doc="test_errors" +# Test syntax restrictions - these are all compile-time errors: +# +for expr in [ "1+2", "x[3]", "(1, 2)" ]: + # Sanity check: is expr is a valid expression by itself? + compile(expr, "testexpr", "exec") + + codestr = "@%s\ndef f(): pass" % expr + self.assertRaises(SyntaxError, compile, codestr, "test", "exec") + +# You can't put multiple decorators on a single line: +# +self.assertRaises(SyntaxError, compile, + "@f1 @f2\ndef f(): pass", "test", "exec") + +# Test runtime errors + +def unimp(func): + raise NotImplementedError +context = dict(nullval=None, unimp=unimp) + +for expr, exc in [ ("undef", NameError), + ("nullval", TypeError), + ("nullval.attr", NameError), # FIXME ("nullval.attr", AttributeError), + ("unimp", NotImplementedError)]: + codestr = "@%s\ndef f(): pass\nassert f() is None" % expr + code = compile(codestr, "test", "exec") + self.assertRaises(exc, eval, code, context) + +doc="test_double" +class C(object): + @funcattrs(abc=1, xyz="haha") + @funcattrs(booh=42) + def foo(self): return 42 +self.assertEqual(C().foo(), 42) +self.assertEqual(C.foo.abc, 1) +self.assertEqual(C.foo.xyz, "haha") +self.assertEqual(C.foo.booh, 42) + + +doc="test_order" +# Test that decorators are applied in the proper order to the function +# they are decorating. +def callnum(num): + """Decorator factory that returns a decorator that replaces the + passed-in function with one that returns the value of 'num'""" + def deco(func): + return lambda: num + return deco +@callnum(2) +@callnum(1) +def foo(): return 42 +self.assertEqual(foo(), 2, + "Application order of decorators is incorrect") + + +doc="test_eval_order" +# Evaluating a decorated function involves four steps for each +# decorator-maker (the function that returns a decorator): +# +# 1: Evaluate the decorator-maker name +# 2: Evaluate the decorator-maker arguments (if any) +# 3: Call the decorator-maker to make a decorator +# 4: Call the decorator +# +# When there are multiple decorators, these steps should be +# performed in the above order for each decorator, but we should +# iterate through the decorators in the reverse of the order they +# appear in the source. +# FIXME class decorator +# actions = [] +# +# def make_decorator(tag): +# actions.append('makedec' + tag) +# def decorate(func): +# actions.append('calldec' + tag) +# return func +# return decorate +# +# class NameLookupTracer (object): +# def __init__(self, index): +# self.index = index +# +# def __getattr__(self, fname): +# if fname == 'make_decorator': +# opname, res = ('evalname', make_decorator) +# elif fname == 'arg': +# opname, res = ('evalargs', str(self.index)) +# else: +# assert False, "Unknown attrname %s" % fname +# actions.append('%s%d' % (opname, self.index)) +# return res +# +# c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ]) +# +# expected_actions = [ 'evalname1', 'evalargs1', 'makedec1', +# 'evalname2', 'evalargs2', 'makedec2', +# 'evalname3', 'evalargs3', 'makedec3', +# 'calldec3', 'calldec2', 'calldec1' ] +# +# actions = [] +# @c1.make_decorator(c1.arg) +# @c2.make_decorator(c2.arg) +# @c3.make_decorator(c3.arg) +# def foo(): return 42 +# self.assertEqual(foo(), 42) +# +# self.assertEqual(actions, expected_actions) +# +# # Test the equivalence claim in chapter 7 of the reference manual. +# # +# actions = [] +# def bar(): return 42 +# bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar))) +# self.assertEqual(bar(), 42) +# self.assertEqual(actions, expected_actions) + +doc="test_simple" +def plain(x): + x.extra = 'Hello' + return x +@plain +class C(object): pass +self.assertEqual(C.extra, 'Hello') + +doc="test_double" +def ten(x): + x.extra = 10 + return x +def add_five(x): + x.extra += 5 + return x + +@add_five +@ten +class C(object): pass +self.assertEqual(C.extra, 15) + +doc="test_order" +def applied_first(x): + x.extra = 'first' + return x +def applied_second(x): + x.extra = 'second' + return x +@applied_second +@applied_first +class C(object): pass +self.assertEqual(C.extra, 'second') +doc="finished" diff --git a/vm/tests/functions.py b/vm/tests/functions.py index da4bf924..aab079f9 100644 --- a/vm/tests/functions.py +++ b/vm/tests/functions.py @@ -21,18 +21,32 @@ def fn2(x,y=1): assert fn2(1,y=4) == 5 # Closure +doc="closure1" +closure1 = lambda x: lambda y: x+y +cf1 = closure1(1) +assert cf1(1) == 2 +assert cf1(2) == 3 + +doc="closure2" +def closure2(*args, **kwargs): + def inc(): + kwargs['x'] += 1 + return kwargs['x'] + return inc +cf2 = closure2(x=1) +assert cf2() == 2 +assert cf2() == 3 -# FIXME something wrong with closures over function arguments... -# doc="counter3" -# def counter3(x): -# def inc(): -# nonlocal x -# x += 1 -# return x -# return inc -# fn3 = counter3(1) -# assert fn3() == 2 -# assert fn3() == 3 +doc="counter3" +def counter3(x): + def inc(): + nonlocal x + x += 1 + return x + return inc +fn3 = counter3(1) +assert fn3() == 2 +assert fn3() == 3 doc="counter4" def counter4(initial): @@ -238,6 +252,4 @@ def fn16_6(*,a,b,c): ck(fn16_5, "fn16_5() missing 2 required keyword-only arguments: 'a' and 'b'") ck(fn16_6, "fn16_6() missing 3 required keyword-only arguments: 'a', 'b', and 'c'") -#FIXME decorators - doc="finished" diff --git a/vm/tests/libtest.py b/vm/tests/libtest.py new file mode 100644 index 00000000..8038556d --- /dev/null +++ b/vm/tests/libtest.py @@ -0,0 +1,57 @@ +# 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. + +# Imitate the calling method of unittest + +def assertRaises(expecting, fn, *args, **kwargs): + """Check the exception was raised - don't check the text""" + try: + fn(*args, **kwargs) + except expecting as e: + pass + else: + assert False, "%s not raised" % (expecting,) + +def assertEqual(first, second, msg=None): + if msg: + assert first == second, "%s not equal" % (msg,) + else: + assert first == second + +def assertIs(expr1, expr2, msg=None): + if msg: + assert expr1 is expr2, "%s is not None" % (msg,) + else: + assert expr1 is expr2 + +def assertIsNone(obj, msg=None): + if msg: + assert obj is None, "%s is not None" % (msg,) + else: + assert obj is None + +def assertTrue(obj, msg=None): + if msg: + assert obj, "%s is not True" % (msg,) + else: + assert obj + +def assertRaisesText(expecting, text, fn, *args, **kwargs): + """Check the exception with text in is raised""" + try: + fn(*args, **kwargs) + except expecting as e: + assert text in e.args[0], "'%s' not found in '%s'" % (text, e.args[0]) + else: + assert False, "%s not raised" % (expecting,) + +def assertTypedEqual(actual, expect, msg=None): + assertEqual(actual, expect, msg) + def recurse(actual, expect): + if isinstance(expect, (tuple, list)): + for x, y in zip(actual, expect): + recurse(x, y) + else: + assertIs(type(actual), type(expect)) + recurse(actual, expect) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy