plugin modules - anssihalmeaho/funl GitHub Wiki
Plugin modules
Plugin modules are implemented as shared object librarys (.so) (supported in FreeBSD/Linux/Mac, not in Windows). See https://github.com/anssihalmeaho/funl/wiki/Importing-modules how import of plugin module works.
Implementation of plugin module requires:
- Fetching funl as Go package by using "go get"
- Writing implementation (.go) in which package is main
- Build (with Makefile or otherwise) -> produces .so library
Getting FunL as Go package:
go get github.com/anssihalmeaho/funl
FunL assumes that plugin implementation implements Setup function in Go which registers all external procedures/functions which are provided for FunL code. Setup is called by FunL interpreter when plugin is imported.
Setup function format is:
func Setup(napi funl.FNIApi) (err error)
Setup function registers external procedures by using funl.FINApi interface:
type FNIApi interface {
RegExtProc(ExtProcType, string) error
}
In building plugin module, it's important to use following options:
- -buildmode=plugin
- -trimpath
Example Makefile to build plugin module:
BINARY_NAME=my_ext_module.so
all: build
build:
go build -trimpath -buildmode=plugin -o $(BINARY_NAME) -v ./...
clean:
go clean
rm -f $(BINARY_NAME)
Create go.mod:
go mod init my_ext_module
go mod tidy
Run the example Makefile:
make
produces --> my_ext_module.so
Using plugin module requires:
- having .so library in some directory
- setting FUNLPATH so that .so library is there (or in some subdirectory)
Setting FUNLPATH:
export FUNLPATH=/home/own/myexample
Example implementation: my_ext_module.go:
package main
import (
"fmt"
"github.com/anssihalmeaho/funl/funl"
)
// Setup registers all external procedures (called by FunL runtime)
func Setup(napi funl.FNIApi) (err error) {
err = napi.RegExtProc(funl.ExtProcType{Impl: MyExternalProc}, "my-external-proc")
if err != nil {
return
}
err = napi.RegExtProc(funl.ExtProcType{Impl: GetSomeOpaqueValue}, "get-some-opaque-value")
if err != nil {
return
}
return
}
// MyExternalProc implements external procedure
func MyExternalProc(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) {
if l := len(arguments); l != 1 {
funl.RunTimeError2(frame, "example: wrong amount of arguments (%d)", l)
}
fmt.Printf("\nMY External Procedure: argument = %#v\n", arguments[0])
retVal = funl.Value{Kind: funl.StringValue, Data: "ok"}
return
}
// GetSomeOpaqueValue implements external procedure
func GetSomeOpaqueValue(frame *funl.Frame, arguments []funl.Value) (retVal funl.Value) {
if l := len(arguments); l != 1 {
funl.RunTimeError2(frame, "example: wrong amount of arguments (%d)", l)
}
opaqueValue := &MyOpaqueValue{s: fmt.Sprintf("%#v", arguments[0])}
retVal = funl.Value{Kind: funl.OpaqueValue, Data: opaqueValue}
return
}
// MyOpaqueValue is example of external modules internal datatype which
// is shown as opaque type for FunL
type MyOpaqueValue struct {
s string
}
// TypeName returns name of opaque type (called by FunL runtime)
func (myOpaque *MyOpaqueValue) TypeName() string {
return "my-example-type"
}
// Str returns data as string (called by FunL runtime)
func (myOpaque *MyOpaqueValue) Str() string {
return "my-example-opaque-value: " + myOpaque.s
}
// Equals implements equality check of two opaque values (called by FunL runtime)
func (myOpaque *MyOpaqueValue) Equals(with funl.OpaqueAPI) bool {
other, ok := with.(*MyOpaqueValue)
if !ok {
return false
}
return myOpaque.s == other.s
}
Example: FunL code using my_ext_module:
ns main
main = proc()
import my_ext_module
_ = print('got: ' call(my_ext_module.my-external-proc 'anything'))
opa-val-1 = call(my_ext_module.get-some-opaque-value list(1 2 3))
opa-val-2 = call(my_ext_module.get-some-opaque-value list(1 2 3))
opa-val-3 = call(my_ext_module.get-some-opaque-value 'this is different')
list(
eq(opa-val-1 opa-val-1)
eq(opa-val-1 opa-val-2)
eq(opa-val-1 opa-val-3)
)
end
endns
...run example...
./funla ext_mod_esim.fnl
->
MY External Procedure: argument = 'anything'
got: ok
list(true, true, false)
Note. Go modules implementation can be very strict about module/package versions so that
module/package versions in FunL interpreter binary (funla) and in plugin shared library are exactly same.
So it may be required in building FunL interpreter (funla) that after cloning FunL repository from Github
(git clone https://github.com/anssihalmeaho/funl.git) all other files and folders are removed except following ones:
- main.go
- Makefile
- go.mod
Also rename module to some other name in go.mod, for example:
module github.com/anssihalmeaho/funl
=>
module funla
And then build funla executable by make.