blob: 9ec94278db55450bb1d8abe0844a2ded874c431f [file] [log] [blame]
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +10001// 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
Brad Fitzpatricke6ff53b2013-07-17 17:09:54 +10005package godoc
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +10006
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
13import (
14 "bytes"
15 "fmt"
16 "io"
17 "text/scanner"
18)
19
20type 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
30func (p *ebnfParser) flush() {
31 p.out.Write(p.src[p.prev:p.pos])
32 p.prev = p.pos
33}
34
35func (p *ebnfParser) next() {
36 p.tok = p.scanner.Scan()
37 p.pos = p.scanner.Position.Offset
38 p.lit = p.scanner.TokenText()
39}
40
41func (p *ebnfParser) printf(format string, args ...interface{}) {
42 p.flush()
43 fmt.Fprintf(p.out, format, args...)
44}
45
46func (p *ebnfParser) errorExpected(msg string) {
47 p.printf(`<span class="highlight">error: expected %s, found %s</span>`, msg, scanner.TokenString(p.tok))
48}
49
50func (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
57func (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
72func (p *ebnfParser) parseTerm() bool {
73 switch p.tok {
74 case scanner.Ident:
75 p.parseIdentifier(false)
76
Agniva De Sarker3c1bb8b2018-06-24 22:37:16 +053077 case scanner.String, scanner.RawString:
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100078 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
107func (p *ebnfParser) parseSequence() {
108 if !p.parseTerm() {
109 p.errorExpected("term")
110 }
111 for p.parseTerm() {
112 }
113}
114
115func (p *ebnfParser) parseExpression() {
116 for {
117 p.parseSequence()
118 if p.tok != '|' {
119 break
120 }
121 p.next()
122 }
123}
124
125func (p *ebnfParser) parseProduction() {
126 p.parseIdentifier(true)
127 p.expect('=')
128 if p.tok != '.' {
129 p.parseExpression()
130 }
131 p.expect('.')
132}
133
134func (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
149var (
150 openTag = []byte(`<pre class="ebnf">`)
151 closeTag = []byte(`</pre>`)
152)
153
154func 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}