blob: ffc25b8728bc512fb1ea7cb5e7a1cbaf613c9626 [file] [log] [blame]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unify
import (
"fmt"
"iter"
"reflect"
)
// A Value represents a structured, non-deterministic value consisting of
// strings, tuples of Values, and string-keyed maps of Values. A
// non-deterministic Value will also contain variables, which are resolved via
// an environment as part of a [Closure].
//
// For debugging, a Value can also track the source position it was read from in
// an input file, and its provenance from other Values.
type Value struct {
Domain Domain
// A Value has either a pos or parents (or neither).
pos *Pos
parents *[2]*Value
}
var (
topValue = &Value{Domain: Top{}}
bottomValue = &Value{Domain: nil}
)
// NewValue returns a new [Value] with the given domain and no position
// information.
func NewValue(d Domain) *Value {
return &Value{Domain: d}
}
// NewValuePos returns a new [Value] with the given domain at position p.
func NewValuePos(d Domain, p Pos) *Value {
return &Value{Domain: d, pos: &p}
}
// newValueFrom returns a new [Value] with the given domain that copies the
// position information of p.
func newValueFrom(d Domain, p *Value) *Value {
return &Value{Domain: d, pos: p.pos, parents: p.parents}
}
func unified(d Domain, p1, p2 *Value) *Value {
return &Value{Domain: d, parents: &[2]*Value{p1, p2}}
}
func (v *Value) Pos() Pos {
if v.pos == nil {
return Pos{}
}
return *v.pos
}
func (v *Value) PosString() string {
var b []byte
for root := range v.Provenance() {
if len(b) > 0 {
b = append(b, ' ')
}
b, _ = root.pos.AppendText(b)
}
return string(b)
}
func (v *Value) WhyNotExact() string {
if v.Domain == nil {
return "v.Domain is nil"
}
return v.Domain.WhyNotExact()
}
func (v *Value) Exact() bool {
if v.Domain == nil {
return false
}
return v.Domain.Exact()
}
// Decode decodes v into a Go value.
//
// v must be exact, except that it can include Top. into must be a pointer.
// [Def]s are decoded into structs. [Tuple]s are decoded into slices. [String]s
// are decoded into strings or ints. Any field can itself be a pointer to one of
// these types. Top can be decoded into a pointer-typed field and will set the
// field to nil. Anything else will allocate a value if necessary.
//
// Any type may implement [Decoder], in which case its DecodeUnified method will
// be called instead of using the default decoding scheme.
func (v *Value) Decode(into any) error {
rv := reflect.ValueOf(into)
if rv.Kind() != reflect.Pointer {
return fmt.Errorf("cannot decode into non-pointer %T", into)
}
return decodeReflect(v, rv.Elem())
}
func decodeReflect(v *Value, rv reflect.Value) error {
var ptr reflect.Value
if rv.Kind() == reflect.Pointer {
if rv.IsNil() {
// Transparently allocate through pointers, *except* for Top, which
// wants to set the pointer to nil.
//
// TODO: Drop this condition if I switch to an explicit Optional[T]
// or move the Top logic into Def.
if _, ok := v.Domain.(Top); !ok {
// Allocate the value to fill in, but don't actually store it in
// the pointer until we successfully decode.
ptr = rv
rv = reflect.New(rv.Type().Elem()).Elem()
}
} else {
rv = rv.Elem()
}
}
var err error
if reflect.PointerTo(rv.Type()).Implements(decoderType) {
// Use the custom decoder.
err = rv.Addr().Interface().(Decoder).DecodeUnified(v)
} else {
err = v.Domain.decode(rv)
}
if err == nil && ptr.IsValid() {
ptr.Set(rv.Addr())
}
return err
}
// Decoder can be implemented by types as a custom implementation of [Decode]
// for that type.
type Decoder interface {
DecodeUnified(v *Value) error
}
var decoderType = reflect.TypeOf((*Decoder)(nil)).Elem()
// Provenance iterates over all of the source Values that have contributed to
// this Value.
func (v *Value) Provenance() iter.Seq[*Value] {
return func(yield func(*Value) bool) {
var rec func(d *Value) bool
rec = func(d *Value) bool {
if d.pos != nil {
if !yield(d) {
return false
}
}
if d.parents != nil {
for _, p := range d.parents {
if !rec(p) {
return false
}
}
}
return true
}
rec(v)
}
}