treerack/whitespace.go
2017-10-29 01:14:31 +02:00

188 lines
4.0 KiB
Go

package treerack
import (
"fmt"
"strconv"
"strings"
)
const whitespaceName = ":ws"
func brokenRegistryError(err error) error {
return fmt.Errorf("broken registry: %v", err)
}
func splitWhitespaceDefs(all map[string]definition) ([]definition, []definition) {
var whitespaceDefs, nonWhitespaceDefs []definition
for _, def := range all {
if def.commitType()&Whitespace != 0 {
def.setCommitType(def.commitType() | Alias)
whitespaceDefs = append(whitespaceDefs, def)
continue
}
nonWhitespaceDefs = append(nonWhitespaceDefs, def)
}
return whitespaceDefs, nonWhitespaceDefs
}
func splitRoot(defs []definition) (definition, []definition) {
var (
root definition
rest []definition
)
for _, def := range defs {
if def.commitType()&Root != 0 {
root = def
continue
}
rest = append(rest, def)
}
return root, rest
}
func mergeWhitespaceDefs(ws []definition) definition {
var names []string
for _, def := range ws {
names = append(names, def.nodeName())
}
return newChoice(whitespaceName, Alias, names)
}
// TODO: validate min and max
func patchName(s ...string) string {
return strings.Join(s, ":")
}
// TODO: check what's more useful: update quantified char classes or not
func applyWhitespaceToSeq(s *sequenceDefinition) []definition {
var (
defs []definition
items []SequenceItem
)
whitespace := SequenceItem{Name: whitespaceName, Min: 0, Max: -1}
for i, item := range s.items {
if item.Max >= 0 && item.Max <= 1 {
if i > 0 {
items = append(items, whitespace)
}
items = append(items, item)
continue
}
singleItem := SequenceItem{Name: item.Name, Min: 1, Max: 1}
restName := patchName(item.Name, s.nodeName(), "wsrest", strconv.Itoa(i))
restDef := newSequence(restName, Alias, []SequenceItem{whitespace, singleItem})
defs = append(defs, restDef)
restItems := SequenceItem{Name: restName, Min: 0, Max: -1}
if item.Min > 0 {
restItems.Min = item.Min - 1
}
if item.Max > 0 {
restItems.Min = item.Max - 1
}
if item.Min > 0 {
if i > 0 {
items = append(items, whitespace)
}
items = append(items, singleItem, restItems)
continue
}
optName := patchName(item.Name, s.nodeName(), "wsopt", strconv.Itoa(i))
optDef := newSequence(optName, Alias, []SequenceItem{whitespace, singleItem, restItems})
defs = append(defs, optDef)
items = append(items, SequenceItem{Name: optName, Min: 0, Max: 1})
}
s = newSequence(s.nodeName(), s.commitType(), items)
defs = append(defs, s)
return defs
}
func applyWhitespace(defs []definition) []definition {
var defsWS []definition
for _, def := range defs {
if def.commitType()&NoWhitespace != 0 {
defsWS = append(defsWS, def)
continue
}
seq, ok := def.(*sequenceDefinition)
if !ok {
defsWS = append(defsWS, def)
continue
}
defsWS = append(defsWS, applyWhitespaceToSeq(seq)...)
}
return defsWS
}
func applyWhitespaceRoot(root definition) (definition, definition) {
original, name := root, root.nodeName()
wsName := patchName(name, "wsroot")
original.setNodeName(wsName)
original.setCommitType(original.commitType() &^ Root)
original.setCommitType(original.commitType() | Alias)
root = newSequence(name, Root, []SequenceItem{{
Name: whitespaceName,
Min: 0,
Max: -1,
}, {
Name: wsName,
Min: 1,
Max: 1,
}, {
Name: whitespaceName,
Min: 0,
Max: -1,
}})
return original, root
}
func registerPatched(r *registry, defs ...definition) {
for _, def := range defs {
if err := r.setDefinition(def); err != nil {
panic(brokenRegistryError(err))
}
}
}
func initWhitespace(r *registry) *registry {
whitespaceDefs, defs := splitWhitespaceDefs(r.definitions)
if len(whitespaceDefs) == 0 {
return r
}
whitespace := mergeWhitespaceDefs(whitespaceDefs)
defs = applyWhitespace(defs)
root, defs := splitRoot(defs)
originalRoot, root := applyWhitespaceRoot(root)
r = newRegistry()
registerPatched(r, whitespace)
registerPatched(r, whitespaceDefs...)
registerPatched(r, defs...)
registerPatched(r, originalRoot, root)
return r
}