// Copyright 2026 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 specexpr import ( "fmt" "iter" "reflect" "strings" "sync" ) type Expr interface { String() string eval(b *Bindings) (any, error) preorder(yield func(Expr) bool) bool } // exprVars yields all Variable nodes in an Expr. func exprVars(e Expr) iter.Seq[Variable] { return func(yield func(Variable) bool) { e.preorder(func(e Expr) bool { if v, ok := e.(Variable); ok { return yield(v) } return true }) } } // Literal is an [Expr] that evaluates to a literal value. // // For [Int] and [SymbolicWidth], you probably just want to use those types // directly. They're literal values, so you could wrap them in a Literal, but // they are valid expressions on their own. type Literal struct { Val any } func (e *Literal) String() string { return fmt.Sprint(e.Val) } func (e *Literal) eval(b *Bindings) (any, error) { if i, ok := e.Val.(int); ok { // The evaluator works with Nums, not ints directly. return Int(i), nil } return e.Val, nil } func (e *Literal) preorder(yield func(Expr) bool) bool { return yield(e) } // Variable is an [Expr] that evaluates to the value of the named variable. type Variable string func (e Variable) String() string { return string(e) } func (e Variable) eval(b *Bindings) (any, error) { val := b.Get(e) if val == nil { panic(fmt.Errorf("variable %s not solved", e)) } return val, nil } func (e Variable) preorder(yield func(Expr) bool) bool { return yield(e) } // Func is a function that can be used in an expression. Use the [Func.Apply] // method to create an [Expr]. type Func struct { Name string Func func([]any) (any, error) } func MakeFunc1[T any](name string, fn func(T) (any, error)) func(e Expr) *Apply { f := &Func{ Name: name, Func: func(a []any) (any, error) { if len(a) != 1 { panic(fmt.Sprintf("%s: got %d arguments, want %d", name, len(a), 1)) } v, ok := a[0].(T) if !ok { panic(fmt.Sprintf("%s: argument is %T, want %T", name, a[0], *new(T))) } return fn(v) }, } return func(e Expr) *Apply { return f.Apply(e) } } func MakeFunc2[T, U any](name string, fn func(T, U) (any, error)) func(e1, e2 Expr) *Apply { f := &Func{ Name: name, Func: func(a []any) (any, error) { if len(a) != 2 { panic(fmt.Sprintf("%s: got %d arguments, want %d", name, len(a), 2)) } v1, ok := a[0].(T) if !ok { panic(fmt.Sprintf("%s: argument is %T, want %T", name, a[0], *new(T))) } v2, ok := a[1].(U) if !ok { panic(fmt.Sprintf("%s: argument is %T, want %T", name, a[1], *new(U))) } return fn(v1, v2) }, } return func(e1, e2 Expr) *Apply { return f.Apply(e1, e2) } } func MakeFunc(name string, fn func([]any) (any, error)) *Func { return &Func{name, fn} } type fieldGetterKey struct { rt reflect.Type fieldName string } var fieldGetters sync.Map // MakeField returns a Func that projects field fieldName from a value of type // T. The returned functions are memoized, so only one *Func is created per type // and field and this is efficient to call repeatedly. func MakeField[T any](fieldName string) *Func { rt := reflect.TypeFor[T]() key := fieldGetterKey{rt, fieldName} get, ok := fieldGetters.Load(key) if !ok { f, ok := rt.FieldByName(fieldName) if !ok { panic(fmt.Sprintf("no such field %s in type %s", fieldName, rt)) } getF := MakeFunc(rt.Name()+"."+fieldName, func(a []any) (any, error) { if len(a) != 1 { panic("expected exactly 1 argument") } var rv reflect.Value if _, ok := a[0].(T); ok { rv = reflect.ValueOf(a[0]) } else if _, ok := a[0].(*T); ok { rv = reflect.ValueOf(a[0]).Elem() } else { panic(fmt.Sprintf("argument is %T, want %s", a[0], rt)) } return rv.FieldByIndex(f.Index).Interface(), nil }) get, _ = fieldGetters.LoadOrStore(key, getF) } return get.(*Func) } func (f *Func) Apply(args ...Expr) *Apply { return &Apply{f, args} } // Apply is an [Expr] that applies a [Func] to a sequence of arguments. type Apply struct { Func *Func Args []Expr } func (e *Apply) String() string { var buf strings.Builder buf.WriteString(e.Func.Name) buf.WriteByte('(') for i, x := range e.Args { if i > 0 { buf.WriteString(", ") } buf.WriteString(x.String()) } buf.WriteByte(')') return buf.String() } func (e *Apply) eval(b *Bindings) (any, error) { vals := make([]any, 0, 16) for _, arg := range e.Args { val, err := arg.eval(b) if err != nil { return nil, err } vals = append(vals, val) } return e.Func.Func(vals) } func (e *Apply) preorder(yield func(Expr) bool) bool { if !yield(e) { return false } for _, a := range e.Args { if !a.preorder(yield) { return false } } return true } // BinExpr is a binary [Expr]. type BinExpr struct { Op BinOp X, Y Expr } func (e *BinExpr) String() string { op := "???" if int(e.Op) < len(opStrings) && opStrings[e.Op] != "" { op = opStrings[e.Op] } return e.X.String() + op + e.Y.String() } func (e *BinExpr) preorder(yield func(Expr) bool) bool { return yield(e) && e.X.preorder(yield) && e.Y.preorder(yield) } type BinOp byte const ( _ BinOp = iota OpTimes // int = int * int or Width = int * Width (or Width * int) OpDiv // int = int / int (must be exact) or Width = Width / int // Comparison operators OpEqual // bool = expr = expr OpNotEqual // bool = expr != expr OpGreaterThan // bool = expr > expr OpLessThan // bool = expr < expr OpGreaterOrEqual // bool = expr >= expr OpLessOrEqual // bool = expr <= expr ) var opStrings = [...]string{ OpTimes: "*", OpDiv: "/", OpEqual: "=", OpNotEqual: "!=", OpGreaterThan: ">", OpLessThan: "<", OpGreaterOrEqual: ">=", OpLessOrEqual: "<=", } func (e *BinExpr) eval(b *Bindings) (any, error) { xVal, err := e.X.eval(b) if err != nil { return nil, err } yVal, err := e.Y.eval(b) if err != nil { return nil, err } xn, okX := xVal.(Num) yn, okY := yVal.(Num) switch e.Op { case OpEqual, OpNotEqual: if okX && okY { // Fall through to numeric operations break } // Otherwise, general equality if reflect.TypeOf(xVal) != reflect.TypeOf(yVal) { panic(fmt.Errorf("incompatible types for comparison: %T and %T", xVal, yVal)) } if e.Op == OpEqual { return xVal == yVal, nil } else { return xVal != yVal, nil } } if !okX { panic(fmt.Errorf("invalid type for %v: %v (%T)", e.Op, xVal, xVal)) } if !okY { panic(fmt.Errorf("invalid type for %v: %v (%T)", e.Op, yVal, yVal)) } switch e.Op { case OpTimes: return xn.Mul(yn) case OpDiv: return xn.Div(yn) case OpEqual, OpNotEqual, OpGreaterThan, OpLessThan, OpGreaterOrEqual, OpLessOrEqual: return e.evalComparison(xn, yn) } panic("bad binop") } func (e *BinExpr) evalComparison(x, y Num) (any, error) { res, ok := x.Compare(y) if !ok { // Incomparable return e.Op == OpNotEqual, nil } switch e.Op { case OpEqual: return res == 0, nil case OpNotEqual: return res != 0, nil case OpGreaterThan: return res > 0, nil case OpLessThan: return res < 0, nil case OpGreaterOrEqual: return res >= 0, nil case OpLessOrEqual: return res <= 0, nil } panic("bad comparison operator") }