242 lines
5.6 KiB
Go
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)
|
|
}
|