147 lines
2.8 KiB
Go
147 lines
2.8 KiB
Go
|
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))
|
||
|
}
|
||
|
}
|