From e9cde5fcf8e89407c50c12534c1b14bb1f840ffa Mon Sep 17 00:00:00 2001 From: wetor Date: Sat, 14 Oct 2023 01:31:15 +0800 Subject: [PATCH 01/10] compile,py: fix closure and decorator --- compile/compile.go | 15 +- py/code.go | 61 ++++---- vm/tests/class.py | 23 ++- vm/tests/decorators.py | 327 +++++++++++++++++++++++++++++++++++++++++ vm/tests/functions.py | 38 +++-- vm/tests/libtest.py | 57 +++++++ 6 files changed, 466 insertions(+), 55 deletions(-) create mode 100644 vm/tests/decorators.py create mode 100644 vm/tests/libtest.py 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/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) From ed47ed361fcbdb6fe2ed87579e569fe86e9d5755 Mon Sep 17 00:00:00 2001 From: vasilev Date: Wed, 22 Nov 2023 19:35:45 +0600 Subject: [PATCH 02/10] Fixed WASM compatibility by avoiding panic due to absence of `os.Executable()` in "js" and "wasip1" targets. --- stdlib/sys/sys.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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{ From 53252dd563c98158a0c88e360727e30e69981822 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Tue, 28 Nov 2023 12:34:04 +0100 Subject: [PATCH 03/10] stdlib/array: first import Signed-off-by: Sebastien Binet --- stdlib/array/array.go | 710 ++++++++++++++++++++++++++ stdlib/array/array_test.go | 15 + stdlib/array/testdata/test.py | 162 ++++++ stdlib/array/testdata/test_golden.txt | 298 +++++++++++ stdlib/stdlib.go | 1 + 5 files changed, 1186 insertions(+) create mode 100644 stdlib/array/array.go create mode 100644 stdlib/array/array_test.go create mode 100644 stdlib/array/testdata/test.py create mode 100644 stdlib/array/testdata/test_golden.txt diff --git a/stdlib/array/array.go b/stdlib/array/array.go new file mode 100644 index 00000000..f28d05ac --- /dev/null +++ b/stdlib/array/array.go @@ -0,0 +1,710 @@ +// 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" +) + +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]], + } + + if descr[0] == 'u' { + // FIXME(sbinet) + return nil, py.NotImplementedError + } + + switch descr[0] { + 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 { + o.WriteString(", [") + for i := 0; i < data.Len(); i++ { + if i > 0 { + o.WriteString(", ") + } + 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': + // FIXME(sbinet) + return nil, py.NotImplementedError + 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 := v.(py.Int) + sli.Index(i).SetInt(int64(vv)) + case 'B', 'H', 'I', 'L', 'Q': + vv := v.(py.Int) + sli.Index(i).SetUint(uint64(vv)) + case 'u': + // FIXME(sbinet) + return nil, py.NotImplementedError + 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) 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) 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..26f26166 --- /dev/null +++ b/stdlib/array/testdata/test.py @@ -0,0 +1,162 @@ +# 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': + # FIXME(sbinet): implement + print(" SKIP: NotImplemented") + continue + if typ in "bhilqfd": + arr = array.array(typ, [-1, -2, -3, -4]) + if typ in "BHILQ": + arr = array.array(typ, [+1, +2, +3, +4]) + print(" array: %s" % (repr(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(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + + try: + arr[10] + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + arr[-2] = 33 + print(" arr[-2]: %s" % (arr[-2],)) + + try: + arr[-10] = 2 + print(" ERROR: 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]) + print(" array: %s" % (repr(arr),)) + print(" len: %s" % (len(arr),)) + + if typ in "bhilqfd": + arr.append(-7) + if typ in "BHILQ": + arr.append(7) + print(" array: %s" % (repr(arr),)) + print(" len: %s" % (len(arr),)) + + try: + arr.append() + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.append([]) + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.append(1, 2) + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.append(None) + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + + try: + arr.extend() + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.extend(None) + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + try: + arr.extend([1,None]) + print(" ERROR: expected an exception") + except: + print(" caught an exception [ok]") + pass + +print("\n") +print("## testing array.array(...)") +try: + arr = array.array() + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array(b"d") + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("?") + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("dd") + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", initializer=[1,2]) + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", [1], []) + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", 1) + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + arr = array.array("d", ["a","b"]) + print("ERROR: expected an exception") +except: + print("caught an exception [ok]") + +try: + ## FIXME(sbinet): implement it at some point. + arr = array.array("u") + print("ERROR: 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..b55665ad --- /dev/null +++ b/stdlib/array/testdata/test_golden.txt @@ -0,0 +1,298 @@ +globals: + +array.typecodes: +'bBuhHiIlLqQfd' + +array.array: + + +typecode 'b' + array: array('b', [-1, -2, -3, -4]) + 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] + +typecode 'B' + array: array('B', [1, 2, 3, 4]) + 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] + +typecode 'u' + SKIP: NotImplemented + +typecode 'h' + array: array('h', [-1, -2, -3, -4]) + 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] + +typecode 'H' + array: array('H', [1, 2, 3, 4]) + 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] + +typecode 'i' + array: array('i', [-1, -2, -3, -4]) + 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] + +typecode 'I' + array: array('I', [1, 2, 3, 4]) + 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] + +typecode 'l' + array: array('l', [-1, -2, -3, -4]) + 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] + +typecode 'L' + array: array('L', [1, 2, 3, 4]) + 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] + +typecode 'q' + array: array('q', [-1, -2, -3, -4]) + 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] + +typecode 'Q' + array: array('Q', [1, 2, 3, 4]) + 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] + +typecode 'f' + array: array('f', [-1, -2, -3, -4]) + itemsize: 4 + typecode: f + 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('f', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('f', [-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] + +typecode 'd' + array: array('d', [-1, -2, -3, -4]) + itemsize: 8 + typecode: d + 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('d', [-1, -2, 33, -4, -5, -6]) + len: 6 + array: array('d', [-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] + + +## 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] +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" From 95c8e39d73c6fb0e1b6e47cefbe5d4790e1d6c0f Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Wed, 29 Nov 2023 10:00:28 +0100 Subject: [PATCH 04/10] stdlib/array: add support for 'u' arrays Signed-off-by: Sebastien Binet --- stdlib/array/array.go | 86 ++++++++++++++++++----- stdlib/array/testdata/test.py | 71 +++++++++++-------- stdlib/array/testdata/test_golden.txt | 98 +++++++++++++++++++++------ 3 files changed, 188 insertions(+), 67 deletions(-) diff --git a/stdlib/array/array.go b/stdlib/array/array.go index f28d05ac..b9e682ae 100644 --- a/stdlib/array/array.go +++ b/stdlib/array/array.go @@ -13,6 +13,11 @@ import ( "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 @@ -197,12 +202,12 @@ func array_new(metatype *py.Type, args py.Tuple, kwargs py.StringDict) (py.Objec esize: descr2esize[descr[0]], } - if descr[0] == 'u' { - // FIXME(sbinet) - return nil, py.NotImplementedError - } - 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 @@ -300,14 +305,22 @@ 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 { - o.WriteString(", [") - for i := 0; i < data.Len(); i++ { - if i > 0 { - o.WriteString(", ") + 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)) } - fmt.Fprintf(o, "%v", data.Index(i)) + o.WriteString("]") } - o.WriteString("]") } o.WriteString(")") return py.String(o.String()), nil @@ -344,8 +357,7 @@ func (arr *array) M__getitem__(k py.Object) (py.Object, error) { case 'B', 'H', 'I', 'L', 'Q': return py.Int(sli.Index(i).Uint()), nil case 'u': - // FIXME(sbinet) - return nil, py.NotImplementedError + return py.String([]rune{rune(sli.Index(i).Int())}), nil case 'f', 'd': return py.Float(sli.Index(i).Float()), nil } @@ -372,14 +384,23 @@ func (arr *array) M__setitem__(k, v py.Object) (py.Object, error) { } switch arr.descr { case 'b', 'h', 'i', 'l', 'q': - vv := v.(py.Int) + 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 := v.(py.Int) + 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': - // FIXME(sbinet) - return nil, py.NotImplementedError + 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) { @@ -401,6 +422,16 @@ func (arr *array) M__setitem__(k, v py.Object) (py.Object, error) { 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 { @@ -491,6 +522,27 @@ func (arr *array) appendF64(v py.Object) (py.Object, error) { 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 { diff --git a/stdlib/array/testdata/test.py b/stdlib/array/testdata/test.py index 26f26166..906ed898 100644 --- a/stdlib/array/testdata/test.py +++ b/stdlib/array/testdata/test.py @@ -19,14 +19,15 @@ def assertEqual(x, y): print("") print("typecode '%s'" % (typ,)) if typ == 'u': - # FIXME(sbinet): implement - print(" SKIP: NotImplemented") - continue - if typ in "bhilqfd": + 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]) - print(" array: %s" % (repr(arr),)) + 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),)) @@ -34,21 +35,23 @@ def assertEqual(x, y): print(" arr[-1]: %s" % (arr[-1],)) try: arr[-10] - print(" ERROR: expected an exception") + print(" ERROR1: expected an exception") except: print(" caught an exception [ok]") try: arr[10] - print(" ERROR: expected an exception") + 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(" ERROR: expected an exception") + print(" ERROR3: expected an exception") except: print(" caught an exception [ok]") @@ -56,6 +59,8 @@ def assertEqual(x, y): 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),)) @@ -63,43 +68,56 @@ def assertEqual(x, y): 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(" ERROR: expected an exception") + print(" ERROR4: expected an exception") except: print(" caught an exception [ok]") try: arr.append([]) - print(" ERROR: expected an exception") + print(" ERROR5: expected an exception") except: print(" caught an exception [ok]") try: arr.append(1, 2) - print(" ERROR: expected an exception") + print(" ERROR6: expected an exception") except: print(" caught an exception [ok]") try: arr.append(None) - print(" ERROR: expected an exception") + print(" ERROR7: expected an exception") except: print(" caught an exception [ok]") try: arr.extend() - print(" ERROR: expected an exception") + print(" ERROR8: expected an exception") except: print(" caught an exception [ok]") try: arr.extend(None) - print(" ERROR: expected an exception") + print(" ERROR9: expected an exception") except: print(" caught an exception [ok]") try: arr.extend([1,None]) - print(" ERROR: expected an exception") + 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 @@ -108,55 +126,48 @@ def assertEqual(x, y): print("## testing array.array(...)") try: arr = array.array() - print("ERROR: expected an exception") + print("ERROR1: expected an exception") except: print("caught an exception [ok]") try: arr = array.array(b"d") - print("ERROR: expected an exception") + print("ERROR2: expected an exception") except: print("caught an exception [ok]") try: arr = array.array("?") - print("ERROR: expected an exception") + print("ERROR3: expected an exception") except: print("caught an exception [ok]") try: arr = array.array("dd") - print("ERROR: expected an exception") + print("ERROR4: expected an exception") except: print("caught an exception [ok]") try: arr = array.array("d", initializer=[1,2]) - print("ERROR: expected an exception") + print("ERROR5: expected an exception") except: print("caught an exception [ok]") try: arr = array.array("d", [1], []) - print("ERROR: expected an exception") + print("ERROR6: expected an exception") except: print("caught an exception [ok]") try: arr = array.array("d", 1) - print("ERROR: expected an exception") + print("ERROR7: expected an exception") except: print("caught an exception [ok]") try: arr = array.array("d", ["a","b"]) - print("ERROR: expected an exception") -except: - print("caught an exception [ok]") - -try: - ## FIXME(sbinet): implement it at some point. - arr = array.array("u") - print("ERROR: expected an exception") + 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 index b55665ad..09e9db3d 100644 --- a/stdlib/array/testdata/test_golden.txt +++ b/stdlib/array/testdata/test_golden.txt @@ -7,7 +7,8 @@ array.array: typecode 'b' - array: array('b', [-1, -2, -3, -4]) + array: array('b', [-1, -2, -3, -4]) ## repr + array: array('b', [-1, -2, -3, -4]) ## str itemsize: 1 typecode: b len: 4 @@ -28,9 +29,12 @@ typecode 'b' 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]) + array: array('B', [1, 2, 3, 4]) ## repr + array: array('B', [1, 2, 3, 4]) ## str itemsize: 1 typecode: B len: 4 @@ -51,12 +55,38 @@ typecode 'B' caught an exception [ok] caught an exception [ok] caught an exception [ok] + caught an exception [ok] + caught an exception [ok] typecode 'u' - SKIP: NotImplemented + 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]) + array: array('h', [-1, -2, -3, -4]) ## repr + array: array('h', [-1, -2, -3, -4]) ## str itemsize: 2 typecode: h len: 4 @@ -77,9 +107,12 @@ typecode 'h' 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]) + array: array('H', [1, 2, 3, 4]) ## repr + array: array('H', [1, 2, 3, 4]) ## str itemsize: 2 typecode: H len: 4 @@ -100,9 +133,12 @@ typecode 'H' 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]) + array: array('i', [-1, -2, -3, -4]) ## repr + array: array('i', [-1, -2, -3, -4]) ## str itemsize: 2 typecode: i len: 4 @@ -123,9 +159,12 @@ typecode 'i' 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]) + array: array('I', [1, 2, 3, 4]) ## repr + array: array('I', [1, 2, 3, 4]) ## str itemsize: 2 typecode: I len: 4 @@ -146,9 +185,12 @@ typecode 'I' 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]) + array: array('l', [-1, -2, -3, -4]) ## repr + array: array('l', [-1, -2, -3, -4]) ## str itemsize: 8 typecode: l len: 4 @@ -169,9 +211,12 @@ typecode 'l' 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]) + array: array('L', [1, 2, 3, 4]) ## repr + array: array('L', [1, 2, 3, 4]) ## str itemsize: 8 typecode: L len: 4 @@ -192,9 +237,12 @@ typecode 'L' 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]) + array: array('q', [-1, -2, -3, -4]) ## repr + array: array('q', [-1, -2, -3, -4]) ## str itemsize: 8 typecode: q len: 4 @@ -215,9 +263,12 @@ typecode 'q' 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]) + array: array('Q', [1, 2, 3, 4]) ## repr + array: array('Q', [1, 2, 3, 4]) ## str itemsize: 8 typecode: Q len: 4 @@ -238,9 +289,12 @@ typecode 'Q' 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]) + array: array('f', [-1, -2, -3, -4]) ## repr + array: array('f', [-1, -2, -3, -4]) ## str itemsize: 4 typecode: f len: 4 @@ -248,11 +302,11 @@ typecode 'f' arr[-1]: -4 caught an exception [ok] caught an exception [ok] - arr[-2]: 33 + arr[-2]: 0.30000001192092896 caught an exception [ok] - array: array('f', [-1, -2, 33, -4, -5, -6]) + array: array('f', [-1, -2, 0.3, -4, -5, -6]) len: 6 - array: array('f', [-1, -2, 33, -4, -5, -6, -7]) + array: array('f', [-1, -2, 0.3, -4, -5, -6, -7]) len: 7 caught an exception [ok] caught an exception [ok] @@ -261,9 +315,12 @@ typecode 'f' 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]) + array: array('d', [-1, -2, -3, -4]) ## repr + array: array('d', [-1, -2, -3, -4]) ## str itemsize: 8 typecode: d len: 4 @@ -271,11 +328,11 @@ typecode 'd' arr[-1]: -4 caught an exception [ok] caught an exception [ok] - arr[-2]: 33 + arr[-2]: 0.3 caught an exception [ok] - array: array('d', [-1, -2, 33, -4, -5, -6]) + array: array('d', [-1, -2, 0.3, -4, -5, -6]) len: 6 - array: array('d', [-1, -2, 33, -4, -5, -6, -7]) + array: array('d', [-1, -2, 0.3, -4, -5, -6, -7]) len: 7 caught an exception [ok] caught an exception [ok] @@ -284,6 +341,8 @@ typecode 'd' caught an exception [ok] caught an exception [ok] caught an exception [ok] + caught an exception [ok] + caught an exception [ok] ## testing array.array(...) @@ -295,4 +354,3 @@ caught an exception [ok] caught an exception [ok] caught an exception [ok] caught an exception [ok] -caught an exception [ok] From 23c0aa29cc22afb51a5985f2b74ff6464675d1e8 Mon Sep 17 00:00:00 2001 From: Natanael dos Santos Feitosa <52074821+natanfeitosa@users.noreply.github.com> Date: Tue, 26 Dec 2023 20:06:07 -0300 Subject: [PATCH 05/10] py: fix automatic addition of __doc__ to dict object Fixes #229. --- py/type.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From 149d52cd50c5a375e92a3c6a7274df7469bdcdea Mon Sep 17 00:00:00 2001 From: wdq <105555429+wdq112@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:11:48 +0800 Subject: [PATCH 06/10] py: implement str.lower and str.upper Updates #232 --- py/string.go | 16 ++++++++++++++++ py/string_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ py/tests/string.py | 8 ++++++++ 3 files changed, 70 insertions(+) diff --git a/py/string.go b/py/string.go index a28e6e74..e470c01d 100644 --- a/py/string.go +++ b/py/string.go @@ -218,6 +218,14 @@ 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") + } // Type of this object @@ -739,6 +747,14 @@ 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 +} + // 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..f2ad6e9b 100644 --- a/py/tests/string.py +++ b/py/tests/string.py @@ -897,6 +897,14 @@ 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" + class Index: def __index__(self): return 1 From 79bb9256ae58ef20f65fd6909672a4c7bd008525 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Fri, 7 Mar 2025 11:07:32 +0100 Subject: [PATCH 07/10] ci: update GitHub actions Signed-off-by: Sebastien Binet --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 From a0c052992576917aac53235396b525632e16f9ec Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 28 Jun 2025 00:18:04 +0900 Subject: [PATCH 08/10] py: implement str.join Fixes #232 --- py/string.go | 27 +++++++++++++++++++++++++++ py/tests/string.py | 9 +++++++++ 2 files changed, 36 insertions(+) diff --git a/py/string.go b/py/string.go index e470c01d..a987a11a 100644 --- a/py/string.go +++ b/py/string.go @@ -226,6 +226,9 @@ replaced.`) 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 @@ -755,6 +758,30 @@ 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/tests/string.py b/py/tests/string.py index f2ad6e9b..d1d16cfd 100644 --- a/py/tests/string.py +++ b/py/tests/string.py @@ -905,6 +905,15 @@ def index(s, i): 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 From cff1e72e91fb927255adcdbb6d2fbc19a29ed052 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 28 Jun 2025 00:18:42 +0900 Subject: [PATCH 09/10] py: remove dead code --- py/int.go | 6 ------ 1 file changed, 6 deletions(-) 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 From 53b7b8e01d998914a7f4179e3e051274f94b8990 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 28 Jun 2025 18:11:55 +0900 Subject: [PATCH 10/10] py: fix import on Windows --- py/import.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/import.go b/py/import.go index 709a4468..5ca6598a 100644 --- a/py/import.go +++ b/py/import.go @@ -7,7 +7,7 @@ package py import ( - "path" + "path/filepath" "strings" ) @@ -101,14 +101,14 @@ func ImportModuleLevelObject(ctx Context, name string, globals, locals StringDic // Convert import's dot separators into path seps parts := strings.Split(name, ".") - srcPathname := path.Join(parts...) + srcPathname := filepath.Join(parts...) opts := CompileOpts{ UseSysPaths: true, } if fromFile, ok := globals["__file__"]; ok { - opts.CurDir = path.Dir(string(fromFile.(String))) + opts.CurDir = filepath.Dir(string(fromFile.(String))) } module, err := RunFile(ctx, srcPathname, opts, name) 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