package parse type choiceDefinition struct { name string commit CommitType elements []string } type choiceParser struct { name string commit CommitType elements []parser including []parser } func newChoice(name string, ct CommitType, elements []string) *choiceDefinition { return &choiceDefinition{ name: name, commit: ct, elements: elements, } } func (d *choiceDefinition) nodeName() string { return d.name } // could store and cache everything that it fulfils func (d *choiceDefinition) parser(r *registry, path []string) (parser, error) { p, ok := r.parser(d.name) if ok { return p, nil } cp := &choiceParser{ name: d.name, commit: d.commit, } r.setParser(cp) var elements []parser path = append(path, d.name) for _, e := range d.elements { element, ok := r.parser(e) if ok { elements = append(elements, element) element.setIncludedBy(cp, path) continue } elementDefinition, ok := r.definition(e) if !ok { return nil, parserNotFound(e) } element, err := elementDefinition.parser(r, path) if err != nil { return nil, err } element.setIncludedBy(cp, path) elements = append(elements, element) } cp.elements = elements return cp, nil } func (d *choiceDefinition) commitType() CommitType { return d.commit } func (p *choiceParser) nodeName() string { return p.name } func (p *choiceParser) setIncludedBy(i parser, path []string) { if stringsContain(path, p.name) { return } p.including = append(p.including, i) } func (p *choiceParser) cacheIncluded(c *context, n *Node) { if !c.excluded(n.from, p.name) { return } nc := newNode(p.name, p.commit, n.from, n.to) nc.append(n) c.cache.set(nc.from, p.name, nc) // maybe it is enough to cache only those that are on the path for _, i := range p.including { i.cacheIncluded(c, nc) } } func (p *choiceParser) parse(t Trace, c *context) { t = t.Extend(p.name) t.Out1("parsing choice", c.offset) if p.commit&Documentation != 0 { t.Out1("fail, doc") c.fail(c.offset) return } if m, ok := c.fromCache(p.name); ok { t.Out1("found in cache, match:", m) return } if c.excluded(c.offset, p.name) { t.Out1("excluded") c.fail(c.offset) return } c.exclude(c.offset, p.name) defer c.include(c.offset, p.name) node := newNode(p.name, p.commit, c.offset, c.offset) var match bool for { elements := p.elements var foundMatch bool // TODO: this can be the entry point for a transformation that enables the // processing of massive amounts of autogenerated rules in parallel in a // continously, dynamically cached way. E.g. teach a machine that learns // everything from a public library. t.Out2("elements again") for len(elements) > 0 { t.Out2("in the choice", c.offset, node.from, elements[0].nodeName()) elements[0].parse(t, c) elements = elements[1:] c.offset = node.from if !c.match || match && c.node.tokenLength() <= node.tokenLength() { t.Out2("skipping") continue } t.Out2("appending", c.node.tokenLength(), node.tokenLength(), "\"", string(c.tokens[node.from:node.to]), "\"", "\"", string(c.tokens[c.node.from:c.node.to]), "\"", c.node.Name, ) match = true foundMatch = true // node.clear() node = newNode(p.name, p.commit, c.offset, c.offset) // TODO: review caching conditions node.append(c.node) c.cache.set(node.from, p.name, node) for _, i := range p.including { i.cacheIncluded(c, node) } // TODO: a simple break here can force PEG-style "priority" choices } if !foundMatch { break } } if match { t.Out1("choice, success") t.Out2("choice done", node.nodeLength()) c.success(node) return } t.Out1("fail") c.cache.set(node.from, p.name, nil) c.fail(node.from) }