File: ebnf.go

package info (click to toggle)
golang-github-alecthomas-participle 0.7.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 564 kB
  • sloc: makefile: 2
file content (120 lines) | stat: -rw-r--r-- 2,309 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package participle

import (
	"fmt"
	"strings"
)

// String returns the EBNF for the grammar.
//
// Productions are always upper case. Lexer tokens are always lower case.
func (p *Parser) String() string {
	seen := map[node]bool{}
	outp := []*ebnfp{}
	ebnf(p.root, seen, nil, &outp)
	out := []string{}
	for _, p := range outp {
		out = append(out, fmt.Sprintf("%s = %s .", p.name, p.out))
	}
	return strings.Join(out, "\n")
}

type ebnfp struct {
	name string
	out  string
}

func ebnf(n node, seen map[node]bool, p *ebnfp, outp *[]*ebnfp) {
	switch n := n.(type) {
	case *disjunction:
		for i, next := range n.nodes {
			if i > 0 {
				p.out += " | "
			}
			ebnf(next, seen, p, outp)
		}
		return

	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)
		ebnf(n.expr, seen, p, outp)
		return

	case *sequence:
		ebnf(n.node, seen, p, outp)
		if n.next != nil {
			p.out += " "
			ebnf(n.next, seen, p, outp)
		}
		return

	case *parseable:
		p.out += n.t.Name()

	case *capture:
		ebnf(n.node, seen, p, outp)

	case *reference:
		p.out += strings.ToLower(n.identifier)

	case *optional:
		ebnf(n.node, seen, p, outp)
		p.out += "?"

	case *repetition:
		ebnf(n.node, seen, p, outp)
		p.out += "*"

	case *negation:
		p.out += "!"
		ebnf(n.node, seen, p, outp)
		return

	case *literal:
		p.out += fmt.Sprintf("%q", n.s)

	case *group:
		composite := (n.mode != groupMatchOnce) && compositeNode(map[node]bool{}, n, false)

		if composite {
			p.out += "("
		}
		if child, ok := n.expr.(*group); ok && child.mode == groupMatchOnce {
			ebnf(child.expr, seen, p, outp)
		} else if child, ok := n.expr.(*capture); ok {
			if grandchild, ok := child.node.(*group); ok && grandchild.mode == groupMatchOnce {
				ebnf(grandchild.expr, seen, p, outp)
			} else {
				ebnf(n.expr, seen, p, outp)
			}
		} else {
			ebnf(n.expr, seen, p, outp)
		}
		if composite {
			p.out += ")"
		}
		switch n.mode {
		case groupMatchNonEmpty:
			p.out += "!"
		case groupMatchZeroOrOne:
			p.out += "?"
		case groupMatchZeroOrMore:
			p.out += "*"
		case groupMatchOneOrMore:
			p.out += "+"
		}
		return

	default:
		panic(fmt.Sprintf("unsupported node type %T", n))
	}
}