golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/ebnflint/ebnflint.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 main
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"go/scanner"
    12  	"go/token"
    13  	"io"
    14  	"os"
    15  	"path/filepath"
    16  
    17  	"golang.org/x/exp/ebnf"
    18  )
    19  
    20  var fset = token.NewFileSet()
    21  var start = flag.String("start", "Start", "name of start production")
    22  
    23  func usage() {
    24  	fmt.Fprintf(os.Stderr, "usage: go tool ebnflint [flags] [filename]\n")
    25  	flag.PrintDefaults()
    26  	os.Exit(1)
    27  }
    28  
    29  // Markers around EBNF sections in .html files
    30  var (
    31  	open  = []byte(`<pre class="ebnf">`)
    32  	close = []byte(`</pre>`)
    33  )
    34  
    35  func report(err error) {
    36  	scanner.PrintError(os.Stderr, err)
    37  	os.Exit(1)
    38  }
    39  
    40  func extractEBNF(src []byte) []byte {
    41  	var buf bytes.Buffer
    42  
    43  	for {
    44  		// i = beginning of EBNF text
    45  		i := bytes.Index(src, open)
    46  		if i < 0 {
    47  			break // no EBNF found - we are done
    48  		}
    49  		i += len(open)
    50  
    51  		// write as many newlines as found in the excluded text
    52  		// to maintain correct line numbers in error messages
    53  		for _, ch := range src[0:i] {
    54  			if ch == '\n' {
    55  				buf.WriteByte('\n')
    56  			}
    57  		}
    58  
    59  		// j = end of EBNF text (or end of source)
    60  		j := bytes.Index(src[i:], close) // close marker
    61  		if j < 0 {
    62  			j = len(src) - i
    63  		}
    64  		j += i
    65  
    66  		// copy EBNF text
    67  		buf.Write(src[i:j])
    68  
    69  		// advance
    70  		src = src[j:]
    71  	}
    72  
    73  	return buf.Bytes()
    74  }
    75  
    76  func main() {
    77  	flag.Parse()
    78  
    79  	var (
    80  		name string
    81  		r    io.Reader
    82  	)
    83  	switch flag.NArg() {
    84  	case 0:
    85  		name, r = "<stdin>", os.Stdin
    86  	case 1:
    87  		name = flag.Arg(0)
    88  	default:
    89  		usage()
    90  	}
    91  
    92  	if err := verify(name, *start, r); err != nil {
    93  		report(err)
    94  	}
    95  }
    96  
    97  func verify(name, start string, r io.Reader) error {
    98  	if r == nil {
    99  		f, err := os.Open(name)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		defer f.Close()
   104  		r = f
   105  	}
   106  
   107  	src, err := io.ReadAll(r)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	if filepath.Ext(name) == ".html" || bytes.Index(src, open) >= 0 {
   113  		src = extractEBNF(src)
   114  	}
   115  
   116  	grammar, err := ebnf.Parse(name, bytes.NewBuffer(src))
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	return ebnf.Verify(grammar, start)
   122  }