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  }