Added subscript support for long var interpolation
- Modified long var interpolation to support dot lookups - Added a time:from-unix function and added time.Time as an object
This commit is contained in:
parent
d3938aec83
commit
ca95ac7008
29
ucl/ast.go
29
ucl/ast.go
|
@ -13,13 +13,24 @@ type astStringStringSpan struct {
|
||||||
Chars *string `parser:"@SingleChar"`
|
Chars *string `parser:"@SingleChar"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type astLongIdentDotSuffix struct {
|
||||||
|
KeyName *string `parser:"@LIIdent"`
|
||||||
|
Pipeline *astPipeline `parser:"| LILp @@ RP"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type astLongIdent struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
VarName string `parser:"@LIIdent"`
|
||||||
|
DotSuffix []astLongIdentDotSuffix `parser:"( LIDot @@ )*"`
|
||||||
|
}
|
||||||
|
|
||||||
type astDoubleStringSpan struct {
|
type astDoubleStringSpan struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
Chars *string `parser:"@Char"`
|
Chars *string `parser:"@Char"`
|
||||||
Escaped *string `parser:"| @Escaped"`
|
Escaped *string `parser:"| @Escaped"`
|
||||||
IdentRef *string `parser:"| @IdentRef"`
|
IdentRef *string `parser:"| @IdentRef"`
|
||||||
LongIdentRef *string `parser:"| @LongIdentRef"`
|
LongIdentRef *astLongIdent `parser:"| LongIdentRef @@ LIEnd"`
|
||||||
SubExpr *astPipeline `parser:"| StartSubExpr @@ RP"`
|
SubExpr *astPipeline `parser:"| StartSubExpr @@ RP"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type astDoubleString struct {
|
type astDoubleString struct {
|
||||||
|
@ -134,10 +145,16 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
||||||
{"Escaped", `\\.`, nil},
|
{"Escaped", `\\.`, nil},
|
||||||
{"StringEnd", `"`, lexer.Pop()},
|
{"StringEnd", `"`, lexer.Pop()},
|
||||||
{"IdentRef", `\$[-]*[a-zA-Z_][\w-]*`, nil},
|
{"IdentRef", `\$[-]*[a-zA-Z_][\w-]*`, nil},
|
||||||
{"LongIdentRef", `\$[{][^}]*[}]`, nil},
|
{"LongIdentRef", `\$[{]`, lexer.Push("LongIdent")},
|
||||||
{"StartSubExpr", `\$[(]`, lexer.Push("Root")},
|
{"StartSubExpr", `\$[(]`, lexer.Push("Root")},
|
||||||
{"Char", `[^$"\\]+`, nil},
|
{"Char", `[^$"\\]+`, nil},
|
||||||
},
|
},
|
||||||
|
"LongIdent": {
|
||||||
|
{"LIIdent", `[-]*[a-zA-Z_][\w-]*`, nil},
|
||||||
|
{"LIDot", `[.]`, nil},
|
||||||
|
{"LILp", `\(`, lexer.Push("Root")},
|
||||||
|
{"LIEnd", `\}`, lexer.Pop()},
|
||||||
|
},
|
||||||
"SingleString": {
|
"SingleString": {
|
||||||
{"SingleStringEnd", `'`, lexer.Pop()},
|
{"SingleStringEnd", `'`, lexer.Pop()},
|
||||||
{"SingleChar", `[^']+`, nil},
|
{"SingleChar", `[^']+`, nil},
|
||||||
|
|
26
ucl/builtins/time.go
Normal file
26
ucl/builtins/time.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Time() ucl.Module {
|
||||||
|
return ucl.Module{
|
||||||
|
Name: "time",
|
||||||
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
|
"from-unix": timeFromUnix,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeFromUnix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var ux int
|
||||||
|
|
||||||
|
if err := args.Bind(&ux); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Unix(int64(ux), 0).UTC(), nil
|
||||||
|
}
|
37
ucl/builtins/time_test.go
Normal file
37
ucl/builtins/time_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package builtins_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTime_FromUnix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "from unix 1", eval: `time:from-unix 0`, want: time.Unix(0, 0).UTC()},
|
||||||
|
{desc: "from unix 2", eval: `time:from-unix 0 | cat`, want: "1970-01-01T00:00:00Z"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Time()),
|
||||||
|
)
|
||||||
|
res, err := inst.Eval(context.Background(), tt.eval)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
40
ucl/eval.go
40
ucl/eval.go
|
@ -3,6 +3,7 @@ package ucl
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -299,10 +300,11 @@ func (e evaluator) interpolateDoubleQuotedString(ctx context.Context, ec *evalCt
|
||||||
sb.WriteString(v.String())
|
sb.WriteString(v.String())
|
||||||
}
|
}
|
||||||
case n.LongIdentRef != nil:
|
case n.LongIdentRef != nil:
|
||||||
identVal := (*n.LongIdentRef)[2 : len(*n.LongIdentRef)-1]
|
v, err := e.interpolateLongIdent(ctx, ec, n.LongIdentRef)
|
||||||
if v, ok := ec.getVar(identVal); ok && v != nil {
|
if err != nil {
|
||||||
sb.WriteString(v.String())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sb.WriteString(v)
|
||||||
case n.SubExpr != nil:
|
case n.SubExpr != nil:
|
||||||
res, err := e.evalPipeline(ctx, ec, n.SubExpr)
|
res, err := e.evalPipeline(ctx, ec, n.SubExpr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -316,6 +318,38 @@ func (e evaluator) interpolateDoubleQuotedString(ctx context.Context, ec *evalCt
|
||||||
return StringObject(sb.String()), nil
|
return StringObject(sb.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e evaluator) interpolateLongIdent(ctx context.Context, ec *evalCtx, n *astLongIdent) (_ string, err error) {
|
||||||
|
res, ok := ec.getVar(n.VarName)
|
||||||
|
if !ok {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dot := range n.DotSuffix {
|
||||||
|
if res == nil {
|
||||||
|
return "", errorWithPos{fmt.Errorf("attempt to get field from nil value '%v'", n.VarName), n.Pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx Object
|
||||||
|
if dot.KeyName != nil {
|
||||||
|
idx = StringObject(*dot.KeyName)
|
||||||
|
} else {
|
||||||
|
idx, err = e.evalPipeline(ctx, ec, dot.Pipeline)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = indexLookup(ctx, res, idx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return res.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) {
|
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) {
|
||||||
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -25,11 +25,15 @@ func TestInst_Eval(t *testing.T) {
|
||||||
{desc: "interpolate string 1", expr: `set what "world" ; firstarg "hello $what"`, want: "hello world"},
|
{desc: "interpolate string 1", expr: `set what "world" ; firstarg "hello $what"`, want: "hello world"},
|
||||||
{desc: "interpolate string 2", expr: `set what "world" ; set when "now" ; firstarg "$when, hello $what"`, want: "now, hello world"},
|
{desc: "interpolate string 2", expr: `set what "world" ; set when "now" ; firstarg "$when, hello $what"`, want: "now, hello world"},
|
||||||
{desc: "interpolate string 3", expr: `set what "world" ; set when "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"},
|
{desc: "interpolate string 3", expr: `set what "world" ; set when "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"},
|
||||||
{desc: "interpolate string 4", expr: `set "crazy var" "unknown" ; firstarg "hello ${crazy var}"`, want: "hello unknown"},
|
{desc: "interpolate string 4", expr: `set crazy [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"},
|
||||||
{desc: "interpolate string 5", expr: `set what "world" ; firstarg "hello $($what)"`, want: "hello world"},
|
{desc: "interpolate string 5", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"},
|
||||||
{desc: "interpolate string 6", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"},
|
{desc: "interpolate string 6", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"},
|
||||||
{desc: "interpolate string 7", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"},
|
{desc: "interpolate string 7", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"},
|
||||||
{desc: "interpolate string 8", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"},
|
{desc: "interpolate string 8", expr: `set words ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"},
|
||||||
|
{desc: "interpolate string 9", expr: `set what "world" ; firstarg "hello $($what)"`, want: "hello world"},
|
||||||
|
{desc: "interpolate string 10", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"},
|
||||||
|
{desc: "interpolate string 11", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"},
|
||||||
|
{desc: "interpolate string 12", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"},
|
||||||
|
|
||||||
// Sub-expressions
|
// Sub-expressions
|
||||||
{desc: "sub expression 1", expr: `firstarg (sjoin "hello")`, want: "hello"},
|
{desc: "sub expression 1", expr: `firstarg (sjoin "hello")`, want: "hello"},
|
||||||
|
|
161
ucl/objs.go
161
ucl/objs.go
|
@ -7,34 +7,35 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lmika/gopkgs/fp/slices"
|
"github.com/lmika/gopkgs/fp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type object interface {
|
type Object interface {
|
||||||
String() string
|
String() string
|
||||||
Truthy() bool
|
Truthy() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type listable interface {
|
type Listable interface {
|
||||||
Len() int
|
Len() int
|
||||||
Index(i int) object
|
Index(i int) Object
|
||||||
}
|
}
|
||||||
|
|
||||||
type hashable interface {
|
type hashable interface {
|
||||||
Len() int
|
Len() int
|
||||||
Value(k string) object
|
Value(k string) Object
|
||||||
Each(func(k string, v object) error) error
|
Each(func(k string, v Object) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type listObject []object
|
type listObject []Object
|
||||||
|
|
||||||
func (lo *listObject) Append(o object) {
|
func (lo *listObject) Append(o Object) {
|
||||||
*lo = append(*lo, o)
|
*lo = append(*lo, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s listObject) String() string {
|
func (s listObject) String() string {
|
||||||
return fmt.Sprintf("%v", []object(s))
|
return fmt.Sprintf("%v", []Object(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s listObject) Truthy() bool {
|
func (s listObject) Truthy() bool {
|
||||||
|
@ -45,11 +46,11 @@ func (s listObject) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s listObject) Index(i int) object {
|
func (s listObject) Index(i int) Object {
|
||||||
return s[i]
|
return s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
type hashObject map[string]object
|
type hashObject map[string]Object
|
||||||
|
|
||||||
func (s hashObject) String() string {
|
func (s hashObject) String() string {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
|
@ -78,11 +79,11 @@ func (s hashObject) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s hashObject) Value(k string) object {
|
func (s hashObject) Value(k string) Object {
|
||||||
return s[k]
|
return s[k]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s hashObject) Each(fn func(k string, v object) error) error {
|
func (s hashObject) Each(fn func(k string, v Object) error) error {
|
||||||
for k, v := range s {
|
for k, v := range s {
|
||||||
if err := fn(k, v); err != nil {
|
if err := fn(k, v); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -91,13 +92,13 @@ func (s hashObject) Each(fn func(k string, v object) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type strObject string
|
type StringObject string
|
||||||
|
|
||||||
func (s strObject) String() string {
|
func (s StringObject) String() string {
|
||||||
return string(s)
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s strObject) Truthy() bool {
|
func (s StringObject) Truthy() bool {
|
||||||
return string(s) != ""
|
return string(s) != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,16 +125,28 @@ func (b boolObject) Truthy() bool {
|
||||||
return bool(b)
|
return bool(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGoValue(obj object) (interface{}, bool) {
|
type timeObject time.Time
|
||||||
|
|
||||||
|
func (t timeObject) String() string {
|
||||||
|
return time.Time(t).Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timeObject) Truthy() bool {
|
||||||
|
return !time.Time(t).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGoValue(obj Object) (interface{}, bool) {
|
||||||
switch v := obj.(type) {
|
switch v := obj.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return nil, true
|
return nil, true
|
||||||
case strObject:
|
case StringObject:
|
||||||
return string(v), true
|
return string(v), true
|
||||||
case intObject:
|
case intObject:
|
||||||
return int(v), true
|
return int(v), true
|
||||||
case boolObject:
|
case boolObject:
|
||||||
return bool(v), true
|
return bool(v), true
|
||||||
|
case timeObject:
|
||||||
|
return time.Time(v), true
|
||||||
case listObject:
|
case listObject:
|
||||||
xs := make([]interface{}, 0, len(v))
|
xs := make([]interface{}, 0, len(v))
|
||||||
for _, va := range v {
|
for _, va := range v {
|
||||||
|
@ -157,42 +170,62 @@ func toGoValue(obj object) (interface{}, bool) {
|
||||||
case proxyObject:
|
case proxyObject:
|
||||||
return v.p, true
|
return v.p, true
|
||||||
case listableProxyObject:
|
case listableProxyObject:
|
||||||
return v.v.Interface(), true
|
return v.orig.Interface(), true
|
||||||
case structProxyObject:
|
case structProxyObject:
|
||||||
return v.v.Interface(), true
|
return v.orig.Interface(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromGoValue(v any) (object, error) {
|
func fromGoValue(v any) (Object, error) {
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
|
case Object:
|
||||||
|
return t, nil
|
||||||
case nil:
|
case nil:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case string:
|
case string:
|
||||||
return strObject(t), nil
|
return StringObject(t), nil
|
||||||
case int:
|
case int:
|
||||||
return intObject(t), nil
|
return intObject(t), nil
|
||||||
case bool:
|
case bool:
|
||||||
return boolObject(t), nil
|
return boolObject(t), nil
|
||||||
|
case time.Time:
|
||||||
|
return timeObject(t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromGoReflectValue(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromGoReflectValue(resVal reflect.Value) (Object, error) {
|
||||||
|
if !resVal.IsValid() {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resVal := reflect.ValueOf(v)
|
|
||||||
switch resVal.Kind() {
|
switch resVal.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return listableProxyObject{resVal}, nil
|
return listableProxyObject{v: resVal, orig: resVal}, nil
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return newStructProxyObject(resVal), nil
|
return newStructProxyObject(resVal, resVal), nil
|
||||||
|
case reflect.Pointer:
|
||||||
|
switch resVal.Elem().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return listableProxyObject{v: resVal.Elem(), orig: resVal}, nil
|
||||||
|
case reflect.Struct:
|
||||||
|
return newStructProxyObject(resVal.Elem(), resVal), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromGoReflectValue(resVal.Elem())
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxyObject{v}, nil
|
return proxyObject{resVal.Interface()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type macroArgs struct {
|
type macroArgs struct {
|
||||||
eval evaluator
|
eval evaluator
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
hasPipe bool
|
hasPipe bool
|
||||||
pipeArg object
|
pipeArg Object
|
||||||
ast *astCmd
|
ast *astCmd
|
||||||
argShift int
|
argShift int
|
||||||
}
|
}
|
||||||
|
@ -239,7 +272,7 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
|
func (ma macroArgs) evalArg(ctx context.Context, n int) (Object, error) {
|
||||||
if n >= len(ma.ast.Args[ma.argShift:]) {
|
if n >= len(ma.ast.Args[ma.argShift:]) {
|
||||||
return nil, errors.New("not enough arguments") // FIX
|
return nil, errors.New("not enough arguments") // FIX
|
||||||
}
|
}
|
||||||
|
@ -247,7 +280,7 @@ func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
|
||||||
return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
|
return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushScope bool) (object, error) {
|
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []Object, pushScope bool) (Object, error) {
|
||||||
obj, err := ma.evalArg(ctx, n)
|
obj, err := ma.evalArg(ctx, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -266,7 +299,7 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushSco
|
||||||
}
|
}
|
||||||
|
|
||||||
return ma.eval.evalBlock(ctx, ec, v.block)
|
return ma.eval.evalBlock(ctx, ec, v.block)
|
||||||
case strObject:
|
case StringObject:
|
||||||
iv := ma.ec.lookupInvokable(string(v))
|
iv := ma.ec.lookupInvokable(string(v))
|
||||||
if iv == nil {
|
if iv == nil {
|
||||||
return nil, errors.New("'" + string(v) + "' is not invokable")
|
return nil, errors.New("'" + string(v) + "' is not invokable")
|
||||||
|
@ -297,7 +330,7 @@ type invocationArgs struct {
|
||||||
eval evaluator
|
eval evaluator
|
||||||
inst *Inst
|
inst *Inst
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
args []object
|
args []Object
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*listObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +373,7 @@ func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
||||||
switch v := ia.args[i].(type) {
|
switch v := ia.args[i].(type) {
|
||||||
case invokable:
|
case invokable:
|
||||||
return v, nil
|
return v, nil
|
||||||
case strObject:
|
case StringObject:
|
||||||
iv := ia.ec.lookupInvokable(string(v))
|
iv := ia.ec.lookupInvokable(string(v))
|
||||||
if iv == nil {
|
if iv == nil {
|
||||||
return nil, errors.New("'" + string(v) + "' is not invokable")
|
return nil, errors.New("'" + string(v) + "' is not invokable")
|
||||||
|
@ -350,7 +383,7 @@ func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
||||||
return nil, errors.New("expected an invokable arg")
|
return nil, errors.New("expected an invokable arg")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) fork(args []object) invocationArgs {
|
func (ia invocationArgs) fork(args []Object) invocationArgs {
|
||||||
return invocationArgs{
|
return invocationArgs{
|
||||||
eval: ia.eval,
|
eval: ia.eval,
|
||||||
inst: ia.inst,
|
inst: ia.inst,
|
||||||
|
@ -373,27 +406,28 @@ func (ia invocationArgs) shift(i int) invocationArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokable is an object that can be executed as a command
|
// invokable is an Object that can be executed as a command
|
||||||
type invokable interface {
|
type invokable interface {
|
||||||
invoke(ctx context.Context, args invocationArgs) (object, error)
|
invoke(ctx context.Context, args invocationArgs) (Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type macroable interface {
|
type macroable interface {
|
||||||
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
invokeMacro(ctx context.Context, args macroArgs) (Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipeInvokable interface {
|
type pipeInvokable interface {
|
||||||
invokable
|
invokable
|
||||||
}
|
}
|
||||||
|
|
||||||
type invokableFunc func(ctx context.Context, args invocationArgs) (object, error)
|
type invokableFunc func(ctx context.Context, args invocationArgs) (Object, error)
|
||||||
|
|
||||||
func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return i(ctx, args)
|
return i(ctx, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
type blockObject struct {
|
type blockObject struct {
|
||||||
block *astBlock
|
block *astBlock
|
||||||
|
closedEC *evalCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bo blockObject) String() string {
|
func (bo blockObject) String() string {
|
||||||
|
@ -404,8 +438,8 @@ func (bo blockObject) Truthy() bool {
|
||||||
return len(bo.block.Statements) > 0
|
return len(bo.block.Statements) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
ec := args.ec.fork()
|
ec := bo.closedEC.fork()
|
||||||
for i, n := range bo.block.Names {
|
for i, n := range bo.block.Names {
|
||||||
if i < len(args.args) {
|
if i < len(args.args) {
|
||||||
ec.setOrDefineVar(n, args.args[i])
|
ec.setOrDefineVar(n, args.args[i])
|
||||||
|
@ -415,13 +449,13 @@ func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object,
|
||||||
return args.eval.evalBlock(ctx, ec, bo.block)
|
return args.eval.evalBlock(ctx, ec, bo.block)
|
||||||
}
|
}
|
||||||
|
|
||||||
type macroFunc func(ctx context.Context, args macroArgs) (object, error)
|
type macroFunc func(ctx context.Context, args macroArgs) (Object, error)
|
||||||
|
|
||||||
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) {
|
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
return i(ctx, args)
|
return i(ctx, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTruthy(obj object) bool {
|
func isTruthy(obj Object) bool {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -441,7 +475,8 @@ func (p proxyObject) Truthy() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
type listableProxyObject struct {
|
type listableProxyObject struct {
|
||||||
v reflect.Value
|
v reflect.Value
|
||||||
|
orig reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p listableProxyObject) String() string {
|
func (p listableProxyObject) String() string {
|
||||||
|
@ -456,7 +491,7 @@ func (p listableProxyObject) Len() int {
|
||||||
return p.v.Len()
|
return p.v.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p listableProxyObject) Index(i int) object {
|
func (p listableProxyObject) Index(i int) Object {
|
||||||
e, err := fromGoValue(p.v.Index(i).Interface())
|
e, err := fromGoValue(p.v.Index(i).Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -465,14 +500,16 @@ func (p listableProxyObject) Index(i int) object {
|
||||||
}
|
}
|
||||||
|
|
||||||
type structProxyObject struct {
|
type structProxyObject struct {
|
||||||
v reflect.Value
|
v reflect.Value
|
||||||
vf []reflect.StructField
|
orig reflect.Value
|
||||||
|
vf []reflect.StructField
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStructProxyObject(v reflect.Value) structProxyObject {
|
func newStructProxyObject(v reflect.Value, orig reflect.Value) structProxyObject {
|
||||||
return structProxyObject{
|
return structProxyObject{
|
||||||
v: v,
|
v: v,
|
||||||
vf: slices.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }),
|
orig: orig,
|
||||||
|
vf: slices.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +525,7 @@ func (s structProxyObject) Len() int {
|
||||||
return len(s.vf)
|
return len(s.vf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s structProxyObject) Value(k string) object {
|
func (s structProxyObject) Value(k string) Object {
|
||||||
f := s.v.FieldByName(k)
|
f := s.v.FieldByName(k)
|
||||||
if !f.IsValid() {
|
if !f.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
|
@ -508,7 +545,7 @@ func (s structProxyObject) Value(k string) object {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
func (s structProxyObject) Each(fn func(k string, v Object) error) error {
|
||||||
for _, f := range s.vf {
|
for _, f := range s.vf {
|
||||||
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -522,9 +559,25 @@ func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpaqueObject struct {
|
||||||
|
v any
|
||||||
|
}
|
||||||
|
|
||||||
|
func Opaque(v any) OpaqueObject {
|
||||||
|
return OpaqueObject{v: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p OpaqueObject) String() string {
|
||||||
|
return fmt.Sprintf("opaque{%T}", p.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p OpaqueObject) Truthy() bool {
|
||||||
|
return p.v != nil
|
||||||
|
}
|
||||||
|
|
||||||
type errBreak struct {
|
type errBreak struct {
|
||||||
isCont bool
|
isCont bool
|
||||||
ret object
|
ret Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errBreak) Error() string {
|
func (e errBreak) Error() string {
|
||||||
|
@ -535,7 +588,7 @@ func (e errBreak) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type errReturn struct {
|
type errReturn struct {
|
||||||
ret object
|
ret Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errReturn) Error() string {
|
func (e errReturn) Error() string {
|
||||||
|
|
Loading…
Reference in a new issue