diff --git a/_examples/named/test.py b/_examples/named/test.py index 846e1db..b5405a5 100644 --- a/_examples/named/test.py +++ b/_examples/named/test.py @@ -6,6 +6,9 @@ from __future__ import print_function import named +import sys + +_PY2 = sys.version_info[0] == 2 ### test docs print("doc(named): %r" % (named.__doc__,)) @@ -96,13 +99,14 @@ try: print("arr = named.Array(range(10))") arr = named.Array(range(10)) print("arr = %s" % (arr,)) -except Exception, err: - print("caught: %s" % (err,)) +except Exception as err: + print("caught: %s" % (str(err),)) pass -print("arr = named.Array(xrange(2))") -arr = named.Array(xrange(2)) -print("arr = %s" % (arr,)) +if _PY2: + print("arr = named.Array(xrange(2))") + arr = named.Array(xrange(2)) + print("arr = %s" % (arr,)) print("s = named.Slice()") s = named.Slice() @@ -116,7 +120,7 @@ print("s = named.Slice(range(10))") s = named.Slice(range(10)) print("s = %s" % (s,)) -print("s = named.Slice(xrange(10))") -s = named.Slice(xrange(10)) -print("s = %s" % (s,)) - +if _PY2: + print("s = named.Slice(xrange(10))") + s = named.Slice(xrange(10)) + print("s = %s" % (s,)) diff --git a/bind/doc.go b/bind/doc.go new file mode 100644 index 0000000..5142660 --- /dev/null +++ b/bind/doc.go @@ -0,0 +1,6 @@ +// Copyright 2017 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 bind provides tools to generate bindings to use Go from Python. +package bind diff --git a/bind/gencffi.go b/bind/gencffi.go index 14d5eea..3a2ccc6 100644 --- a/bind/gencffi.go +++ b/bind/gencffi.go @@ -16,6 +16,7 @@ const ( // """) // discuss: https://github.com/go-python/gopy/pull/93#discussion_r119652220 cffiPreamble = `"""%[1]s""" +import collections import os import sys import cffi as _cffi_backend @@ -56,6 +57,7 @@ extern void cgopy_incref(void* p0); extern void cgopy_decref(void* p0); extern void cgo_pkg_%[2]s_init(); + ` cffiHelperPreamble = `""") @@ -230,6 +232,17 @@ func (g *cffiGen) genWrappedPy() { n := g.pkg.pkg.Name() g.wrapper.Printf(cffiHelperPreamble, n) g.wrapper.Indent() + + // first, process slices, arrays + names := g.pkg.syms.names() + for _, n := range names { + sym := g.pkg.syms.sym(n) + if !sym.isType() { + continue + } + g.genTypeConverter(sym) + } + for _, s := range g.pkg.structs { g.genStructConversion(s) } @@ -240,6 +253,14 @@ func (g *cffiGen) genWrappedPy() { g.wrapper.Printf("# make sure Cgo is loaded and initialized\n") g.wrapper.Printf("_cffi_helper.lib.cgo_pkg_%[1]s_init()\n", n) + for _, n := range names { + sym := g.pkg.syms.sym(n) + if !sym.isType() { + continue + } + g.genType(sym) + } + for _, s := range g.pkg.structs { g.genStruct(s) } diff --git a/bind/gencffi_cdef.go b/bind/gencffi_cdef.go index d4a0e10..79e936d 100644 --- a/bind/gencffi_cdef.go +++ b/bind/gencffi_cdef.go @@ -5,18 +5,22 @@ package bind import ( + "fmt" "go/types" "strconv" "strings" ) +// genCdefStruct generates C definitions for a Go struct. func (g *cffiGen) genCdefStruct(s Struct) { g.wrapper.Printf("// A type definition of the %[1]s.%[2]s for wrapping.\n", s.Package().Name(), s.sym.cgoname) g.wrapper.Printf("typedef void* %s;\n", s.sym.cgoname) g.wrapper.Printf("extern void* cgo_func_%s_new();\n", s.sym.id) } +// genCdefType generates C definitions for a Go type. func (g *cffiGen) genCdefType(sym *symbol) { + g.wrapper.Printf("// C definitions for Go type %[1]s.%[2]s.\n", g.pkg.pkg.Name(), sym.id) if !sym.isType() { return } @@ -29,14 +33,64 @@ func (g *cffiGen) genCdefType(sym *symbol) { return } + g.genTypeCdefInit(sym) + + if sym.isNamed() { + typ := sym.GoType().(*types.Named) + for imeth := 0; imeth < typ.NumMethods(); imeth++ { + m := typ.Method(imeth) + if !m.Exported() { + continue + } + mname := types.ObjectString(m, nil) + msym := g.pkg.syms.sym(mname) + if msym == nil { + panic(fmt.Errorf( + "gopy: could not find symbol for [%[1]T] (%#[1]v) (%[2]s)", + m.Type(), + m.Name()+" || "+m.FullName(), + )) + } + g.genTypeCdefFunc(sym, msym) + } + } + g.genCdefTypeTPStr(sym) + g.wrapper.Printf("\n") +} + +// genTypeCdefInit generates cgo_func_XXXX_new() for a Go type. +func (g *cffiGen) genTypeCdefInit(sym *symbol) { if sym.isBasic() { btyp := g.pkg.syms.symtype(sym.GoType().Underlying()) - g.wrapper.Printf("typedef %s %s;\n\n", btyp.cgoname, sym.cgoname) + g.wrapper.Printf("typedef %[1]s %[2]s;\n", btyp.cgoname, sym.cgoname) + g.wrapper.Printf("extern %[1]s cgo_func_%[2]s_new();\n", btyp.cgoname, sym.id) } else { - g.wrapper.Printf("typedef void* %s;\n\n", sym.cgoname) + g.wrapper.Printf("typedef void* %s;\n", sym.cgoname) + g.wrapper.Printf("extern void* cgo_func_%s_new();\n", sym.id) + } + + switch { + case sym.isBasic(): + case sym.isArray(): + typ := sym.GoType().Underlying().(*types.Array) + elemType := g.pkg.syms.symtype(typ.Elem()) + g.wrapper.Printf("extern void cgo_func_%[1]s_ass_item(void* p0, GoInt p1, %[2]s p2);\n", sym.id, elemType.cgoname) + case sym.isSlice(): + typ := sym.GoType().Underlying().(*types.Slice) + elemType := g.pkg.syms.symtype(typ.Elem()) + g.wrapper.Printf("extern void cgo_func_%[1]s_append(void* p0, %[2]s p1);\n", sym.id, elemType.cgoname) + case sym.isMap(): + case sym.isSignature(): + case sym.isInterface(): + default: + panic(fmt.Errorf( + "gopy: cdef for %s not handled", + sym.gofmt(), + )) } } +// genCdefFunc generates a C definition for a Go function. func (g *cffiGen) genCdefFunc(f Func) { var params []string var retParams []string @@ -68,6 +122,51 @@ func (g *cffiGen) genCdefFunc(f Func) { g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, f.id, paramString) } +// genTypeCdefFunc generates a C definition for a Go type's method. +func (g *cffiGen) genTypeCdefFunc(sym *symbol, fsym *symbol) { + if !sym.isType() { + return + } + + if sym.isStruct() { + return + } + + if sym.isBasic() && !sym.isNamed() { + return + } + isMethod := (sym != nil) + sig := fsym.GoType().Underlying().(*types.Signature) + args := sig.Params() + res := sig.Results() + var cdef_ret string + var params []string + switch res.Len() { + case 0: + cdef_ret = "void" + case 1: + ret := res.At(0) + cdef_ret = g.pkg.syms.symtype(ret.Type()).cgoname + } + + if isMethod { + params = append(params, sym.cgoname+" p0") + } + + for i := 0; i < args.Len(); i++ { + arg := args.At(i) + index := i + if isMethod { + index++ + } + paramVar := g.pkg.syms.symtype(arg.Type()).cgoname + " " + "p" + strconv.Itoa(index) + params = append(params, paramVar) + } + paramString := strings.Join(params, ", ") + g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, fsym.id, paramString) +} + +// genCdefMethod generates a C definition for a Go Struct's method. func (g *cffiGen) genCdefMethod(f Func) { var retParams []string var cdef_ret string @@ -99,6 +198,7 @@ func (g *cffiGen) genCdefMethod(f Func) { g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, f.id, paramString) } +// genCdefStructMemberGetter generates C definitions of Getter/Setter for a Go struct. func (g *cffiGen) genCdefStructMemberGetter(s Struct, i int, f types.Object) { pkg := s.Package() ft := f.Type() @@ -116,6 +216,7 @@ func (g *cffiGen) genCdefStructMemberGetter(s Struct, i int, f types.Object) { } } +// genCdefStructMemberSetter generates C defintion of Setter for a Go struct. func (g *cffiGen) genCdefStructMemberSetter(s Struct, i int, f types.Object) { pkg := s.Package() ft := f.Type() @@ -126,6 +227,12 @@ func (g *cffiGen) genCdefStructMemberSetter(s Struct, i int, f types.Object) { g.wrapper.Printf("extern void cgo_func_%[1]s_setter_%[2]d(void* p0, %[3]s p1);\n", s.sym.id, i+1, cdef_value) } +// genCdefStructTPStr generates C definitions of str method for a Go struct. func (g *cffiGen) genCdefStructTPStr(s Struct) { g.wrapper.Printf("extern GoString cgo_func_%[1]s_str(void* p0);\n", s.sym.id) } + +// genCdefTypeTPStr generates C definitions of str method for a Go type. +func (g *cffiGen) genCdefTypeTPStr(sym *symbol) { + g.wrapper.Printf("extern GoString cgo_func_%[1]s_str(%[2]s p0);\n", sym.id, sym.cgoname) +} diff --git a/bind/gencffi_func.go b/bind/gencffi_func.go index 69286b2..080b56e 100644 --- a/bind/gencffi_func.go +++ b/bind/gencffi_func.go @@ -6,6 +6,7 @@ package bind import ( "fmt" + "go/types" "strings" ) @@ -21,7 +22,7 @@ func (g *cffiGen) genMethod(s Struct, m Func) { g.wrapper.Printf(` # pythonization of: %[1]s.%[2]s def %[2]s(%[3]s): - """%[4]s""" + ""%[4]q"" `, g.pkg.pkg.Name(), m.GoName(), @@ -117,7 +118,7 @@ func (g *cffiGen) genFunc(o Func) { g.wrapper.Printf(` # pythonization of: %[1]s.%[2]s def %[2]s(%[3]s): - """%[4]s""" + ""%[4]q"" `, g.pkg.pkg.Name(), o.GoName(), @@ -253,3 +254,76 @@ func (g *cffiGen) genFuncBody(f Func) { panic(fmt.Errorf("gopy: Not yet implemeted for multiple return.")) } } + +func (g *cffiGen) genTypeFunc(sym *symbol, fsym *symbol) { + isMethod := (sym != nil) + sig := fsym.GoType().Underlying().(*types.Signature) + args := sig.Params() + nargs := 0 + var funcArgs []string + + if isMethod { + funcArgs = append(funcArgs, "self") + } + + if args != nil { + nargs = args.Len() + for i := 0; i < nargs; i++ { + arg := args.At(i) + if args == nil { + panic(fmt.Errorf( + "gopy: could not find symbol for %q", + arg.String(), + )) + } + funcArgs = append(funcArgs, arg.Name()) + } + } + + g.wrapper.Printf( + `def %[1]s(%[2]s): + ""%[3]q"" +`, + fsym.goname, + strings.Join(funcArgs, ", "), + fsym.doc, + ) + g.wrapper.Indent() + g.genTypeFuncBody(sym, fsym) + g.wrapper.Outdent() +} + +func (g *cffiGen) genTypeFuncBody(sym *symbol, fsym *symbol) { + isMethod := (sym != nil) + sig := fsym.GoType().Underlying().(*types.Signature) + args := sig.Params() + res := sig.Results() + nargs := 0 + nres := 0 + var funcArgs []string + if isMethod { + funcArgs = append(funcArgs, "self.cgopy") + } + + if args != nil { + nargs = args.Len() + for i := 0; i < nargs; i++ { + arg := args.At(i) + funcArgs = append(funcArgs, arg.Name()) + } + } + + if res != nil { + nres = res.Len() + } + + if nres > 0 { + g.wrapper.Printf("res = ") + g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s(%[2]s)\n", fsym.id, strings.Join(funcArgs, ", ")) + ret := res.At(0) + sret := g.pkg.syms.symtype(ret.Type()) + g.wrapper.Printf("return _cffi_helper.cffi_%[1]s(res)\n", sret.c2py) + } else { + g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s(%[2]s)\n", fsym.id, strings.Join(funcArgs, ", ")) + } +} diff --git a/bind/gencffi_struct.go b/bind/gencffi_struct.go index cb627f6..274e056 100644 --- a/bind/gencffi_struct.go +++ b/bind/gencffi_struct.go @@ -14,7 +14,7 @@ func (g *cffiGen) genStruct(s Struct) { g.wrapper.Printf(` # Python type for struct %[1]s.%[2]s class %[2]s(object): - """%[3]s""" + ""%[3]q"" `, pkgname, s.GoName(), diff --git a/bind/gencffi_type.go b/bind/gencffi_type.go new file mode 100644 index 0000000..4231fdf --- /dev/null +++ b/bind/gencffi_type.go @@ -0,0 +1,198 @@ +// Copyright 2017 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 bind + +import ( + "fmt" + "go/types" +) + +// genType generates a Python class wrapping the given Go type. +func (g *cffiGen) genType(sym *symbol) { + if !sym.isType() { + return + } + if sym.isStruct() { + return + } + if sym.isBasic() && !sym.isNamed() { + return + } + + pkgname := g.pkg.pkg.Name() + g.wrapper.Printf(` +# Python type for %[1]s.%[2]s +class %[2]s(object): + ""%[3]q"" +`, pkgname, + sym.goname, + sym.doc, + ) + g.wrapper.Indent() + g.genTypeInit(sym) + g.genTypeMethod(sym) + g.genTypeTPStr(sym) + if sym.isSlice() { + g.genTypeIAdd(sym) + } + g.wrapper.Outdent() +} + +// genTypeIAdd generates Type __iadd__. +func (g *cffiGen) genTypeIAdd(sym *symbol) { + g.wrapper.Printf("def __iadd__(self, value):\n") + g.wrapper.Indent() + switch { + case sym.isSlice(): + g.wrapper.Printf("if not isinstance(value, collections.Iterable):\n") + g.wrapper.Indent() + g.wrapper.Printf("raise TypeError('%[1]s.__iadd__ takes a iterable type as argument')\n", sym.goname) + g.wrapper.Outdent() + typ := sym.GoType().Underlying().(*types.Slice) + esym := g.pkg.syms.symtype(typ.Elem()) + g.wrapper.Printf("for elt in value:\n") + g.wrapper.Indent() + g.wrapper.Printf("pyitem = _cffi_helper.cffi_%[1]s(elt)\n", esym.py2c) + g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_append(self.cgopy, pyitem)\n", sym.id) + g.wrapper.Outdent() + default: + panic(fmt.Errorf( + "gopy: __iadd__ for %s not handled", + sym.gofmt(), + )) + } + g.wrapper.Printf("return self\n") + g.wrapper.Outdent() + g.wrapper.Printf("\n") +} + +// genTypeInit generates Type __init__. +func (g *cffiGen) genTypeInit(sym *symbol) { + nargs := 1 + g.wrapper.Printf("def __init__(self, *args, **kwargs):\n") + g.wrapper.Indent() + g.wrapper.Printf("nkwds = len(kwargs)\n") + g.wrapper.Printf("nargs = len(args)\n") + g.wrapper.Printf("if nkwds + nargs > %[1]d:\n", nargs) + g.wrapper.Indent() + g.wrapper.Printf("raise TypeError('%s.__init__ takes at most %d argument(s)')\n", + sym.goname, + nargs, + ) + g.wrapper.Outdent() + g.wrapper.Printf("self.cgopy = _cffi_helper.lib.cgo_func_%[1]s_new()\n", sym.id) + switch { + case sym.isBasic(): + bsym := g.pkg.syms.symtype(sym.GoType().Underlying()) + g.wrapper.Printf("if len(args) == 1:\n") + g.wrapper.Indent() + g.wrapper.Printf("self.cgopy = _cffi_helper.cffi_%[1]s(args[0])\n", + bsym.py2c, + ) + g.wrapper.Outdent() + case sym.isArray(): + g.wrapper.Printf("if args:\n") + g.wrapper.Indent() + g.wrapper.Printf("if not isinstance(args[0], collections.Sequence):\n") + g.wrapper.Indent() + g.wrapper.Printf("raise TypeError('%[1]s.__init__ takes a sequence as argument')\n", sym.goname) + g.wrapper.Outdent() + typ := sym.GoType().Underlying().(*types.Array) + esym := g.pkg.syms.symtype(typ.Elem()) + g.wrapper.Printf("if len(args[0]) > %[1]d:\n", typ.Len()) + g.wrapper.Indent() + g.wrapper.Printf("raise ValueError('%[1]s.__init__ takes a sequence of size at most %[2]d')\n", + sym.goname, + typ.Len(), + ) + g.wrapper.Outdent() + g.wrapper.Printf("for idx, elt in enumerate(args[0]):\n") + g.wrapper.Indent() + g.wrapper.Printf("pyitem = _cffi_helper.cffi_%[1]s(elt)\n", esym.py2c) + g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_ass_item(self.cgopy, idx, pyitem)\n", sym.id) + g.wrapper.Outdent() + g.wrapper.Outdent() + case sym.isSlice(): + g.wrapper.Printf("if args:\n") + g.wrapper.Indent() + g.wrapper.Printf("if not isinstance(args[0], collections.Iterable):\n") + g.wrapper.Indent() + g.wrapper.Printf("raise TypeError('%[1]s.__init__ takes a sequence as argument')\n", sym.goname) + g.wrapper.Outdent() + typ := sym.GoType().Underlying().(*types.Slice) + esym := g.pkg.syms.symtype(typ.Elem()) + g.wrapper.Printf("for elt in args[0]:\n") + g.wrapper.Indent() + g.wrapper.Printf("pyitem = _cffi_helper.cffi_%[1]s(elt)\n", esym.py2c) + g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_append(self.cgopy, pyitem)\n", sym.id) + g.wrapper.Outdent() + g.wrapper.Outdent() + case sym.isMap(): + case sym.isSignature(): + //TODO(corona10) + case sym.isInterface(): + //TODO(corona10) + default: + panic(fmt.Errorf( + "gopy: init for %s not handled", + sym.gofmt(), + )) + } + g.wrapper.Outdent() + g.wrapper.Printf("\n") +} + +// genTypeConverter generates a Type converter between C and Python. +func (g *cffiGen) genTypeConverter(sym *symbol) { + g.wrapper.Printf("# converters for %s - %s\n", sym.id, sym.goname) + g.wrapper.Printf("@staticmethod\n") + g.wrapper.Printf("def cffi_cgopy_cnv_py2c_%[1]s(o):\n", sym.id) + g.wrapper.Indent() + g.wrapper.Printf("raise NotImplementedError\n") + g.wrapper.Outdent() + g.wrapper.Printf("\n") + g.wrapper.Printf("@staticmethod\n") + g.wrapper.Printf("def cffi_cgopy_cnv_c2py_%[1]s(c):\n", sym.id) + g.wrapper.Indent() + g.wrapper.Printf("raise NotImplementedError\n") + g.wrapper.Outdent() + g.wrapper.Printf("\n") +} + +// genTypeMethod generates Type methods. +func (g *cffiGen) genTypeMethod(sym *symbol) { + g.wrapper.Printf("# methods for %s\n", sym.gofmt()) + if sym.isNamed() { + typ := sym.GoType().(*types.Named) + for imeth := 0; imeth < typ.NumMethods(); imeth++ { + m := typ.Method(imeth) + if !m.Exported() { + continue + } + mname := types.ObjectString(m, nil) + msym := g.pkg.syms.sym(mname) + if msym == nil { + panic(fmt.Errorf( + "gopy: could not find symbol for [%[1]T] (%#[1]v) (%[2]s)", + m.Type(), + m.Name()+" || "+m.FullName(), + )) + } + g.genTypeFunc(sym, msym) + } + } + g.wrapper.Printf("\n") +} + +// genTypeTPStr generates Type __str__ method. +func (g *cffiGen) genTypeTPStr(sym *symbol) { + g.wrapper.Printf("def __str__(self):\n") + g.wrapper.Indent() + g.wrapper.Printf("cret = _cffi_helper.lib.cgo_func_%[1]s_str(self.cgopy)\n", sym.id) + g.wrapper.Printf("ret = _cffi_helper.cffi_cgopy_cnv_c2py_string(cret)\n") + g.wrapper.Printf("return ret\n") + g.wrapper.Outdent() + g.wrapper.Printf("\n") +} diff --git a/main_test.go b/main_test.go index c1fb946..ea789c8 100644 --- a/main_test.go +++ b/main_test.go @@ -499,6 +499,72 @@ s = named.Slice(range(10)) s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s = named.Slice(xrange(10)) s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +`), + }) + + testPkgWithCFFI(t, pkg{ + path: "_examples/named", + want: []byte(`doc(named): 'package named tests various aspects of named types.\n' +doc(named.Float): '' +doc(named.Float.Value): 'Value() float\n\nValue returns a float32 value\n' +v = named.Float() +v = 0 +v.Value() = 0.0 +x = named.X() +x = 0 +x.Value() = 0.0 +x = named.XX() +x = 0 +x.Value() = 0.0 +x = named.XXX() +x = 0 +x.Value() = 0.0 +x = named.XXXX() +x = 0 +x.Value() = 0.0 +v = named.Float(42) +v = 42 +v.Value() = 42.0 +v = named.Float(42.0) +v = 42 +v.Value() = 42.0 +x = named.X(42) +x = 42 +x.Value() = 42.0 +x = named.XX(42) +x = 42 +x.Value() = 42.0 +x = named.XXX(42) +x = 42 +x.Value() = 42.0 +x = named.XXXX(42) +x = 42 +x.Value() = 42.0 +x = named.XXXX(42.0) +x = 42 +x.Value() = 42.0 +s = named.Str() +s = "" +s.Value() = '' +s = named.Str('string') +s = "string" +s.Value() = 'string' +arr = named.Array() +arr = named.Array{0, 0} +arr = named.Array([1,2]) +arr = named.Array{1, 2} +arr = named.Array(range(10)) +caught: Array.__init__ takes a sequence of size at most 2 +arr = named.Array(xrange(2)) +arr = named.Array{0, 1} +s = named.Slice() +s = named.Slice(nil) +s = named.Slice([1,2]) +s = named.Slice{1, 2} +s = named.Slice(range(10)) +s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +s = named.Slice(xrange(10)) +s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} `), }) } @@ -663,6 +729,28 @@ s += [1,2] s = seqs.Slice{1, 2} s += [10,20] s = seqs.Slice{1, 2, 10, 20} +`), + }) + + testPkgWithCFFI(t, pkg{ + path: "_examples/seqs", + want: []byte(`doc(seqs): 'package seqs tests various aspects of sequence types.\n' +arr = seqs.Array(xrange(2)) +arr = seqs.Array{0, 1, 0, 0, 0, 0, 0, 0, 0, 0} +s = seqs.Slice() +s = seqs.Slice(nil) +s = seqs.Slice([1,2]) +s = seqs.Slice{1, 2} +s = seqs.Slice(range(10)) +s = seqs.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +s = seqs.Slice(xrange(10)) +s = seqs.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +s = seqs.Slice() +s = seqs.Slice(nil) +s += [1,2] +s = seqs.Slice{1, 2} +s += [10,20] +s = seqs.Slice{1, 2, 10, 20} `), }) }