Interactive Go interpreter and debugger with REPL, Eval, generics and Lisp-like macros

gomacro - interactive Go interpreter and debugger with generics and macros

gomacro is an almost complete Go interpreter, implemented in pure Go. It offers both an interactive REPL and a scripting mode, and does not require a Go toolchain at runtime (except in one very specific case: import of a 3rd party package at runtime).

It has two dependencies beyond the Go standard library: github.com/peterh/liner and golang.org/x/tools/go/packages

Gomacro can be used as:

  • a standalone executable with interactive Go REPL, line editing and code completion: just run gomacro from your command line, then type Go code. Example:

    $ gomacro
    [greeting message...]
    
    gomacro> import "fmt"
    gomacro> fmt.Println("hello, world!")
    hello, world!
    14      // int
    <nil>   // error
    gomacro>
    

    press TAB to autocomplete a word, and press it again to cycle on possible completions.

    Line editing follows mostly Emacs: Ctrl+A or Home jumps to start of line, Ctrl+E or End jumps to end of line, Ald+D deletes word starting at cursor... For the full list of key bindings, see https://github.com/peterh/liner

  • a tool to experiment with Go generics: see Generics

  • a Go source code debugger: see Debugger

  • an interactive tool to make science more productive and more fun. If you use compiled Go with scientific libraries (physics, bioinformatics, statistics...) you can import the same libraries from gomacro REPL (immediate on Linux and Mac OS X, requires restarting on other platforms, see Importing packages below), call them interactively, inspect the results, feed them to other functions/libraries, all in a single session. The imported libraries will be compiled, not interpreted, so they will be as fast as in compiled Go.

    For a graphical user interface on top of gomacro, see Gophernotes. It is a Go kernel for Jupyter notebooks and nteract, and uses gomacro for Go code evaluation.

  • a library that adds Eval() and scripting capabilities to your Go programs in few lines of code:

     package main
     import (
     	"fmt"
     	"reflect"
     	"github.com/cosmos72/gomacro/fast"
     )
     func RunGomacro(toeval string) reflect.Value {
     	interp := fast.New()
     	vals, _ := interp.Eval(toeval)
     	// for simplicity, only use the first returned value
     	return vals[0].ReflectValue()
     }
     func main() {
     	fmt.Println(RunGomacro("1+1"))
     }

    Also, github issue #13 explains how to have your application's functions, variable, constants and types available in the interpreter.

    Note: gomacro license is MPL 2.0, which imposes some restrictions on programs that use gomacro. See MPL 2.0 FAQ for common questions regarding the license terms and conditions.

  • a way to execute Go source code on-the-fly without a Go compiler: you can either run gomacro FILENAME.go (works on every supported platform)

    or you can insert a line #!/usr/bin/env gomacro at the beginning of a Go source file, then mark the file as executable with chmod +x FILENAME.go and finally execute it with ./FILENAME.go (works only on Unix-like systems: Linux, *BSD, Mac OS X ...)

  • a Go code generation tool: gomacro was started as an experiment to add Lisp-like macros to Go, and they are extremely useful (in the author's opinion) to simplify code generation. Macros are normal Go functions, they are special only in one aspect: they are executed before compiling code, and their input and output is code (abstract syntax trees, in the form of go/ast.Node)

    Don't confuse them with C preprocessor macros: in Lisp, Scheme and now in Go, macros are regular functions written in the same programming language as the rest of the source code. They can perform arbitrary computations and call any other function or library: they can even read and write files, open network connections, etc... as a normal Go function can do.

    See doc/code_generation.pdf for an introduction to the topic.

Installation

Prerequites

Supported platforms

Gomacro is pure Go, and in theory it should work on any platform supported by the Go compiler. The following combinations are tested and known to work:

  • Linux: amd64, 386, arm64, arm, mips, ppc64le
  • Mac OS X: amd64, 386 (386 binaries running on amd64 system)
  • Windows: amd64, 386
  • FreeBSD: amd64, 386
  • Android: arm64, arm (tested with Termux and the Go compiler distributed with it)

How to install

The command

go get -u github.com/cosmos72/gomacro

downloads, compiles and installs gomacro and its dependencies

Current Status

Almost complete.

The main limitations and missing features are:

  • importing 3rd party libraries at runtime currently only works on Linux and Mac OS X. On other systems as Windows, Android and *BSD it is cumbersome and requires recompiling - see Importing packages.
  • conversions from/to unsafe.Pointer are not supported
  • some corner cases using interpreted interfaces, as interface -> interface type assertions and type switches, are not implemented yet.
  • some corner cases using recursive types may not work correctly.
  • goto can only jump backward, not forward
  • out-of-order code is under testing - some corner cases, as for example out-of-order declarations used in keys of composite literals, are not supported. Clearly, at REPL code is still executed as soon as possible, so it makes a difference mostly if you separate multiple declarations with ; on a single line. Example: var a = b; var b = 42
    Support for "batch mode" is in progress - it reads as much source code as possible before executing it, and it's useful mostly to execute whole files or directories.

The documentation also contains the full list of features and limitations

Extensions

Compared to compiled Go, gomacro supports several extensions:

  • generics (experimental) - see Generics

  • an integrated debugger, see Debugger

  • configurable special commands. Type :help at REPL to list them, and see cmd.go:37 for the documentation and API to define new ones.

  • untyped constants can be manipulated directly at REPL. Examples:

    gomacro> 1<<100
    {int 1267650600228229401496703205376}	// untyped.Lit
    gomacro> const c = 1<<100; c * c / 100000000000
    {int 16069380442589902755419620923411626025222029937827}	// untyped.Lit
    

    This provides a handy arbitrary-precision calculator.

    Note: operations on large untyped integer constants are always exact, while operations on large untyped float constants are implemented with go/constant.Value, and are exact as long as both numerator and denominator are <= 5e1232.

    Beyond that, go/constant.Value switches from *big.Rat to *big.Float with precision = 512, which can accumulate rounding errors.

    If you need exact results, convert the untyped float constant to *big.Rat (see next item) before exceeding 5e1232.

  • untyped constants can be converted implicitly to *big.Int, *big.Rat and *big.Float. Examples:

    import "math/big"
    var i *big.Int = 1<<1000                 // exact - would overflow int
    var r *big.Rat = 1.000000000000000000001 // exact - different from 1.0
    var s *big.Rat = 5e1232                  // exact - would overflow float64
    var t *big.Rat = 1e1234                  // approximate, exceeds 5e1232
    var f *big.Float = 1e646456992           // largest untyped float constant that is different from +Inf

    Note: every time such a conversion is evaluated, it creates a new value - no risk to modify the constant.

    Be aware that converting a huge value to string, as typing f at REPL would do, can be very slow.

  • zero value constructors: for any type T, the expression T() returns the zero value of the type

  • macros, quoting and quasiquoting: see doc/code_generation.pdf

and slightly relaxed checks:

  • unused variables and unused return values never cause errors

Examples

Some short, notable examples - to run them on non-Linux platforms, see Importing packages first.

plot mathematical functions

  • install libraries: go get gonum.org/v1/plot gonum.org/v1/plot/plotter gonum.org/v1/plot/vg
  • start the interpreter: gomacro
  • at interpreter prompt, paste the whole Go code listed at https://github.com/gonum/plot/wiki/Example-plots#functions (the source code starts after the picture under the section "Functions", and ends just before the section "Histograms")
  • still at interpreter prompt, enter main() If all goes well, it will create a file named "functions.png" in current directory containing the plotted functions.

simple mandelbrot web server

  • install libraries: go get github.com/sverrirab/mandelbrot-go
  • chdir to mandelbrot-go source folder: cd; cd go/src/github.com/sverrirab/mandelbrot-go
  • start interpreter with arguments: gomacro -i mbrot.go
  • at interpreter prompt, enter init(); main()
  • visit http://localhost:8090/ Be patient, rendering and zooming mandelbrot set with an interpreter is a little slow.

Further examples are listed by Gophernotes

Importing packages

Gomacro supports the standard Go syntax import, including package renaming. Examples:

import "fmt"
import (
    "io"
    "net/http"
    r "reflect"
)

Third party packages - i.e. packages not in Go standard library - can also be imported with the same syntax, as long as the package is already installed.

To install a package, follow its installation procedure: quite often it is the command go get PACKAGE-PATH

The next steps depend on the system you are running gomacro on:

Linux and Mac OS X

If you are running gomacro on Linux or Mac OS X, import will then just work: it will automatically download, compile and import a package. Example:

$ gomacro
[greeting message...]

gomacro> import "gonum.org/v1/plot"
// debug: looking for package "gonum.org/v1/plot" ...
// debug: compiling "/home/max/go/src/gomacro.imports/gonum.org/v1/plot/plot.go" ...
go: finding module for package gonum.org/v1/plot/vg/draw
go: finding module for package gonum.org/v1/plot
go: found gonum.org/v1/plot in gonum.org/v1/plot v0.0.0-20200226011204-b25252b0d522
gomacro> plot.New()
&{...} // *plot.Plot
<nil>  // error

Note: internally, gomacro will compile and load a Go plugin containing the package's exported declarations. Go plugins are currently supported only on Linux and Mac OS X.

WARNING On Mac OS X, never execute strip gomacro: it breaks plugin support, and loading third party packages stops working.

Other systems

On all other systems as Windows, Android and *BSD you can still use import, but there are more steps: you need to manually download the package, and you also need to recompile gomacro after the import (it will tell you). Example:

$ go get gonum.org/v1/plot
$ gomacro
[greeting message...]

gomacro> import "gonum.org/v1/plot"
// warning: created file "/home/max/go/src/github.com/cosmos72/gomacro/imports/thirdparty/gonum_org_v1_plot.go", recompile gomacro to use it

Now quit gomacro, recompile and reinstall it:

gomacro> :quit
$ go install github.com/cosmos72/gomacro

Finally restart it. Your import is now linked inside gomacro and will work:

$ gomacro
[greeting message...]

gomacro> import "gonum.org/v1/plot"
gomacro> plot.New()
&{...} // *plot.Plot
<nil>  // error

Note: if you need several packages, you can first import all of them, then quit and recompile gomacro only once.

Generics

gomacro contains two alternative, experimental versions of Go generics:

  • the first version is modeled after C++ templates, and is appropriately named "C++ style"
    See doc/generics-c++.md for how to enable and use them.

  • the second version is named "contracts are interfaces" - or more briefly "CTI". It is modeled after several published proposals for Go generics, most notably Ian Lance Taylor's Type Parameters in Go It has some additions inspired from Haskell generics and original contributions from the author - in particular to create a simpler alternative to Go 2 contracts

    For their design document and reasoning behind some of the design choices, see doc/generics-cti.md

The second version of generics "CTI" is enabled by default in gomacro.

They are in beta status, and at the moment only generic types and functions are supported. Syntax and examples:

// declare a generic type with two type arguments T and U
type Pair#[T,U] struct {
	First T
	Second U
}

// instantiate the generic type using explicit types for T and U,
// and create a variable of such type.
var pair Pair#[complex64, struct{}]

// equivalent:
pair := Pair#[complex64, struct{}] {}

// a more complex example, showing higher-order functions
func Transform#[T,U](slice []T, trans func(T) U) []U {
	ret := make([]U, len(slice))
	for i := range slice {
		ret[i] = trans(slice[i])
	}
	return ret
}
Transform#[string,int] // returns func([]string, func(string) int) []int

// returns []int{3, 2, 1} i.e. the len() of each string in input slice:
Transform#[string,int]([]string{"abc","xy","z"}, func(s string) int { return len(s) })

Contracts specify the available methods of a generic type. For simplicity, they do not introduce a new syntax or new language concepts: contracts are just (generic) interfaces. With a tiny addition, actually: the ability to optionally indicate the receiver type.

For example, the contract specifying that values of type T can be compared with each other to determine if the first is less, equal or greater than the second is:

type Comparable#[T] interface {
	// returns -1 if a is less than b
	// returns  0 if a is equal to b
	// returns  1 if a is greater than b
	func (a T) Cmp(b T) int
}

A type T implements Comparable#[T] if it has a method func (T) Cmp(T) int. This interface is carefully chosen to match the existing methods of *math/big.Float, *math/big.Int and *math/big.Rat. In other words, *math/big.Float, *math/big.Int and *math/big.Rat already implement it.

What about basic types as int8, int16, int32, uint... float*, complex* ... ? Gomacro extends them, automatically adding many methods equivalent to the ones declared on *math/big.Int to perform arithmetic and comparison, including Cmp which is internally defined as (no need to define it yourself):

func (a int) Cmp(b int) int {
	if a < b {
		return -1
	} else if a > b {
		return 1
	} else {
		return 0
	}
}

Thus the generic functions Min and Max can be written as

func Min#[T: Comparable] (a, b T) T {
	if a.Cmp(b) < 0 { // also <= would work
		return a
	}
	return b
}
func Max#[T: Comparable] (a, b T) T {
	if a.Cmp(b) > 0 { // also >= would work
		return a
	}
	return b
}

Where the syntax #[T: Comparable] or equivalently #[T: Comparable#[T]] indicates that T must satisfy the contract (implement the interface) Comparable#[T]

Such functions Min and Max will then work automatically for every type T that satisfies the contract (implements the interface) Comparable#[T]:
all basic integers and floats, plus *math/big.Float, *math/big.Int and *math/big.Rat, plus every user-defined type T that has a method func (T) Cmp(T) int

If you do not specify the contract(s) that a type must satisfy, generic functions cannot access the fields and methods of a such type, which is then treated as a "black box", similarly to interface{}.

Two values of type T can be added if T has an appropriate method. But which name and signature should we choose to add values? Copying again from math/big, the method we choose is func (T) Add(T,T) T If receiver is a pointer, it will be set to the result - in any case, the result will also be returned. Similarly to Comparable, the contract Addable is then

type Addable#[T] interface {
	// Add two values a, b and return the result.
	// If recv is a pointer, it must be non-nil
	// and it will be set to the result
	func (recv T) Add(a, b T) T
}

With such a contract, a generic function Sum is quite straightforward:

func Sum#[T: Addable] (args ...T) T {
	// to create the zero value of T,
	// one can write 'var sum T' or equivalently 'sum := T()'
	// Unluckily, that's not enough for math/big numbers, which require
	// the receiver of method calls to be created with a function `New()`
	// Once math/big numbers have such method, the following
	// will be fully general - currently it works only on basic types.
	sum := T().New()

	for _, elem := range args {
		// use the method T.Add(T, T)
		//
		// as an optimization, relevant at least for math/big numbers,
		// also use sum as the receiver where result of Add will be stored
		// if the method Add has pointer receiver.
		//
		// To cover the case where method Add has instead value receiver,
		// also assign the returned value to sum
		sum = sum.Add(sum, elem)
	}
	return sum
}
Sum#[int]         // returns func(...int) int
Sum#[int] (1,2,3) // returns int(6)

Sum#[complex64]                 // returns func(...complex64) complex64
Sum#[complex64] (1.1+2.2i, 3.3) // returns complex64(4.4+2.2i)

Sum#[string]                         // returns func(...string) string
Sum#[string]("abc.","def.","xy","z") // returns "abc.def.xyz"

Partial and full specialization of generics is not supported in CTI generics, both for simplicity and to avoid accidentally providing Turing completeness at compile-time.

Instantiation of generic types and functions is on-demand.

Current limitations:

  • type inference on generic arguments #[...] is not yet implemented, thus generic arguments #[...] must be explicit.
  • generic methods are not yet implemented.
  • types are not checked to actually satisfy contracts.

Debugger

Since version 2.6, gomacro also has an integrated debugger. There are three ways to enter it:

  • hit CTRL+C while interpreted code is running.
  • type :debug STATEMENT-OR-FUNCTION-CALL at the prompt.
  • add a statement (an expression is not enough) "break" or _ = "break" to your code, then execute it normally.

In all cases, execution will be suspended and you will get a debug> prompt, which accepts the following commands:
step, next, finish, continue, env [NAME], inspect EXPR, list, print EXPR-OR-STATEMENT

Also,

  • commands can be abbreviated.
  • print fully supports expressions or statements with side effects, including function calls and modifying local variables.
  • env without arguments prints all global and local variables.
  • an empty command (i.e. just pressing enter) repeats the last command.

Only interpreted statements can be debugged: expressions and compiled code will be executed, but you cannot step into them.

The debugger is quite new, and may have some minor glitches.

Why it was created

First of all, to experiment with Go :)

Second, to simplify Go code generation tools (keep reading for the gory details)


Problem: "go generate" and many other Go tools automatically create Go source code from some kind of description - usually an interface specifications as WSDL, XSD, JSON...

Such specification may be written in Go, for example when creating JSON marshallers/unmarshallers from Go structs, or in some other language, for example when creating Go structs from JSON sample data.

In both cases, a variety of external programs are needed to generate Go source code: such programs need to be installed separately from the code being generated and compiled.

Also, Go is currently lacking generics (read: C++-like templates) because of the rationale "we do not yet know how to do them right, and once you do them wrong everybody is stuck with them"

The purpose of Lisp-like macros is to execute arbitrary code while compiling, in particular to generate source code.

This makes them very well suited (although arguably a bit low level) for both purposes: code generation and C++-like templates, which are a special case of code generation - for a demonstration of how to implement C++-like templates on top of Lisp-like macros, see for example the project https://github.com/cosmos72/cl-parametric-types from the same author.

Building a Go interpreter that supports Lisp-like macros, allows to embed all these code-generation activities into regular Go source code, without the need for external programs (except for the interpreter itself).

As a free bonus, we get support for Eval()

LEGAL

Gomacro is distributed under the terms of Mozilla Public License 2.0 or any later version.

Owner
Massimiliano Ghilardi
Massimiliano Ghilardi
Comments
  • Allow to import from Go modules

    Allow to import from Go modules

    It would be nice to be able to import packages from Go module in current directory. My use case is launching gomacro from project directory to interactively test functions.

    What do you think?

  • go:generate gomacro -genimport

    go:generate gomacro -genimport

    Having just been bitten by stale dependencies, I wonder if gomacro could add a flag to do this part of package import:

    import _i "import/path/for/your/application"

    under command from a go generate run. In other words, if there was a flag (call it -genimport for discussion) to the gomacro binary that caused import _i output to be generated for the current working directory, then a

    go:generate gomacro -genimport

    line could be added to a file in the package we wish to keep up to date. Upon every build I always run go generate to keep the other parts up to date, and this would result in the x_package.go file also being kept up to date at the same time.

    Would that make sense? I didn't see such a flag already but maybe there is already a way? Thanks for considering it.

  • Cannot use Pointer Struct

    Cannot use Pointer Struct

    The following code produces problem err-> reflect.Set: value of type *req.Response is not assignable to type req.Response

    problem code

    package main
    
    import (
    	`strings`
    
    	"github.com/cosmos72/gomacro/fast"
    
    	_ "my-project/core/gomacro-package-init" // inject `github.com/imroc/req/v3` package, becase code long ,so i do no't out code
    )
    
    const source = `
    
    import (
    	"fmt"
    	"github.com/imroc/req/v3"
    )
    
    func getData() string {
        client := req.C()
        // send http url request 
        response, err := client.R().Get("https://api.github.com/users/cosmos72")
        // get response data
        fmt.Println(len(response.Bytes()), err)
        return "finish"
    }
    
    `
    
    func main() {
    	src := strings.NewReader(source)
    
    	interp := fast.New()
    	if _, err := interp.EvalReader(src); err != nil {
    		panic(err)
    	}
    	interp.ChangePackage("main", "main")
    	interp.Eval1("getData()")
    }
    
    
    
    

    golang version: go version go1.17.5 linux/amd64 system: ubuntu 21.10

    so, I'd like to know where is the problem.

  • Help choosing new gomacro license?

    Help choosing new gomacro license?

    As Gomacro author, I recently discovered that the current LGPL license is more restrictive than I want. Thanks to @glycerine for pointing it out.

    LGPL 3.0, section 4. Combined Works imposes the following when someone wants to distribute a commercial program that includes a (modified or unmodified) copy of LGPL code:


    1. [you must] Do one of the following:

      1. Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.

      2. Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.


    Unluckily, dynamic linking in Go is cumbersome and extremely limited - the plugin module is more similar to dlopen() than to dynamic linking, and currently only works on Linux and Go 1.8+ (support on Mac OS X and Go 1.10 is still alpha-quality in my opinion, and other platforms don't have it at all). In my opinion, dynamic linking is not a viable solution for Go code.

    LGPL allows static linking (the norm for Go) of LGPL code with commercial code, but then the commercial code must be also provided as object files that can be relinked with a newer version of LGPL code. Again, this is very cumbersome. I have no wish to impose such a restriction on commercial programs that use gomacro.

    I am looking for a license that

    1. allows binary-only distribution of statically linked programs that include gomacro
    2. requires the distribution of such programs to include a copy of the gomacro source code they used, including any modifications they made, under their original license
    3. does not require releasing the source code for the rest of such programs.

    Suggestions welcome :)

  • Gomacro and local modules

    Gomacro and local modules

    I've been attempting to generate code in a module that reflects on itself. I don't know if I'm just going about it wrong but I suspect there is a bug so I created a repro repo: https://github.com/au-phiware/gomacro-issue-82. When running gomacro -w it appears to fall over on the import statement for the module in the working directory (with error loading package). I noticed that golang.org/x/tools/go/packages is being used and thought that there should be no reason for not finding it. So I took a closer look and tried removing the explicit Dir Config field; this allowed the package to be found, only to be not found again at a later stage, when the genimport plugin is built. This lead me to jump into the residual directory created by genimport (i.e. $GOPATH/src/gomacro.imports/...) and start hacking at the go.mod file. I stripped it back to just the module directive and added a replace directive:

    module gomacro.imports/github.com/au-phiware/gomacro-issue-82
    
    replace github.com/au-phiware/gomacro-issue-82 => /home/corin/src/github.com/au-phiware/gomacro-issue-82
    

    This now built successfully with GO111MODULE=on go build -buildmode=plugin.

    In conclusion it seems that it would be prudent for genimport to add that replace directive.

    Another thing I've noticed is that in more complex modules that use replace directives in their own go.mod then I see strange errors about missing types and such (although this may be caused by the working directory having being set to /).

  • Build fails on aarch64 with Go 1.14 beta1

    Build fails on aarch64 with Go 1.14 beta1

    Fedora Rawhide on aarch64 with Go 1.14 beta1

    # github.com/cosmos72/gomacro/imports/syscall
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1724:31: undefined: syscall.SYS_EPOLL_CREATE
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1728:29: undefined: syscall.SYS_EPOLL_WAIT
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1758:28: undefined: syscall.SYS_FUTIMESAT
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1769:26: undefined: syscall.SYS_GETPGRP
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1803:25: undefined: syscall.SYS_LCHOWN
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1846:24: undefined: syscall.SYS_PAUSE
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1951:23: undefined: syscall.SYS_TIME
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1968:24: undefined: syscall.SYS_USTAT
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1969:24: undefined: syscall.SYS_UTIME
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1971:25: undefined: syscall.SYS_UTIMES
    _build/src/github.com/cosmos72/gomacro/imports/syscall/syscall_linux_arm64.go:1971:25: too many errors
    

    Probably due to this change https://github.com/golang/go/commit/cedb561617ea40c72dc018b38c4d5f0862595fe8

  • readsig.go:66: unknown SigElemType: '9'

    readsig.go:66: unknown SigElemType: '9'

    I tried to :env inspect a large package (not publicly available) and got this error, readsig.go:66: unknown SigElemType: '9', followed by gomacro stopping.

    $ gomacro
    gomacro
    // GOMACRO, an interactive Go interpreter with generics and macros
    // Copyright (C) 2017-2018 Massimiliano Ghilardi <https://github.com/cosmos72/gomacro>
    // License MPL v2.0+: Mozilla Public License version 2.0 or later <http://mozilla.org/MPL/2.0/>
    // This is free software with ABSOLUTELY NO WARRANTY.
    //
    // Type :help for help
    gomacro> :package "github.com/my/private/pkg"
    ...
    gomacro> :env
    :env
    // ----- builtin binds -----
    Eval            = {0x47df830 ...
    ...
    // ----- "github.com/my/private/pkg" binds -----
    ...
    readsig.go:66: unknown SigElemType: '9'
    $
    
  • Does gomacro have JIT support?

    Does gomacro have JIT support?

    Hi and thanks for your awesome compiler. It's extremely helpful and helps me learn Go a lot faster and better. I was wondering though if Gomacro has a JIT compiler for interpreted code like when you run a file. I'd settle for a way to compile slow functions. I know there's a JIT sub-package, but it seems to be something else. Thanks a lot!

  • strings.Builder type not recognized by gomacro

    strings.Builder type not recognized by gomacro

    The strings.Builder type is apparently not being properly recognized by gomacro.

    The example code from the go documentation: https://play.golang.org/p/Dv96oDHG0tF

    Does not correctly run in gomacro:

    $ gomacro
    // GOMACRO, an interactive Go interpreter with generics and macros
    // Copyright (C) 2017-2018 Massimiliano Ghilardi <https://github.com/cosmos72/gomacro>
    // License MPL v2.0+: Mozilla Public License version 2.0 or later <http://mozilla.org/MPL/2.0/>
    // This is free software with ABSOLUTELY NO WARRANTY.
    //
    // Type :help for help
    gomacro> package main
    // debug: package main has no effect. To switch to a different package, use package "PACKAGE/FULL/PATH" - note the quotes
    gomacro> 
    gomacro> import (
    . . . .    "fmt"
    . . . .    "strings"
    . . . .    )
    gomacro> 
    gomacro> func main() {
    . . . .    var b strings.Builder
    . . . .    for i := 3; i >= 1; i-- {
    . . . .      fmt.Fprintf(&b, "%d...", i)
    . . . .      }
    . . . .    b.WriteString("ignition")
    . . . .    fmt.Println(b.String())
    . . . .    
    . . . .    }
    repl.go:2:7: not a type: strings.Builder <*ast.SelectorExpr>
    gomacro> 
    
  • reflect error working with go-redis/redis client

    reflect error working with go-redis/redis client

    Repro below assumes a local Redis server running on default port

    // GOMACRO, an interactive Go interpreter with generics and macros
    // Copyright (C) 2017-2018 Massimiliano Ghilardi <https://github.com/cosmos72/gomacro>
    // License MPL v2.0+: Mozilla Public License version 2.0 or later <http://mozilla.org/MPL/2.0/>
    // This is free software with ABSOLUTELY NO WARRANTY.
    //
    // Type :help for help
    
    import "github.com/go-redis/redis"
    // debug: created file "~/go/src/gomacro_imports/github.com/go-redis/redis/redis.go"...
    // debug: compiling "~/go/src/gomacro_imports/github.com/go-redis/redis/redis.go" ...
    client := redis.NewClient(&redis.Options{
    . . . .      Addr:     "localhost:6379",
    . . . .      Password: "", // no password set
    . . . .      DB:       0,  // use default DB
    . . . .      })
    
    client
    // Redis<localhost:6379 db:0>	// *github.com/go-redis/redis.Client
    
    pong, err := client.Ping().Result()
    // reflect: Field index out of range
    
    woot := client.Ping()
    //reflect: Field index out of range
    

    Should this "just work"? I don't even know where to start here...

    I discovered this using GopherNotes, but just the bare CLI REPL shows the same errors.

    Thanks for this great package and any help!

  • Support reloading files and single function definitions.

    Support reloading files and single function definitions.

    First - wow! This is a great solution for Lisp-style interactive programming in Go. I'm very impressed.

    Now I'm hoping to duplicate even more of the experience of programming in Lisp. When running in Emacs, I can reload either all of the definitions in the entire file, or single variable/function definitions. Trying this now with EvalFile in cmd.go results in issues with the package declaration.

    I'm thinking of a workflow like:

    1. Start gomacro (or another REPL that wraps it).
    2. Run my compiled function bound to "mypkg.F" and see an issue
    3. Edit the definition for "mypkg.F" and recompile it, as described above. This updates the imports.Package binding to point to the recompiled definition.
    4. Run "mypkg.F" again, though now with the new definition...

    How feasible is this?

  • Cannot convert struct to interface if it has an embedded struct with a method

    Cannot convert struct to interface if it has an embedded struct with a method

    I upgraded a project from 23a0d19a6b1e to 3d21d22d88e2 and am now having troubles converting a struct to an interface.

    The exact problem stems from converting a struct with an embedded struct, wherein both structs have unique methods, to a typed interface. If only one of the structs has methods, the problem goes away.

    Minimal reproducible code:

    package main
    
    import (
            "github.com/cosmos72/gomacro/fast"
    )
    
    func main() {
            interp := fast.New()
    
            src := `
            type VEntity interface {
                    A() int
                    B() int
            }
    
            type BaseEntity struct {
            }
    
            func (e *BaseEntity) A() int {
                    return 0
            }
    
            type ExtendedEntity struct {
                    BaseEntity
            }
    
            func (e *ExtendedEntity) B() int {
                    return 1
            }
    
            entity := &ExtendedEntity{}
    
            ventity := VEntity(entity) // <-- this line causes panic: runtime error: invalid memory address or nil pointer dereference
            `
    
            interp.Compile(src)
    }
    
    

    Error output:

    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x111c8b9]
    
    goroutine 1 [running]:
    github.com/cosmos72/gomacro/xreflect.Type.AssignableTo(0x2193770?, 0x1?)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/xreflect/api.go:50 +0x19
    github.com/cosmos72/gomacro/fast.(*Comp).converterToEmulatedInterface(0xc0002ee300, 0xc0003dd8b0, 0xc0003dd490)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/interface.go:194 +0x21f
    github.com/cosmos72/gomacro/fast.(*Comp).Converter(0xc0002ee300, 0xc0003dd8b0, 0xc0003dd490)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/convert.go:197 +0x292
    github.com/cosmos72/gomacro/fast.(*Comp).convert(0xc0002ee300, 0xc0004c2500, 0xc0003dd490, {0x19de210?, 0xc0004b0200})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/convert.go:150 +0xb2b
    github.com/cosmos72/gomacro/fast.(*Comp).Convert(0xc0002ee300?, {0x19de210, 0xc0004b0200}, 0xc0003dd8b0?)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/convert.go:31 +0x4b
    github.com/cosmos72/gomacro/fast.(*Comp).CallExpr(0x30?, 0xc0004b4340)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/call.go:69 +0x7d
    github.com/cosmos72/gomacro/fast.(*Comp).expr(0xc0002ee300, {0x19ddf10?, 0xc0004b4340?}, 0x7ff9b19a45b8?)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/expr.go:134 +0x345
    github.com/cosmos72/gomacro/fast.(*Comp).expr1(0xc0002ee300, {0x19ddf10?, 0xc0004b4340?}, 0x87b087?)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/expr.go:101 +0x174
    github.com/cosmos72/gomacro/fast.(*Comp).exprs(0x15e0540?, {0xc0003dd1c0, 0x1, 0x7ff988bc26d0?})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/expr.go:63 +0x99
    github.com/cosmos72/gomacro/fast.(*Comp).ExprsMultipleValues(0x0?, {0xc0003dd1c0?, 0xc0003497f0?, 0x8bfd92?}, 0x8?)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/expr.go:47 +0x38
    github.com/cosmos72/gomacro/fast.(*Comp).prepareDeclConstsOrVars(0xc0003d6c88?, {0xc0003dd9a0, 0x1, 0x1}, {0x0?, 0x0?}, {0xc0003dd1c0?, 0xc0003dd910?, 0xc0003fcac0?})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/declaration.go:228 +0xb8
    github.com/cosmos72/gomacro/fast.(*Comp).DeclVarsShort(0xc0002ee300, {0xc0003dd1a0, 0x1, 0xc0003498f8?}, {0xc0003dd1c0, 0x1, 0x1})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/declaration.go:200 +0x168
    github.com/cosmos72/gomacro/fast.(*Comp).Assign(0xc0002ee300, 0xc0004b4300)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/assignment.go:51 +0x156
    github.com/cosmos72/gomacro/fast.(*Comp).Stmt(0xc0002ee300, {0x19ddd90?, 0xc0004b4300?})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/statement.go:58 +0x825
    github.com/cosmos72/gomacro/fast.(*Comp).compileNode(0xc0002ee300, {0x19db9f8?, 0xc0004b4300?}, 0x8)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/compile.go:468 +0x47a
    github.com/cosmos72/gomacro/fast.(*Comp).compileDecl(0xc0002ee300?, 0xc0004b4400?)
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/compile.go:415 +0x1f0
    github.com/cosmos72/gomacro/fast.(*Comp).Compile(0xc0002ee300, {0x19e2d20, 0xc0003f9d40})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/compile.go:375 +0x4b0
    github.com/cosmos72/gomacro/fast.(*Interp).CompileAst(0xc0003dceb0?, {0x19e2d20?, 0xc0003f9d40?})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/repl.go:95 +0x12b
    github.com/cosmos72/gomacro/fast.(*Interp).Compile(0x0?, {0x17d518a?, 0xc0000061a0?})
            /home/kts/go/pkg/mod/github.com/cosmos72/[email protected]/fast/repl.go:75 +0x35
    main.main()
            /home/kts/Develop/gotest/macro/main.go:36 +0x2a
    exit status 2
    
  • Different compilation behaviour from native go

    Different compilation behaviour from native go

    package main
    
    import (
    	"fmt"
    	"github.com/cosmos72/gomacro/fast"
    )
    
    func main() {
    	// case1() // as expected
    	// case2() // panic: expression returns 1 value, expecting 2: "1"
    	case3() // Different compilation behaviour from native go. Why did the compilation not fail?
    }
    
    func case1() {
    	interp := fast.New()
    	script := `func Execute()(string,error){
    			return "1",nil
    		}`
    	interp.Eval(script)
    
    	vs, _ := interp.Eval("Execute()")
    	fmt.Println(vs[0].Interface()) // "1"
    	fmt.Println(vs[1].Interface()) // nil
    }
    
    func case2() {
    	interp := fast.New()
    	script := `func Execute()(string,error){
    			return "1"
    		}`
    	interp.Eval(script)
    
    	vs, _ := interp.Eval("Execute()")
    	fmt.Println(vs[0].Interface())
    	fmt.Println(vs[1].Interface())
    }
    
    func case3() {
    	interp := fast.New()
    	script := `func Execute()(string,error){
    
    	}`
    	interp.Eval(script)
    
    	vs, _ := interp.Eval("Execute()")
    	fmt.Println(vs[0].Interface()) // ""
    	fmt.Println(vs[1].Interface()) // nil
    }
    

    Is this as expected?

  • can't import fsm package

    can't import fsm package

    I install gomacro from master branch: go install github.com/cosmos72/gomacro@master and then run gomacro cmd:

    gomacro> import "github.com/looplab/fsm"
    gomacro> f := fsm.NewFSM("empty", fsm.Events{}, map[string]fsm.Callback{})
    reflect: Call using map[string]xreflect.Forward as type map[string]fsm.Callback
    

    but got error: reflect: Call using map[string]xreflect.Forward as type map[string]fsm.Callback

    fsm.NewFSM is defined as:

    type Callback func(*Event)
    
    ......
    func NewFSM(initial string, events []EventDesc, callbacks map[string]Callback) *FSM {
    ......
    }
    
  • Fix 3rd Party Imports Cross-Platform

    Fix 3rd Party Imports Cross-Platform

    What information does an external contributor need to fix this specific issue; such that pre-installed Go modules (by this user; before runtime) can be used by the interpreter at runtime programmatically (without requiring the user to recompile each time)?

    We are implementing dynamic template functionality with the use of an interpreter. We are currently using yaegi, which has trouble supporting third party go modules during interpretation without prior reflection. It seems to me that gomacro can use 3rd party libraries programmatically, with a few limitations: recompilation required on platforms with majority-usage share. For more information, read Usage Share of Operating Systems.

    Example

    Let's say that I want to be able to import an already-installed function in generate.go by using:

    import (
    	"fmt"
    
    	"github.com/switchupcb/copygen/cli/generator" // third party module; not reflected
    	"github.com/switchupcb/copygen/cli/models" //  extracted module
    )
    
    func Generate(gen *models.Generator) (string, error) {
    	content := string(gen.Keep) + "\n"
    	fmt.Println(generator.GenerateFunction) // custom type in third party module (a string alias) // or any other object
            ...
    }
    

    When I have already go get github.com/switchupcb/copygen@... prior to running this code.

    Further Inquiry

    Is using gomacro possible in gomacro?

  • Cannot use co-dependent types with each other that implement a common interface

    Cannot use co-dependent types with each other that implement a common interface

    The following code produces cannot use <*main.SceneB> as <main.Scene> in argument to switchTo:

    package main
    
    import (
            "github.com/cosmos72/gomacro/fast"
    )
    
    func main() {
            interp := fast.New()
    
            interp.Eval(`
                    import "fmt"
    
                    type Scene interface {
                            Init()
                            Destroy()
                    }
                    var globalScene Scene
    
                    func switchTo(s Scene) {
                            if globalScene != nil {
                                    globalScene.Destroy()
                            }
                            globalScene = s
                            s.Init()
                    }
    
                    type SceneA struct {
                    }
                    func (s *SceneA) Init() {
                            fmt.Println("Init")
                            switchTo(&SceneB{})
                    }
                    func (s *SceneA) Destroy() {
                            fmt.Println("Destroy")
                    }
    
                    type SceneB struct {
                    }
                    func (s *SceneB) Init() {
                            fmt.Println("Init")
                            switchTo(&SceneA{})
                    }
                    func (s *SceneB) Destroy() {
                            fmt.Println("Destroy")
                    }
    
                    switchTo(&SceneA{})
            `)
    }
    

    In the actual code I'm using, where this problem cropped up, the switchTo function isn't called in the global scope, but rather by an interpreter function that is invoked by the host-side. Same problem in that context.

    This deviates from expected Go behavior, as the evaluated code compiles and runs under standard Go.

Calculadora de macros minimalista escrita em Go.
Calculadora de macros minimalista escrita em Go.

Calculadora de Macros Objetivo Construir uma calculadora de macronutrientes baseada no livro The Max Muscle Plan do Dr Brad Schoenfield visando automa

Feb 6, 2022
Lisp Interpreter

golisp Lisp Interpreter Usage $ golisp < foo.lisp Installation $ go get github.com/mattn/golisp/cmd/golisp Features Call Go functions. Print random in

Dec 15, 2022
Mini lisp interpreter written in Go.

Mini Go Lisp Mini lisp interpreter written in Go. It is implemented with reference to the d-tsuji/SDLisp repository written in Java. Support System Fu

Nov 25, 2022
Toy Lisp 1.5 interpreter

Lisp 1.5 To install: go get robpike.io/lisp. This is an implementation of the language defined, with sublime concision, in the first few pages of the

Jan 1, 2023
Interpreter - The Official Interpreter for the Infant Lang written in Go

Infant Lang Interpreter Infant Lang Minimalistic Less Esoteric Programming Langu

Jan 10, 2022
A dialect of Lisp extended to support concurrent programming, written in Go.

LispEx A dialect of Lisp extended to support concurrent programming. Overview LispEx is another Lisp Interpreter implemented with Go. The syntax, sema

Nov 22, 2022
Sabre is highly customisable, embeddable LISP engine for Go. :computer:

Sabre DEPRECATED: This repository is deprecated in favour much better slurp project and will be archived/removed soon. Sabre is highly customizable, e

May 23, 2021
A Lisp-dialect written in Go
A Lisp-dialect written in Go

Lispy ✏️ Intro Lispy is a programming language that is inspired by Scheme and Clojure. It's a simple Lisp-dialect I built to better understand Lisp an

Dec 8, 2022
A shell parser, formatter, and interpreter with bash support; includes shfmt

sh A shell parser, formatter, and interpreter. Supports POSIX Shell, Bash, and mksh. Requires Go 1.14 or later. Quick start To parse shell scripts, in

Jan 8, 2023
Small Clojure interpreter, linter and formatter.
Small Clojure interpreter, linter and formatter.

Joker is a small Clojure interpreter, linter and formatter written in Go. Installation On macOS, the easiest way to install Joker is via Homebrew: bre

Dec 30, 2022
Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter.

quickjs Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter. These bindings are a WIP and do not match full parity wit

Dec 28, 2022
Monkey programming language project from 'Writing An Interpreter In Go'and 'Writing A Compiler In Go' Books
Monkey programming language project from 'Writing An Interpreter In Go'and 'Writing A Compiler In Go' Books

Monkey Monkey programming language ?? project from "Writing An Interpreter In Go

Dec 16, 2021
Scriptable interpreter written in golang
Scriptable interpreter written in golang

Anko Anko is a scriptable interpreter written in Go. (Picture licensed under CC BY-SA 3.0, photo by Ocdp) Usage Example - Embedded package main impor

Dec 23, 2022
A POSIX-compliant AWK interpreter written in Go

GoAWK: an AWK interpreter written in Go AWK is a fascinating text-processing language, and somehow after reading the delightfully-terse The AWK Progra

Dec 31, 2022
A BASIC interpreter written in golang.
A BASIC interpreter written in golang.

05 PRINT "Index" 10 PRINT "GOBASIC!" 20 PRINT "Limitations" Arrays Line Numbers IF Statement DATA / READ Statements Builtin Functions Types 30 PRINT "

Dec 24, 2022
Prolog interpreter in Go

golog Prolog interpreter in Go with aspirations to be ISO compatible. See the full package documentation for usage details. Install with go get github

Nov 12, 2022
A simple virtual machine - compiler & interpreter - written in golang

go.vm Installation Build without Go Modules (Go before 1.11) Build with Go Modules (Go 1.11 or higher) Usage Opcodes Notes The compiler The interprete

Dec 17, 2022
A JavaScript interpreter in Go (golang)

otto -- import "github.com/robertkrimen/otto" Package otto is a JavaScript parser and interpreter written natively in Go. http://godoc.org/github.com/

Jan 2, 2023
Yaegi is Another Elegant Go Interpreter
Yaegi is Another Elegant Go Interpreter

Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go

Dec 30, 2022