From a733d0ae1391dc8642d21f8784696e95047b7923 Mon Sep 17 00:00:00 2001 From: Adam Scarr Date: Sun, 13 Aug 2017 19:50:41 +1000 Subject: [PATCH] Improve debugging output --- debugon.go | 17 ++++--- parser.go | 15 ------ readme.md | 125 +++++++++++++++++++++++++++++-------------------- result.go | 36 ++++++++++++++ result_test.go | 15 ++++++ 5 files changed, 133 insertions(+), 75 deletions(-) create mode 100644 result.go create mode 100644 result_test.go diff --git a/debugon.go b/debugon.go index 60efe60..302b0ec 100644 --- a/debugon.go +++ b/debugon.go @@ -44,16 +44,13 @@ func (dp *debugParser) logf(ps *State, result *Result, format string, args ...in buf.WriteString(fmt.Sprintf("%"+strconv.Itoa(longestLocation)+"s | ", dp.Location)) buf.WriteString(fmt.Sprintf("%-15s", ps.Preview(15))) buf.WriteString(" | ") - output := "" - if ps.Errored() { - output = "fail" - } else if result != nil { - output = result.Token - } - buf.WriteString(fmt.Sprintf("%-10s | ", output)) buf.WriteString(strings.Repeat(" ", len(activeParsers)-1)) buf.WriteString(fmt.Sprintf(format, args...)) - buf.WriteString(fmt.Sprintf(" > %#v", result)) + if ps.Errored() { + buf.WriteString(fmt.Sprintf(" did not find %s", ps.Error.expected)) + } else if result != nil { + buf.WriteString(fmt.Sprintf(" found %s", result.String())) + } buf.WriteRune('\n') return buf.String() } @@ -64,7 +61,7 @@ func (dp *debugParser) logStart(ps *State) { fmt.Fprint(log, pendingOpenLog) pendingOpenLog = "" } - pendingOpenLog = dp.logf(ps, nil, dp.Name()) + pendingOpenLog = dp.logf(ps, nil, dp.Name()+" {") } } @@ -73,6 +70,8 @@ func (dp *debugParser) logEnd(ps *State, result *Result) { if pendingOpenLog != "" { fmt.Fprintf(log, dp.logf(ps, result, dp.Name())) pendingOpenLog = "" + } else { + fmt.Fprintf(log, dp.logf(ps, result, "}")) } } } diff --git a/parser.go b/parser.go index 763eff4..fecf635 100644 --- a/parser.go +++ b/parser.go @@ -7,17 +7,6 @@ import ( "unicode/utf8" ) -var TrashResult = &Result{} - -// Result is the output of a parser. Usually only one of its fields will be set and should be though of -// more as a union type. having it avoids interface{} littered all through the parsing code and makes -// the it easy to do the two most common operations, getting a token and finding a child. -type Result struct { - Token string - Child []Result - Result interface{} -} - // Parser is the workhorse of parsify. A parser takes a State and returns a result, consuming some // of the State in the process. // Given state is shared there are a few rules that should be followed: @@ -64,10 +53,6 @@ func Parsify(p Parserish) Parser { } case string: return Exact(p) - case VoidParser: - return func(ptr *State, node *Result) { - p(ptr) - } case func(*State): return func(ptr *State, node *Result) { p(ptr) diff --git a/readme.md b/readme.md index 3091d23..ba35397 100644 --- a/readme.md +++ b/readme.md @@ -28,58 +28,81 @@ When a parser isnt working as you intended you can build with debugging and enab This works great with tests, eg in the goparsify source tree ``` -$ cd html -$ go test -tags debug -parselogs -html.go:50 | hello

hello

hello

hello

hello

hello

hello

| > -html.go:26 | hello

| < | < -html.go:20 | color="blue">w | p | identifier -html.go:35 | color="blue">w | | attrs -html.go:34 | color="blue">w | | attr -html.go:20 | ="blue">worldworld

world

world

world

| > -html.go:26 | world

| world | text -html.go:25 |

| | element -html.go:21 |

| fail | text -html.go:50 |

| | tag -html.go:45 |

| | tstart -html.go:45 | /p> | < | < -html.go:20 | /p> | fail | identifier -html.go:46 |

| | tend -html.go:46 | p> | | p | identifier -html.go:46 | | > | > -html.go:25 | | | element -html.go:21 | | fail | text -html.go:50 | | | tag -html.go:45 | | | tstart -html.go:45 | /body> | < | < -html.go:20 | /body> | fail | identifier -html.go:46 | | | tend -html.go:46 | body> | | body | identifier -html.go:46 | | > | > +adam:goparsify(master)$ go test -tags debug ./html -v +=== RUN TestParse +html.go:48 | hello

hello

hello

hello

hello

hello

hello

hello

hello

found > +html.go:43 | hello

] +html.go:24 | hello

+html.go:48 |

| < found < +html.go:20 | color="blue">w | identifier found p +html.go:33 | color="blue">w | attrs { +html.go:32 | color="blue">w | attr { +html.go:20 | ="blue">worldworld

world

world

world

world

world

world

found > +html.go:43 | world

] +html.go:24 | world

| text found world +html.go:23 |

| } found "world" +html.go:23 |

| element { +html.go:21 |

| text did not find <> +html.go:48 |

| tag { +html.go:43 |

| tstart { +html.go:43 | /p> | < found < +html.go:20 | /p> | identifier did not find [a-zA-Z][a-zA-Z0-9]* +html.go:43 |

| } did not find [a-zA-Z][a-zA-Z0-9]* +html.go:48 |

| } did not find [a-zA-Z][a-zA-Z0-9]* +html.go:23 |

| } did not find [a-zA-Z][a-zA-Z0-9]* +html.go:24 |

| } found ["world"] +html.go:44 |

| tend { +html.go:44 | p> | | identifier found p +html.go:44 | | > found > +html.go:44 | | } found [] +html.go:48 | | } found "hello " +html.go:23 | | } found html.htmlTag{Name:"p", Attributes:map[string]string{"color":"blue"}, Body:[]interface {}{"world"}} +html.go:23 | | element { +html.go:48 | | tag { +html.go:43 | | tstart { +html.go:43 | /body> | < found < +html.go:20 | /body> | identifier did not find [a-zA-Z][a-zA-Z0-9]* +html.go:43 | | } did not find [a-zA-Z][a-zA-Z0-9]* +html.go:48 | | } did not find [a-zA-Z][a-zA-Z0-9]* +html.go:21 | | text did not find <> +html.go:23 | | } did not find [a-zA-Z][a-zA-Z0-9]* +html.go:24 | | } found ["hello ",html.htmlTag{Name:"p", Attributes:map[string]string{"color":"blue"}, Body:[]interface {}{"world"}}] +html.go:44 | | tend { +html.go:44 | body> | | identifier found body +html.go:44 | | > found > +html.go:44 | | } found [] +html.go:48 | | } found [[<,body,,map[string]string{},>],,[]interface {}{"hello ", html.htmlTag{Name:"p", Attributes:map[string]string{"color":"blue"}, Body:[]interface {}{"world"}}},[]] +--- PASS: TestParse (0.00s) PASS -ok github.com/vektah/goparsify/html 0.118s +ok github.com/vektah/goparsify/html 0.117s ``` ### debugging performance diff --git a/result.go b/result.go new file mode 100644 index 0000000..cdad054 --- /dev/null +++ b/result.go @@ -0,0 +1,36 @@ +package goparsify + +import ( + "fmt" + "strings" +) + +var TrashResult = &Result{} + +// Result is the output of a parser. Usually only one of its fields will be set and should be though of +// more as a union type. having it avoids interface{} littered all through the parsing code and makes +// the it easy to do the two most common operations, getting a token and finding a child. +type Result struct { + Token string + Child []Result + Result interface{} +} + +func (r Result) String() string { + if r.Result != nil { + if rs, ok := r.Result.(fmt.Stringer); ok { + return rs.String() + } + return fmt.Sprintf("%#v", r.Result) + } + + if len(r.Child) > 0 { + children := []string{} + for _, child := range r.Child { + children = append(children, child.String()) + } + return "[" + strings.Join(children, ",") + "]" + } + + return r.Token +} diff --git a/result_test.go b/result_test.go new file mode 100644 index 0000000..3a3d6b8 --- /dev/null +++ b/result_test.go @@ -0,0 +1,15 @@ +package goparsify + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestResult_String(t *testing.T) { + require.Equal(t, "Hello", Result{Token: "Hello"}.String()) + require.Equal(t, "[Hello,World]", Result{Child: []Result{{Token: "Hello"}, {Token: "World"}}}.String()) + require.Equal(t, "10", Result{Result: 10}.String()) + require.Equal(t, "10", Result{Result: big.NewInt(10)}.String()) +}