Brian Picciano
4870455430
The new gg format is based on a BNF file which can be found in the `gg` directory. The code for decoding `.gg` files has been refactored to mirror that file. The result is more resilient parsing, better errors, and a greater ability to extend the format in the future. The new decoder is notable in that it does not use a lexer. Both lexing and parsing are done in a single step. The format syntax itself has also been modified. Rather than using semi-colons everywhere, commas are used as separators in tuples. Additionally the final comma/semi-colon is no longer required.
234 lines
5.7 KiB
Go
234 lines
5.7 KiB
Go
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
|
|
})
|
|
}
|
|
|
|
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.OptionalValue, gg.Value, edgeFn](
|
|
edge,
|
|
func(ggVal gg.Value) (edgeFn, error) {
|
|
return valToEdgeFn(Value{Value: ggVal})
|
|
},
|
|
func(ggEdgeVal gg.OptionalValue, 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...)
|
|
}))
|
|
}
|
|
|
|
var edgeVal Value
|
|
if ggEdgeVal.Valid {
|
|
edgeVal.Value = ggEdgeVal.Value
|
|
}
|
|
|
|
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
|
|
}
|