ucl/ucl/inst.go
Leon Mika d111d84dbf
Some checks failed
Build / build (push) Failing after 1m40s
Fixed tests
2024-12-11 22:37:23 +11:00

157 lines
3.7 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("toUpper", invokableFunc(toUpperBuiltin))
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, errors.New("result not convertable to go")
}
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)
}