lang: types: Misc function changes

This will probably change up again in the future, but this is what we
need for now to make lambdas work.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
This commit is contained in:
James Shubin
2023-09-25 16:39:22 -04:00
parent 8d63b98212
commit 5440ef7eb2
2 changed files with 102 additions and 99 deletions

View File

@@ -196,15 +196,15 @@ func ValueToExpr(val types.Value) (interfaces.Expr, error) {
Fields: fields, Fields: fields,
} }
case *types.FuncValue: //case *types.FuncValue:
// TODO: this particular case is particularly untested! // // TODO: this particular case is particularly untested!
expr = &ExprFunc{ // expr = &ExprFunc{
Title: "<func from ValueToExpr>", // TODO: change this? // Title: "<func from ValueToExpr>", // TODO: change this?
// TODO: symmetrically, it would have used x.Func() here // // TODO: symmetrically, it would have used x.Func() here
Values: []*types.FuncValue{ // Values: []*types.FuncValue{
x, // just one! // x, // just one!
}, // },
} // }
case *types.VariantValue: case *types.VariantValue:
// TODO: should this be allowed, or should we unwrap them? // TODO: should this be allowed, or should we unwrap them?

View File

@@ -54,7 +54,7 @@ type Value interface {
List() []Value List() []Value
Map() map[Value]Value // keys must all have same type, same for values Map() map[Value]Value // keys must all have same type, same for values
Struct() map[string]Value Struct() map[string]Value
Func() func([]Value) (Value, error) Func() interface{} // func(interfaces.Txn, []interfaces.Func) (interfaces.Func, error)
} }
// ValueOfGolang is a helper that takes a golang value, and produces the mcl // ValueOfGolang is a helper that takes a golang value, and produces the mcl
@@ -410,39 +410,39 @@ func Into(v Value, rv reflect.Value) error {
} }
return nil return nil
case *FuncValue: //case *FuncValue:
if err := mustInto(reflect.Func); err != nil { // if err := mustInto(reflect.Func); err != nil {
return err // return err
} // }
//
// wrap our function with the translation that is necessary // // wrap our function with the translation that is necessary
fn := func(args []reflect.Value) (results []reflect.Value) { // build // fn := func(args []reflect.Value) (results []reflect.Value) { // build
innerArgs := []Value{} // innerArgs := []Value{}
for _, x := range args { // for _, x := range args {
v, err := ValueOf(x) // reflect.Value -> Value // v, err := ValueOf(x) // reflect.Value -> Value
if err != nil { // if err != nil {
panic(fmt.Errorf("can't determine value of %+v", x)) // panic(fmt.Errorf("can't determine value of %+v", x))
} // }
innerArgs = append(innerArgs, v) // innerArgs = append(innerArgs, v)
} // }
result, err := v.V(innerArgs) // call it // result, err := v.V(innerArgs) // call it
if err != nil { // if err != nil {
// when calling our function with the Call method, then // // when calling our function with the Call method, then
// we get the error output and have a chance to decide // // we get the error output and have a chance to decide
// what to do with it, but when calling it from within // // what to do with it, but when calling it from within
// a normal golang function call, the error represents // // a normal golang function call, the error represents
// that something went horribly wrong, aka a panic... // // that something went horribly wrong, aka a panic...
panic(fmt.Errorf("function panic: %+v", err)) // panic(fmt.Errorf("function panic: %+v", err))
} // }
out := reflect.New(rv.Type().Out(0)) // out := reflect.New(rv.Type().Out(0))
// convert the lang result back to a Go value // // convert the lang result back to a Go value
if err := Into(result, out); err != nil { // if err := Into(result, out); err != nil {
panic(fmt.Errorf("function return conversion panic: %+v", err)) // panic(fmt.Errorf("function return conversion panic: %+v", err))
} // }
return []reflect.Value{out} // only one result // return []reflect.Value{out} // only one result
} // }
rv.Set(reflect.MakeFunc(rv.Type(), fn)) // rv.Set(reflect.MakeFunc(rv.Type(), fn))
return nil // return nil
case *VariantValue: case *VariantValue:
return Into(v.V, rv) return Into(v.V, rv)
@@ -506,7 +506,7 @@ func (obj *base) Struct() map[string]Value {
// Func represents the value of this type as a function if it is one. If this is // Func represents the value of this type as a function if it is one. If this is
// not a function, then this panics. // not a function, then this panics.
func (obj *base) Func() func([]Value) (Value, error) { func (obj *base) Func() interface{} {
panic("not a func") panic("not a func")
} }
@@ -1136,23 +1136,33 @@ func (obj *StructValue) Lookup(k string) (value Value, exists bool) {
return v, exists return v, exists
} }
// FuncValue represents a function value. The defined function takes a list of // FuncValue represents a function which takes a list of Value arguments and
// Value arguments and returns a Value. It can also return an error which could // returns a Value. It can also return an error which could represent that
// represent that something went horribly wrong. (Think, an internal panic.) // something went horribly wrong. (Think, an internal panic.)
//
// This is not general enough to represent all functions in the language (see
// the full.FuncValue), but it is a useful common case.
//
// FuncValue is not a Value, but it is a useful building block for implementing
// Func nodes.
type FuncValue struct { type FuncValue struct {
base base
V func([]Value) (Value, error) V func([]Value) (Value, error)
T *Type // contains ordered field types, arg names are a bonus part T *Type // contains ordered field types, arg names are a bonus part
} }
// NewFunc creates a new function with the specified type. // NewFunc creates a useless function which will get overwritten by something
// more useful later.
func NewFunc(t *Type) *FuncValue { func NewFunc(t *Type) *FuncValue {
if t.Kind != KindFunc { if t.Kind != KindFunc {
return nil // sanity check return nil // sanity check
} }
v := func([]Value) (Value, error) { v := func([]Value) (Value, error) {
return nil, fmt.Errorf("nil function") // TODO: is this correct? // You were not supposed to call the temporary function, you
// were supposed to replace it with a real implementation!
return nil, fmt.Errorf("nil function")
} }
// return an empty interface{}
return &FuncValue{ return &FuncValue{
V: v, V: v,
T: t, T: t,
@@ -1168,73 +1178,66 @@ func (obj *FuncValue) String() string {
func (obj *FuncValue) Type() *Type { return obj.T } func (obj *FuncValue) Type() *Type { return obj.T }
// Less compares to value and returns true if we're smaller. This panics if the // Less compares to value and returns true if we're smaller. This panics if the
// two types aren't the same. // two types aren't the same. In this situation, they can't be compared so we
// panic.
func (obj *FuncValue) Less(v Value) bool { func (obj *FuncValue) Less(v Value) bool {
V := v.(*FuncValue) //V := v.(*FuncValue)
return obj.String() < V.String() // FIXME: implement a proper less func //return obj.String() < V.String() // FIXME: implement a proper less func
panic("you cannot compare functions")
} }
// Cmp returns an error if this value isn't the same as the arg passed in. // Cmp returns an error if this value isn't the same as the arg passed in. In
// this situation, they can't be compared so we panic.
func (obj *FuncValue) Cmp(val Value) error { func (obj *FuncValue) Cmp(val Value) error {
if obj == nil || val == nil { //if obj == nil || val == nil {
return fmt.Errorf("cannot cmp to nil") // return fmt.Errorf("cannot cmp to nil")
} //}
if err := obj.Type().Cmp(val.Type()); err != nil { //if err := obj.Type().Cmp(val.Type()); err != nil {
return errwrap.Wrapf(err, "cannot cmp types") // return errwrap.Wrapf(err, "cannot cmp types")
} //}
//
return fmt.Errorf("cannot cmp funcs") // TODO: can we ? //return fmt.Errorf("cannot cmp funcs") // TODO: can we ?
panic("you cannot compare functions")
} }
// Copy returns a copy of this value. // Copy returns a copy of this value.
func (obj *FuncValue) Copy() Value { func (obj *FuncValue) Copy() Value {
return &FuncValue{ return &FuncValue{
V: obj.V, // FIXME: can we copy the function, or do we need to? V: obj.V,
T: obj.T.Copy(), T: obj.T.Copy(),
} }
} }
// Value returns the raw value of this type. // Value returns the raw value of this type.
func (obj *FuncValue) Value() interface{} { func (obj *FuncValue) Value() interface{} {
typ := obj.T.Reflect() //typ := obj.T.Reflect()
//
// wrap our function with the translation that is necessary //// wrap our function with the translation that is necessary
fn := func(args []reflect.Value) (results []reflect.Value) { // build //fn := func(args []reflect.Value) (results []reflect.Value) { // build
innerArgs := []Value{} // innerArgs := []Value{}
for _, x := range args { // for _, x := range args {
v, err := ValueOf(x) // reflect.Value -> Value // v, err := ValueOf(x) // reflect.Value -> Value
if err != nil { // if err != nil {
panic(fmt.Sprintf("can't determine value of %+v", x)) // panic(fmt.Sprintf("can't determine value of %+v", x))
} // }
innerArgs = append(innerArgs, v) // innerArgs = append(innerArgs, v)
} // }
result, err := obj.V(innerArgs) // call it // result, err := obj.V(innerArgs) // call it
if err != nil { // if err != nil {
// when calling our function with the Call method, then // // when calling our function with the Call method, then
// we get the error output and have a chance to decide // // we get the error output and have a chance to decide
// what to do with it, but when calling it from within // // what to do with it, but when calling it from within
// a normal golang function call, the error represents // // a normal golang function call, the error represents
// that something went horribly wrong, aka a panic... // // that something went horribly wrong, aka a panic...
panic(fmt.Sprintf("function panic: %+v", err)) // panic(fmt.Sprintf("function panic: %+v", err))
} // }
return []reflect.Value{reflect.ValueOf(result.Value())} // only one result // return []reflect.Value{reflect.ValueOf(result.Value())} // only one result
} //}
val := reflect.MakeFunc(typ, fn) //val := reflect.MakeFunc(typ, fn)
return val.Interface() //return val.Interface()
}
// Func represents the value of this type as a function if it is one. If this is
// not a function, then this panics.
func (obj *FuncValue) Func() func([]Value) (Value, error) {
return obj.V return obj.V
} }
// Set sets the function value to be a new function.
func (obj *FuncValue) Set(fn func([]Value) (Value, error)) error { // TODO: change method name?
obj.V = fn
return nil // TODO: can we do any sort of checking here?
}
// Call runs the function value and returns its result. It returns an error if // Call runs the function value and returns its result. It returns an error if
// something goes wrong during execution, and panic's if you call this with // something goes wrong during execution, and panic's if you call this with
// inappropriate input types, or if it returns an inappropriate output type. // inappropriate input types, or if it returns an inappropriate output type.
@@ -1385,6 +1388,6 @@ func (obj *VariantValue) Struct() map[string]Value {
// Func represents the value of this type as a function if it is one. If this is // Func represents the value of this type as a function if it is one. If this is
// not a function, then this panics. // not a function, then this panics.
func (obj *VariantValue) Func() func([]Value) (Value, error) { func (obj *VariantValue) Func() interface{} {
return obj.V.Func() return obj.V.Func()
} }