ucl/ucl/inst.go
Leon Mika 531dd9bf4e
All checks were successful
Build / build (push) Successful in 2m7s
Started building out the site.
2025-01-18 16:02:35 +11:00

156 lines
3.6 KiB
Go

package ucl
import (
"context"
"errors"
"io"
"os"
"strings"
)
type Inst struct {
out io.Writer
missingBuiltinHandler MissingBuiltinHandler
echoPrinter EchoPrinter
rootEC *evalCtx
}
type InstOption func(*Inst)
func WithOut(out io.Writer) InstOption {
return func(i *Inst) {
i.out = out
}
}
func WithMissingBuiltinHandler(handler MissingBuiltinHandler) InstOption {
return func(i *Inst) {
i.missingBuiltinHandler = handler
}
}
func WithModule(module Module) InstOption {
return func(i *Inst) {
for name, builtin := range module.Builtins {
i.SetBuiltin(module.Name+":"+name, builtin)
}
}
}
type EchoPrinter func(ctx context.Context, w io.Writer, args []any) error
func WithCustomEchoPrinter(echoPrinter EchoPrinter) InstOption {
return func(i *Inst) {
i.echoPrinter = echoPrinter
}
}
type Module struct {
Name string
Builtins map[string]BuiltinHandler
}
func New(opts ...InstOption) *Inst {
rootEC := &evalCtx{}
rootEC.root = rootEC
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
rootEC.addCmd("set", invokableFunc(setBuiltin))
rootEC.addCmd("len", invokableFunc(lenBuiltin))
rootEC.addCmd("keys", invokableFunc(keysBuiltin))
rootEC.addCmd("index", invokableFunc(indexBuiltin))
rootEC.addCmd("call", invokableFunc(callBuiltin))
rootEC.addCmd("seq", invokableFunc(seqBuiltin))
rootEC.addCmd("map", invokableFunc(mapBuiltin))
rootEC.addCmd("filter", invokableFunc(filterBuiltin))
rootEC.addCmd("head", invokableFunc(firstBuiltin))
rootEC.addCmd("reduce", invokableFunc(reduceBuiltin))
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
rootEC.addCmd("ne", invokableFunc(neBuiltin))
rootEC.addCmd("gt", invokableFunc(gtBuiltin))
rootEC.addCmd("ge", invokableFunc(geBuiltin))
rootEC.addCmd("lt", invokableFunc(ltBuiltin))
rootEC.addCmd("le", invokableFunc(leBuiltin))
rootEC.addCmd("str", invokableFunc(strBuiltin))
rootEC.addCmd("int", invokableFunc(intBuiltin))
rootEC.addCmd("add", invokableFunc(addBuiltin))
rootEC.addCmd("sub", invokableFunc(subBuiltin))
rootEC.addCmd("mup", invokableFunc(mupBuiltin))
rootEC.addCmd("div", invokableFunc(divBuiltin))
rootEC.addCmd("mod", invokableFunc(modBuiltin))
rootEC.addCmd("and", invokableFunc(andBuiltin))
rootEC.addCmd("or", invokableFunc(orBuiltin))
rootEC.addCmd("not", invokableFunc(notBuiltin))
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
rootEC.addCmd("break", invokableFunc(breakBuiltin))
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
rootEC.addCmd("return", invokableFunc(returnBuiltin))
rootEC.addMacro("if", macroFunc(ifBuiltin))
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
rootEC.addMacro("proc", macroFunc(procBuiltin))
rootEC.addMacro("try", macroFunc(tryBuiltin))
inst := &Inst{
out: os.Stdout,
rootEC: rootEC,
}
for _, opt := range opts {
opt(inst)
}
return inst
}
func (inst *Inst) SetVar(name string, value any) {
obj, err := fromGoValue(value)
if err != nil {
return
}
inst.rootEC.setOrDefineVar(name, obj)
}
func (inst *Inst) Out() io.Writer {
if inst.out == nil {
return os.Stdout
}
return inst.out
}
func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) {
res, err := inst.eval(ctx, expr)
if err != nil {
if errors.Is(err, ErrHalt) {
return nil, nil
}
return nil, err
}
goRes, ok := toGoValue(res)
if !ok {
return nil, ErrNotConvertable
}
return goRes, nil
}
func (inst *Inst) eval(ctx context.Context, expr string) (Object, error) {
ast, err := parse(strings.NewReader(expr))
if err != nil {
return nil, err
}
eval := evaluator{inst: inst}
// TODO: this should be a separate forkAndIsolate() session
return eval.evalScript(ctx, inst.rootEC, ast)
}