-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreduce.go
145 lines (118 loc) · 4.23 KB
/
reduce.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package combinator
import (
"context"
"errors"
"slices"
)
var MaxFrames = 1000000
// Reduces the tree `tree` using basis `b`
func reduce(ctx context.Context, root *Tree, b Basis, applicativeOrder bool, frameCount int) (*Tree, error) {
if ctx.Err() != nil {
return nil, ctx.Err()
}
if frameCount > MaxFrames {
return nil, errors.New("loop detected")
}
if root.IsLeaf && !root.IsRoot {
return root, nil
}
// Our algorithm is outer-first (we attempt to rewrite before recursing
// into the left and right child)
newTree, err := rewrite(ctx, root, b, applicativeOrder, frameCount+1)
if err != nil {
return nil, err
}
if newTree.IsLeaf {
return newTree, nil
}
// Out algorithm is also left-first (we recurse the left subtree first)
newTreeLeft, err := reduce(ctx, newTree.Left, b, applicativeOrder, frameCount+1)
if err != nil {
return nil, err
}
newTree.Left = newTreeLeft
newTreeRight, err := reduce(ctx, newTree.Right, b, applicativeOrder, frameCount+1)
newTree.Right = newTreeRight
if err != nil {
return nil, err
}
return newTree, nil
}
func rewrite(ctx context.Context, root *Tree, b Basis, applicativeOrder bool, frameCount int) (*Tree, error) {
if ctx.Err() != nil {
return nil, ctx.Err()
}
if frameCount > MaxFrames {
return nil, errors.New("loop detected")
}
// Rewrite attempts use the left-most leaf of this subtree
leftMostLeaf := getLeftMostLeaf(root)
// Retrieve the left-most leaf's combinator if applicable
combinator, ok := findCombinator(leftMostLeaf.Leaf, b)
// How many variables does the combinator's definition require before rewriting makes sense?
numArgs := len(combinator.Arguments)
// The amount of nodes between the left-most leaf and the root of our subtree
numNodesToRoot := numNodesToRoot(leftMostLeaf, root)
// If we found a combinator, and have enough arguments, attempt a rewrite
if ok && numArgs <= numNodesToRoot {
// Construct new tree based off of combinator
combinatorRoot := parse(combinator.Definition)
// Get our list of arguments (all subtrees themselves)
argumentNodes := getNRightSiblings(leftMostLeaf, numArgs)
// Store a reference to the root of the subtree we are rewriting
var rewriteRoot *Tree
if len(argumentNodes) > 0 {
rewriteRoot = argumentNodes[len(argumentNodes)-1].Parent
} else {
rewriteRoot = leftMostLeaf
}
// Applicative order is when we reduce our arguments before applying our combinator
if applicativeOrder {
for i := 0; i < len(argumentNodes); i++ {
reducedArgumentNode, err := reduce(ctx, argumentNodes[i], b, applicativeOrder, frameCount+1)
if err != nil {
return nil, err
}
argumentNodes[i] = reducedArgumentNode
}
}
// Apply the combinator
combinatorRoot = apply(combinator, argumentNodes, combinatorRoot)
// Hook our post-rewrite subtree into the rest of the tree
if !rewriteRoot.IsRoot {
combinatorRoot.IsRoot = false
combinatorRoot.Parent = rewriteRoot.Parent
// Only set the parent's child if it's not our original root
if rewriteRoot != root {
combinatorRoot.Parent.Left = combinatorRoot
}
}
// Find the original root
originalRoot := getNthParent(combinatorRoot, numNodesToRoot-numArgs)
// Recursively rewrite
return rewrite(ctx, originalRoot, b, applicativeOrder, frameCount+1)
}
return root, nil
}
// Recursively pplies the arguments in `argumentNodes` to the Combinator.
func apply(combinator Combinator, argumentNodes []*Tree, combinatorRoot *Tree) *Tree {
if combinatorRoot.IsLeaf {
// Find the argument the combinator definition is referring to
index := slices.Index(combinator.Arguments, combinatorRoot.Leaf)
// For "improper" combinators. That is, combinators that are "defined" from other combinators,
// just return whatever was in the definition (likely another combinator)
if index == -1 {
return combinatorRoot
}
// Get a fresh copy of the argument to apply
arg := copy(argumentNodes[index])
// Set any root/parent info and return
arg.IsRoot = combinatorRoot.IsRoot
arg.Parent = combinatorRoot.Parent
return arg
}
// Recurse down the combinator definition
combinatorRoot.Left = apply(combinator, argumentNodes, combinatorRoot.Left)
combinatorRoot.Right = apply(combinator, argumentNodes, combinatorRoot.Right)
return combinatorRoot
}