1 | // Copyright 2009 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package godoc |
6 | |
7 | // This file contains the mechanism to "linkify" html source |
8 | // text containing EBNF sections (as found in go_spec.html). |
9 | // The result is the input source text with the EBNF sections |
10 | // modified such that identifiers are linked to the respective |
11 | // definitions. |
12 | |
13 | import ( |
14 | "bytes" |
15 | "fmt" |
16 | "io" |
17 | "text/scanner" |
18 | ) |
19 | |
20 | type ebnfParser struct { |
21 | out io.Writer // parser output |
22 | src []byte // parser input |
23 | scanner scanner.Scanner |
24 | prev int // offset of previous token |
25 | pos int // offset of current token |
26 | tok rune // one token look-ahead |
27 | lit string // token literal |
28 | } |
29 | |
30 | func (p *ebnfParser) flush() { |
31 | p.out.Write(p.src[p.prev:p.pos]) |
32 | p.prev = p.pos |
33 | } |
34 | |
35 | func (p *ebnfParser) next() { |
36 | p.tok = p.scanner.Scan() |
37 | p.pos = p.scanner.Position.Offset |
38 | p.lit = p.scanner.TokenText() |
39 | } |
40 | |
41 | func (p *ebnfParser) printf(format string, args ...interface{}) { |
42 | p.flush() |
43 | fmt.Fprintf(p.out, format, args...) |
44 | } |
45 | |
46 | func (p *ebnfParser) errorExpected(msg string) { |
47 | p.printf(`<span class="highlight">error: expected %s, found %s</span>`, msg, scanner.TokenString(p.tok)) |
48 | } |
49 | |
50 | func (p *ebnfParser) expect(tok rune) { |
51 | if p.tok != tok { |
52 | p.errorExpected(scanner.TokenString(tok)) |
53 | } |
54 | p.next() // make progress in any case |
55 | } |
56 | |
57 | func (p *ebnfParser) parseIdentifier(def bool) { |
58 | if p.tok == scanner.Ident { |
59 | name := p.lit |
60 | if def { |
61 | p.printf(`<a id="%s">%s</a>`, name, name) |
62 | } else { |
63 | p.printf(`<a href="#%s" class="noline">%s</a>`, name, name) |
64 | } |
65 | p.prev += len(name) // skip identifier when printing next time |
66 | p.next() |
67 | } else { |
68 | p.expect(scanner.Ident) |
69 | } |
70 | } |
71 | |
72 | func (p *ebnfParser) parseTerm() bool { |
73 | switch p.tok { |
74 | case scanner.Ident: |
75 | p.parseIdentifier(false) |
76 | |
77 | case scanner.String, scanner.RawString: |
78 | p.next() |
79 | const ellipsis = '…' // U+2026, the horizontal ellipsis character |
80 | if p.tok == ellipsis { |
81 | p.next() |
82 | p.expect(scanner.String) |
83 | } |
84 | |
85 | case '(': |
86 | p.next() |
87 | p.parseExpression() |
88 | p.expect(')') |
89 | |
90 | case '[': |
91 | p.next() |
92 | p.parseExpression() |
93 | p.expect(']') |
94 | |
95 | case '{': |
96 | p.next() |
97 | p.parseExpression() |
98 | p.expect('}') |
99 | |
100 | default: |
101 | return false // no term found |
102 | } |
103 | |
104 | return true |
105 | } |
106 | |
107 | func (p *ebnfParser) parseSequence() { |
108 | if !p.parseTerm() { |
109 | p.errorExpected("term") |
110 | } |
111 | for p.parseTerm() { |
112 | } |
113 | } |
114 | |
115 | func (p *ebnfParser) parseExpression() { |
116 | for { |
117 | p.parseSequence() |
118 | if p.tok != '|' { |
119 | break |
120 | } |
121 | p.next() |
122 | } |
123 | } |
124 | |
125 | func (p *ebnfParser) parseProduction() { |
126 | p.parseIdentifier(true) |
127 | p.expect('=') |
128 | if p.tok != '.' { |
129 | p.parseExpression() |
130 | } |
131 | p.expect('.') |
132 | } |
133 | |
134 | func (p *ebnfParser) parse(out io.Writer, src []byte) { |
135 | // initialize ebnfParser |
136 | p.out = out |
137 | p.src = src |
138 | p.scanner.Init(bytes.NewBuffer(src)) |
139 | p.next() // initializes pos, tok, lit |
140 | |
141 | // process source |
142 | for p.tok != scanner.EOF { |
143 | p.parseProduction() |
144 | } |
145 | p.flush() |
146 | } |
147 | |
148 | // Markers around EBNF sections |
149 | var ( |
150 | openTag = []byte(`<pre class="ebnf">`) |
151 | closeTag = []byte(`</pre>`) |
152 | ) |
153 | |
154 | func Linkify(out io.Writer, src []byte) { |
155 | for len(src) > 0 { |
156 | // i: beginning of EBNF text (or end of source) |
157 | i := bytes.Index(src, openTag) |
158 | if i < 0 { |
159 | i = len(src) - len(openTag) |
160 | } |
161 | i += len(openTag) |
162 | |
163 | // j: end of EBNF text (or end of source) |
164 | j := bytes.Index(src[i:], closeTag) // close marker |
165 | if j < 0 { |
166 | j = len(src) - i |
167 | } |
168 | j += i |
169 | |
170 | // write text before EBNF |
171 | out.Write(src[0:i]) |
172 | // process EBNF |
173 | var p ebnfParser |
174 | p.parse(out, src[i:j]) |
175 | |
176 | // advance |
177 | src = src[j:] |
178 | } |
179 | } |
180 |
Members