1
0

only use tracing when explicitly enabled

This commit is contained in:
Arpad Ryszka 2026-06-06 21:48:50 +02:00
parent 752e4d2275
commit a009c1918d
14 changed files with 127 additions and 84 deletions

View File

@ -20,6 +20,9 @@ type checkOptions struct {
// InputString specifies the input content as an inline string.
InputString *string
// MaxTraceLength enables tracing when set to a positive integer.
MaxTraceLength int
}
// check parses input content against the provided syntax definition and fails if the input does not match.
@ -38,7 +41,7 @@ func check(o checkOptions, stdin io.Reader, args ...string) error {
}
defer finalizeInput()
s := &treerack.Syntax{}
s := &treerack.Syntax{MaxTraceLength: o.MaxTraceLength}
if err := s.ReadSyntax(syntax); err != nil {
if terr := treerack.Trace(os.Stderr, err); terr != nil {
err = errors.Join(err, terr)

View File

@ -14,6 +14,9 @@ type checkSyntaxOptions struct {
// SyntaxString specifies the syntax as an inline string.
SyntaxString *string
// MaxTraceLength enables tracing when set to a positive integer.
MaxTraceLength int
}
// checkSyntax validates a syntax definition. The syntax may be provided via a file path (using an option or a
@ -25,7 +28,7 @@ func checkSyntax(o checkSyntaxOptions, stdin io.Reader, args ...string) error {
}
defer finalize()
s := &treerack.Syntax{}
s := &treerack.Syntax{MaxTraceLength: o.MaxTraceLength}
if err := s.ReadSyntax(syntax); err != nil {
if terr := treerack.Trace(os.Stderr, err); terr != nil {
err = errors.Join(err, terr)

View File

@ -12,28 +12,32 @@ func init() {
docreflect.Register("main.checkOptions", "")
docreflect.Register("main.checkOptions.Input", "Input specifies the filename of the input content to be validated.\n")
docreflect.Register("main.checkOptions.InputString", "InputString specifies the input content as an inline string.\n")
docreflect.Register("main.checkOptions.MaxTraceLength", "MaxTraceLength enables tracing when set to a positive integer.\n")
docreflect.Register("main.checkOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
docreflect.Register("main.checkOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
docreflect.Register("main.checkSyntax", "checkSyntax validates a syntax definition. The syntax may be provided via a file path (using an option or a\npositional argument), an inline string, or piped from standard input.\n\nfunc(o, stdin, args)")
docreflect.Register("main.checkSyntaxOptions", "")
docreflect.Register("main.checkSyntaxOptions.MaxTraceLength", "MaxTraceLength enables tracing when set to a positive integer.\n")
docreflect.Register("main.checkSyntaxOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
docreflect.Register("main.checkSyntaxOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
docreflect.Register("main.errInvalidFilename", "")
docreflect.Register("main.errMultipleInputs", "")
docreflect.Register("main.errNoInput", "")
docreflect.Register("main.format", "format input syntax. Accepts syntax from one or more files, inline syntax string or stdin. Use the --in-place\noption, when formatting files in place, or print the formatted syntax to stdout.\n\nfunc(o, stdin, stdout, syntax)")
docreflect.Register("main.formatFile", "\nfunc(name, inPlace, out)")
docreflect.Register("main.formatFiles", "\nfunc(files, inPlace, out)")
docreflect.Register("main.formatInline", "\nfunc(syntax, out)")
docreflect.Register("main.formatFile", "\nfunc(name, inPlace, maxTraceLength, out)")
docreflect.Register("main.formatFiles", "\nfunc(files, inPlace, maxTraceLength, out)")
docreflect.Register("main.formatInline", "\nfunc(syntax, out, maxTraceLength)")
docreflect.Register("main.formatOptions", "")
docreflect.Register("main.formatOptions.InPlace", "")
docreflect.Register("main.formatOptions.Syntax", "")
docreflect.Register("main.formatOptions.SyntaxString", "")
docreflect.Register("main.formatStdin", "\nfunc(in, out)")
docreflect.Register("main.formatSyntax", "\nfunc(in, out)")
docreflect.Register("main.formatOptions.InPlace", "InPlace specifies if an input file should be formatted in-place instead of printing the formatted\nresults to the standard output.\n")
docreflect.Register("main.formatOptions.MaxTraceLength", "MaxTraceLength enables tracing when set to a positive integer.\n")
docreflect.Register("main.formatOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
docreflect.Register("main.formatOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
docreflect.Register("main.formatStdin", "\nfunc(in, out, maxTraceLength)")
docreflect.Register("main.formatSyntax", "\nfunc(in, out, maxTraceLength)")
docreflect.Register("main.generate", "generate generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded\nin an application.\n\nThe syntax may be provided via a file path (using an option or a positional argument), an\ninline string, or piped from standard input.\n\nfunc(o, stdin, stdout, args)")
docreflect.Register("main.generateOptions", "")
docreflect.Register("main.generateOptions.Export", "Export determines whether the generated parse function is exported (visible outside its package).\n")
docreflect.Register("main.generateOptions.MaxTraceLength", "MaxTraceLength enables tracing when set to a positive integer.\n")
docreflect.Register("main.generateOptions.PackageName", "PackageName specifies the package name for the generated code. Defaults to main.\n")
docreflect.Register("main.generateOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
docreflect.Register("main.generateOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
@ -53,6 +57,7 @@ func init() {
docreflect.Register("main.showOptions.Indent", "Indent specifies a custom indentation string for the output.\n")
docreflect.Register("main.showOptions.Input", "Input specifies the filename of the input content to be validated.\n")
docreflect.Register("main.showOptions.InputString", "InputString specifies the input content as an inline string.\n")
docreflect.Register("main.showOptions.MaxTraceLength", "MaxTraceLength enables tracing when set to a positive integer.\n")
docreflect.Register("main.showOptions.Pretty", "Pretty enables indented, human-readable output.\n")
docreflect.Register("main.showOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
docreflect.Register("main.showOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")

View File

@ -10,13 +10,23 @@ import (
)
type formatOptions struct {
InPlace bool
// InPlace specifies if an input file should be formatted in-place instead of printing the formatted
// results to the standard output.
InPlace bool
// SyntaxString specifies the syntax as an inline string.
SyntaxString *string
Syntax []string
// Syntax specifies the filename of the syntax definition file.
Syntax []string
// MaxTraceLength enables tracing when set to a positive integer.
MaxTraceLength int
}
func formatSyntax(in io.Reader, out io.Writer) error {
s := new(treerack.Syntax)
func formatSyntax(in io.Reader, out io.Writer, maxTraceLength int) error {
s := treerack.Syntax{MaxTraceLength: maxTraceLength}
if err := s.ReadSyntax(in); err != nil {
if terr := treerack.Trace(os.Stderr, err); err != nil {
err = errors.Join(err, terr)
@ -36,7 +46,7 @@ func formatSyntax(in io.Reader, out io.Writer) error {
return nil
}
func formatFile(name string, inPlace bool, out io.Writer) error {
func formatFile(name string, inPlace bool, maxTraceLength int, out io.Writer) error {
var (
inBytes []byte
buf *bytes.Buffer
@ -48,7 +58,7 @@ func formatFile(name string, inPlace bool, out io.Writer) error {
}
buf = bytes.NewBuffer(inBytes)
if err = formatSyntax(buf, buf); err != nil {
if err = formatSyntax(buf, buf, maxTraceLength); err != nil {
return err
}
@ -65,9 +75,9 @@ func formatFile(name string, inPlace bool, out io.Writer) error {
return os.WriteFile(name, buf.Bytes(), 0644)
}
func formatFiles(files []string, inPlace bool, out io.Writer) error {
func formatFiles(files []string, inPlace bool, maxTraceLength int, out io.Writer) error {
for _, f := range files {
if err := formatFile(f, inPlace, out); err != nil {
if err := formatFile(f, inPlace, maxTraceLength, out); err != nil {
return err
}
}
@ -75,13 +85,13 @@ func formatFiles(files []string, inPlace bool, out io.Writer) error {
return nil
}
func formatInline(syntax string, out io.Writer) error {
func formatInline(syntax string, out io.Writer, maxTraceLength int) error {
buf := bytes.NewBufferString(syntax)
return formatSyntax(buf, out)
return formatSyntax(buf, out, maxTraceLength)
}
func formatStdin(in io.Reader, out io.Writer) error {
return formatSyntax(in, out)
func formatStdin(in io.Reader, out io.Writer, maxTraceLength int) error {
return formatSyntax(in, out, maxTraceLength)
}
// format input syntax. Accepts syntax from one or more files, inline syntax string or stdin. Use the --in-place
@ -109,12 +119,12 @@ func format(o formatOptions, stdin io.Reader, stdout io.Writer, syntax ...string
}
if len(files) > 0 {
return formatFiles(files, o.InPlace, stdout)
return formatFiles(files, o.InPlace, o.MaxTraceLength, stdout)
}
if o.SyntaxString != nil {
return formatInline(*o.SyntaxString, stdout)
return formatInline(*o.SyntaxString, stdout, o.MaxTraceLength)
}
return formatSyntax(stdin, stdout)
return formatSyntax(stdin, stdout, o.MaxTraceLength)
}

View File

@ -20,6 +20,9 @@ type generateOptions struct {
// Export determines whether the generated parse function is exported (visible outside its package).
Export bool
// MaxTraceLength enables tracing when set to a positive integer.
MaxTraceLength int
}
// generate generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded
@ -34,7 +37,7 @@ func generate(o generateOptions, stdin io.Reader, stdout io.Writer, args ...stri
}
defer finalizeSyntax()
s := &treerack.Syntax{}
s := &treerack.Syntax{MaxTraceLength: o.MaxTraceLength}
if err := s.ReadSyntax(syntax); err != nil {
if terr := treerack.Trace(os.Stderr, err); err != nil {
err = errors.Join(err, terr)

View File

@ -32,6 +32,7 @@ argument), an inline string, or piped from standard input.
#### Options:
- --max-trace-length int: enables tracing when set to a positive integer.
- --syntax string: specifies the filename of the syntax definition file.
- --syntax-string string: specifies the syntax as an inline string.
- --help: Show help.
@ -57,6 +58,7 @@ positional argument filename, an inline string option, or piped from standard in
- --input string: specifies the filename of the input content to be validated.
- --input-string string: specifies the input content as an inline string.
- --max-trace-length int: enables tracing when set to a positive integer.
- --syntax string: specifies the filename of the syntax definition file.
- --syntax-string string: specifies the syntax as an inline string.
- --help: Show help.
@ -83,6 +85,7 @@ filename option, a positional argument filename, an inline string option, or pip
- --indent string: specifies a custom indentation string for the output.
- --input string: specifies the filename of the input content to be validated.
- --input-string string: specifies the input content as an inline string.
- --max-trace-length int: enables tracing when set to a positive integer.
- --pretty bool: enables indented, human-readable output.
- --syntax string: specifies the filename of the syntax definition file.
- --syntax-string string: specifies the syntax as an inline string.
@ -110,6 +113,7 @@ piped from standard input.
#### Options:
- --export bool: determines whether the generated parse function is exported (visible outside its package).
- --max-trace-length int: enables tracing when set to a positive integer.
- --package-name string: specifies the package name for the generated code. Defaults to main.
- --syntax string: specifies the filename of the syntax definition file.
- --syntax-string string: specifies the syntax as an inline string.
@ -131,9 +135,11 @@ when formatting files in place, or print the formatted syntax to stdout.
#### Options:
- --in-place bool:
- --syntax string \[\*\]:
- --syntax-string string:
- --in-place bool: specifies if an input file should be formatted in-place instead of printing the formatted
results to the standard output.
- --max-trace-length int: enables tracing when set to a positive integer.
- --syntax string \[\*\]: specifies the filename of the syntax definition file.
- --syntax-string string: specifies the syntax as an inline string.
- --help: Show help.
### treerack version

View File

@ -27,6 +27,9 @@ type showOptions struct {
// Indent specifies a custom indentation string for the output.
Indent string
// MaxTraceLength enables tracing when set to a positive integer.
MaxTraceLength int
}
type node struct {
@ -72,7 +75,7 @@ func show(o showOptions, stdin io.Reader, stdout io.Writer, args ...string) erro
}
defer finalizeInput()
s := &treerack.Syntax{}
s := &treerack.Syntax{MaxTraceLength: o.MaxTraceLength}
if err := s.ReadSyntax(syntax); err != nil {
if terr := treerack.Trace(os.Stderr, err); err != nil {
err = errors.Join(err, terr)

View File

@ -7,33 +7,33 @@ import (
"unicode"
)
const maxTraceEntries = 36
type context struct {
reader io.RuneReader
keywords []parser
offset int
readOffset int
consumed int
offsetLimit int
failOffset int
failingParser parser
readErr error
eof bool
results *results
tokens []rune
matchLast bool
level int
tr []TraceEntry
reader io.RuneReader
keywords []parser
offset int
readOffset int
consumed int
offsetLimit int
failOffset int
failingParser parser
readErr error
eof bool
results *results
tokens []rune
matchLast bool
level int
tr []TraceEntry
maxTraceLength int
}
func newContext(r io.RuneReader, keywords []parser) *context {
func newContext(r io.RuneReader, keywords []parser, maxTraceLength int) *context {
return &context{
reader: r,
keywords: keywords,
results: &results{},
offsetLimit: -1,
failOffset: -1,
reader: r,
keywords: keywords,
results: &results{},
offsetLimit: -1,
failOffset: -1,
maxTraceLength: maxTraceLength,
}
}
@ -199,11 +199,15 @@ func (c *context) finalizeParse(root parser) error {
}
func (c *context) trace(p parser, from, to int, event TraceEvent, reason ...string) {
if c.maxTraceLength <= 0 {
return
}
if p.commitType()&userDefined == 0 || p.commitType()&FailPass != 0 {
return
}
if len(c.tr) == maxTraceEntries {
if len(c.tr) == c.maxTraceLength {
c.tr = c.tr[1:]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -603,28 +603,27 @@ func (r *results) unmarkPending(offset, id int) {
}
}
const maxTraceEntries = 36
type context struct {
reader io.RuneReader
keywords []parser
offset int
readOffset int
consumed int
offsetLimit int
failOffset int
failingParser parser
readErr error
eof bool
results *results
tokens []rune
matchLast bool
level int
tr []TraceEntry
reader io.RuneReader
keywords []parser
offset int
readOffset int
consumed int
offsetLimit int
failOffset int
failingParser parser
readErr error
eof bool
results *results
tokens []rune
matchLast bool
level int
tr []TraceEntry
maxTraceLength int
}
func newContext(r io.RuneReader, keywords []parser) *context {
return &context{reader: r, keywords: keywords, results: &results{}, offsetLimit: -1, failOffset: -1}
func newContext(r io.RuneReader, keywords []parser, maxTraceLength int) *context {
return &context{reader: r, keywords: keywords, results: &results{}, offsetLimit: -1, failOffset: -1, maxTraceLength: maxTraceLength}
}
func (c *context) read() bool {
if c.eof || c.readErr != nil {
@ -753,10 +752,13 @@ func (c *context) finalizeParse(root parser) error {
return c.parseError(root, false, root.nodeID())
}
func (c *context) trace(p parser, from, to int, event TraceEvent, reason ...string) {
if c.maxTraceLength <= 0 {
return
}
if p.commitType()&userDefined == 0 || p.commitType()&FailPass != 0 {
return
}
if len(c.tr) == maxTraceEntries {
if len(c.tr) == c.maxTraceLength {
c.tr = c.tr[1:]
}
switch event {
@ -877,8 +879,8 @@ func (pe *ParseError) Error() string {
}
return fmt.Sprintf("%s:%d:%d:parse failed, parsing: %s, at %d:%d", pe.Input, pe.Line+1, pe.Column+1, pe.Definition, pe.Line+1, pe.Column+1)
}
func parseInput(r io.Reader, p parser, b builder, kw []parser) (Node, error) {
c := newContext(bufio.NewReader(r), kw)
func parseInput(r io.Reader, p parser, b builder, kw []parser, maxTraceLength int) (Node, error) {
c := newContext(bufio.NewReader(r), kw, maxTraceLength)
p.parse(c)
if c.readErr != nil {
return Node{}, c.readErr
@ -1468,5 +1470,5 @@ func Parse(r io.Reader) (Node, error) {
var keywords = []parser{}
return parseInput(r, &p183, &b183, keywords)
return parseInput(r, &p183, &b183, keywords, 0)
}

View File

@ -776,7 +776,7 @@ func TestCharBuildNoop(t *testing.T) {
c := newChar("foo", false, nil, nil)
c.init(newRegistry())
b := c.builder()
ctx := newContext(bufio.NewReader(bytes.NewBuffer(nil)), nil)
ctx := newContext(bufio.NewReader(bytes.NewBuffer(nil)), nil, 0)
if n, ok := b.build(ctx); len(n) != 0 || ok {
t.Error("char build not noop")
}

View File

@ -62,6 +62,10 @@ type Syntax struct {
explicitRoot bool
keywords []definition
root definition
// MaxTraceLength can be used to enable tracing by setting a positive value. It should be set before
// parsing.
MaxTraceLength int
}
// GeneratorOptions control the behavior of the Go code generator.
@ -570,7 +574,7 @@ func (s *Syntax) Generate(o GeneratorOptions, w io.Writer) error {
fprintln()
fprintln()
fprintf(`return parseInput(r, &p%d, &b%d, keywords)`, s.root.parser().nodeID(), s.root.builder().nodeID())
fprintf(`return parseInput(r, &p%d, &b%d, keywords, 0)`, s.root.parser().nodeID(), s.root.builder().nodeID())
fprintln()
fprint(`}`)
fprintln()
@ -584,7 +588,7 @@ func (s *Syntax) Parse(r io.Reader) (Node, error) {
return Node{}, err
}
return parseInput(r, s.root.parser(), s.root.builder(), s.keywordParsers())
return parseInput(r, s.root.parser(), s.root.builder(), s.keywordParsers(), s.MaxTraceLength)
}
// Format prints the loaded syntax definition to the output in a formatted way.

View File

@ -199,8 +199,8 @@ func (pe *ParseError) Error() string {
)
}
func parseInput(r io.Reader, p parser, b builder, kw []parser) (Node, error) {
c := newContext(bufio.NewReader(r), kw)
func parseInput(r io.Reader, p parser, b builder, kw []parser, maxTraceLength int) (Node, error) {
c := newContext(bufio.NewReader(r), kw, maxTraceLength)
p.parse(c)
if c.readErr != nil {
return Node{}, c.readErr