<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/subex/main.go
blob: e2cec0b5229cd5e85d5135033ba0fef8b0e33b50 (plain)
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package subex

import (
	"main/walk"
)

type Transducer struct {
	storeSize int
	initialState SubexState
}

type StoreItem struct {}
// Where slots are stored
type Store []walk.OutputList
// Return a new store with all the data from this one
func (store Store) clone() Store {
	newStore := make([]walk.OutputList, len(store))
	copy(newStore, store)
	return newStore
}
// Return a copy of this store but with an additional slot set
func (store Store) withValue(key int, value walk.OutputList) Store {
	newStore := store.clone()
	newStore[key] = value
	return newStore
}

type SlotMap struct {
	nextId int
	ids map[rune]int
}
func (m *SlotMap) getId(slot rune) int {
	id, exists := m.ids[slot]
	if exists {
		return id
	}
	id = m.nextId
	m.nextId++
	m.ids[slot] = id
	return id
}

// Compile the SubexAST into a transducer SubexState that can be run
func CompileTransducer(transducerAst SubexAST) Transducer {
	slotMap := SlotMap {
		nextId: 0,
		ids: make(map[rune]int),
	}
	initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, false)
	return Transducer {
		storeSize: slotMap.nextId,
		initialState: initial,
	}
}

// An immutable stack for outputting to
type OutputStack struct {
	head walk.OutputList
	tail *OutputStack
}
func (stack OutputStack) pop() (walk.OutputList, OutputStack) {
	return stack.head, *stack.tail
}
func (stack OutputStack) push(atoms walk.OutputList) OutputStack {
	return OutputStack {
		head: atoms,
		tail: &stack,
	}
}
func (stack OutputStack) replace(atoms walk.OutputList) OutputStack {
	return OutputStack {
		head: atoms,
		tail: stack.tail,
	}
}
func (stack OutputStack) peek() walk.OutputList {
	return stack.head
}

func topAppend(outputStack OutputStack, values []walk.Value) OutputStack {
	head, isValues := outputStack.peek().(walk.ValueList)
	if !isValues {
		panic("Tried to append a value to an output of non-values")
	}
	head = append(walk.ValueList{}, head...)
	head = append(head, values...)
	return outputStack.replace(head)
}

func topAppendRunes(outputStack OutputStack, runes walk.RuneList) OutputStack {
	head, isRunes := outputStack.peek().(walk.RuneList)
	if !isRunes {
		panic("Tried to append runes to a non-rune list")
	}
	head = append(walk.RuneList{}, head...)
	head = append(head, runes...)
	return outputStack.replace(head)
}

// Addition state that goes along with a subex state in an execution branch
type auxiliaryState struct {
	// Content of slots in this branch
	store Store
	// The output stack. At the end of the program, whatever is on top of this will be output
	// States may push or pop to the stack as they wish, creating sort of a call stack that allows states to capture the output of other states
	outputStack OutputStack
	// How deeply nested the current execution is inside of the overall value
	// i.e. starts at zero, is incremented to one when entering an array
	nesting int
}

func (aux auxiliaryState) cloneStore() auxiliaryState {
	aux.store = aux.store.clone()
	return aux
}

func (aux auxiliaryState) withValue(slot int, value walk.OutputList) auxiliaryState {
	aux.store = aux.store.withValue(slot, value)
	return aux
}

func (aux auxiliaryState) pushOutput(data walk.OutputList) auxiliaryState {
	aux.outputStack = aux.outputStack.push(data)
	return aux
}

func (aux auxiliaryState) popOutput() (walk.OutputList, auxiliaryState) {
	data, output := aux.outputStack.pop()
	aux.outputStack = output
	return data, aux
}

func (aux auxiliaryState) topAppend(values walk.OutputList) auxiliaryState {
	switch output := values.(type) {
	case walk.ValueList:
		aux.outputStack = topAppend(aux.outputStack, output)
	case walk.RuneList:
		aux.outputStack = topAppendRunes(aux.outputStack, output)
	}
	return aux
}

func (aux auxiliaryState) incNest() auxiliaryState {
	aux.nesting++
	return aux
}

func (aux auxiliaryState) decNest() auxiliaryState {
	aux.nesting--
	return aux
}

// One branch of subex execution
type SubexBranch struct {
	// State in this branch
	state SubexState
	// Axiliary state
	aux auxiliaryState
}
// Read a single character and return all the branches resulting from this branch consuming it
func (pair SubexBranch) eat(char walk.Edible) []SubexBranch {
	return pair.state.eat(pair.aux, char)
}
func (pair SubexBranch) accepting() []OutputStack {
	return pair.state.accepting(pair.aux)
}

func equalStates(left SubexBranch, right SubexBranch) bool {
	// Only care about if they are the same pointer
	return left.state == right.state && left.aux.nesting == right.aux.nesting
}

// If two branches have the same state, only the first has a chance of being successful
// This function removes all of the pointless execution branches to save execution time
func pruneStates(states []SubexBranch) []SubexBranch {
	uniqueStates := 0
	outer: for _, state := range states {
		for i := 0; i < uniqueStates; i++ {
			if equalStates(state, states[i]) {
				continue outer
			}
		}
		states[uniqueStates] = state
		uniqueStates++
	}
	return states[:uniqueStates]
}

func processInput(states []SubexBranch, input walk.StructureIter, nesting int) []SubexBranch {
	if len(states) == 0 {
		return states
	}
	var tmp []SubexBranch
	newStates := make([]SubexBranch, 0, 2)
	for {
		piece := input.Next()
		if piece == nil {
			break
		}

		// TODO: break if there are no states at this nesting level left
		// TODO: What to do if there are remaining nested states after all pieces have been used?
		for _, state := range states {
			if state.aux.nesting == nesting {
				newStates = append(newStates, state.eat(piece)...)
			} else {
				newStates = append(newStates, state)
			}
		}

		structure, isStructure := piece.(walk.Structure)
		if isStructure {
			iter := structure.Iter()
			newStates = processInput(newStates, iter, nesting + 1)
		}

		tmp = states
		states = pruneStates(newStates)
		newStates = tmp[:0]
		if len(states) == 0 {
			return states
		}
	}
	return states
}

// Run the subex transducer
func RunTransducer(transducer Transducer, input walk.ValueList) (output []walk.Value, err bool) {
	states := []SubexBranch{{
		state: transducer.initialState,
		aux: auxiliaryState {
			outputStack: OutputStack {
				head: walk.ValueList{},
				tail: nil,
			},
			store: make([]walk.OutputList, transducer.storeSize),
			nesting: 0,
		},
	}}

	states = processInput(states, walk.NewValueIter(input), 0)

	for _, state := range states {
		acceptingStacks := state.accepting()
		for _, stack := range acceptingStacks {
			output, isValues := stack.head.(walk.ValueList)
			if !isValues {
				panic("Tried to output a non-values list")
			}
			return output, false
		}
	}
	return nil, true
}