blob: 966dadf4f7cc9627774778369ebebe5b0ef704c2 [file] [log] [blame]
// 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.
package main
import (
"bytes"
"flag"
"fmt"
"go/scanner"
"go/token"
"io"
"os"
"path/filepath"
"golang.org/x/exp/ebnf"
)
var fset = token.NewFileSet()
var start = flag.String("start", "Start", "name of start production")
func usage() {
fmt.Fprintf(os.Stderr, "usage: go tool ebnflint [flags] [filename]\n")
flag.PrintDefaults()
os.Exit(1)
}
// Markers around EBNF sections in .html files
var (
open = []byte(`<pre class="ebnf">`)
close = []byte(`</pre>`)
)
func report(err error) {
scanner.PrintError(os.Stderr, err)
os.Exit(1)
}
func extractEBNF(src []byte) []byte {
var buf bytes.Buffer
for {
// i = beginning of EBNF text
i := bytes.Index(src, open)
if i < 0 {
break // no EBNF found - we are done
}
i += len(open)
// write as many newlines as found in the excluded text
// to maintain correct line numbers in error messages
for _, ch := range src[0:i] {
if ch == '\n' {
buf.WriteByte('\n')
}
}
// j = end of EBNF text (or end of source)
j := bytes.Index(src[i:], close) // close marker
if j < 0 {
j = len(src) - i
}
j += i
// copy EBNF text
buf.Write(src[i:j])
// advance
src = src[j:]
}
return buf.Bytes()
}
func main() {
flag.Parse()
var (
name string
r io.Reader
)
switch flag.NArg() {
case 0:
name, r = "<stdin>", os.Stdin
case 1:
name = flag.Arg(0)
default:
usage()
}
if err := verify(name, *start, r); err != nil {
report(err)
}
}
func verify(name, start string, r io.Reader) error {
if r == nil {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
r = f
}
src, err := io.ReadAll(r)
if err != nil {
return err
}
if filepath.Ext(name) == ".html" || bytes.Index(src, open) >= 0 {
src = extractEBNF(src)
}
grammar, err := ebnf.Parse(name, bytes.NewBuffer(src))
if err != nil {
return err
}
return ebnf.Verify(grammar, start)
}