github.com/v2fly/tools@v0.100.0/godoc/spec.go (about) 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 }