glycerine/zygomys

How to call a user defined function from Go?

Closed this issue · 7 comments

Hi,

I am currently playing around with zygomys which looks very nice. I'd like to be able to run a user defined function, not just the whole env.

So, for example, the user defines a function in lisp like:

(defn PostRowHook [linenum, row] (...))

Then how can I execute only this specific function from Go and check if it exists?

Or - even better - could I provide a list to the lisp code, which the user can fill with her own functions, just like you do in emacs with add-hook, e.g.:

(add-hook %post-row-hook (fn blah [linenum, row] (...))

and then iterate over the list post-row-hook and execute the functions there? Does that make sense?

best regards,
Tom

I'm not exactly sure what you mean by "not just the whole env".

Maybe the sandbox example will help: https://github.com/glycerine/zygomys/blob/master/zygo/environment_test.go#L12

In general, calling env.EvalString(s) will cause the zygo interpreter to evaluate the string s.
If the s is a function call expression, that function will be called. You could use the
lower level functionality too, but the top level "eval" is simple, is fully general, and perhaps suffices? In your Go code, read the name of the function, if you like it, construct a string the is the call you want to make, like "(callme arg1 arg2)" and then evaluate it in order to call the function callme with argments arg1 and arg2.

If I'm not in the ballpark, elaborate on what you're trying to do. It's kind of fuzzy.

other hints: if you are using Go struct to back your lisp records, you can convert the lisp string to a Go struct with this Go code:

xh, err := zygo.ToGoFunction(env, "togo", []zygo.Sexp{an_sexp_string_here})

but you can also access the lisp record as a Go hash map, the Go that represents the lisp record,
and access that in Go too. Here is how you would get both of these:

sexpHash = xh.(*zygo.SexpHash)
shad := sexpHash.GoShadowStruct.(YourGoStructTypeHere) // the Go struct has been instantiated by the "togo" method, and only after it has been called will it be available

hope this helps... look through the tests especially and see if they give you ideas...

See also https://github.com/glycerine/zygomys/blob/master/zygo/jsonmsgp.go#L20 for an overview of how to convert stuff... the callgo_test.go uses SexpToGoStructs() alot as an example of that.

Hi,

sorry for the late reply.

If I'm not in the ballpark, elaborate on what you're trying to do. It's kind of fuzzy.

Ok, I'm planning a basic plugin system, an app does some stuff with data and it will provide on each step a point (hook) where user provided functions could be executed on the data (e.g. for post processing, filtering, converting etc). But I want the user to be able to provide multiple such functions per each hook, so that it would be possible to install multiple plugins doing similar things (e.g. two filtering plugins).

So, the first thing the app does is to load all user provided scripts, each script defines arbitrary code (variables, functions etc) and at the end registers one or more functions to one or more of said hooks.

Then the app does it's thing and when it reaches one of those points, it iterates over the list of functions registered for this particular hook, and executes each with context specific arguments. For example a filter function registered with the pre-filter-hook, would get an item of a list as argument and would be expected to return a boolean value to signal to the app to include or exclude that item.

And so on.

But you already provided a couple of hints. I'll take a look into that and start experimenting with it. Thanks a lot so far!

Ok, I am trying but it doesn't work:

type Table struct {
	Headers []string   `json:"headers" msg:"headers"`
	Rows    [][]string `json:"rows" msg:"rows"`
}

env := zygo.NewZlisp()

zygo.GoStructRegistry.RegisterUserdef(
	&zygo.RegisteredType{GenDefMap: true, Factory: func(env *zygo.Zlisp, h *zygo.SexpHash) (interface{}, error) {
		return &Table{}, nil
	}}, true, "table")

code := `(def t (table headers:[] rows:[]))`
x, err := env.EvalString(code)
if err != nil {
	log.Fatalf(env.GetStackTrace(err))
}

tmp, err := zygo.SexpToGoStructs(x, &Table{}, env, nil)
if err != nil {
	log.Fatalf(err.Error())
}

switch f := tmp.(type) {
case *Table:
	repr.Println(f)
default:
	log.Fatalf("wrong type!")
}

When I run this, I get:

error in __main:4: symbol `table` not found

See https://github.com/glycerine/zygomys/blob/master/zygo/callgo2_test.go I turned your example into a test. It runs. It is green.

You were only missing two things:

env.StandardSetup()
and
{(defmap table)}

Now by rights you should only really have to write (defmap table) ; without the infix curly braces. Keep reading to understand why that won't suffice at the moment.

I made it work, but along the way ran into a bug that I still have not figured out how to fix
(see comments in callgo2_test.go). That's why it took a while to reply; I was
hoping to fix the bug. But I'm out of time to mess with it at the moment.

The short story is, in
v6.0.1 and before there's no issue, because macros were run in separate environments.
But in supplying feature/fixing issue #54, I turned that off.

The real issue is that macros can trash the data stack; and that should really be fixed. But I
don't know how at the moment.

The two workarounds at the moment are: a) wrap macros is curly braces { } ; or b) use v6.0.1 or before.

Since the Go code is going to read the top of the datastack's value, in order to bring it back into Go, it is sensitive to having that stack messed up. Running the defmap macro is necessary but unfortunately trashes the data stack at the moment, denying the Go code its data without the corrective curly braces.

I've just pushed v6.0.6 that fixes things; you no longer need the curly braces around the defmap call, and the 019 test in callgo2_test.go reflects that.