Documentation
¶
Overview ¶
Package kukel embeds the Kukel language in Go programs: compile Kukel source once, run it in as many interpreters as needed, call its functions, and give it Go functions of your own.
app := kukel.NewModule("app").
Func("greet", func(in *kukel.Interp, args []kukel.Value) (kukel.Value, error) {
return kukel.String("hello, " + args[0].String()), nil
})
prog, err := kukel.CompileSource("rules.kkl", `
use app;
discount(total) { return total > 100 ? 0.1 : 0; }
main() { println(app::greet("world")); }`,
kukel.CompileOptions{Modules: []*kukel.Module{app}})
in, err := kukel.New(prog, kukel.Options{Stdout: os.Stdout, StepLimit: 1_000_000})
d, err := in.Call("discount", kukel.Int(250)) // 0.1
The language is described in docs/reference.md of the repository, the virtual machine in docs/vm.md, and this package in docs/embedding.md.
For code that is not trusted, CompileOptions limit what a program may use (modules, built-in functions such as readfile), Options.StepLimit bounds the work of every call, and CallContext stops a call when its context ends. Memory is not limited.
Example ¶
Compile a program once and call its functions from Go.
package main
import (
"fmt"
"github.com/maarten-boot/kukel"
)
func main() {
prog, err := kukel.CompileSource("pricing.kkl", `
const VAT = 0.21;
price(net, qty) {
local total = net * qty;
if (qty >= 10) total *= 0.9; // volume discount
return round(total * (1 + VAT) * 100) / 100.0;
}
main() {}`, kukel.CompileOptions{})
if err != nil {
fmt.Println(err)
return
}
in, err := kukel.New(prog, kukel.Options{})
if err != nil {
fmt.Println(err)
return
}
for _, qty := range []int64{1, 10} {
p, err := in.Call("price", kukel.Float(9.99), kukel.Int(qty))
fmt.Println(qty, p, err)
}
}
Output: 1 12.09 <nil> 10 108.79 <nil>
Index ¶
- Variables
- func Builtins() []string
- func StandardModules() []string
- type CompileError
- type CompileOptions
- type Error
- type ExitError
- type Frame
- type Func
- type Interp
- func (in *Interp) Call(name string, args ...Value) (Value, error)
- func (in *Interp) CallContext(ctx context.Context, name string, args ...Value) (Value, error)
- func (in *Interp) CallValue(fn Value, args ...Value) (Value, error)
- func (in *Interp) Close()
- func (in *Interp) Global(name string) (Value, error)
- func (in *Interp) Run(args ...string) (int, error)
- func (in *Interp) RunContext(ctx context.Context, args ...string) (int, error)
- type Module
- type Options
- type Program
- type Value
- func (v Value) AsBool() (b, ok bool)
- func (v Value) AsFloat() (float64, bool)
- func (v Value) AsInt() (int64, bool)
- func (v Value) AsString() (string, bool)
- func (v Value) Get(key Value) Value
- func (v Value) Index(i int) Value
- func (v Value) IsNil() bool
- func (v Value) Keys() []Value
- func (v Value) Kind() string
- func (v Value) Len() int
- func (v Value) Push(e Value) error
- func (v Value) SetKey(key, val Value) error
- func (v Value) String() string
- func (v Value) ToGo() (any, error)
- func (v Value) Truthy() bool
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Nil = Value{}
Nil is the Kukel value nil.
var SystemBuiltins = []string{"readfile", "writefile", "appendfile", "readline", "getenv", "exit"}
SystemBuiltins are the built-in functions that reach outside the program: files (readfile, writefile, appendfile), standard input (readline), the environment (getenv) and ending the process (exit). CompileOptions{DenyBuiltins: kukel.SystemBuiltins} keeps them away from a program.
Functions ¶
func StandardModules ¶
func StandardModules() []string
StandardModules lists the modules written in Go that come with Kukel: events, json, strings, time, worker.
Types ¶
type CompileError ¶
CompileError is an error in Kukel source.
func (*CompileError) Error ¶
func (e *CompileError) Error() string
type CompileOptions ¶
type CompileOptions struct {
// Modules are the program's own modules, written in Go ("use app;").
// A module here hides a standard module of the same name.
Modules []*Module
// StandardModules lists the standard modules the program may use
// (nil: all of them; an empty, non-nil slice: none).
StandardModules []string
// DenyBuiltins lists built-in functions the program may not use
// (using one is a compile error); see SystemBuiltins.
DenyBuiltins []string
// Path lists directories where "use name;" looks for name.kkl, after
// the directory of the file that uses it (CompileFiles only).
Path []string
// NoFileModules forbids reading program files: "use" finds only
// modules written in Go, and worker::startfile is refused.
NoFileModules bool
}
CompileOptions say what a program may use beyond its own code.
type Error ¶
type Error struct {
Kind string // "TypeError", "ValueError", ..., or "" for a thrown value that is not an Error object
Message string // the Error object's message, or the thrown value as text
Value Value // the thrown value (Nil for the step limit and cancellation)
Trace []Frame // the calls, innermost first
// contains filtered or unexported fields
}
Error is an exception of a Kukel program that nothing caught, or the end of a call by its step limit (Kind "StepLimitError") or its context (Kind "CancelledError").
type ExitError ¶
type ExitError struct{ Code int }
ExitError is what Call gives when the program calls exit(code).
type Func ¶
Func is a Go function that Kukel code calls. in is the interpreter that calls it (to call back into Kukel with in.CallValue). The arguments are valid during the call only; keep copies (ToGo) of what is needed later. An error becomes a Kukel exception: an *Error keeps its kind (see Errorf), any other error has the kind "HostError".
type Interp ¶
type Interp struct {
// contains filtered or unexported fields
}
Interp is an interpreter running a Program: it has its own globals, output and state. Its methods must not be called from several goroutines at once; use one Interp per goroutine (they can share a Program). A Func may call back into the Interp that called it.
func New ¶
New makes an interpreter for p. It fails if p uses a built-in or module that its CompileOptions do not allow (a program from LoadProgram).
func (*Interp) Call ¶
Call calls the program's function name ("discount", or a module's: "util::f") with args and gives its result. A Kukel exception that nothing catches is an *Error; exit(code) gives an *ExitError.
func (*Interp) CallContext ¶
CallContext is Call, stopped when ctx ends: the program stops at its next step (a function written in Go that waits, such as time::sleep, returns at once) with an *Error of kind "CancelledError" that it cannot catch. This also holds for a call a Func makes back into Kukel. Workers started during the call are cancelled if ctx ends while it runs; Close cancels those that outlive it.
Example ¶
Run code that is not trusted: no files, no workers, a step budget and a time limit.
package main
import (
"context"
"fmt"
"strings"
"time"
"github.com/maarten-boot/kukel"
)
func main() {
opts := kukel.CompileOptions{
DenyBuiltins: kukel.SystemBuiltins,
StandardModules: []string{"json", "strings"},
NoFileModules: true,
}
if _, err := kukel.CompileSource("evil.kkl", `main() { println(readfile("/etc/passwd")); }`, opts); err != nil {
fmt.Println(err)
}
prog, _ := kukel.CompileSource("slow.kkl", `spin() { while (true) {} } main() {}`, opts)
in, _ := kukel.New(prog, kukel.Options{})
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
_, err := in.CallContext(ctx, "spin")
fmt.Println(strings.SplitN(err.Error(), "\n", 2)[0])
}
Output: evil.kkl:1:18: built-in function readfile is not available in this program CancelledError: the program was cancelled: context deadline exceeded
func (*Interp) CallValue ¶
CallValue calls a Kukel function value (such as a callback a Func received) with args.
func (*Interp) Close ¶ added in v0.2.1
func (in *Interp) Close()
Close stops the interpreter for good: workers it started that are still running are cancelled, and what they write later is dropped. Calls after Close give an error. Close may be called more than once.
func (*Interp) Global ¶
Global gives the value of a global of the program (a variable, function or class), after the global initializers have run.
func (*Interp) Run ¶
Run runs the program's main, with args as its parameter (a vector of strings) if it has one, and gives the exit code: main's result if that is an int, else 0; or the code of exit(code). The global initializers run first, the first time anything is called; if they fail, every call gives their error. Run does not wait for workers the program started (see Close).
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module is a module written in Go for one program: Kukel code that says "use name;" reaches its functions and constants as name::f and name::K. Add everything before the module is first used in CompileOptions.
func NewModule ¶
NewModule makes an empty module. name must be a Kukel identifier.
Example ¶
Give Kukel code functions written in Go, in a module of its own.
package main
import (
"fmt"
"os"
"github.com/maarten-boot/kukel"
)
func main() {
stock := map[string]int64{"pen": 12, "ink": 0}
shop := kukel.NewModule("shop").
Const("currency", kukel.String("EUR")).
Func("stock", func(in *kukel.Interp, args []kukel.Value) (kukel.Value, error) {
item, ok := args[0].AsString()
if !ok {
return kukel.Nil, kukel.Errorf("TypeError", "an item is a string")
}
n, known := stock[item]
if !known {
return kukel.Nil, kukel.Errorf("KeyError", "no item %q", item)
}
return kukel.Int(n), nil
})
prog, err := kukel.CompileSource("shop.kkl", `use shop;
main() {
for (local item in ["pen", "ink", "cap"]) {
try {
println(item, shop::stock(item) > 0 ? "in stock" : "sold out");
} catch (e) {
println(item, e->kind, e->message);
}
}
println("prices in", shop::currency);
}`, kukel.CompileOptions{Modules: []*kukel.Module{shop}})
if err != nil {
fmt.Println(err)
return
}
in, _ := kukel.New(prog, kukel.Options{Stdout: os.Stdout})
in.Run()
}
Output: pen in stock ink sold out cap KeyError no item "cap" prices in EUR
type Options ¶
type Options struct {
Stdout io.Writer // what print writes to; nil: discarded
Stderr io.Writer // what eprint writes to; nil: discarded
Stdin io.Reader // what readline reads; nil: empty
// StepLimit bounds each call from Go (Run, Call, CallValue): after that
// many steps (backward jumps, calls and throws, so every loop counts)
// the call ends with an *Error of kind "StepLimitError" that the
// program cannot catch. Calls a Func makes back into Kukel share the
// budget of the call that runs the Func. 0: no limit. Workers the
// program starts get the same limit for their whole run.
StepLimit int
}
Options configure an interpreter.
type Program ¶
type Program struct {
// contains filtered or unexported fields
}
Program is a compiled program. It does not change when it runs, so any number of interpreters (New) may run it, also at the same time.
func CompileFiles ¶
func CompileFiles(files []string, opts CompileOptions) (*Program, error)
CompileFiles compiles source files as one program, with the modules they use. An error in the source is a *CompileError.
func CompileSource ¶
func CompileSource(name, src string, opts CompileOptions) (*Program, error)
CompileSource compiles a program given as text; name is used in messages. Modules written in Kukel are looked for in opts.Path only.
func LoadProgram ¶
func LoadProgram(data []byte, opts CompileOptions) (*Program, error)
LoadProgram reads a program from the bytes of a .kkc file (Encode, or kukel build). opts say what it may use; New refuses a program that uses more.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is a Kukel value: nil, a boolean, an int (64 bits), a float, a string (bytes), a vector, a map, a function, a class or an object. The zero Value is nil.
Vectors, maps and objects are shared, not copied: a Value taken from an interpreter refers to the interpreter's data. Use it only while that interpreter does not run on another goroutine.
func FromGo ¶
FromGo makes a Value of a Go value: nil, a bool, any integer or float type, a string or []byte (a string), a Value, a slice (a vector) or a map with string keys (a map, keys in sorted order), also nested.
Example ¶
Convert between Go and Kukel values.
package main
import (
"fmt"
"github.com/maarten-boot/kukel"
)
func main() {
v, _ := kukel.FromGo(map[string]any{"name": "Kukel", "tags": []string{"small", "fun"}})
fmt.Println(v, v.Get(kukel.String("tags")).Len())
g, _ := v.ToGo()
fmt.Printf("%v\n", g)
}
Output: {"name": "Kukel", "tags": ["small", "fun"]} 2 map[name:Kukel tags:[small fun]]
func MapOf ¶
MapOf makes a new map of m, with the keys in sorted order (Kukel maps keep the order in which keys were added).
func (Value) Index ¶
Index gives element i of a vector (Nil if v is not a vector or i is out of range).
func (Value) Kind ¶
Kind is what typeof gives in Kukel: "nil", "bool", "int", "float", "string", "vector", "map", "function", "class" or "object".
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
kukel
command
Command kukel compiles and runs Kukel programs.
|
Command kukel compiles and runs Kukel programs. |
|
internal
|
|
|
ast
Package ast declares the syntax tree of Kukel programs (syntax.ebnf, sections 1-3).
|
Package ast declares the syntax tree of Kukel programs (syntax.ebnf, sections 1-3). |
|
builtins
Package builtins implements Kukel's built-in functions.
|
Package builtins implements Kukel's built-in functions. |
|
bytecode
Package bytecode defines the instruction set of the Kukel stack machine and the compiled program image.
|
Package bytecode defines the instruction set of the Kukel stack machine and the compiled program image. |
|
compiler
Package compiler translates syntax trees into bytecode for the stack machine.
|
Package compiler translates syntax trees into bytecode for the stack machine. |
|
diag
Package diag defines the error type shared by the lexer, parser and compiler: a message at a source position.
|
Package diag defines the error type shared by the lexer, parser and compiler: a message at a source position. |
|
lexer
Package lexer turns Kukel source text into tokens (syntax.ebnf, section 4).
|
Package lexer turns Kukel source text into tokens (syntax.ebnf, section 4). |
|
loader
Package loader reads the source files of a program together with the modules they use ("use name;"), directly or through other modules.
|
Package loader reads the source files of a program together with the modules they use ("use name;"), directly or through other modules. |
|
modules/eventsmod
Package eventsmod is the native module "events": an event loop that calls Kukel functions when timers expire or other modules post events.
|
Package eventsmod is the native module "events": an event loop that calls Kukel functions when timers expire or other modules post events. |
|
modules/jsonmod
Package jsonmod is the native module "json": encode Kukel values as JSON text and decode JSON text into Kukel values.
|
Package jsonmod is the native module "json": encode Kukel values as JSON text and decode JSON text into Kukel values. |
|
modules/stringsmod
Package stringsmod is the native module "strings": a Builder class that builds a long string piece by piece without copying it again for every piece (s += x copies s each time).
|
Package stringsmod is the native module "strings": a Builder class that builds a long string piece by piece without copying it again for every piece (s += x copies s each time). |
|
modules/timemod
Package timemod is the native module "time": the class time::Time (an instant with a time zone), and sleep.
|
Package timemod is the native module "time": the class time::Time (an instant with a time zone), and sleep. |
|
modules/workermod
Package workermod is the native module "worker": workers are Kukel interpreters that run in parallel, each on its own goroutine, and talk to the interpreter that started them only through messages.
|
Package workermod is the native module "worker": workers are Kukel interpreters that run in parallel, each on its own goroutine, and talk to the interpreter that started them only through messages. |
|
native
Package native lets Go code define Kukel modules: functions, constants and native classes, whose objects carry a Go value and whose member functions are written in Go.
|
Package native lets Go code define Kukel modules: functions, constants and native classes, whose objects carry a Go value and whose member functions are written in Go. |
|
parser
Package parser builds the syntax tree of a Kukel source file (syntax.ebnf, sections 1-3) by recursive descent.
|
Package parser builds the syntax tree of a Kukel source file (syntax.ebnf, sections 1-3) by recursive descent. |
|
token
Package token defines the lexical tokens of Kukel (syntax.ebnf, section 4).
|
Package token defines the lexical tokens of Kukel (syntax.ebnf, section 4). |
|
value
Package value defines Kukel's runtime values and the semantics of the operators on them.
|
Package value defines Kukel's runtime values and the semantics of the operators on them. |
|
vm
Package vm executes compiled Kukel programs on a stack machine.
|
Package vm executes compiled Kukel programs on a stack machine. |