stred-go

Stred: Streaming Tree Editor. Like sed but for JSON. This is the go implementation
git clone https://shtanton.xyz/git/stred-go.git
Log | Files | Refs | README

main.go (6597B)


      1 package subex
      2 
      3 import (
      4 	"main/walk"
      5 )
      6 
      7 type Transducer struct {
      8 	storeSize int
      9 	initialState SubexState
     10 }
     11 
     12 type StoreItem struct {}
     13 // Where slots are stored
     14 type Store []walk.OutputList
     15 // Return a new store with all the data from this one
     16 func (store Store) clone() Store {
     17 	newStore := make([]walk.OutputList, len(store))
     18 	copy(newStore, store)
     19 	return newStore
     20 }
     21 // Return a copy of this store but with an additional slot set
     22 func (store Store) withValue(key int, value walk.OutputList) Store {
     23 	newStore := store.clone()
     24 	newStore[key] = value
     25 	return newStore
     26 }
     27 
     28 type SlotMap struct {
     29 	nextId int
     30 	ids map[rune]int
     31 }
     32 func (m *SlotMap) getId(slot rune) int {
     33 	id, exists := m.ids[slot]
     34 	if exists {
     35 		return id
     36 	}
     37 	id = m.nextId
     38 	m.nextId++
     39 	m.ids[slot] = id
     40 	return id
     41 }
     42 
     43 // Compile the SubexAST into a transducer SubexState that can be run
     44 func CompileTransducer(transducerAst SubexAST) Transducer {
     45 	slotMap := SlotMap {
     46 		nextId: 0,
     47 		ids: make(map[rune]int),
     48 	}
     49 	initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, false)
     50 	return Transducer {
     51 		storeSize: slotMap.nextId,
     52 		initialState: initial,
     53 	}
     54 }
     55 
     56 // An immutable stack for outputting to
     57 type OutputStack struct {
     58 	head walk.OutputList
     59 	tail *OutputStack
     60 }
     61 func (stack OutputStack) pop() (walk.OutputList, OutputStack) {
     62 	return stack.head, *stack.tail
     63 }
     64 func (stack OutputStack) push(atoms walk.OutputList) OutputStack {
     65 	return OutputStack {
     66 		head: atoms,
     67 		tail: &stack,
     68 	}
     69 }
     70 func (stack OutputStack) replace(atoms walk.OutputList) OutputStack {
     71 	return OutputStack {
     72 		head: atoms,
     73 		tail: stack.tail,
     74 	}
     75 }
     76 func (stack OutputStack) peek() walk.OutputList {
     77 	return stack.head
     78 }
     79 
     80 func topAppend(outputStack OutputStack, values []walk.Value) OutputStack {
     81 	head, isValues := outputStack.peek().(walk.ValueList)
     82 	if !isValues {
     83 		panic("Tried to append a value to an output of non-values")
     84 	}
     85 	head = append(walk.ValueList{}, head...)
     86 	head = append(head, values...)
     87 	return outputStack.replace(head)
     88 }
     89 
     90 func topAppendRunes(outputStack OutputStack, runes walk.RuneList) OutputStack {
     91 	head, isRunes := outputStack.peek().(walk.RuneList)
     92 	if !isRunes {
     93 		panic("Tried to append runes to a non-rune list")
     94 	}
     95 	head = append(walk.RuneList{}, head...)
     96 	head = append(head, runes...)
     97 	return outputStack.replace(head)
     98 }
     99 
    100 // Addition state that goes along with a subex state in an execution branch
    101 type auxiliaryState struct {
    102 	// Content of slots in this branch
    103 	store Store
    104 	// The output stack. At the end of the program, whatever is on top of this will be output
    105 	// 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
    106 	outputStack OutputStack
    107 	// How deeply nested the current execution is inside of the overall value
    108 	// i.e. starts at zero, is incremented to one when entering an array
    109 	nesting int
    110 }
    111 
    112 func (aux auxiliaryState) cloneStore() auxiliaryState {
    113 	aux.store = aux.store.clone()
    114 	return aux
    115 }
    116 
    117 func (aux auxiliaryState) withValue(slot int, value walk.OutputList) auxiliaryState {
    118 	aux.store = aux.store.withValue(slot, value)
    119 	return aux
    120 }
    121 
    122 func (aux auxiliaryState) pushOutput(data walk.OutputList) auxiliaryState {
    123 	aux.outputStack = aux.outputStack.push(data)
    124 	return aux
    125 }
    126 
    127 func (aux auxiliaryState) popOutput() (walk.OutputList, auxiliaryState) {
    128 	data, output := aux.outputStack.pop()
    129 	aux.outputStack = output
    130 	return data, aux
    131 }
    132 
    133 func (aux auxiliaryState) topAppend(values walk.OutputList) auxiliaryState {
    134 	switch output := values.(type) {
    135 	case walk.ValueList:
    136 		aux.outputStack = topAppend(aux.outputStack, output)
    137 	case walk.RuneList:
    138 		aux.outputStack = topAppendRunes(aux.outputStack, output)
    139 	}
    140 	return aux
    141 }
    142 
    143 func (aux auxiliaryState) incNest() auxiliaryState {
    144 	aux.nesting++
    145 	return aux
    146 }
    147 
    148 func (aux auxiliaryState) decNest() auxiliaryState {
    149 	aux.nesting--
    150 	return aux
    151 }
    152 
    153 // One branch of subex execution
    154 type SubexBranch struct {
    155 	// State in this branch
    156 	state SubexState
    157 	// Axiliary state
    158 	aux auxiliaryState
    159 }
    160 // Read a single character and return all the branches resulting from this branch consuming it
    161 func (pair SubexBranch) eat(char walk.Edible) []SubexBranch {
    162 	return pair.state.eat(pair.aux, char)
    163 }
    164 func (pair SubexBranch) accepting() []OutputStack {
    165 	return pair.state.accepting(pair.aux)
    166 }
    167 
    168 func equalStates(left SubexBranch, right SubexBranch) bool {
    169 	// Only care about if they are the same pointer
    170 	return left.state == right.state && left.aux.nesting == right.aux.nesting
    171 }
    172 
    173 // If two branches have the same state, only the first has a chance of being successful
    174 // This function removes all of the pointless execution branches to save execution time
    175 func pruneStates(states []SubexBranch) []SubexBranch {
    176 	uniqueStates := 0
    177 	outer: for _, state := range states {
    178 		for i := 0; i < uniqueStates; i++ {
    179 			if equalStates(state, states[i]) {
    180 				continue outer
    181 			}
    182 		}
    183 		states[uniqueStates] = state
    184 		uniqueStates++
    185 	}
    186 	return states[:uniqueStates]
    187 }
    188 
    189 func processInput(states []SubexBranch, input walk.StructureIter, nesting int) []SubexBranch {
    190 	if len(states) == 0 {
    191 		return states
    192 	}
    193 	var tmp []SubexBranch
    194 	newStates := make([]SubexBranch, 0, 2)
    195 	for {
    196 		piece := input.Next()
    197 		if piece == nil {
    198 			break
    199 		}
    200 
    201 		// TODO: break if there are no states at this nesting level left
    202 		// TODO: What to do if there are remaining nested states after all pieces have been used?
    203 		for _, state := range states {
    204 			if state.aux.nesting == nesting {
    205 				newStates = append(newStates, state.eat(piece)...)
    206 			} else {
    207 				newStates = append(newStates, state)
    208 			}
    209 		}
    210 
    211 		structure, isStructure := piece.(walk.Structure)
    212 		if isStructure {
    213 			iter := structure.Iter()
    214 			newStates = processInput(newStates, iter, nesting + 1)
    215 		}
    216 
    217 		tmp = states
    218 		states = pruneStates(newStates)
    219 		newStates = tmp[:0]
    220 		if len(states) == 0 {
    221 			return states
    222 		}
    223 	}
    224 	return states
    225 }
    226 
    227 // Run the subex transducer
    228 func RunTransducer(transducer Transducer, input walk.ValueList) (output []walk.Value, err bool) {
    229 	states := []SubexBranch{{
    230 		state: transducer.initialState,
    231 		aux: auxiliaryState {
    232 			outputStack: OutputStack {
    233 				head: walk.ValueList{},
    234 				tail: nil,
    235 			},
    236 			store: make([]walk.OutputList, transducer.storeSize),
    237 			nesting: 0,
    238 		},
    239 	}}
    240 
    241 	states = processInput(states, walk.NewValueIter(input), 0)
    242 
    243 	for _, state := range states {
    244 		acceptingStacks := state.accepting()
    245 		for _, stack := range acceptingStacks {
    246 			output, isValues := stack.head.(walk.ValueList)
    247 			if !isValues {
    248 				panic("Tried to output a non-values list")
    249 			}
    250 			return output, false
    251 		}
    252 	}
    253 	return nil, true
    254 }