package dyn import ( "fmt" "slices" ) type Value struct { v any k Kind // List of locations this value is defined at. The first location in the slice // is the location returned by the `.Location()` method and is typically used // for reporting errors and warnings associated with the value. 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 } // InvalidValue is equal to the zero-value of Value. var InvalidValue = Value{ k: KindInvalid, } // NilValue is a convenient constant for a nil value. var NilValue = Value{ k: KindNil, } // V constructs a new Value with the given value. func V(v any) Value { return NewValue(v, []Location{}) } // NewValue constructs a new Value with the given value and location. func NewValue(v any, loc []Location) Value { switch vin := v.(type) { case map[string]Value: v = newMappingFromGoMap(vin) } return Value{ v: v, k: kindOf(v), // create a copy of the locations, so that mutations to the original slice // don't affect new value. l: slices.Clone(loc), } } // WithLocations returns a new Value with its location set to the given value. func (v Value) WithLocations(loc []Location) Value { return Value{ v: v.v, k: v.k, // create a copy of the locations, so that mutations to the original slice // don't affect new value. l: slices.Clone(loc), } } func (v Value) AppendLocationsFromValue(w Value) Value { return Value{ v: v.v, k: v.k, l: append(v.l, w.l...), } } func (v Value) Kind() Kind { return v.k } func (v Value) Value() any { return v.v } func (v Value) Locations() []Location { return v.l } func (v Value) Location() Location { if len(v.l) == 0 { return Location{} } return v.l[0] } func (v Value) IsValid() bool { return v.k != KindInvalid } func (v Value) AsAny() any { switch v.k { case KindInvalid: panic("invoked AsAny on invalid value") case KindMap: m := v.v.(Mapping) out := make(map[string]any, m.Len()) for _, pair := range m.pairs { pk := pair.Key pv := pair.Value out[pk.MustString()] = pv.AsAny() } return out case KindSequence: vv := v.v.([]Value) a := make([]any, len(vv)) for i, v := range vv { a[i] = v.AsAny() } return a 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: t := v.v.(Time) return t.Time() default: // Panic because we only want to deal with known types. panic(fmt.Sprintf("invalid kind: %d", v.k)) } } func (v Value) Get(key string) Value { m, ok := v.AsMap() if !ok { return InvalidValue } vv, ok := m.GetByString(key) if !ok { return InvalidValue } return vv } func (v Value) Index(i int) Value { s, ok := v.v.([]Value) if !ok { return InvalidValue } if i < 0 || i >= len(s) { return InvalidValue } return s[i] } func (v Value) MarkAnchor() Value { return Value{ v: v.v, k: v.k, l: v.l, anchor: true, } } func (v Value) IsAnchor() bool { return v.anchor } // 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 { return false } if !slices.Equal(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) 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] default: return v.v == w.v } }