diff --git a/char.go b/char.go index 71f30b3..d725b11 100644 --- a/char.go +++ b/char.go @@ -23,33 +23,30 @@ func newChar( } } -func (p *charParser) nodeName() string { return p.name } -func (p *charParser) setNodeName(n string) { p.name = n } -func (p *charParser) nodeID() int { return p.id } -func (p *charParser) setID(id int) { p.id = id } -func (p *charParser) commitType() CommitType { return Alias } -func (p *charParser) setCommitType(ct CommitType) {} -func (p *charParser) validate(*registry, *idSet) error { return nil } -func (p *charParser) normalize(*registry, *idSet) error { return nil } +func (p *charParser) nodeName() string { return p.name } +func (p *charParser) setNodeName(n string) { p.name = n } +func (p *charParser) nodeID() int { return p.id } +func (p *charParser) setID(id int) { p.id = id } +func (p *charParser) commitType() CommitType { return Alias } +func (p *charParser) setCommitType(ct CommitType) {} +func (p *charParser) validate(*registry) error { return nil } +func (p *charParser) init(*registry) {} -func (p *charParser) init(r *registry) error { return nil } - -func (p *charParser) setIncludedBy(r *registry, includedBy int, parsers *idSet) error { - p.includedBy = appendIfMissing(p.includedBy, includedBy) - return nil -} - -func (p *charParser) parser(r *registry, parsers *idSet) (parser, error) { - if parsers.has(p.id) { - panic(cannotIncludeParsers(p.name)) +func (p *charParser) setIncludedBy(r *registry, includedBy int) { + if intsContain(p.includedBy, includedBy) { + return } + p.includedBy = append(p.includedBy, includedBy) +} + +func (p *charParser) parser(r *registry) parser { if _, ok := r.parser(p.name); ok { - return p, nil + return p } r.setParser(p) - return p, nil + return p } func (p *charParser) builder() builder { diff --git a/choice.go b/choice.go index 43ec7a1..f367ca5 100644 --- a/choice.go +++ b/choice.go @@ -1,12 +1,14 @@ package treerack type choiceDefinition struct { - name string - id int - commit CommitType - elements []string - includedBy []int - cbuilder *choiceBuilder + name string + id int + commit CommitType + elements []string + includedBy []int + cbuilder *choiceBuilder + validated bool + initialized bool } type choiceParser struct { @@ -40,30 +42,34 @@ func (d *choiceDefinition) setID(id int) { d.id = id } func (d *choiceDefinition) commitType() CommitType { return d.commit } func (d *choiceDefinition) setCommitType(ct CommitType) { d.commit = ct } -func (d *choiceDefinition) validate(r *registry, path *idSet) error { +func (d *choiceDefinition) validate(r *registry) error { + if d.validated { + return nil + } + + d.validated = true + for i := range d.elements { - if _, ok := r.definitions[d.elements[i]]; !ok { + e, ok := r.definitions[d.elements[i]] + if !ok { return parserNotFound(d.elements[i]) } + + if err := e.validate(r); err != nil { + return err + } } return nil } -func (d *choiceDefinition) normalize(r *registry, path *idSet) error { - if path.has(d.id) { - return nil +func (d *choiceDefinition) init(r *registry) { + if d.initialized { + return } - path.set(d.id) - for i := range d.elements { - r.definitions[d.elements[i]].normalize(r, path) - } + d.initialized = true - return nil -} - -func (d *choiceDefinition) init(r *registry) error { if d.cbuilder == nil { d.cbuilder = &choiceBuilder{ name: d.name, @@ -74,21 +80,19 @@ func (d *choiceDefinition) init(r *registry) error { } for _, e := range d.elements { - // TODO: handle undefined reference - d.cbuilder.elements = append(d.cbuilder.elements, r.definitions[e].builder()) + def := r.definitions[e] + d.cbuilder.elements = append(d.cbuilder.elements, def.builder()) + def.init(r) + def.setIncludedBy(r, d.id) } - - parsers := &idSet{} - parsers.set(d.id) - return setItemsIncludedBy(r, d.elements, d.id, parsers) } -func (d *choiceDefinition) setIncludedBy(r *registry, includedBy int, parsers *idSet) error { - if parsers.has(d.id) { - return nil +func (d *choiceDefinition) setIncludedBy(r *registry, includedBy int) { + if intsContain(d.includedBy, includedBy) { + return } - d.includedBy = appendIfMissing(d.includedBy, includedBy) + d.includedBy = append(d.includedBy, includedBy) if d.cbuilder == nil { d.cbuilder = &choiceBuilder{ @@ -101,18 +105,19 @@ func (d *choiceDefinition) setIncludedBy(r *registry, includedBy int, parsers *i d.cbuilder.includedBy.set(includedBy) - parsers.set(d.id) - return setItemsIncludedBy(r, d.elements, includedBy, parsers) + for _, e := range d.elements { + r.definitions[e].setIncludedBy(r, includedBy) + } } // TODO: // - it may be possible to initialize the parsers non-recursively // - maybe the whole definition, parser and builder can be united -func (d *choiceDefinition) parser(r *registry, parsers *idSet) (parser, error) { +func (d *choiceDefinition) parser(r *registry) parser { p, ok := r.parser(d.name) if ok { - return p, nil + return p } cp := &choiceParser{ @@ -125,8 +130,6 @@ func (d *choiceDefinition) parser(r *registry, parsers *idSet) (parser, error) { r.setParser(cp) var elements []parser - parsers.set(d.id) - defer parsers.unset(d.id) for _, e := range d.elements { element, ok := r.parser(e) if ok { @@ -134,16 +137,12 @@ func (d *choiceDefinition) parser(r *registry, parsers *idSet) (parser, error) { continue } - element, err := r.definitions[e].parser(r, parsers) - if err != nil { - return nil, err - } - + element = r.definitions[e].parser(r) elements = append(elements, element) } cp.elements = elements - return cp, nil + return cp } func (d *choiceDefinition) builder() builder { diff --git a/parse.go b/parse.go index 5a7d907..01a41d2 100644 --- a/parse.go +++ b/parse.go @@ -9,11 +9,10 @@ type definition interface { commitType() CommitType setCommitType(CommitType) setID(int) - validate(*registry, *idSet) error - normalize(*registry, *idSet) error - init(*registry) error - setIncludedBy(*registry, int, *idSet) error - parser(*registry, *idSet) (parser, error) + validate(*registry) error + init(*registry) + setIncludedBy(*registry, int) + parser(*registry) parser builder() builder } @@ -47,36 +46,6 @@ func intsContain(is []int, i int) bool { return false } -func appendIfMissing(is []int, i int) []int { - if intsContain(is, i) { - return is - } - - return append(is, i) -} - -func setItemsIncludedBy(r *registry, items []string, includedBy int, parsers *idSet) error { - for _, item := range items { - di, ok := r.definition(item) - if !ok { - return ErrNoParsersDefined - } - - di.setIncludedBy(r, includedBy, parsers) - } - - return nil -} - -func sequenceItemNames(items []SequenceItem) []string { - names := make([]string, len(items)) - for i := range items { - names[i] = items[i].Name - } - - return names -} - func parse(p parser, c *context) error { p.parse(c) if c.readErr != nil { diff --git a/registry.go b/registry.go index 497e348..c6f0fbd 100644 --- a/registry.go +++ b/registry.go @@ -8,13 +8,19 @@ type registry struct { parsers map[string]parser } -func newRegistry() *registry { - return ®istry{ +func newRegistry(defs ...definition) *registry { + r := ®istry{ ids: make(map[string]int), names: make(map[int]string), definitions: make(map[string]definition), parsers: make(map[string]parser), } + + for _, def := range defs { + r.setDefinition(def) + } + + return r } func (r *registry) definition(name string) (definition, bool) { @@ -45,3 +51,12 @@ func (r *registry) setDefinition(d definition) error { func (r *registry) setParser(p parser) { r.parsers[p.nodeName()] = p } + +func (r *registry) getDefinitions() []definition { + var defs []definition + for _, def := range r.definitions { + defs = append(defs, def) + } + + return defs +} diff --git a/sequence.go b/sequence.go index 6b7bc2e..30246ba 100644 --- a/sequence.go +++ b/sequence.go @@ -1,14 +1,16 @@ package treerack type sequenceDefinition struct { - name string - id int - commit CommitType - items []SequenceItem - includedBy []int - ranges [][]int - sbuilder *sequenceBuilder - allChars bool + name string + id int + commit CommitType + items []SequenceItem + includedBy []int + ranges [][]int + sbuilder *sequenceBuilder + allChars bool + validated bool + initialized bool } type sequenceParser struct { @@ -46,36 +48,22 @@ func (d *sequenceDefinition) setID(id int) { d.id = id } func (d *sequenceDefinition) commitType() CommitType { return d.commit } func (d *sequenceDefinition) setCommitType(ct CommitType) { d.commit = ct } -func (d *sequenceDefinition) validate(r *registry, path *idSet) error { - for i := range d.items { - if _, ok := r.definition(d.items[i].Name); !ok { - return parserNotFound(d.items[i].Name) - } - } - - return nil -} - -func (d *sequenceDefinition) normalizeItems() { - for i := range d.items { - if d.items[i].Min == 0 && d.items[i].Max == 0 { - d.items[i].Min, d.items[i].Max = 1, 1 - } else if d.items[i].Max == 0 { - d.items[i].Max = -1 - } - } -} - -func (d *sequenceDefinition) normalize(r *registry, path *idSet) error { - if path.has(d.id) { +func (d *sequenceDefinition) validate(r *registry) error { + if d.validated { return nil } - // d.normalizeItems() + d.validated = true - path.set(d.id) for i := range d.items { - r.definitions[d.items[i].Name].normalize(r, path) + ii, ok := r.definition(d.items[i].Name) + if !ok { + return parserNotFound(d.items[i].Name) + } + + if err := ii.validate(r); err != nil { + return err + } } return nil @@ -85,7 +73,21 @@ func (d *sequenceDefinition) includeItems() bool { return len(d.items) == 1 && d.items[0].Min == 1 && d.items[0].Max == 1 } -func (d *sequenceDefinition) init(r *registry) error { +func (d *sequenceDefinition) init(r *registry) { + if d.initialized { + return + } + + d.initialized = true + + for i := range d.items { + if d.items[i].Min == 0 && d.items[i].Max == 0 { + d.items[i].Min, d.items[i].Max = 1, 1 + } else if d.items[i].Max == 0 { + d.items[i].Max = -1 + } + } + if d.sbuilder == nil { d.sbuilder = &sequenceBuilder{ name: d.name, @@ -113,6 +115,8 @@ func (d *sequenceDefinition) init(r *registry) error { allChars = false } } + + def.init(r) } d.sbuilder.ranges = d.ranges @@ -120,20 +124,18 @@ func (d *sequenceDefinition) init(r *registry) error { d.allChars = allChars if !d.includeItems() { - return nil + return } - parsers := &idSet{} - parsers.set(d.id) - return setItemsIncludedBy(r, sequenceItemNames(d.items), d.id, parsers) + r.definitions[d.items[0].Name].setIncludedBy(r, d.id) } -func (d *sequenceDefinition) setIncludedBy(r *registry, includedBy int, parsers *idSet) error { - if parsers.has(d.id) { - return nil +func (d *sequenceDefinition) setIncludedBy(r *registry, includedBy int) { + if intsContain(d.includedBy, includedBy) { + return } - d.includedBy = appendIfMissing(d.includedBy, includedBy) + d.includedBy = append(d.includedBy, includedBy) if d.sbuilder == nil { d.sbuilder = &sequenceBuilder{ @@ -147,21 +149,16 @@ func (d *sequenceDefinition) setIncludedBy(r *registry, includedBy int, parsers d.sbuilder.includedBy.set(includedBy) if !d.includeItems() { - return nil + return } - parsers.set(d.id) - return setItemsIncludedBy(r, sequenceItemNames(d.items), includedBy, parsers) + r.definitions[d.items[0].Name].setIncludedBy(r, includedBy) } -func (d *sequenceDefinition) parser(r *registry, parsers *idSet) (parser, error) { - if parsers.has(d.id) { - panic(cannotIncludeParsers(d.name)) - } - +func (d *sequenceDefinition) parser(r *registry) parser { p, ok := r.parser(d.name) if ok { - return p, nil + return p } sp := &sequenceParser{ @@ -175,8 +172,6 @@ func (d *sequenceDefinition) parser(r *registry, parsers *idSet) (parser, error) r.setParser(sp) var items []parser - parsers.set(d.id) - defer parsers.unset(d.id) for _, item := range d.items { pi, ok := r.parser(item.Name) if ok { @@ -184,17 +179,13 @@ func (d *sequenceDefinition) parser(r *registry, parsers *idSet) (parser, error) continue } - pi, err := r.definitions[item.Name].parser(r, parsers) - if err != nil { - return nil, err - } - + pi = r.definitions[item.Name].parser(r) items = append(items, pi) } sp.items = items sp.ranges = d.ranges - return sp, nil + return sp } func (d *sequenceDefinition) builder() builder { diff --git a/syntax.go b/syntax.go index 4e6657c..10dbf89 100644 --- a/syntax.go +++ b/syntax.go @@ -234,35 +234,22 @@ func (s *Syntax) Init() error { return ErrRootWhitespace } - s.registry = initWhitespace(s.registry) + defs := s.registry.getDefinitions() - for _, def := range s.registry.definitions { - if def.commitType()&Root != 0 { - s.root = def - break - } + if hasWhitespace(defs) { + defs, s.root = applyWhitespace(defs) + s.registry = newRegistry(defs...) } - if err := s.root.validate(s.registry, &idSet{}); err != nil { - return err - } - - if err := s.root.normalize(s.registry, &idSet{}); err != nil { - return err - } - - for _, p := range s.registry.definitions { - p.init(s.registry) - } - - var err error - s.parser, err = s.root.parser(s.registry, &idSet{}) - if err != nil { + if err := s.root.validate(s.registry); err != nil { s.initFailed = true return err } + s.root.init(s.registry) + s.parser = s.root.parser(s.registry) s.builder = s.root.builder() + s.initialized = true return nil } diff --git a/whitespace.go b/whitespace.go index ff00779..efd8c82 100644 --- a/whitespace.go +++ b/whitespace.go @@ -12,9 +12,9 @@ func brokenRegistryError(err error) error { return fmt.Errorf("broken registry: %v", err) } -func splitWhitespaceDefs(all map[string]definition) ([]definition, []definition) { +func splitWhitespaceDefs(defs []definition) ([]definition, []definition) { var whitespaceDefs, nonWhitespaceDefs []definition - for _, def := range all { + for _, def := range defs { if def.commitType()&Whitespace != 0 { def.setCommitType(def.commitType() | Alias) whitespaceDefs = append(whitespaceDefs, def) @@ -54,14 +54,10 @@ func mergeWhitespaceDefs(ws []definition) definition { 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 @@ -113,7 +109,7 @@ func applyWhitespaceToSeq(s *sequenceDefinition) []definition { return defs } -func applyWhitespace(defs []definition) []definition { +func applyWhitespaceToDefs(defs []definition) []definition { var defsWS []definition for _, def := range defs { if def.commitType()&NoWhitespace != 0 { @@ -133,7 +129,7 @@ func applyWhitespace(defs []definition) []definition { return defsWS } -func applyWhitespaceRoot(root definition) (definition, definition) { +func applyWhitespaceToRoot(root definition) (definition, definition) { original, name := root, root.nodeName() wsName := patchName(name, "wsroot") @@ -158,30 +154,34 @@ func applyWhitespaceRoot(root definition) (definition, definition) { return original, root } -func registerPatched(r *registry, defs ...definition) { - for _, def := range defs { - if err := r.setDefinition(def); err != nil { - panic(brokenRegistryError(err)) +func hasWhitespace(defs []definition) bool { + for i := range defs { + if defs[i].commitType()&Whitespace != 0 { + return true } } + + return false } -func initWhitespace(r *registry) *registry { - whitespaceDefs, defs := splitWhitespaceDefs(r.definitions) - if len(whitespaceDefs) == 0 { - return r - } - +func applyWhitespace(defs []definition) ([]definition, definition) { + whitespaceDefs, defs := splitWhitespaceDefs(defs) whitespace := mergeWhitespaceDefs(whitespaceDefs) - defs = applyWhitespace(defs) + + defs = applyWhitespaceToDefs(defs) root, defs := splitRoot(defs) - originalRoot, root := applyWhitespaceRoot(root) + originalRoot, root := applyWhitespaceToRoot(root) - r = newRegistry() - registerPatched(r, whitespace) - registerPatched(r, whitespaceDefs...) - registerPatched(r, defs...) - registerPatched(r, originalRoot, root) - return r + defs = append( + append( + defs, + whitespaceDefs..., + ), + whitespace, + originalRoot, + root, + ) + + return defs, root }