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"`
|
||||
}
|
||||
|
||||
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 {
|
||||
Pos lexer.Position
|
||||
Chars *string `parser:"@Char"`
|
||||
Escaped *string `parser:"| @Escaped"`
|
||||
IdentRef *string `parser:"| @IdentRef"`
|
||||
LongIdentRef *string `parser:"| @LongIdentRef"`
|
||||
SubExpr *astPipeline `parser:"| StartSubExpr @@ RP"`
|
||||
Chars *string `parser:"@Char"`
|
||||
Escaped *string `parser:"| @Escaped"`
|
||||
IdentRef *string `parser:"| @IdentRef"`
|
||||
LongIdentRef *astLongIdent `parser:"| LongIdentRef @@ LIEnd"`
|
||||
SubExpr *astPipeline `parser:"| StartSubExpr @@ RP"`
|
||||
}
|
||||
|
||||
type astDoubleString struct {
|
||||
|
@ -134,10 +145,16 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
|||
{"Escaped", `\\.`, nil},
|
||||
{"StringEnd", `"`, lexer.Pop()},
|
||||
{"IdentRef", `\$[-]*[a-zA-Z_][\w-]*`, nil},
|
||||
{"LongIdentRef", `\$[{][^}]*[}]`, nil},
|
||||
{"LongIdentRef", `\$[{]`, lexer.Push("LongIdent")},
|
||||
{"StartSubExpr", `\$[(]`, lexer.Push("Root")},
|
||||
{"Char", `[^$"\\]+`, nil},
|
||||
},
|
||||
"LongIdent": {
|
||||
{"LIIdent", `[-]*[a-zA-Z_][\w-]*`, nil},
|
||||
{"LIDot", `[.]`, nil},
|
||||
{"LILp", `\(`, lexer.Push("Root")},
|
||||
{"LIEnd", `\}`, lexer.Pop()},
|
||||
},
|
||||
"SingleString": {
|
||||
{"SingleStringEnd", `'`, lexer.Pop()},
|
||||
{"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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -299,10 +300,11 @@ func (e evaluator) interpolateDoubleQuotedString(ctx context.Context, ec *evalCt
|
|||
sb.WriteString(v.String())
|
||||
}
|
||||
case n.LongIdentRef != nil:
|
||||
identVal := (*n.LongIdentRef)[2 : len(*n.LongIdentRef)-1]
|
||||
if v, ok := ec.getVar(identVal); ok && v != nil {
|
||||
sb.WriteString(v.String())
|
||||
v, err := e.interpolateLongIdent(ctx, ec, n.LongIdentRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sb.WriteString(v)
|
||||
case n.SubExpr != nil:
|
||||
res, err := e.evalPipeline(ctx, ec, n.SubExpr)
|
||||
if err != nil {
|
||||
|
@ -316,6 +318,38 @@ func (e evaluator) interpolateDoubleQuotedString(ctx context.Context, ec *evalCt
|
|||
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) {
|
||||
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
||||
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 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 4", expr: `set "crazy var" "unknown" ; firstarg "hello ${crazy var}"`, want: "hello unknown"},
|
||||
{desc: "interpolate string 5", expr: `set what "world" ; firstarg "hello $($what)"`, want: "hello world"},
|
||||
{desc: "interpolate string 6", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"},
|
||||
{desc: "interpolate string 7", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"},
|
||||
{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 4", expr: `set crazy [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"},
|
||||
{desc: "interpolate string 5", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"},
|
||||
{desc: "interpolate string 6", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"},
|
||||
{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: `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
|
||||
{desc: "sub expression 1", expr: `firstarg (sjoin "hello")`, want: "hello"},
|
||||
|
|
161
ucl/objs.go
161
ucl/objs.go
|
@ -7,34 +7,35 @@ import (
|
|||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lmika/gopkgs/fp/slices"
|
||||
)
|
||||
|
||||
type object interface {
|
||||
type Object interface {
|
||||
String() string
|
||||
Truthy() bool
|
||||
}
|
||||
|
||||
type listable interface {
|
||||
type Listable interface {
|
||||
Len() int
|
||||
Index(i int) object
|
||||
Index(i int) Object
|
||||
}
|
||||
|
||||
type hashable interface {
|
||||
Len() int
|
||||
Value(k string) object
|
||||
Each(func(k string, v object) error) error
|
||||
Value(k string) Object
|
||||
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)
|
||||
}
|
||||
|
||||
func (s listObject) String() string {
|
||||
return fmt.Sprintf("%v", []object(s))
|
||||
return fmt.Sprintf("%v", []Object(s))
|
||||
}
|
||||
|
||||
func (s listObject) Truthy() bool {
|
||||
|
@ -45,11 +46,11 @@ func (s listObject) Len() int {
|
|||
return len(s)
|
||||
}
|
||||
|
||||
func (s listObject) Index(i int) object {
|
||||
func (s listObject) Index(i int) Object {
|
||||
return s[i]
|
||||
}
|
||||
|
||||
type hashObject map[string]object
|
||||
type hashObject map[string]Object
|
||||
|
||||
func (s hashObject) String() string {
|
||||
if len(s) == 0 {
|
||||
|
@ -78,11 +79,11 @@ func (s hashObject) Len() int {
|
|||
return len(s)
|
||||
}
|
||||
|
||||
func (s hashObject) Value(k string) object {
|
||||
func (s hashObject) Value(k string) Object {
|
||||
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 {
|
||||
if err := fn(k, v); err != nil {
|
||||
return err
|
||||
|
@ -91,13 +92,13 @@ func (s hashObject) Each(fn func(k string, v object) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type strObject string
|
||||
type StringObject string
|
||||
|
||||
func (s strObject) String() string {
|
||||
func (s StringObject) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s strObject) Truthy() bool {
|
||||
func (s StringObject) Truthy() bool {
|
||||
return string(s) != ""
|
||||
}
|
||||
|
||||
|
@ -124,16 +125,28 @@ func (b boolObject) Truthy() bool {
|
|||
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) {
|
||||
case nil:
|
||||
return nil, true
|
||||
case strObject:
|
||||
case StringObject:
|
||||
return string(v), true
|
||||
case intObject:
|
||||
return int(v), true
|
||||
case boolObject:
|
||||
return bool(v), true
|
||||
case timeObject:
|
||||
return time.Time(v), true
|
||||
case listObject:
|
||||
xs := make([]interface{}, 0, len(v))
|
||||
for _, va := range v {
|
||||
|
@ -157,42 +170,62 @@ func toGoValue(obj object) (interface{}, bool) {
|
|||
case proxyObject:
|
||||
return v.p, true
|
||||
case listableProxyObject:
|
||||
return v.v.Interface(), true
|
||||
return v.orig.Interface(), true
|
||||
case structProxyObject:
|
||||
return v.v.Interface(), true
|
||||
return v.orig.Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func fromGoValue(v any) (object, error) {
|
||||
func fromGoValue(v any) (Object, error) {
|
||||
switch t := v.(type) {
|
||||
case Object:
|
||||
return t, nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
case string:
|
||||
return strObject(t), nil
|
||||
return StringObject(t), nil
|
||||
case int:
|
||||
return intObject(t), nil
|
||||
case bool:
|
||||
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() {
|
||||
case reflect.Slice:
|
||||
return listableProxyObject{resVal}, nil
|
||||
return listableProxyObject{v: resVal, orig: resVal}, nil
|
||||
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 {
|
||||
eval evaluator
|
||||
ec *evalCtx
|
||||
hasPipe bool
|
||||
pipeArg object
|
||||
pipeArg Object
|
||||
ast *astCmd
|
||||
argShift int
|
||||
}
|
||||
|
@ -239,7 +272,7 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
|||
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:]) {
|
||||
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])
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
case strObject:
|
||||
case StringObject:
|
||||
iv := ma.ec.lookupInvokable(string(v))
|
||||
if iv == nil {
|
||||
return nil, errors.New("'" + string(v) + "' is not invokable")
|
||||
|
@ -297,7 +330,7 @@ type invocationArgs struct {
|
|||
eval evaluator
|
||||
inst *Inst
|
||||
ec *evalCtx
|
||||
args []object
|
||||
args []Object
|
||||
kwargs map[string]*listObject
|
||||
}
|
||||
|
||||
|
@ -340,7 +373,7 @@ func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
|||
switch v := ia.args[i].(type) {
|
||||
case invokable:
|
||||
return v, nil
|
||||
case strObject:
|
||||
case StringObject:
|
||||
iv := ia.ec.lookupInvokable(string(v))
|
||||
if iv == nil {
|
||||
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")
|
||||
}
|
||||
|
||||
func (ia invocationArgs) fork(args []object) invocationArgs {
|
||||
func (ia invocationArgs) fork(args []Object) invocationArgs {
|
||||
return invocationArgs{
|
||||
eval: ia.eval,
|
||||
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 {
|
||||
invoke(ctx context.Context, args invocationArgs) (object, error)
|
||||
invoke(ctx context.Context, args invocationArgs) (Object, error)
|
||||
}
|
||||
|
||||
type macroable interface {
|
||||
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
||||
invokeMacro(ctx context.Context, args macroArgs) (Object, error)
|
||||
}
|
||||
|
||||
type pipeInvokable interface {
|
||||
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)
|
||||
}
|
||||
|
||||
type blockObject struct {
|
||||
block *astBlock
|
||||
block *astBlock
|
||||
closedEC *evalCtx
|
||||
}
|
||||
|
||||
func (bo blockObject) String() string {
|
||||
|
@ -404,8 +438,8 @@ func (bo blockObject) Truthy() bool {
|
|||
return len(bo.block.Statements) > 0
|
||||
}
|
||||
|
||||
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
||||
ec := args.ec.fork()
|
||||
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
ec := bo.closedEC.fork()
|
||||
for i, n := range bo.block.Names {
|
||||
if i < len(args.args) {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func isTruthy(obj object) bool {
|
||||
func isTruthy(obj Object) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -441,7 +475,8 @@ func (p proxyObject) Truthy() bool {
|
|||
}
|
||||
|
||||
type listableProxyObject struct {
|
||||
v reflect.Value
|
||||
v reflect.Value
|
||||
orig reflect.Value
|
||||
}
|
||||
|
||||
func (p listableProxyObject) String() string {
|
||||
|
@ -456,7 +491,7 @@ func (p listableProxyObject) Len() int {
|
|||
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())
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -465,14 +500,16 @@ func (p listableProxyObject) Index(i int) object {
|
|||
}
|
||||
|
||||
type structProxyObject struct {
|
||||
v reflect.Value
|
||||
vf []reflect.StructField
|
||||
v reflect.Value
|
||||
orig reflect.Value
|
||||
vf []reflect.StructField
|
||||
}
|
||||
|
||||
func newStructProxyObject(v reflect.Value) structProxyObject {
|
||||
func newStructProxyObject(v reflect.Value, orig reflect.Value) structProxyObject {
|
||||
return structProxyObject{
|
||||
v: v,
|
||||
vf: slices.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }),
|
||||
v: v,
|
||||
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)
|
||||
}
|
||||
|
||||
func (s structProxyObject) Value(k string) object {
|
||||
func (s structProxyObject) Value(k string) Object {
|
||||
f := s.v.FieldByName(k)
|
||||
if !f.IsValid() {
|
||||
return nil
|
||||
|
@ -508,7 +545,7 @@ func (s structProxyObject) Value(k string) object {
|
|||
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 {
|
||||
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
||||
if err != nil {
|
||||
|
@ -522,9 +559,25 @@ func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
|||
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 {
|
||||
isCont bool
|
||||
ret object
|
||||
ret Object
|
||||
}
|
||||
|
||||
func (e errBreak) Error() string {
|
||||
|
@ -535,7 +588,7 @@ func (e errBreak) Error() string {
|
|||
}
|
||||
|
||||
type errReturn struct {
|
||||
ret object
|
||||
ret Object
|
||||
}
|
||||
|
||||
func (e errReturn) Error() string {
|
||||
|
|
Loading…
Reference in a new issue