diff --git a/combinator.go b/combinator.go index 3cb8c6d..7dc4147 100644 --- a/combinator.go +++ b/combinator.go @@ -1,17 +1,17 @@ -package parsec +package goparsify import ( "bytes" ) -func Nil(ps *State) *Node { +var Nil = NewParser("Nil", func(ps *State) *Node { return nil -} +}) -func Never(ps *State) *Node { +var Never = NewParser("Never", func(ps *State) *Node { ps.ErrorHere("not anything") return nil -} +}) func And(parsers ...Parserish) Parser { if len(parsers) == 0 { @@ -20,7 +20,7 @@ func And(parsers ...Parserish) Parser { parserfied := ParsifyAll(parsers...) - return func(ps *State) *Node { + return NewParser("And", func(ps *State) *Node { var nodes = make([]*Node, 0, len(parserfied)) startpos := ps.Pos for _, parser := range parserfied { @@ -34,7 +34,7 @@ func And(parsers ...Parserish) Parser { } } return &Node{Children: nodes} - } + }) } func Any(parsers ...Parserish) Parser { @@ -44,7 +44,7 @@ func Any(parsers ...Parserish) Parser { parserfied := ParsifyAll(parsers...) - return func(ps *State) *Node { + return NewParser("Any", func(ps *State) *Node { longestError := Error{} startpos := ps.Pos for _, parser := range parserfied { @@ -62,23 +62,23 @@ func Any(parsers ...Parserish) Parser { ps.Error = longestError ps.Pos = startpos return nil - } + }) } func Kleene(opScan Parserish, sepScan ...Parserish) Parser { - return manyImpl(0, opScan, Never, sepScan...) + return NewParser("Kleene", manyImpl(0, opScan, Never, sepScan...)) } func KleeneUntil(opScan Parserish, untilScan Parserish, sepScan ...Parserish) Parser { - return manyImpl(0, opScan, untilScan, sepScan...) + return NewParser("KleeneUntil", manyImpl(0, opScan, untilScan, sepScan...)) } func Many(opScan Parserish, sepScan ...Parserish) Parser { - return manyImpl(1, opScan, Never, sepScan...) + return NewParser("Many", manyImpl(1, opScan, Never, sepScan...)) } func ManyUntil(opScan Parserish, untilScan Parserish, sepScan ...Parserish) Parser { - return manyImpl(1, opScan, untilScan, sepScan...) + return NewParser("ManyUntil", manyImpl(1, opScan, untilScan, sepScan...)) } func manyImpl(min int, op Parserish, until Parserish, sep ...Parserish) Parser { @@ -132,7 +132,7 @@ func manyImpl(min int, op Parserish, until Parserish, sep ...Parserish) Parser { func Maybe(parser Parserish) Parser { parserfied := Parsify(parser) - return func(ps *State) *Node { + return NewParser("Maybe", func(ps *State) *Node { node := parserfied(ps) if ps.Errored() { ps.ClearError() @@ -140,19 +140,19 @@ func Maybe(parser Parserish) Parser { } return node - } + }) } func Map(parser Parserish, f func(n *Node) *Node) Parser { p := Parsify(parser) - return func(ps *State) *Node { + return NewParser("Map", func(ps *State) *Node { node := p(ps) if ps.Errored() { return nil } return f(node) - } + }) } func flatten(n *Node) string { @@ -172,7 +172,7 @@ func flatten(n *Node) string { } func Merge(parser Parserish) Parser { - return Map(parser, func(n *Node) *Node { + return NewParser("Merge", Map(parser, func(n *Node) *Node { return &Node{Token: flatten(n)} - }) + })) } diff --git a/combinator_test.go b/combinator_test.go index 0cd5ea5..0f10b01 100644 --- a/combinator_test.go +++ b/combinator_test.go @@ -1,4 +1,4 @@ -package parsec +package goparsify import ( "testing" diff --git a/debugoff.go b/debugoff.go new file mode 100644 index 0000000..8eb4790 --- /dev/null +++ b/debugoff.go @@ -0,0 +1,11 @@ +// +build !debug + +package goparsify + +func NewParser(description string, p Parser) Parser { + return p +} + +func DumpDebugStats() { + +} diff --git a/debugon.go b/debugon.go new file mode 100644 index 0000000..79acf0f --- /dev/null +++ b/debugon.go @@ -0,0 +1,83 @@ +// +build debug + +package goparsify + +import ( + "fmt" + "runtime" + "sort" + "strings" + "time" +) + +var parsers []*DebugParser + +type DebugParser struct { + Description string + Caller string + Next Parser + Time time.Duration + Calls int +} + +func (dp *DebugParser) Parse(ps *State) *Node { + start := time.Now() + + ret := dp.Next(ps) + + dp.Time = dp.Time + time.Since(start) + dp.Calls++ + + return ret +} + +func getPackageName(f *runtime.Func) string { + parts := strings.Split(f.Name(), ".") + pl := len(parts) + + if parts[pl-2][0] == '(' { + return strings.Join(parts[0:pl-2], ".") + } else { + return strings.Join(parts[0:pl-1], ".") + } +} + +func NewParser(description string, p Parser) Parser { + fpcs := make([]uintptr, 1) + caller := "" + + for i := 1; i < 10; i++ { + n := runtime.Callers(i, fpcs) + + if n != 0 { + frame := runtime.FuncForPC(fpcs[0] - 1) + pkg := getPackageName(frame) + + if pkg != "github.com/vektah/goparsify" { + file, line := frame.FileLine(fpcs[0] - 1) + caller = fmt.Sprintf("%s:%d", file, line) + break + } + } + } + + dp := &DebugParser{ + Description: description, + Next: p, + Caller: caller, + } + + parsers = append(parsers, dp) + return dp.Parse +} + +func DumpDebugStats() { + sort.Slice(parsers, func(i, j int) bool { + return parsers[i].Time >= parsers[j].Time + }) + + fmt.Println("Parser stats:") + for _, parser := range parsers { + fmt.Printf("%20s\t%10s\t%10d\tcalls\t%s\n", parser.Description, parser.Time.String(), parser.Calls, parser.Caller) + } +} diff --git a/json/json_test.go b/json/json_test.go index 84fdd5c..8f2e148 100644 --- a/json/json_test.go +++ b/json/json_test.go @@ -6,6 +6,7 @@ import ( parsecJson "github.com/prataprc/goparsec/json" "github.com/stretchr/testify/require" + "github.com/vektah/goparsify" ) func TestUnmarshal(t *testing.T) { @@ -58,6 +59,7 @@ func BenchmarkUnmarshalParsify(b *testing.B) { _, err := Unmarshal(benchmarkString) require.NoError(b, err) } + goparsify.DumpDebugStats() } func BenchmarkUnmarshalStdlib(b *testing.B) { diff --git a/parser.go b/parser.go index 30a3a01..4db525b 100644 --- a/parser.go +++ b/parser.go @@ -1,4 +1,4 @@ -package parsec +package goparsify import ( "bytes" @@ -33,8 +33,8 @@ type Parserish interface{} func Parsify(p Parserish) Parser { switch p := p.(type) { - case func(*State) *Node: - return Parser(p) + //case func(*State) *Node: + // return NewParser("anonymous func", p) case Parser: return p case *Parser: @@ -70,7 +70,7 @@ func ParseString(parser Parserish, input string) (result interface{}, remaining } func Exact(match string) Parser { - return func(ps *State) *Node { + return NewParser(match, func(ps *State) *Node { if !strings.HasPrefix(ps.Get(), match) { ps.ErrorHere(match) return nil @@ -79,7 +79,7 @@ func Exact(match string) Parser { ps.Advance(len(match)) return &Node{Token: match} - } + }) } func parseRepetition(defaultMin, defaultMax int, repetition ...int) (min int, max int) { @@ -126,11 +126,11 @@ func parseMatcher(matcher string) (matches string, ranges [][]rune) { } func Chars(matcher string, repetition ...int) Parser { - return charsImpl(matcher, false, repetition...) + return NewParser("["+matcher+"]", charsImpl(matcher, false, repetition...)) } func NotChars(matcher string, repetition ...int) Parser { - return charsImpl(matcher, true, repetition...) + return NewParser("!["+matcher+"]", charsImpl(matcher, true, repetition...)) } func charsImpl(matcher string, stopOn bool, repetition ...int) Parser { @@ -173,7 +173,7 @@ func charsImpl(matcher string, stopOn bool, repetition ...int) Parser { } } -var ws = Chars("\t\n\v\f\r \x85\xA0", 0) +var ws = NewParser("WS", Chars("\t\n\v\f\r \x85\xA0", 0)) func WS(ps *State) *Node { ws(ps) @@ -181,7 +181,7 @@ func WS(ps *State) *Node { } func String(quote rune) Parser { - return func(ps *State) *Node { + return NewParser("string", func(ps *State) *Node { var r rune var w int var matched int @@ -213,5 +213,5 @@ func String(quote rune) Parser { ps.ErrorHere("\"") return nil - } + }) } diff --git a/parser_test.go b/parser_test.go index acb569e..d7ab0d4 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,4 +1,4 @@ -package parsec +package goparsify import ( "testing" diff --git a/state.go b/state.go index f8df3f1..e7a5fe4 100644 --- a/state.go +++ b/state.go @@ -1,4 +1,4 @@ -package parsec +package goparsify import "fmt" diff --git a/state_test.go b/state_test.go index fe04a58..c735353 100644 --- a/state_test.go +++ b/state_test.go @@ -1,4 +1,4 @@ -package parsec +package goparsify import ( "testing"