Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 70 additions & 34 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package main
import (
"flag"
"fmt"
"io"
"log"
"os"

Expand All @@ -19,17 +20,17 @@ import (
var (
Version = "dev"

inline = flag.Bool("inline", false, "parse rule inlining")
_switch = flag.Bool("switch", false, "replace if-else if-else like blocks with switch blocks")
// Avoid redefinition of built-in function print.
inline = flag.Bool("inline", false, "parse rule inlining")
switchFlag = flag.Bool("switch", false, "replace if-else if-else like blocks with switch blocks")
printFlag = flag.Bool("print", false, "directly dump the syntax tree")
syntax = flag.Bool("syntax", false, "print out the syntax tree")
noast = flag.Bool("noast", false, "disable AST")
strict = flag.Bool("strict", false, "treat compiler warnings as errors")
filename = flag.String("output", "", "specify name of output file")
outputFile = flag.String("output", "", "output to `FILE` (\"-\" for stdout)")
showVersion = flag.Bool("version", false, "print the version and exit")
)

// main is the entry point for the PEG compiler.
func main() {
flag.Parse()

Expand All @@ -38,49 +39,84 @@ func main() {
return
}

if flag.NArg() != 1 {
flag.Usage()
log.Fatalf("FILE: the peg file to compile")
}
file := flag.Arg(0)
err := parse(
func(p *Peg[uint32], out io.Writer) error {
if *printFlag {
p.Print()
}
if *syntax {
p.PrintSyntaxTree()
}

buffer, err := os.ReadFile(file)
p.Strict = *strict
if err := p.Compile(*outputFile, os.Args, out); err != nil {
return err
}
return nil
},
)
if err != nil {
log.Fatal(err)
}

p := &Peg[uint32]{Tree: tree.New(*inline, *_switch, *noast), Buffer: string(buffer)}
_ = p.Init(Pretty[uint32](true), Size[uint32](1<<15))
if err := p.Parse(); err != nil {
log.Fatal(err)
if *strict {
log.Fatal(err)
}
fmt.Fprintln(os.Stderr, "warning:", err)
Comment on lines +61 to +62
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling logic is incorrect. When *strict is true, the code calls log.Fatal(err), but when false, it prints a warning. However, this happens outside the conditional check for err != nil. The log.Fatal(err) should only be called when there's actually an error.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both logging statements are inside the conditional check for err != nil. This looks like a Copilot mistake.

}
}

p.Execute()
// getIO returns input and output streams based on command-line flags.
func getIO() (in io.ReadCloser, out io.WriteCloser, err error) {
in, out = os.Stdin, os.Stdout

if *printFlag {
p.Print()
}
if *syntax {
p.PrintSyntaxTree()
if flag.NArg() > 0 && flag.Arg(0) != "-" {
in, err = os.Open(flag.Arg(0))
if err != nil {
return nil, nil, err
}
if *outputFile == "" {
*outputFile = flag.Arg(0) + ".go"
}
}

if *filename == "" {
*filename = file + ".go"
if *outputFile != "" && *outputFile != "-" {
out, err = os.OpenFile(*outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
if in != nil && in != os.Stdin {
in.Close()
}
Comment on lines +83 to +85
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resource cleanup logic is duplicated between lines 83-85 and 100-102. Consider extracting this into a helper function or using defer statements to avoid code duplication.

Suggested change
if in != nil && in != os.Stdin {
in.Close()
}
closeResource(in, os.Stdin)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree with this suggestion: the reader would have to jump to another function containing only 2 lines of code and then jump back to resume reading.

So, adding closeResource is tedious for the reader and serves no functional benefit.

return nil, nil, err
}
}
out, err := os.OpenFile(*filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)

return in, out, nil
}

// parse reads input, parses, executes, and compiles the PEG grammar.
func parse(compile func(*Peg[uint32], io.Writer) error) error {
in, out, err := getIO()
if err != nil {
fmt.Printf("%v: %v\n", *filename, err)
return
return err
}
defer func() {
err := out.Close()
if err != nil {
log.Fatal(err)
if in != nil && in != os.Stdin {
in.Close()
}
if out != nil && out != os.Stdout {
out.Close()
}
}()

p.Strict = *strict
if err = p.Compile(*filename, os.Args, out); err != nil {
log.Fatal(err)
buffer, err := io.ReadAll(in)
if err != nil {
return err
}

p := &Peg[uint32]{Tree: tree.New(*inline, *switchFlag, *noast), Buffer: string(buffer)}
_ = p.Init(Pretty[uint32](true), Size[uint32](1<<15))
if err = p.Parse(); err != nil {
return err
}

p.Execute()

return compile(p, out)
}