ginger/gg/gg.go
Brian Picciano c5aa582226 Improve semantics of tokens and values obtained from them.
Now gg.Values can carry the token used to parse them, which will be
useful later when generating errors.
2021-12-28 09:49:02 -07:00

347 lines
6.8 KiB
Go

// Package gg implements ginger graph creation, traversal, and (de)serialization
package gg
import (
"fmt"
"strings"
)
// ZeroValue is a Value with no fields set.
var ZeroValue Value
// Value represents a value being stored in a Graph.
type Value struct {
// Only one of these fields may be set
Name *string
Number *int64
Graph *Graph
// TODO coming soon!
// String *string
// Optional fields indicating the token which was used to construct this
// Value, if any.
LexerToken *LexerToken
}
// IsZero returns true if the Value is the zero value (none of the sub-value
// fields are set). LexerToken is ignored for this check.
func (v Value) IsZero() bool {
v.LexerToken = nil
return v == Value{}
}
// Equal returns true if the passed in Value is equivalent.
func (v Value) Equal(v2 Value) bool {
switch {
case v.IsZero() && v2.IsZero():
return true
case v.Name != nil && v2.Name != nil && *v.Name == *v2.Name:
return true
case v.Number != nil && v2.Number != nil && *v.Number == *v2.Number:
return true
case v.Graph != nil && v2.Graph != nil && Equal(v.Graph, v2.Graph):
return true
default:
return false
}
}
func (v Value) String() string {
switch {
case v.IsZero():
return "<zero>"
case v.Name != nil:
return *v.Name
case v.Number != nil:
return fmt.Sprint(*v.Number)
case v.Graph != nil:
return v.Graph.String()
default:
panic("unknown value kind")
}
}
// VertexType enumerates the different possible vertex types.
type VertexType string
const (
// ValueVertex is a Vertex which contains exactly one value and has at least
// one edge (either input or output).
ValueVertex VertexType = "val"
// TupleVertex is a Vertex which contains two or more in edges and
// exactly one out edge
//
// TODO ^ what about 0 or 1 in edges?
TupleVertex VertexType = "tup"
)
////////////////////////////////////////////////////////////////////////////////
// OpenEdge is an un-realized Edge which can't be used for anything except
// constructing graphs. It has no meaning on its own.
type OpenEdge struct {
fromV vertex
val Value
}
// WithEdgeVal returns a copy of the OpenEdge with the edge value replaced by
// the given one.
func (oe OpenEdge) WithEdgeVal(val Value) OpenEdge {
oe.val = val
return oe
}
func (oe OpenEdge) String() string {
return fmt.Sprintf("%s(%s, %s)", oe.fromV.VertexType, oe.fromV.String(), oe.val.String())
}
// ValueOut creates a OpenEdge which, when used to construct a Graph, represents
// an edge (with edgeVal attached to it) coming from the ValueVertex containing
// val.
func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{fromV: mkVertex(ValueVertex, val), val: edgeVal}
}
// TupleOut creates an OpenEdge which, when used to construct a Graph,
// represents an edge (with edgeVal attached to it) coming from the
// TupleVertex comprised of the given ordered-set of input edges.
//
// If len(ins) == 1 && edgeVal.IsZero(), then that single OpenEdge is
// returned as-is.
func TupleOut(ins []OpenEdge, edgeVal Value) OpenEdge {
if len(ins) == 1 {
if edgeVal.IsZero() {
return ins[0]
}
if ins[0].val.IsZero() {
return ins[0].WithEdgeVal(edgeVal)
}
}
return OpenEdge{
fromV: mkVertex(TupleVertex, ZeroValue, ins...),
val: edgeVal,
}
}
func (oe OpenEdge) equal(oe2 OpenEdge) bool {
return oe.val.Equal(oe2.val) && oe.fromV.equal(oe2.fromV)
}
type vertex struct {
VertexType
val Value
tup []OpenEdge
}
func mkVertex(typ VertexType, val Value, tupIns ...OpenEdge) vertex {
return vertex{
VertexType: typ,
val: val,
tup: tupIns,
}
}
func (v vertex) equal(v2 vertex) bool {
if v.VertexType != v2.VertexType {
return false
}
if v.VertexType == ValueVertex {
return v.val.Equal(v2.val)
}
if len(v.tup) != len(v2.tup) {
return false
}
for i := range v.tup {
if !v.tup[i].equal(v2.tup[i]) {
return false
}
}
return true
}
func (v vertex) String() string {
switch v.VertexType {
case ValueVertex:
return v.val.String()
case TupleVertex:
strs := make([]string, len(v.tup))
for i := range v.tup {
strs[i] = v.tup[i].String()
}
return fmt.Sprintf("[%s]", strings.Join(strs, ", "))
default:
panic("unknown vertix kind")
}
}
type graphValueIn struct {
val Value
edges []OpenEdge
}
func (valIn graphValueIn) cp() graphValueIn {
cp := valIn
cp.edges = make([]OpenEdge, len(valIn.edges))
copy(cp.edges, valIn.edges)
return valIn
}
func (valIn graphValueIn) equal(valIn2 graphValueIn) bool {
if !valIn.val.Equal(valIn2.val) {
return false
}
if len(valIn.edges) != len(valIn2.edges) {
return false
}
outer:
for _, edge := range valIn.edges {
for _, edge2 := range valIn2.edges {
if edge.equal(edge2) {
continue outer
}
}
return false
}
return true
}
// Graph is an immutable container of a set of vertices. The Graph keeps track
// of all Values which terminate an OpenEdge (which may be a tree of Value/Tuple
// vertices).
//
// NOTE The current implementation of Graph is incredibly inefficient, there's
// lots of O(N) operations, unnecessary copying on changes, and duplicate data
// in memory.
type Graph struct {
valIns []graphValueIn
}
// ZeroGraph is the root empty graph, and is the base off which all graphs are
// built.
var ZeroGraph = &Graph{}
func (g *Graph) cp() *Graph {
cp := &Graph{
valIns: make([]graphValueIn, len(g.valIns)),
}
copy(cp.valIns, g.valIns)
return cp
}
func (g *Graph) String() string {
var strs []string
for _, valIn := range g.valIns {
for _, oe := range valIn.edges {
strs = append(
strs,
fmt.Sprintf("valIn(%s, %s)", oe.String(), valIn.val.String()),
)
}
}
return fmt.Sprintf("graph(%s)", strings.Join(strs, ", "))
}
func (g *Graph) valIn(val Value) graphValueIn {
for _, valIn := range g.valIns {
if valIn.val.Equal(val) {
return valIn
}
}
return graphValueIn{val: val}
}
// AddValueIn takes a OpenEdge and connects it to the Value Vertex containing
// val, returning the new Graph which reflects that connection. Any Vertices
// referenced within toe OpenEdge which do not yet exist in the Graph will also
// be created in this step.
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
valIn := g.valIn(val)
for _, existingOE := range valIn.edges {
if existingOE.equal(oe) {
return g
}
}
valIn = valIn.cp()
valIn.edges = append(valIn.edges, oe)
g = g.cp()
for i, existingValIn := range g.valIns {
if existingValIn.val.Equal(val) {
g.valIns[i] = valIn
return g
}
}
g.valIns = append(g.valIns, valIn)
return g
}
// Equal returns whether or not the two Graphs are equivalent in value.
func Equal(g1, g2 *Graph) bool {
if len(g1.valIns) != len(g2.valIns) {
return false
}
outer:
for _, valIn1 := range g1.valIns {
for _, valIn2 := range g2.valIns {
if valIn1.equal(valIn2) {
continue outer
}
}
return false
}
return true
}