Rename Operation to Function, plus some cleanup
This commit is contained in:
parent
7d0fcbf28a
commit
21c91731e9
235
vm/function.go
Normal file
235
vm/function.go
Normal file
@ -0,0 +1,235 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
"github.com/mediocregopher/ginger/graph"
|
||||
)
|
||||
|
||||
// Function is an entity which accepts an argument Value and performs some
|
||||
// internal processing on that argument to return a resultant Value.
|
||||
type Function interface {
|
||||
Perform(Value) Value
|
||||
}
|
||||
|
||||
// FunctionFunc is a function which implements the Function interface.
|
||||
type FunctionFunc func(Value) Value
|
||||
|
||||
// Perform calls the underlying FunctionFunc directly.
|
||||
func (f FunctionFunc) Perform(arg Value) Value {
|
||||
return f(arg)
|
||||
}
|
||||
|
||||
// Identity returns an Function which always returns the given Value,
|
||||
// regardless of the input argument.
|
||||
//
|
||||
// TODO this might not be the right name
|
||||
func Identity(val Value) Function {
|
||||
return FunctionFunc(func(Value) Value {
|
||||
return val
|
||||
})
|
||||
}
|
||||
|
||||
type graphFn struct {
|
||||
*gg.Graph
|
||||
scope Scope
|
||||
}
|
||||
|
||||
var (
|
||||
valNameIn = Value{Value: gg.Name("in")}
|
||||
valNameOut = Value{Value: gg.Name("out")}
|
||||
valNameIf = Value{Value: gg.Name("if")}
|
||||
valNameRecur = Value{Value: gg.Name("recur")}
|
||||
valNumberZero = Value{Value: gg.Number(0)}
|
||||
)
|
||||
|
||||
// FunctionFromGraph wraps the given Graph such that it can be used as an
|
||||
// Function. The given Scope determines what values outside of the Graph are
|
||||
// available for use within the Function.
|
||||
func FunctionFromGraph(g *gg.Graph, scope Scope) (Function, error) {
|
||||
|
||||
// edgeFn is distinct from a generic Function in that the Value passed into
|
||||
// Perform will _always_ be the value of "in" for the overall Function.
|
||||
//
|
||||
// edgeFns will wrap each other, passing "in" downwards to the leaf edgeFns.
|
||||
type edgeFn Function
|
||||
|
||||
var compileEdge func(*gg.OpenEdge) (edgeFn, error)
|
||||
|
||||
// TODO memoize?
|
||||
valToEdgeFn := func(val Value) (edgeFn, error) {
|
||||
|
||||
if val.Name == nil {
|
||||
return edgeFn(Identity(val)), nil
|
||||
}
|
||||
|
||||
name := *val.Name
|
||||
|
||||
if val.Equal(valNameIn) {
|
||||
return edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||
return inArg
|
||||
})), nil
|
||||
}
|
||||
|
||||
// TODO intercept if and recur?
|
||||
|
||||
edgesIn := g.ValueIns(val.Value)
|
||||
|
||||
if l := len(edgesIn); l == 0 {
|
||||
|
||||
val, err := scope.Resolve(name)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving name %q from the outer scope: %w", name, err)
|
||||
}
|
||||
|
||||
return edgeFn(Identity(val)), nil
|
||||
|
||||
} else if l != 1 {
|
||||
return nil, fmt.Errorf("resolved name %q to %d input edges, rather than one", name, l)
|
||||
}
|
||||
|
||||
edge := edgesIn[0]
|
||||
|
||||
return compileEdge(edge)
|
||||
}
|
||||
|
||||
// "out" resolves to more than a static value, treat the graph as a full
|
||||
// operation.
|
||||
|
||||
// thisFn is used to support recur. It will get filled in with the Function
|
||||
// which is returned by this function, once that Function is created.
|
||||
thisFn := new(Function)
|
||||
|
||||
compileEdge = func(edge *gg.OpenEdge) (edgeFn, error) {
|
||||
|
||||
return graph.MapReduce[gg.Value, gg.Value, edgeFn](
|
||||
edge,
|
||||
func(ggVal gg.Value) (edgeFn, error) {
|
||||
return valToEdgeFn(Value{Value: ggVal})
|
||||
},
|
||||
func(ggEdgeVal gg.Value, inEdgeFns []edgeFn) (edgeFn, error) {
|
||||
|
||||
if ggEdgeVal.Equal(valNameIf.Value) {
|
||||
|
||||
if len(inEdgeFns) != 3 {
|
||||
return nil, fmt.Errorf("'if' requires a 3-tuple argument")
|
||||
}
|
||||
|
||||
return edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||
|
||||
if pred := inEdgeFns[0].Perform(inArg); pred.Equal(valNumberZero) {
|
||||
return inEdgeFns[2].Perform(inArg)
|
||||
}
|
||||
|
||||
return inEdgeFns[1].Perform(inArg)
|
||||
|
||||
})), nil
|
||||
}
|
||||
|
||||
// "if" statements (above) are the only case where we want the
|
||||
// input edges to remain separated, otherwise they should always
|
||||
// be combined into a single edge whose value is a tuple. Do
|
||||
// that here.
|
||||
|
||||
inEdgeFn := inEdgeFns[0]
|
||||
|
||||
if len(inEdgeFns) > 1 {
|
||||
inEdgeFn = edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||
tupVals := make([]Value, len(inEdgeFns))
|
||||
|
||||
for i := range inEdgeFns {
|
||||
tupVals[i] = inEdgeFns[i].Perform(inArg)
|
||||
}
|
||||
|
||||
return Tuple(tupVals...)
|
||||
}))
|
||||
}
|
||||
|
||||
edgeVal := Value{Value: ggEdgeVal}
|
||||
|
||||
if edgeVal.IsZero() {
|
||||
return inEdgeFn, nil
|
||||
}
|
||||
|
||||
if edgeVal.Equal(valNameRecur) {
|
||||
return edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||
return (*thisFn).Perform(inEdgeFn.Perform(inArg))
|
||||
})), nil
|
||||
}
|
||||
|
||||
if edgeVal.Graph != nil {
|
||||
|
||||
opFromGraph, err := FunctionFromGraph(
|
||||
edgeVal.Graph,
|
||||
scope.NewScope(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("compiling graph to operation: %w", err)
|
||||
}
|
||||
|
||||
edgeVal = Value{Function: opFromGraph}
|
||||
}
|
||||
|
||||
// The Function is known at compile-time, so we can wrap it
|
||||
// directly into an edgeVal using the existing inEdgeFn as the
|
||||
// input.
|
||||
if edgeVal.Function != nil {
|
||||
return edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||
return edgeVal.Function.Perform(inEdgeFn.Perform(inArg))
|
||||
})), nil
|
||||
}
|
||||
|
||||
// the edgeVal is not an Function at compile time, and so
|
||||
// it must become one at runtime. We must resolve edgeVal to an
|
||||
// edgeFn as well (edgeEdgeFn), and then at runtime that is
|
||||
// given the inArg and (hopefully) the resultant Function is
|
||||
// called.
|
||||
|
||||
edgeEdgeFn, err := valToEdgeFn(edgeVal)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return edgeFn(FunctionFunc(func(inArg Value) Value {
|
||||
|
||||
runtimeEdgeVal := edgeEdgeFn.Perform(inArg)
|
||||
|
||||
if runtimeEdgeVal.Graph != nil {
|
||||
|
||||
runtimeFn, err := FunctionFromGraph(
|
||||
runtimeEdgeVal.Graph,
|
||||
scope.NewScope(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("compiling graph to operation: %v", err))
|
||||
}
|
||||
|
||||
runtimeEdgeVal = Value{Function: runtimeFn}
|
||||
}
|
||||
|
||||
if runtimeEdgeVal.Function == nil {
|
||||
panic("edge value must be an operation")
|
||||
}
|
||||
|
||||
return runtimeEdgeVal.Function.Perform(inEdgeFn.Perform(inArg))
|
||||
|
||||
})), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
graphFn, err := valToEdgeFn(valNameOut)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
*thisFn = Function(graphFn)
|
||||
|
||||
return Function(graphFn), nil
|
||||
}
|
235
vm/op.go
235
vm/op.go
@ -1,235 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
"github.com/mediocregopher/ginger/graph"
|
||||
)
|
||||
|
||||
// Operation is an entity which accepts an argument Value and performs some
|
||||
// internal processing on that argument to return a resultant Value.
|
||||
type Operation interface {
|
||||
Perform(Value) Value
|
||||
}
|
||||
|
||||
// OperationFunc is a function which implements the Operation interface.
|
||||
type OperationFunc func(Value) Value
|
||||
|
||||
// Perform calls the underlying OperationFunc directly.
|
||||
func (f OperationFunc) Perform(arg Value) Value {
|
||||
return f(arg)
|
||||
}
|
||||
|
||||
// Identity returns an Operation which always returns the given Value,
|
||||
// regardless of the input argument.
|
||||
//
|
||||
// TODO this might not be the right name
|
||||
func Identity(val Value) Operation {
|
||||
return OperationFunc(func(Value) Value {
|
||||
return val
|
||||
})
|
||||
}
|
||||
|
||||
type graphOp struct {
|
||||
*gg.Graph
|
||||
scope Scope
|
||||
}
|
||||
|
||||
var (
|
||||
valNameIn = Value{Value: gg.Name("in")}
|
||||
valNameOut = Value{Value: gg.Name("out")}
|
||||
valNameIf = Value{Value: gg.Name("if")}
|
||||
valNameRecur = Value{Value: gg.Name("recur")}
|
||||
valNumberZero = Value{Value: gg.Number(0)}
|
||||
)
|
||||
|
||||
// OperationFromGraph wraps the given Graph such that it can be used as an
|
||||
// Operation. The given Scope determines what values outside of the Graph are
|
||||
// available for use within the Operation.
|
||||
func OperationFromGraph(g *gg.Graph, scope Scope) (Operation, error) {
|
||||
|
||||
// edgeOp is distinct from a generic Operation in that the Value passed into
|
||||
// Perform will _always_ be the value of "in" for the overall Operation.
|
||||
//
|
||||
// edgeOps will wrap each other, passing "in" downwards to the leaf edgeOps.
|
||||
type edgeOp Operation
|
||||
|
||||
var compileEdge func(*gg.OpenEdge) (edgeOp, error)
|
||||
|
||||
// TODO memoize?
|
||||
valToEdgeOp := func(val Value) (edgeOp, error) {
|
||||
|
||||
if val.Name == nil {
|
||||
return edgeOp(Identity(val)), nil
|
||||
}
|
||||
|
||||
name := *val.Name
|
||||
|
||||
if val.Equal(valNameIn) {
|
||||
return edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
return inArg
|
||||
})), nil
|
||||
}
|
||||
|
||||
// TODO intercept if and recur?
|
||||
|
||||
edgesIn := g.ValueIns(val.Value)
|
||||
|
||||
if l := len(edgesIn); l == 0 {
|
||||
|
||||
val, err := scope.Resolve(name)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving name %q from the outer scope: %w", name, err)
|
||||
}
|
||||
|
||||
return edgeOp(Identity(val)), nil
|
||||
|
||||
} else if l != 1 {
|
||||
return nil, fmt.Errorf("resolved name %q to %d input edges, rather than one", name, l)
|
||||
}
|
||||
|
||||
edge := edgesIn[0]
|
||||
|
||||
return compileEdge(edge)
|
||||
}
|
||||
|
||||
// "out" resolves to more than a static value, treat the graph as a full
|
||||
// operation.
|
||||
|
||||
// thisOp is used to support recur. It will get filled in with the Operation
|
||||
// which is returned by this function, once that Operation is created.
|
||||
thisOp := new(Operation)
|
||||
|
||||
compileEdge = func(edge *gg.OpenEdge) (edgeOp, error) {
|
||||
|
||||
return graph.MapReduce[gg.Value, gg.Value, edgeOp](
|
||||
edge,
|
||||
func(ggVal gg.Value) (edgeOp, error) {
|
||||
return valToEdgeOp(Value{Value: ggVal})
|
||||
},
|
||||
func(ggEdgeVal gg.Value, inEdgeOps []edgeOp) (edgeOp, error) {
|
||||
|
||||
if ggEdgeVal.Equal(valNameIf.Value) {
|
||||
|
||||
if len(inEdgeOps) != 3 {
|
||||
return nil, fmt.Errorf("'if' requires a 3-tuple argument")
|
||||
}
|
||||
|
||||
return edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
|
||||
if pred := inEdgeOps[0].Perform(inArg); pred.Equal(valNumberZero) {
|
||||
return inEdgeOps[2].Perform(inArg)
|
||||
}
|
||||
|
||||
return inEdgeOps[1].Perform(inArg)
|
||||
|
||||
})), nil
|
||||
}
|
||||
|
||||
// "if" statements (above) are the only case where we want the
|
||||
// input edges to remain separated, otherwise they should always
|
||||
// be combined into a single edge whose value is a tuple. Do
|
||||
// that here.
|
||||
|
||||
inEdgeOp := inEdgeOps[0]
|
||||
|
||||
if len(inEdgeOps) > 1 {
|
||||
inEdgeOp = edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
tupVals := make([]Value, len(inEdgeOps))
|
||||
|
||||
for i := range inEdgeOps {
|
||||
tupVals[i] = inEdgeOps[i].Perform(inArg)
|
||||
}
|
||||
|
||||
return Tuple(tupVals...)
|
||||
}))
|
||||
}
|
||||
|
||||
edgeVal := Value{Value: ggEdgeVal}
|
||||
|
||||
if edgeVal.IsZero() {
|
||||
return inEdgeOp, nil
|
||||
}
|
||||
|
||||
if edgeVal.Equal(valNameRecur) {
|
||||
return edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
return (*thisOp).Perform(inEdgeOp.Perform(inArg))
|
||||
})), nil
|
||||
}
|
||||
|
||||
if edgeVal.Graph != nil {
|
||||
|
||||
opFromGraph, err := OperationFromGraph(
|
||||
edgeVal.Graph,
|
||||
scope.NewScope(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("compiling graph to operation: %w", err)
|
||||
}
|
||||
|
||||
edgeVal = Value{Operation: opFromGraph}
|
||||
}
|
||||
|
||||
// The Operation is known at compile-time, so we can wrap it
|
||||
// directly into an edgeVal using the existing inEdgeOp as the
|
||||
// input.
|
||||
if edgeVal.Operation != nil {
|
||||
return edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
return edgeVal.Operation.Perform(inEdgeOp.Perform(inArg))
|
||||
})), nil
|
||||
}
|
||||
|
||||
// the edgeVal is not an Operation at compile time, and so
|
||||
// it must become one at runtime. We must resolve edgeVal to an
|
||||
// edgeOp as well (edgeEdgeOp), and then at runtime that is
|
||||
// given the inArg and (hopefully) the resultant Operation is
|
||||
// called.
|
||||
|
||||
edgeEdgeOp, err := valToEdgeOp(edgeVal)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return edgeOp(OperationFunc(func(inArg Value) Value {
|
||||
|
||||
runtimeEdgeVal := edgeEdgeOp.Perform(inArg)
|
||||
|
||||
if runtimeEdgeVal.Graph != nil {
|
||||
|
||||
runtimeOp, err := OperationFromGraph(
|
||||
runtimeEdgeVal.Graph,
|
||||
scope.NewScope(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("compiling graph to operation: %v", err))
|
||||
}
|
||||
|
||||
runtimeEdgeVal = Value{Operation: runtimeOp}
|
||||
}
|
||||
|
||||
if runtimeEdgeVal.Operation == nil {
|
||||
panic("edge value must be an operation")
|
||||
}
|
||||
|
||||
return runtimeEdgeVal.Operation.Perform(inEdgeOp.Perform(inArg))
|
||||
|
||||
})), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
graphOp, err := valToEdgeOp(valNameOut)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
*thisOp = Operation(graphOp)
|
||||
|
||||
return Operation(graphOp), nil
|
||||
}
|
65
vm/scope.go
65
vm/scope.go
@ -2,8 +2,6 @@ package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
)
|
||||
|
||||
// Scope encapsulates a set of name->Value mappings.
|
||||
@ -62,66 +60,3 @@ func (s *scopeWith) Resolve(name string) (Value, error) {
|
||||
}
|
||||
return s.Scope.Resolve(name)
|
||||
}
|
||||
|
||||
type graphScope struct {
|
||||
*gg.Graph
|
||||
parent Scope
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
TODO I don't think this is actually necessary
|
||||
|
||||
// ScopeFromGraph returns a Scope which will use the given Graph for name
|
||||
// resolution.
|
||||
//
|
||||
// When a name is resolved, that name will be looked up in the Graph. The name's
|
||||
// vertex must have only a single OpenEdge leading to it. That edge will be
|
||||
// compiled into an Operation and returned.
|
||||
//
|
||||
// If a name does not appear in the Graph, then the given parent Scope will be
|
||||
// used to resolve that name. If the parent Scope is nil then an error is
|
||||
// returned.
|
||||
//
|
||||
// NewScope will return the parent scope, if one is given, or an empty ScopeMap
|
||||
// if not.
|
||||
func ScopeFromGraph(g *gg.Graph, parent Scope) Scope {
|
||||
return &graphScope{
|
||||
Graph: g,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *graphScope) Resolve(name string) (Value, error) {
|
||||
|
||||
var ggNameVal gg.Value
|
||||
ggNameVal.Name = &name
|
||||
|
||||
log.Printf("resolving %q", name)
|
||||
edgesIn := g.ValueIns(ggNameVal)
|
||||
|
||||
if l := len(edgesIn); l == 0 && g.parent != nil {
|
||||
|
||||
return g.parent.Resolve(name)
|
||||
|
||||
} else if l != 1 {
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"%q must have exactly one input edge, found %d input edges",
|
||||
name, l,
|
||||
)
|
||||
}
|
||||
|
||||
return CompileEdge(edgesIn[0], g)
|
||||
}
|
||||
|
||||
func (g *graphScope) NewScope() Scope {
|
||||
|
||||
if g.parent == nil {
|
||||
return ScopeMap{}
|
||||
}
|
||||
|
||||
return g.parent
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
)
|
||||
|
||||
func globalOp(fn func(Value) (Value, error)) Value {
|
||||
func globalFn(fn func(Value) (Value, error)) Value {
|
||||
return Value{
|
||||
Operation: OperationFunc(func(in Value) Value {
|
||||
Function: FunctionFunc(func(in Value) Value {
|
||||
res, err := fn(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -22,7 +22,7 @@ func globalOp(fn func(Value) (Value, error)) Value {
|
||||
// any operation in a ginger program.
|
||||
var GlobalScope = ScopeMap{
|
||||
|
||||
"add": globalOp(func(val Value) (Value, error) {
|
||||
"add": globalFn(func(val Value) (Value, error) {
|
||||
|
||||
var sum int64
|
||||
|
||||
@ -39,7 +39,7 @@ var GlobalScope = ScopeMap{
|
||||
|
||||
}),
|
||||
|
||||
"tupEl": globalOp(func(val Value) (Value, error) {
|
||||
"tupEl": globalFn(func(val Value) (Value, error) {
|
||||
|
||||
tup, i := val.Tuple[0], val.Tuple[1]
|
||||
|
||||
@ -47,7 +47,7 @@ var GlobalScope = ScopeMap{
|
||||
|
||||
}),
|
||||
|
||||
"isZero": globalOp(func(val Value) (Value, error) {
|
||||
"isZero": globalFn(func(val Value) (Value, error) {
|
||||
|
||||
if *val.Number == 0 {
|
||||
one := int64(1)
|
||||
|
22
vm/vm.go
22
vm/vm.go
@ -13,12 +13,12 @@ import (
|
||||
// ZeroValue is a Value with no fields set. It is equivalent to the 0-tuple.
|
||||
var ZeroValue Value
|
||||
|
||||
// Value extends a gg.Value to include Operations and Tuples as a possible
|
||||
// Value extends a gg.Value to include Functions and Tuples as a possible
|
||||
// types.
|
||||
type Value struct {
|
||||
gg.Value
|
||||
|
||||
Operation
|
||||
Function
|
||||
Tuple []Value
|
||||
}
|
||||
|
||||
@ -47,8 +47,8 @@ func (v Value) Equal(v2g graph.Value) bool {
|
||||
case !v.Value.IsZero() || !v2.Value.IsZero():
|
||||
return v.Value.Equal(v2.Value)
|
||||
|
||||
case v.Operation != nil || v2.Operation != nil:
|
||||
// for now we say that Operations can't be compared. This will probably
|
||||
case v.Function != nil || v2.Function != nil:
|
||||
// for now we say that Functions can't be compared. This will probably
|
||||
// get revisted later.
|
||||
return false
|
||||
|
||||
@ -76,10 +76,10 @@ func (v Value) String() string {
|
||||
|
||||
switch {
|
||||
|
||||
case v.Operation != nil:
|
||||
case v.Function != nil:
|
||||
|
||||
// We can try to get better strings for ops later
|
||||
return "<op>"
|
||||
return "<fn>"
|
||||
|
||||
case !v.Value.IsZero():
|
||||
return v.Value.String()
|
||||
@ -100,12 +100,6 @@ func (v Value) String() string {
|
||||
|
||||
}
|
||||
|
||||
func nameVal(n string) Value {
|
||||
var val Value
|
||||
val.Name = &n
|
||||
return val
|
||||
}
|
||||
|
||||
// EvaluateSource reads and parses the io.Reader as an operation, input is used
|
||||
// as the argument to the operation, and the resultant value is returned.
|
||||
//
|
||||
@ -119,11 +113,11 @@ func EvaluateSource(opSrc io.Reader, input Value, scope Scope) (Value, error) {
|
||||
return Value{}, err
|
||||
}
|
||||
|
||||
op, err := OperationFromGraph(g, scope.NewScope())
|
||||
fn, err := FunctionFromGraph(g, scope.NewScope())
|
||||
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
|
||||
return op.Perform(input), nil
|
||||
return fn.Perform(input), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user