2023-12-22 13:20:45 +00:00
|
|
|
package dyn
|
2023-10-20 12:56:59 +00:00
|
|
|
|
2023-10-24 11:12:36 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
)
|
2023-10-20 12:56:59 +00:00
|
|
|
|
|
|
|
type Value struct {
|
|
|
|
v any
|
2023-10-24 11:12:36 +00:00
|
|
|
|
|
|
|
k Kind
|
2023-10-20 12:56:59 +00:00
|
|
|
l Location
|
|
|
|
|
|
|
|
// Whether or not this value is an anchor.
|
|
|
|
// If this node doesn't map to a type, we don't need to warn about it.
|
|
|
|
anchor bool
|
|
|
|
}
|
|
|
|
|
2024-01-05 13:02:04 +00:00
|
|
|
// InvalidValue is equal to the zero-value of Value.
|
|
|
|
var InvalidValue = Value{
|
|
|
|
k: KindInvalid,
|
|
|
|
}
|
|
|
|
|
|
|
|
// NilValue is a convenient constant for a nil value.
|
2023-10-24 11:12:36 +00:00
|
|
|
var NilValue = Value{
|
|
|
|
k: KindNil,
|
|
|
|
}
|
|
|
|
|
|
|
|
// V constructs a new Value with the given value.
|
|
|
|
func V(v any) Value {
|
|
|
|
return Value{
|
|
|
|
v: v,
|
|
|
|
k: kindOf(v),
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 12:56:59 +00:00
|
|
|
|
|
|
|
// NewValue constructs a new Value with the given value and location.
|
|
|
|
func NewValue(v any, loc Location) Value {
|
|
|
|
return Value{
|
|
|
|
v: v,
|
2023-10-24 11:12:36 +00:00
|
|
|
k: kindOf(v),
|
2023-10-20 12:56:59 +00:00
|
|
|
l: loc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-24 11:12:36 +00:00
|
|
|
func (v Value) Kind() Kind {
|
|
|
|
return v.k
|
|
|
|
}
|
|
|
|
|
2024-01-17 14:26:33 +00:00
|
|
|
func (v Value) Value() any {
|
|
|
|
return v.v
|
|
|
|
}
|
|
|
|
|
2023-10-20 12:56:59 +00:00
|
|
|
func (v Value) Location() Location {
|
|
|
|
return v.l
|
|
|
|
}
|
|
|
|
|
2023-11-24 13:21:47 +00:00
|
|
|
func (v Value) IsValid() bool {
|
|
|
|
return v.k != KindInvalid
|
|
|
|
}
|
|
|
|
|
2023-10-20 12:56:59 +00:00
|
|
|
func (v Value) AsAny() any {
|
2023-10-24 11:12:36 +00:00
|
|
|
switch v.k {
|
|
|
|
case KindInvalid:
|
|
|
|
panic("invoked AsAny on invalid value")
|
|
|
|
case KindMap:
|
|
|
|
vv := v.v.(map[string]Value)
|
|
|
|
m := make(map[string]any, len(vv))
|
2023-10-20 12:56:59 +00:00
|
|
|
for k, v := range vv {
|
|
|
|
m[k] = v.AsAny()
|
|
|
|
}
|
|
|
|
return m
|
2023-10-24 11:12:36 +00:00
|
|
|
case KindSequence:
|
|
|
|
vv := v.v.([]Value)
|
2023-10-20 12:56:59 +00:00
|
|
|
a := make([]any, len(vv))
|
|
|
|
for i, v := range vv {
|
|
|
|
a[i] = v.AsAny()
|
|
|
|
}
|
|
|
|
return a
|
2023-10-24 11:12:36 +00:00
|
|
|
case KindNil:
|
|
|
|
return v.v
|
|
|
|
case KindString:
|
|
|
|
return v.v
|
|
|
|
case KindBool:
|
|
|
|
return v.v
|
|
|
|
case KindInt:
|
|
|
|
return v.v
|
|
|
|
case KindFloat:
|
|
|
|
return v.v
|
|
|
|
case KindTime:
|
|
|
|
return v.v
|
2023-10-20 12:56:59 +00:00
|
|
|
default:
|
|
|
|
// Panic because we only want to deal with known types.
|
2023-10-24 11:12:36 +00:00
|
|
|
panic(fmt.Sprintf("invalid kind: %d", v.k))
|
2023-10-20 12:56:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Value) Get(key string) Value {
|
|
|
|
m, ok := v.AsMap()
|
|
|
|
if !ok {
|
|
|
|
return NilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
vv, ok := m[key]
|
|
|
|
if !ok {
|
|
|
|
return NilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
return vv
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Value) Index(i int) Value {
|
|
|
|
s, ok := v.v.([]Value)
|
|
|
|
if !ok {
|
|
|
|
return NilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
if i < 0 || i >= len(s) {
|
|
|
|
return NilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
return s[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Value) MarkAnchor() Value {
|
|
|
|
return Value{
|
|
|
|
v: v.v,
|
2023-10-24 11:12:36 +00:00
|
|
|
k: v.k,
|
2023-10-20 12:56:59 +00:00
|
|
|
l: v.l,
|
|
|
|
|
|
|
|
anchor: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Value) IsAnchor() bool {
|
|
|
|
return v.anchor
|
|
|
|
}
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
|
|
|
|
// eq is an internal only method that compares two values.
|
|
|
|
// It is used to determine if a value has changed during a visit.
|
|
|
|
// We need a custom implementation because maps and slices
|
|
|
|
// cannot be compared with the regular == operator.
|
|
|
|
func (v Value) eq(w Value) bool {
|
|
|
|
if v.k != w.k || v.l != w.l {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch v.k {
|
|
|
|
case KindMap:
|
|
|
|
// Compare pointers to the underlying map.
|
|
|
|
// This is safe because we don't allow maps to be mutated.
|
|
|
|
return &v.v == &w.v
|
|
|
|
case KindSequence:
|
|
|
|
vs := v.v.([]Value)
|
|
|
|
ws := w.v.([]Value)
|
2024-02-05 16:54:41 +00:00
|
|
|
lv := len(vs)
|
|
|
|
lw := len(ws)
|
|
|
|
// If both slices are empty, they are equal.
|
|
|
|
if lv == 0 && lw == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
// If they have different lengths, they are not equal.
|
|
|
|
if lv != lw {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// They are both non-empty and have the same length.
|
|
|
|
// Compare pointers to the underlying slice.
|
|
|
|
// This is safe because we don't allow slices to be mutated.
|
|
|
|
return &vs[0] == &ws[0]
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
default:
|
|
|
|
return v.v == w.v
|
|
|
|
}
|
|
|
|
}
|