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 Class
- 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 Method
- type Module
- type Object
- 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) Object() (*Object, bool)
- 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: db, events, json, strings, time, worker, zmq.
Types ¶
type Class ¶ added in v0.9.0
type Class struct {
// contains filtered or unexported fields
}
Class is a class written in Go for a host module. Its objects carry a Go value (their state) and have no data members: everything goes through member functions. Kukel code makes objects with new name::C(...) when the class has a constructor, and may then derive classes from it; the derived constructor calls name::C::C(...) to set up the Go value. Without a constructor, objects come from the module's functions (Class.Object).
NewClass, New, Method, Static and Show panic on programming mistakes: a bad name, a nil function, a name used twice, or an addition after the module is in use.
func NewClass ¶ added in v0.9.0
NewClass makes an empty class. name must be an identifier.
Example ¶
A class written in Go: Kukel code uses it and derives a class from it that overrides a step (changed).
package main
import (
"fmt"
"os"
"github.com/maarten-boot/kukel"
)
func main() {
type counter struct{ n int64 }
counterClass := kukel.NewClass("Counter").
New(func(in *kukel.Interp, this *kukel.Object, args []kukel.Value) (kukel.Value, error) {
this.SetState(&counter{}) // the constructor sets the state
return kukel.Nil, nil
}).
Method("inc", func(in *kukel.Interp, this *kukel.Object, args []kukel.Value) (kukel.Value, error) {
c := this.State().(*counter)
c.n++
if _, err := this.Call(in, "changed", kukel.Int(c.n)); err != nil { // overridable
return kukel.Nil, err
}
return this.Value(), nil
}).
Method("changed", func(in *kukel.Interp, this *kukel.Object, args []kukel.Value) (kukel.Value, error) {
return kukel.Nil, nil
}).
Show(func(this *kukel.Object) string {
return fmt.Sprintf("app::Counter(%d)", this.State().(*counter).n)
})
app := kukel.NewModule("app").Class(counterClass)
prog, err := kukel.CompileSource("main.kkl", `use app;
class Loud : app::Counter { Loud(); changed(n); }
Loud::Loud() { app::Counter::Counter(); } // sets up the Go state
Loud::changed(n) { println("now", n); }
main() {
local c = new app::Counter();
c->inc()->inc();
println(c); // app::Counter(2)
new Loud()->inc(); // now 1
}`, kukel.CompileOptions{Modules: []*kukel.Module{app}})
if err != nil {
fmt.Println(err)
return
}
in, err := kukel.New(prog, kukel.Options{Stdout: os.Stdout})
if err != nil {
fmt.Println(err)
return
}
in.Run()
}
Output: app::Counter(2) now 1
func (*Class) New ¶ added in v0.9.0
New sets the constructor: new name::C(args) calls fn with the new object, whose Go value fn sets (this.SetState).
func (*Class) Object ¶ added in v0.9.0
Object makes an object of the class holding state, for a Func to return. The class must be in a module that a compiled program uses.
func (*Class) Show ¶ added in v0.9.0
Show sets how str() and println show an object of the class (without it: "name::C{}").
type CompileError ¶
type CompileError struct {
File string
Line, Col int
Message string
More []*CompileError
}
CompileError is an error in Kukel source. A compile can find several: the first is the CompileError itself, the others are in More (in the order of the source), and Error() gives all of them, one per line.
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 (or
// name.kkc, a module compiled on its own), 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 Method ¶ added in v0.9.0
Method is a member function written in Go: this is the object it is called on (also an object of a Kukel class derived from the Go class). For the constructor (Class.New), it sets the object's Go value with this.SetState; its result is not used.
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
func (*Module) Class ¶ added in v0.9.0
Class adds a class written in Go (see NewClass); Kukel code reaches it as name::C: new name::C(...), class D : name::C, name::C::f(...).
type Object ¶ added in v0.9.0
type Object struct {
// contains filtered or unexported fields
}
Object is an object, as a Method gets it (this) or Value.Object gives it.
func (*Object) Call ¶ added in v0.9.0
Call calls the member function name on the object, as p->name(args) does: a Kukel class derived from a Go class may override it.
func (*Object) ClassName ¶ added in v0.9.0
ClassName gives the name of the object's class ("app::Counter", or the Kukel class derived from it).
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.
Example (StepLimit) ¶
A step limit stops code that runs too long (each call from Go gets the whole budget), and a panic in a Go function is a HostError for Kukel.
package main
import (
"errors"
"fmt"
"github.com/maarten-boot/kukel"
)
func main() {
app := kukel.NewModule("app").Func("crash", func(in *kukel.Interp, args []kukel.Value) (kukel.Value, error) {
var m map[string]int
m["x"] = 1 // panics
return kukel.Nil, nil
})
prog, err := kukel.CompileSource("limits.kkl", `use app;
spin() { while (true) {} }
count(n) { local i = 0; while (i < n) i++; return i; }
crash() { try { app::crash(); } catch (e) { return e->kind; } }
main() {}`, kukel.CompileOptions{Modules: []*kukel.Module{app}})
if err != nil {
fmt.Println(err)
return
}
in, _ := kukel.New(prog, kukel.Options{StepLimit: 10000})
_, err = in.Call("spin")
var e *kukel.Error
if errors.As(err, &e) {
fmt.Println(e.Kind + ": " + e.Message)
}
fmt.Println(in.Call("count", kukel.Int(5000))) // a new budget for this call
fmt.Println(in.Call("crash"))
}
Output: StepLimitError: more than 10000 steps 5000 <nil> HostError <nil>
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) Object ¶ added in v0.9.0
Object gives v as an object (of a class written in Kukel or in Go).
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. |
|
format
Package format formats Kukel source (kukel fmt): it re-indents lines and normalizes the spaces between tokens.
|
Package format formats Kukel source (kukel fmt): it re-indents lines and normalizes the spaces between tokens. |
|
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/dbmod
Package dbmod is the native module "db": SQL databases through Go's database/sql: SQLite (modernc.org/sqlite), PostgreSQL (jackc/pgx) and MySQL or MariaDB (go-sql-driver/mysql), all pure Go, so no C library is needed.
|
Package dbmod is the native module "db": SQL databases through Go's database/sql: SQLite (modernc.org/sqlite), PostgreSQL (jackc/pgx) and MySQL or MariaDB (go-sql-driver/mysql), all pure Go, so no C library is needed. |
|
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/fsmod
Package fsmod is the native module "fs": directories and files of the local file system (TODO.md Q61).
|
Package fsmod is the native module "fs": directories and files of the local file system (TODO.md Q61). |
|
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/p9mod
Package p9mod is the native module "p9": the 9P file protocol of Plan 9 and Linux (TODO.md Q62), as a client and as a server, in both versions: 9P2000 and 9P2000.L.
|
Package p9mod is the native module "p9": the 9P file protocol of Plan 9 and Linux (TODO.md Q62), as a client and as a server, in both versions: 9P2000 and 9P2000.L. |
|
modules/remod
Package remod is the native module "re": regular expressions, with Go's RE2 syntax (TODO.md Q59).
|
Package remod is the native module "re": regular expressions, with Go's RE2 syntax (TODO.md Q59). |
|
modules/stringsmod
Package stringsmod is the native module "strings": a Builder class that builds a long string from many pieces.
|
Package stringsmod is the native module "strings": a Builder class that builds a long string from many pieces. |
|
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/uuidmod
Package uuidmod is the native module "uuid": new UUIDs as strings (TODO.md Q58).
|
Package uuidmod is the native module "uuid": new UUIDs as strings (TODO.md Q58). |
|
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. |
|
modules/zmqmod
Package zmqmod is the native module "zmq": ZeroMQ sockets, to talk with other programs (in any language) and other machines.
|
Package zmqmod is the native module "zmq": ZeroMQ sockets, to talk with other programs (in any language) and other machines. |
|
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. |