kuvia2/vendor/github.com/alecthomas/participle/v2/ebnf.go

147 lines
2.8 KiB
Go
Raw Normal View History

2022-01-14 23:09:03 +00:00
package participle
import (
"fmt"
"strings"
)
// String returns the EBNF for the grammar.
//
// Productions are always upper cased. Lexer tokens are always lower case.
func (p *Parser) String() string {
return ebnf(p.root)
}
type ebnfp struct {
name string
out string
}
func ebnf(n node) string {
outp := []*ebnfp{}
switch n.(type) {
case *strct:
buildEBNF(true, n, map[node]bool{}, nil, &outp)
out := []string{}
for _, p := range outp {
out = append(out, fmt.Sprintf("%s = %s .", p.name, p.out))
}
return strings.Join(out, "\n")
default:
out := &ebnfp{}
buildEBNF(true, n, map[node]bool{}, out, &outp)
return out.out
}
}
func buildEBNF(root bool, n node, seen map[node]bool, p *ebnfp, outp *[]*ebnfp) {
switch n := n.(type) {
case *disjunction:
if !root {
p.out += "("
}
for i, next := range n.nodes {
if i > 0 {
p.out += " | "
}
buildEBNF(false, next, seen, p, outp)
}
if !root {
p.out += ")"
}
case *strct:
name := strings.ToUpper(n.typ.Name()[:1]) + n.typ.Name()[1:]
if p != nil {
p.out += name
}
if seen[n] {
return
}
seen[n] = true
p = &ebnfp{name: name}
*outp = append(*outp, p)
buildEBNF(true, n.expr, seen, p, outp)
case *sequence:
group := n.next != nil && !root
if group {
p.out += "("
}
for n != nil {
buildEBNF(false, n.node, seen, p, outp)
n = n.next
if n != nil {
p.out += " "
}
}
if group {
p.out += ")"
}
case *parseable:
p.out += n.t.Name()
case *capture:
buildEBNF(false, n.node, seen, p, outp)
case *reference:
p.out += "<" + strings.ToLower(n.identifier) + ">"
case *optional:
buildEBNF(false, n.node, seen, p, outp)
p.out += "?"
case *repetition:
buildEBNF(false, n.node, seen, p, outp)
p.out += "*"
case *negation:
p.out += "~"
buildEBNF(false, n.node, seen, p, outp)
case *literal:
p.out += fmt.Sprintf("%q", n.s)
case *group:
if child, ok := n.expr.(*group); ok && child.mode == groupMatchOnce {
buildEBNF(false, child.expr, seen, p, outp)
} else if child, ok := n.expr.(*capture); ok {
if grandchild, ok := child.node.(*group); ok && grandchild.mode == groupMatchOnce {
buildEBNF(false, grandchild.expr, seen, p, outp)
} else {
buildEBNF(false, n.expr, seen, p, outp)
}
} else {
buildEBNF(false, n.expr, seen, p, outp)
}
switch n.mode {
case groupMatchNonEmpty:
p.out += "!"
case groupMatchZeroOrOne:
p.out += "?"
case groupMatchZeroOrMore:
p.out += "*"
case groupMatchOneOrMore:
p.out += "+"
case groupMatchOnce:
}
case *lookaheadGroup:
if !n.negative {
p.out += "(?= "
} else {
p.out += "(?! "
}
buildEBNF(true, n.expr, seen, p, outp)
p.out += ")"
case *trace:
buildEBNF(root, n.node, seen, p, outp)
default:
panic(fmt.Sprintf("unsupported node type %T", n))
}
}