add generate command

This commit is contained in:
Arpad Ryszka 2018-01-07 01:45:56 +01:00
parent db2022d46d
commit 9e25538b94
11 changed files with 799 additions and 607 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
*.out *.out
.coverprofile .coverprofile
codecov codecov
cmd/treerack/treerack

View File

@ -13,6 +13,10 @@ imports: $(SOURCES)
build: $(SOURCES) build: $(SOURCES)
go build go build
go build -o cmd/treerack/treerack ./cmd/treerack
install: $(SOURCES)
go install ./cmd/treerack
head: $(SOURCES) head: $(SOURCES)
go run scripts/createhead.go -- \ go run scripts/createhead.go -- \
@ -26,15 +30,15 @@ head: $(SOURCES)
syntaxhead.go \ syntaxhead.go \
> head.go > head.go
generate: $(SOURCES) $(PARSERS) head generate: $(SOURCES) $(PARSERS) head install
go run scripts/boot.go < syntax.treerack > self/self.go.next treerack generate -export -package-name self < syntax.treerack > self/self.go.next
@mv self/self.go{.next,} @mv self/self.go{.next,}
@gofmt -s -w self/self.go @gofmt -s -w self/self.go
regenerate: $(SOURCES) $(PARSERS) head regenerate: $(SOURCES) $(PARSERS) head install
go run scripts/boot.go < syntax.treerack > self/self.go.next treerack generate -export -package-name self < syntax.treerack > self/self.go.next
@mv self/self.go{.next,} @mv self/self.go{.next,}
go run scripts/boot.go < syntax.treerack > self/self.go.next treerack generate -export -package-name self < syntax.treerack > self/self.go.next
@mv self/self.go{.next,} @mv self/self.go{.next,}
@gofmt -s -w self/self.go @gofmt -s -w self/self.go

25
cmd/treerack/doc.go Normal file
View File

@ -0,0 +1,25 @@
package main
const summary = `treerack - parser generator - https://github.com/aryszka/treerack`
const commandsHelp = `Available commands:
generate generates a parser from a syntax definition
help prints the current help
See more details about a particular command by calling:
treerack <command> -help`
const docRef = "See more documentation about the definition syntax and the parser output at https://github.com/aryszka/treerack."
const syntaxFileUsage = "path to the syntax file in treerack format"
const syntaxStringUsage = "inline syntax in treerack format"
const packageNameUsage = `package name of the generated code`
const exportUsage = `when the export flag is set, the generated code will have exported symbols to allow using it as a separate package`
const generateUsage = `treerack generate takes a syntax description from the standard input, or a file, or inline string, and generates code implementing a parser. It prints the parser code to the standard output.`
const generateExample = `Example:
treerack generate < syntax.treerack > parser.go`

125
cmd/treerack/generate.go Normal file
View File

@ -0,0 +1,125 @@
package main
import (
"bytes"
"flag"
"io"
"os"
"github.com/aryszka/treerack"
"golang.org/x/crypto/ssh/terminal"
)
type generateOptions struct {
syntax string
syntaxFile string
packageName string
export bool
}
var isTest bool
func flagSet(o *generateOptions, output io.Writer) *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Usage = func() {}
fs.SetOutput(output)
fs.StringVar(&o.syntax, "syntax-string", "", syntaxStringUsage)
fs.StringVar(&o.syntaxFile, "syntax", "", syntaxFileUsage)
fs.StringVar(&o.packageName, "package-name", "", packageNameUsage)
fs.BoolVar(&o.export, "export", false, exportUsage)
return fs
}
func helpGenerate() {
stdout(generateUsage)
stdout()
stdout("Options:")
fs := flagSet(&generateOptions{}, os.Stdout)
fs.PrintDefaults()
stdout()
stdout(generateExample)
}
func flagError(fs *flag.FlagSet) {
stderr()
stderr("Options:")
fs.PrintDefaults()
}
func multipleInputsError(fs *flag.FlagSet) {
stderr("only one of syntax file or syntax string is allowed")
stderr()
stderr("Options:")
fs.PrintDefaults()
}
func noInputError(fs *flag.FlagSet) {
stderr("missing syntax input")
stderr()
stderr("Options:")
fs.PrintDefaults()
}
func generate(args []string) int {
if len(args) > 0 && args[0] == "-help" {
helpGenerate()
return 0
}
var options generateOptions
fs := flagSet(&options, os.Stderr)
if err := fs.Parse(args); err != nil {
flagError(fs)
return -1
}
if options.syntaxFile != "" && options.syntax != "" {
multipleInputsError(fs)
return -1
}
var hasInput bool
if options.syntaxFile == "" && options.syntax == "" {
fdint := int(os.Stdin.Fd())
hasInput = !isTest && !terminal.IsTerminal(fdint)
}
if !hasInput && options.syntaxFile == "" && options.syntax == "" {
noInputError(fs)
return -1
}
var input io.Reader
if hasInput {
input = os.Stdin
} else if options.syntaxFile != "" {
f, err := os.Open(options.syntaxFile)
if err != nil {
stderr(err)
return -1
}
defer f.Close()
input = f
} else if options.syntax != "" {
input = bytes.NewBufferString(options.syntax)
}
s := &treerack.Syntax{}
if err := s.ReadSyntax(input); err != nil {
stderr(err)
return -1
}
var goptions treerack.GeneratorOptions
goptions.PackageName = options.packageName
goptions.Export = options.export
if err := s.Generate(goptions, os.Stdout); err != nil {
stderr(err)
return -1
}
return 0
}

37
cmd/treerack/main.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"os"
)
func mainHelp() {
stdout(summary)
stdout()
stdout(commandsHelp)
stdout()
stdout(docRef)
}
func main() {
if len(os.Args) == 1 {
stderr("missing command")
stderr()
stderr(commandsHelp)
stdout()
stdout(docRef)
os.Exit(-1)
}
switch os.Args[1] {
case "generate":
exit := generate(os.Args[2:])
os.Exit(exit)
case "help", "-help":
mainHelp()
default:
stderr("invalid command")
stderr()
stderr(commandsHelp)
os.Exit(-1)
}
}

14
cmd/treerack/out.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func stderr(a ...interface{}) {
fmt.Fprintln(os.Stderr, a...)
}
func stdout(a ...interface{}) {
fmt.Fprintln(os.Stderr, a...)
}

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
package main
import (
"log"
"os"
"github.com/aryszka/treerack"
)
func main() {
s := &treerack.Syntax{}
if err := s.ReadSyntax(os.Stdin); err != nil {
log.Fatalln(err)
}
if err := s.Generate(treerack.GeneratorOptions{PackageName: "self"}, os.Stdout); err != nil {
log.Fatalln(err)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@ type Syntax struct {
type GeneratorOptions struct { type GeneratorOptions struct {
PackageName string PackageName string
Export bool
} }
// applied in a non-type-checked way // applied in a non-type-checked way
@ -329,7 +330,12 @@ func (s *Syntax) Generate(o GeneratorOptions, w io.Writer) error {
fprintln() fprintln()
fprintln() fprintln()
fprint(`func Parse(r io.Reader) (*Node, error) {`) if o.Export {
fprint(`func Parse(r io.Reader) (*Node, error) {`)
} else {
fprint(`func parse(r io.Reader) (*Node, error) {`)
}
fprintln() fprintln()
done := make(map[string]bool) done := make(map[string]bool)
@ -344,7 +350,7 @@ func (s *Syntax) Generate(o GeneratorOptions, w io.Writer) error {
fprintln() fprintln()
fprintln() fprintln()
fprintf(`return parse(r, &p%d, &b%d)`, s.parser.nodeID(), s.builder.nodeID()) fprintf(`return parseInput(r, &p%d, &b%d)`, s.parser.nodeID(), s.builder.nodeID())
fprintln() fprintln()
fprint(`}`) fprint(`}`)
fprintln() fprintln()
@ -357,5 +363,5 @@ func (s *Syntax) Parse(r io.Reader) (*Node, error) {
return nil, err return nil, err
} }
return parse(r, s.parser, s.builder) return parseInput(r, s.parser, s.builder)
} }

View File

@ -80,7 +80,7 @@ func (pe *ParseError) Error() string {
) )
} }
func parse(r io.Reader, p parser, b builder) (*Node, error) { func parseInput(r io.Reader, p parser, b builder) (*Node, error) {
c := newContext(bufio.NewReader(r)) c := newContext(bufio.NewReader(r))
p.parse(c) p.parse(c)
if c.readErr != nil { if c.readErr != nil {