blob: 6bf121afc843002ff78ad828ed0242caac49b76f [file]
// 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) 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.
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 v.Domain.decode(rv)
}
func preDecode(rv reflect.Value, kind reflect.Kind, name string) (reflect.Value, error) {
if rv.Kind() == kind {
return rv, nil
}
if rv.Kind() == reflect.Pointer && rv.Type().Elem().Kind() == kind {
if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem()))
}
return rv.Elem(), nil
}
return reflect.Value{}, fmt.Errorf("cannot decode %s into %s", name, rv.Type())
}
// 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)
}
}