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
5package 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
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
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
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}
180