Leon Mika
c433e4bf53
All checks were successful
Build / build (push) Successful in 2m5s
Previously undefined variables would be set to nil, which introduced subtle errors. Changed this so that referencing an undefined variable will produce an error. This behaviour can changed as part of the instance configuration by declaring a missing-var handler.
306 lines
5.8 KiB
Go
306 lines
5.8 KiB
Go
package ucl
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
|
|
"github.com/lmika/gopkgs/fp/slices"
|
|
)
|
|
|
|
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
|
|
|
|
type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error)
|
|
type MissingVarHandler func(ctx context.Context, name string) (any, error)
|
|
|
|
type CallArgs struct {
|
|
args invocationArgs
|
|
}
|
|
|
|
func (ca *CallArgs) NArgs() int {
|
|
return len(ca.args.args)
|
|
}
|
|
|
|
func (ca *CallArgs) Bind(vars ...interface{}) error {
|
|
if len(ca.args.args) < len(vars) {
|
|
return errors.New("wrong number of arguments")
|
|
}
|
|
|
|
for i, v := range vars {
|
|
if err := ca.bindArg(v, ca.args.args[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
ca.args = ca.args.shift(len(vars))
|
|
return nil
|
|
}
|
|
|
|
func (ca *CallArgs) CanBind(vars ...interface{}) bool {
|
|
if len(ca.args.args) < len(vars) {
|
|
return false
|
|
}
|
|
|
|
for i, v := range vars {
|
|
if !canBindArg(v, ca.args.args[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (ca *CallArgs) Shift(n int) {
|
|
ca.args = ca.args.shift(n)
|
|
}
|
|
|
|
func (ca CallArgs) IsTopLevel() bool {
|
|
return ca.args.ec.parent == nil || ca.args.ec == ca.args.ec.root
|
|
}
|
|
|
|
func (ca CallArgs) HasSwitch(name string) bool {
|
|
if ca.args.kwargs == nil {
|
|
return false
|
|
}
|
|
|
|
_, ok := ca.args.kwargs[name]
|
|
return ok
|
|
}
|
|
|
|
func (ca CallArgs) BindSwitch(name string, val interface{}) error {
|
|
if ca.args.kwargs == nil {
|
|
return nil
|
|
}
|
|
|
|
vars, ok := ca.args.kwargs[name]
|
|
if !ok || len(*vars) != 1 {
|
|
return nil
|
|
}
|
|
|
|
return ca.bindArg(val, (*vars)[0])
|
|
}
|
|
|
|
func (inst *Inst) SetBuiltin(name string, fn BuiltinHandler) {
|
|
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
|
}
|
|
|
|
type userBuiltin struct {
|
|
fn func(ctx context.Context, args CallArgs) (any, error)
|
|
}
|
|
|
|
func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
|
v, err := u.fn(ctx, CallArgs{args: args})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fromGoValue(v)
|
|
}
|
|
|
|
func (ca CallArgs) bindArg(v interface{}, arg Object) error {
|
|
switch t := v.(type) {
|
|
case *Object:
|
|
*t = arg
|
|
return nil
|
|
case *interface{}:
|
|
*t, _ = toGoValue(arg)
|
|
return nil
|
|
case *Invokable:
|
|
i, ok := arg.(invokable)
|
|
if !ok {
|
|
return errors.New("exepected invokable")
|
|
}
|
|
*t = Invokable{
|
|
inv: i,
|
|
eval: ca.args.eval,
|
|
inst: ca.args.inst,
|
|
ec: ca.args.ec,
|
|
}
|
|
return nil
|
|
case *Listable:
|
|
i, ok := arg.(Listable)
|
|
if !ok {
|
|
return errors.New("exepected listable")
|
|
}
|
|
*t = i
|
|
return nil
|
|
case *ModListable:
|
|
if i, ok := arg.(ModListable); ok {
|
|
*t = i
|
|
return nil
|
|
}
|
|
return errors.New("exepected listable")
|
|
case *string:
|
|
if arg != nil {
|
|
*t = arg.String()
|
|
} else {
|
|
*t = ""
|
|
}
|
|
return nil
|
|
case *int:
|
|
if iArg, ok := arg.(IntObject); ok {
|
|
*t = int(iArg)
|
|
} else {
|
|
return errors.New("invalid arg")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
switch t := arg.(type) {
|
|
case OpaqueObject:
|
|
if v == nil {
|
|
return errors.New("opaque object not bindable to nil")
|
|
}
|
|
|
|
vr := reflect.ValueOf(v)
|
|
tr := reflect.ValueOf(t.v)
|
|
if vr.Kind() != reflect.Pointer {
|
|
return errors.New("expected pointer for an opaque object bind")
|
|
}
|
|
|
|
if !tr.Type().AssignableTo(vr.Elem().Type()) {
|
|
return errors.New("opaque object not assignable to passed in value")
|
|
}
|
|
|
|
vr.Elem().Set(tr)
|
|
return nil
|
|
case proxyObject:
|
|
return bindProxyObject(v, reflect.ValueOf(t.p))
|
|
case listableProxyObject:
|
|
return bindProxyObject(v, t.v)
|
|
case structProxyObject:
|
|
return bindProxyObject(v, t.v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func canBindArg(v interface{}, arg Object) bool {
|
|
switch v.(type) {
|
|
case *string:
|
|
return true
|
|
case *int:
|
|
_, ok := arg.(IntObject)
|
|
return ok
|
|
}
|
|
|
|
switch t := arg.(type) {
|
|
case OpaqueObject:
|
|
vr := reflect.ValueOf(v)
|
|
tr := reflect.ValueOf(t.v)
|
|
if vr.Kind() != reflect.Pointer {
|
|
return false
|
|
}
|
|
|
|
if !tr.Type().AssignableTo(vr.Elem().Type()) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
case proxyObject:
|
|
return canBindProxyObject(v, reflect.ValueOf(t.p))
|
|
case listableProxyObject:
|
|
return canBindProxyObject(v, t.v)
|
|
case structProxyObject:
|
|
return canBindProxyObject(v, t.v)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func bindProxyObject(v interface{}, r reflect.Value) error {
|
|
argValue := reflect.ValueOf(v)
|
|
if argValue.Kind() != reflect.Ptr {
|
|
return errors.New("v must be a pointer to a struct")
|
|
}
|
|
|
|
for {
|
|
if r.Type().AssignableTo(argValue.Elem().Type()) {
|
|
argValue.Elem().Set(r)
|
|
return nil
|
|
}
|
|
if r.Type().Kind() != reflect.Pointer {
|
|
return nil
|
|
}
|
|
|
|
r = r.Elem()
|
|
}
|
|
}
|
|
|
|
func canBindProxyObject(v interface{}, r reflect.Value) bool {
|
|
argValue := reflect.ValueOf(v)
|
|
if argValue.Kind() != reflect.Ptr {
|
|
return false
|
|
}
|
|
|
|
for {
|
|
if r.Type().AssignableTo(argValue.Elem().Type()) {
|
|
argValue.Elem().Set(r)
|
|
return true
|
|
}
|
|
if r.Type().Kind() != reflect.Pointer {
|
|
return true
|
|
}
|
|
|
|
r = r.Elem()
|
|
}
|
|
}
|
|
|
|
func (inst *Inst) missingHandlerInvokable(name string) missingHandlerInvokable {
|
|
return missingHandlerInvokable{name: name, handler: inst.missingBuiltinHandler}
|
|
}
|
|
|
|
type missingHandlerInvokable struct {
|
|
name string
|
|
handler MissingBuiltinHandler
|
|
}
|
|
|
|
func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
|
v, err := m.handler(ctx, m.name, CallArgs{args: args})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fromGoValue(v)
|
|
}
|
|
|
|
type Invokable struct {
|
|
inv invokable
|
|
eval evaluator
|
|
inst *Inst
|
|
ec *evalCtx
|
|
}
|
|
|
|
func (i Invokable) IsNil() bool {
|
|
return i.inv == nil
|
|
}
|
|
|
|
func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
|
|
if i.inv == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var err error
|
|
invArgs := invocationArgs{
|
|
eval: i.eval,
|
|
ec: i.ec,
|
|
inst: i.inst,
|
|
}
|
|
|
|
invArgs.args, err = slices.MapWithError(args, func(a any) (Object, error) {
|
|
return fromGoValue(a)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := i.inv.invoke(ctx, invArgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
goRes, ok := toGoValue(res)
|
|
if !ok {
|
|
return nil, errors.New("cannot convert result to Go Value")
|
|
}
|
|
return goRes, err
|
|
}
|