| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- // Copyright 2024 The tcl9.0-go 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 tcl9.0 is an idiomatic Go wrapper for [libtcl9.0].
- //
- // [libtcl9.0]: https://pkg.go.dev/modernc.org/libtcl9.0
- package tcl9_0 // import "modernc.org/tcl9.0"
- import (
- "fmt"
- "os"
- "path/filepath"
- "sync"
- "sync/atomic"
- "unsafe"
- "github.com/evilsocket/islazy/zip"
- "modernc.org/libc"
- lib "modernc.org/libtcl9.0"
- tcllib "modernc.org/libtcl9.0/library"
- )
- var (
- token atomic.Uintptr
- objectMu sync.Mutex
- objects = map[uintptr]interface{}{}
- )
- func addObject(o interface{}) uintptr {
- t := token.Add(1)
- objectMu.Lock()
- objects[t] = o
- objectMu.Unlock()
- return t
- }
- func getObject(t uintptr) interface{} {
- objectMu.Lock()
- o := objects[t]
- if o == nil {
- panic(todo("%#x", t))
- }
- objectMu.Unlock()
- return o
- }
- func removeObject(t uintptr) {
- objectMu.Lock()
- if _, ok := objects[t]; !ok {
- panic(todo("%#x"))
- }
- delete(objects, t)
- objectMu.Unlock()
- }
- // EvalFlag is a type used to parameterize Interp.Eval().
- type EvalFlag int32
- const (
- // If this flag bit is set, the script is not compiled to bytecodes; instead it
- // is executed directly as is done by Tcl_EvalEx. The TCL_EVAL_DIRECT flag is
- // useful in situations where the contents of a value are going to change
- // immediately, so the bytecodes will not be reused in a future execution. In
- // this case, it is faster to execute the script directly.
- EvalDirect = lib.TCL_EVAL_DIRECT
- // If this flag is set, the script is evaluated in the global namespace instead
- // of the current namespace and its variable context consists of global
- // variables only (it ignores any Tcl procedures that are active).
- EvalGlobal = lib.TCL_EVAL_GLOBAL
- )
- var (
- onceStdlib sync.Once
- onceStdlibErr error
- stdlib string
- )
- // Stdlib returns the path to the Tcl standard library or an error, if any. It
- // once creates a temporary directory where the standard library is written.
- // Subsequent calls to Stdlib share the same temporary directory.
- //
- // Stdlib is safe for concurrent access by multiple goroutines.
- func Stdlib() (string, error) {
- onceStdlib.Do(func() {
- dir, err := os.MkdirTemp("", "tcl-library-")
- defer func() { onceStdlibErr = err }()
- if err != nil {
- return
- }
- fn := filepath.Join(dir, "library.zip")
- if err = os.WriteFile(fn, []byte(tcllib.Zip), 0600); err != nil {
- return
- }
- if _, err = zip.Unzip(fn, dir); err != nil {
- return
- }
- stdlib = filepath.Join(dir, "library")
- })
- return stdlib, onceStdlibErr
- }
- // MustStdlib is like Stdlib but panics on error.
- func MustStdlib() (r string) {
- r, err := Stdlib()
- if err != nil {
- panic(err)
- }
- return r
- }
- // Interp represents a Tcl interpreter instance.
- type Interp struct {
- tls *libc.TLS
- interp uintptr
- }
- // NewInterp returns a newly created Interp or an error, if any.
- //
- // The 'tclvars' argument is used to set any interpreter variables before
- // initializing it.
- func NewInterp(tclvars map[string]string) (r *Interp, err error) {
- tls := libc.NewTLS()
- interp := lib.XTcl_CreateInterp(tls)
- if interp == 0 {
- tls.Close()
- return nil, fmt.Errorf("failed to create a Tcl interpreter")
- }
- r = &Interp{tls, interp}
- for k, v := range tclvars {
- cmd := fmt.Sprintf(`set {%s} {%s};`, k, v)
- if _, err := r.Eval(fmt.Sprintf(cmd), EvalGlobal); err != nil {
- r.Close()
- return nil, fmt.Errorf("eval failed: %s", cmd)
- }
- }
- if rc := lib.XTcl_Init(tls, interp); rc != lib.TCL_OK {
- r.Close()
- return nil, fmt.Errorf("failed to initialize the Tcl interpreter")
- }
- return r, nil
- }
- // TLS returns the thread local storage of the underlying interpreter. It is
- // used when calling libtcl directly
- func (in *Interp) TLS() *libc.TLS { return in.tls }
- // Handle returns the handle of the underlying interpreter. It is used when
- // calling libtcl directly:
- func (in *Interp) Handle() uintptr { return in.interp }
- // Close invalidates the interpreter and releases all its associated resources.
- func (in *Interp) Close() error {
- lib.XTcl_DeleteInterp(in.tls, in.interp)
- in.tls.Close()
- *in = Interp{}
- return nil
- }
- // Eval evaluates script and returns the interpreter's result and an error, if any.
- func (in *Interp) Eval(script string, flag EvalFlag) (r string, err error) {
- s, err := libc.CString(script)
- if err != nil {
- return "", err
- }
- defer libc.Xfree(in.tls, s)
- lib.XTcl_Preserve(in.tls, in.interp)
- defer lib.XTcl_Release(in.tls, in.interp)
- if rc := lib.XTcl_EvalEx(in.tls, in.interp, s, lib.TTcl_Size(len(script)), int32(flag)); rc != lib.TCL_OK {
- err = fmt.Errorf("return code: %d", rc)
- }
- return in.StringResult(), err
- }
- // StringResult returns the interpreter result as a string.
- func (in *Interp) StringResult() (r string) {
- return libc.GoString(lib.XTcl_GetString(in.tls, lib.XTcl_GetObjResult(in.tls, in.interp)))
- }
- // SetResult sets the result of the interpreter.
- func (in *Interp) SetResult(s string) error {
- cs, err := libc.CString(s)
- if err != nil {
- return err
- }
- defer libc.Xfree(in.tls, cs)
- lib.XTcl_SetObjResult(in.tls, in.interp, lib.XTcl_NewStringObj(in.tls, cs, lib.TTcl_Size(len(s))))
- return nil
- }
- // CmdProc is a Tcl command implemented in Go.
- type CmdProc func(clientData interface{}, in *Interp, args []string) int
- // DeleteProc is a function called when CmdProc is deleted.
- type DeleteProc func(clientData interface{})
- type cmdProc struct {
- clientData interface{}
- del DeleteProc
- f CmdProc
- in *Interp
- }
- func fp(f interface{}) uintptr {
- type iface [2]uintptr
- return (*iface)(unsafe.Pointer(&f))[1]
- }
- var (
- runCmdP = fp(runCmd)
- delCmdP = fp(delCmd)
- )
- func runCmd(tls *libc.TLS, clientData, in uintptr, argc int32, argv uintptr) int32 {
- cmd := getObject(clientData).(*cmdProc)
- var a []string
- for i := int32(0); i < argc; i++ {
- p := *(*uintptr)(unsafe.Pointer(argv))
- argv += unsafe.Sizeof(argv)
- a = append(a, libc.GoString(p))
- }
- return int32(cmd.f(cmd.clientData, cmd.in, a))
- }
- func delCmd(tls *libc.TLS, clientData uintptr) {
- cmd := getObject(clientData).(*cmdProc)
- if cmd.del != nil {
- cmd.del(cmd.clientData)
- }
- removeObject(clientData)
- }
- // RegisterCommand creates a new Tcl command.
- func (in *Interp) RegisterCommand(name string, proc CmdProc, clientData interface{}, del DeleteProc) (err error) {
- nm, err := libc.CString(name)
- if err != nil {
- return err
- }
- lib.XTcl_Preserve(in.tls, in.interp)
- defer func() {
- libc.Xfree(in.tls, nm)
- lib.XTcl_Release(in.tls, in.interp)
- }()
- p := &cmdProc{f: proc, clientData: clientData, del: del, in: in}
- h := addObject(p)
- cmd := lib.XTcl_CreateCommand(in.tls, in.interp, nm, runCmdP, h, delCmdP)
- if cmd == 0 {
- return fmt.Errorf("failed to create command: %s", name)
- }
- return nil
- }
|