You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
3.5 KiB

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