Skip to content

plugin modules

Anssi Halmeaho edited this page Nov 10, 2021 · 4 revisions

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:

  1. Fetching funl as Go package by using "go get"
  2. Writing implementation (.go) in which package is main
  3. 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:

  1. having .so library in some directory
  2. 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.

Clone this wiki locally