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", ...
Message string // the Error object's message
Value Value // the thrown Error object (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, CallContext, CallValue and Global give when the program calls exit(code) (Run gives the code instead). A Func may return one to end the program as exit(code) does.
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", also a private "util::_g", or "json::encode" of a Go module the program may use) 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). If the program fails, Run gives 1 and the error. The global initializers run first, the first time anything is called; if they throw an exception, every call gives it (a step limit or cancellation lets the next call run them again). 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 that is not a keyword. NewModule, Func and Const panic on such programming mistakes: a bad name, a nil function, a name used twice, a constant that is not nil, a boolean, a number or a string, or an addition after the module is in use.
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, any bool, integer, float or string type, []byte (a string), a Value, a slice or array (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".
func (Value) ToGo ¶
ToGo gives v as a Go value: nil, bool, int64, float64, string, []any for a vector and map[string]any for a map (keys as str() shows them), nested. A function, class or object stays a Value. A vector or map that contains itself is an error, and so is a map with two keys that give the same Go key (1 and "1").
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. |