Using FunL as embedded language - anssihalmeaho/funl GitHub Wiki

Usage as embedded language

FunL can be used is Go application also so that application defines its own main package and main function in Go.

First funl needs to be fetched as Go package:

go get github.com/anssihalmeaho/funl

FunL interpreted is executed from application code by calling funl.FunlMainWithArgs:

func FunlMainWithArgs(content string, argsItems []*Item, name, srcFileName string, initSTD func(*Interpreter) error) (retValue Value, err error)

In this call initSTD function is called by FunL interpreter to initialize built-in libraries/modules. If just normal standard libraries are used std.InitSTD can be given.

Note. application can add own external module implementations as built-in libraries by defining own initSTD function which is given to funl.FunlMainWithArgs.

Example: application using FunL in embedded way (embedded_appl.go):

package main

import (
	"fmt"

	"github.com/anssihalmeaho/funl/funl"
	"github.com/anssihalmeaho/funl/std"
)

func main() {
	body := "import stdfu call(stdfu.filter list(1 2 3) func(x) true end)"
	program := fmt.Sprintf("ns main main = proc() %s end endns", body)

	retv, err := funl.FunlMainWithArgs(program, []*funl.Item{}, "main", "dummy.fnl", std.InitSTD)
	if err != nil {
		fmt.Println("Error: ", err)
		return
	}
	fmt.Println(fmt.Sprintf("Result is %v", retv))
}

Create go.mod:

go mod init embedded_appl
go mod tidy

Running example application prints:

go run embedded_appl.go
-> Result is list(1, 2, 3)

Embedding FunL source to Go program by using Go 1.16 file embedding

As Go 1.16 supports embedding static files to executable it's possible to make Go program "shell" for FunL code.

Go program main just initializes modules and goes to execute FunL interpreter.

Here's example of simple program storing some food items to valuez storage. There's separate FunL-module for reading storage (just demo for having FunL module there).

There are two FunL source files (example.fnl and food_reader_module.fnl) and one Go source file (app.go).

app.go:

package main

import (
	_ "embed"
	"fmt"

	"github.com/anssihalmeaho/funl/funl"
	"github.com/anssihalmeaho/funl/std"
	"github.com/anssihalmeaho/fuvaluez/fuvaluez"
)

//go:embed example.fnl
var program string

//go:embed food_reader_module.fnl
var mymodule string

func addMyOwnModule(interpreter *funl.Interpreter) (err error) {
	return funl.AddFunModToNamespace("food_reader_module", []byte(mymodule), interpreter)
}

func init() {
	funl.AddExtensionInitializer(initMyExt)
	funl.AddExtensionInitializer(addMyOwnModule)
}

func convGetter(inGetter func(string) fuvaluez.FZProc) func(string) std.StdFuncType {
	return func(name string) std.StdFuncType {
		return std.StdFuncType(inGetter(name))
	}
}

func initMyExt(interpreter *funl.Interpreter) (err error) {
	stdModuleName := "valuez"
	topFrame := funl.NewTopFrameWithInterpreter(interpreter)
	stdFuncs := []std.StdFuncInfo{
		{
			Name:   "open",
			Getter: convGetter(fuvaluez.GetVZOpen),
		},
		{
			Name:   "new-col",
			Getter: convGetter(fuvaluez.GetVZNewCol),
		},
		{
			Name:   "get-col",
			Getter: convGetter(fuvaluez.GetVZGetCol),
		},
		{
			Name:   "get-col-names",
			Getter: convGetter(fuvaluez.GetVZGetColNames),
		},
		{
			Name:   "put-value",
			Getter: convGetter(fuvaluez.GetVZPutValue),
		},
		{
			Name:   "get-values",
			Getter: convGetter(fuvaluez.GetVZGetValues),
		},
		{
			Name:   "take-values",
			Getter: convGetter(fuvaluez.GetVZTakeValues),
		},
		{
			Name:   "update",
			Getter: convGetter(fuvaluez.GetVZUpdate),
		},
		{
			Name:   "trans",
			Getter: convGetter(fuvaluez.GetVZTrans),
		},
		{
			Name:   "view",
			Getter: convGetter(fuvaluez.GetVZView),
		},
		{
			Name:   "del-col",
			Getter: convGetter(fuvaluez.GetVZDelCol),
		},
		{
			Name:   "close",
			Getter: convGetter(fuvaluez.GetVZClose),
		},
	}
	err = std.SetSTDFunctions(topFrame, stdModuleName, stdFuncs, interpreter)
	return
}

func main() {
	retv, err := funl.FunlMainWithArgs(program, []*funl.Item{}, "main", "example.fnl", std.InitSTD)
	if err != nil {
		fmt.Println("Error: ", err)
		return
	}
	fmt.Println(fmt.Sprintf("Result is %v", retv))
}

example.fnl:

ns main

import valuez
import stddbc

import food_reader_module

main = proc()
	open-ok open-err db = call(valuez.open 'foods'):
	_ = call(stddbc.assert open-ok open-err)

	col-ok col-err col = call(valuez.new-col db 'food-col'):
	_ = call(stddbc.assert col-ok col-err)

	_ = call(valuez.put-value col 'Pizza')
	_ = call(valuez.put-value col 'Pasta')
	_ = call(valuez.put-value col 'Meatballs')

	all-foods = call(food_reader_module.get-all-foods col)
	_ = call(valuez.close db)

	all-foods
end

endns

food_reader_module.fnl:

ns food_reader_module

import valuez

get-all-foods = proc(col)	
	call(valuez.get-values col func(x) true end)
end

endns

Build and run executable:

go mod init app
go mod tidy

go build app.go
./app.exe
Result is list('Pizza', 'Pasta', 'Meatballs')