ucl/ucl/inst.go
Leon Mika 2e8e60f904
All checks were successful
Test / build (push) Successful in 1m1s
Removed set and upgraded Go to 1.24
2025-05-24 10:10:32 +10:00

242 lines
5.6 KiB
Go

package ucl
import (
"context"
"errors"
"io"
"os"
"strings"
)
type Inst struct {
out io.Writer
missingBuiltinHandler MissingBuiltinHandler
missingPseudoVarHandler pseudoVar
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("len", invokableFunc(lenBuiltin))
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("reduce", invokableFunc(reduceBuiltin))
rootEC.addCmd("head", invokableFunc(firstBuiltin))
rootEC.addCmd("keys", invokableFunc(keysBuiltin))
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.addCmd("error", invokableFunc(errorBuiltin))
rootEC.addCmd("assert", invokableFunc(assertBuiltin))
rootEC.addMacro("if", macroFunc(ifBuiltin))
rootEC.addMacro("for", macroFunc(foreachBuiltin))
rootEC.addMacro("while", macroFunc(whileBuiltin))
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) SetPseudoVar(name string, h PseudoVarHandler) {
if inst.rootEC.pseudoVars == nil {
inst.rootEC.pseudoVars = make(map[string]pseudoVar)
}
inst.rootEC.pseudoVars[name] = nativePseudoVarHandler{h: h}
}
func (inst *Inst) SetMissingPseudoVarHandler(h MissingPseudoVarHandler) {
inst.missingPseudoVarHandler = nativeMissingPseudoVarHandler{h: h}
}
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 res, nil
}
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)
}
type PseudoVarHandler interface {
Get(ctx context.Context) (any, error)
}
type ModifiablePseudoVarHandler interface {
PseudoVarHandler
Set(ctx context.Context, v any) error
}
type MissingPseudoVarHandler interface {
Get(ctx context.Context, name string) (any, error)
}
type MissingModifiablePseudoVarHandler interface {
MissingPseudoVarHandler
Set(ctx context.Context, string, v any) error
}
type nativePseudoVarHandler struct {
h PseudoVarHandler
}
func (n nativePseudoVarHandler) get(ctx context.Context, name string) (Object, error) {
gv, err := n.h.Get(ctx)
if err != nil {
return nil, err
}
return fromGoValue(gv)
}
func (n nativePseudoVarHandler) set(ctx context.Context, name string, v Object) error {
mpvh, ok := n.h.(ModifiablePseudoVarHandler)
if !ok {
return errors.New("cannot set read-only pseudo-var")
}
gv, ok := toGoValue(v)
if !ok {
return mpvh.Set(ctx, v)
}
return mpvh.Set(ctx, gv)
}
type nativeMissingPseudoVarHandler struct {
h MissingPseudoVarHandler
}
func (n nativeMissingPseudoVarHandler) get(ctx context.Context, name string) (Object, error) {
gv, err := n.h.Get(ctx, name)
if err != nil {
return nil, err
}
return fromGoValue(gv)
}
func (n nativeMissingPseudoVarHandler) set(ctx context.Context, name string, v Object) error {
mpvh, ok := n.h.(MissingModifiablePseudoVarHandler)
if !ok {
return errors.New("cannot set read-only pseudo-var")
}
gv, ok := toGoValue(v)
if !ok {
return mpvh.Set(ctx, name, v)
}
return mpvh.Set(ctx, name, gv)
}