summaryrefslogtreecommitdiff
path: root/src/cmd/godoc/spec.go
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2009-09-03 09:58:13 -0700
committerRobert Griesemer <gri@golang.org>2009-09-03 09:58:13 -0700
commit43699f220df53ee4e3d72b4ed829055303dba5eb (patch)
tree7f7eea70d5c9d7de7e2d74966fccfc0210cee9fd /src/cmd/godoc/spec.go
parent5f07a19c6ce6ab2b73e58527c55735e4c43ca1c5 (diff)
downloadgolang-43699f220df53ee4e3d72b4ed829055303dba5eb.tar.gz
linkify EBNF sections in spec when served via godoc
R=rsc DELTA=217 (216 added, 0 deleted, 1 changed) OCL=34279 CL=34306
Diffstat (limited to 'src/cmd/godoc/spec.go')
-rw-r--r--src/cmd/godoc/spec.go201
1 files changed, 201 insertions, 0 deletions
diff --git a/src/cmd/godoc/spec.go b/src/cmd/godoc/spec.go
new file mode 100644
index 000000000..39c00a8f7
--- /dev/null
+++ b/src/cmd/godoc/spec.go
@@ -0,0 +1,201 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains the mechanism to "linkify" html source
+// text containing EBNF sections (as found in go_spec.html).
+// The result is the input source text with the EBNF sections
+// modified such that identifiers are linked to the respective
+// definitions.
+
+package main
+
+import (
+ "bytes";
+ "fmt";
+ "go/scanner";
+ "go/token";
+ "io";
+ "strings";
+)
+
+
+type ebnfParser struct {
+ out io.Writer; // parser output
+ src []byte; // parser source
+ scanner scanner.Scanner;
+ prev int; // offset of previous token
+ pos token.Position; // token position
+ tok token.Token; // one token look-ahead
+ lit []byte; // token literal
+}
+
+
+func (p *ebnfParser) flush() {
+ p.out.Write(p.src[p.prev : p.pos.Offset]);
+ p.prev = p.pos.Offset;
+}
+
+
+func (p *ebnfParser) next() {
+ p.flush();
+ p.pos, p.tok, p.lit = p.scanner.Scan();
+ if p.tok.IsKeyword() {
+ // TODO Should keyword mapping always happen outside scanner?
+ // Or should there be a flag to scanner to enable keyword mapping?
+ p.tok = token.IDENT;
+ }
+}
+
+
+func (p *ebnfParser) Error (pos token.Position, msg string) {
+ fmt.Fprintf(p.out, "<font color=red>error: %s</font>", msg);
+}
+
+
+func (p *ebnfParser) errorExpected(pos token.Position, msg string) {
+ msg = "expected " + msg;
+ if pos.Offset == p.pos.Offset {
+ // the error happened at the current position;
+ // make the error message more specific
+ msg += ", found '" + p.tok.String() + "'";
+ if p.tok.IsLiteral() {
+ msg += " " + string(p.lit);
+ }
+ }
+ p.Error(pos, msg);
+}
+
+
+func (p *ebnfParser) expect(tok token.Token) token.Position {
+ pos := p.pos;
+ if p.tok != tok {
+ p.errorExpected(pos, "'" + tok.String() + "'");
+ }
+ p.next(); // make progress in any case
+ return pos;
+}
+
+
+func (p *ebnfParser) parseIdentifier(def bool) {
+ name := string(p.lit);
+ p.expect(token.IDENT);
+ if def {
+ fmt.Fprintf(p.out, `<a id="%s">%s</a>`, name, name);
+ } else {
+ fmt.Fprintf(p.out, `<a href="#%s" style="text-decoration: none;">%s</a>`, name, name);
+ }
+ p.prev += len(name); // skip identifier when calling flush
+}
+
+
+func (p *ebnfParser) parseTerm() bool {
+ switch p.tok {
+ case token.IDENT:
+ p.parseIdentifier(false);
+
+ case token.STRING:
+ p.next();
+ if p.tok == token.ELLIPSIS {
+ p.next();
+ p.expect(token.STRING);
+ }
+
+ case token.LPAREN:
+ p.next();
+ p.parseExpression();
+ p.expect(token.RPAREN);
+
+ case token.LBRACK:
+ p.next();
+ p.parseExpression();
+ p.expect(token.RBRACK);
+
+ case token.LBRACE:
+ p.next();
+ p.parseExpression();
+ p.expect(token.RBRACE);
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+func (p *ebnfParser) parseSequence() {
+ for p.parseTerm() {
+ }
+}
+
+
+func (p *ebnfParser) parseExpression() {
+ for {
+ p.parseSequence();
+ if p.tok != token.OR {
+ break;
+ }
+ p.next();
+ }
+}
+
+
+func (p *ebnfParser) parseProduction() {
+ p.parseIdentifier(true);
+ p.expect(token.ASSIGN);
+ p.parseExpression();
+ p.expect(token.PERIOD);
+}
+
+
+func (p *ebnfParser) parse(out io.Writer, src []byte) {
+ // initialize ebnfParser
+ p.out = out;
+ p.src = src;
+ p.scanner.Init("", src, p, 0);
+ p.next(); // initializes pos, tok, lit
+
+ // process source
+ for p.tok != token.EOF {
+ p.parseProduction();
+ }
+ p.flush();
+}
+
+
+// Markers around EBNF sections
+var (
+ open = strings.Bytes(`<pre class="ebnf">`);
+ close = strings.Bytes(`</pre>`);
+)
+
+
+func linkify(out io.Writer, src []byte) {
+ for len(src) > 0 {
+ n := len(src);
+
+ // i: beginning of EBNF text (or end of source)
+ i := bytes.Index(src, open);
+ if i < 0 {
+ i = n-len(open);
+ }
+ i += len(open);
+
+ // j: end of EBNF text (or end of source)
+ j := bytes.Index(src[i : n], close); // close marker
+ if j < 0 {
+ j = n-i;
+ }
+ j += i;
+
+ // write text before EBNF
+ out.Write(src[0 : i]);
+ // parse and write EBNF
+ var p ebnfParser;
+ p.parse(out, src[i : j]);
+
+ // advance
+ src = src[j : n];
+ }
+}