ADDED src/0dev.org/parse/parse.go Index: src/0dev.org/parse/parse.go ================================================================== --- /dev/null +++ src/0dev.org/parse/parse.go @@ -0,0 +1,81 @@ +// Package parse provides a simple parser combinator library +package parse + +// The N struct is a parser result node. +type N struct { + // Match indicates whether the parser succeeded. + Matched bool + // Content contains whatever was matched by the parser. + Content string + // Nodes contains any result nodes by nested parsers. + Nodes []N +} + +// Parser function type +// Takes a string and returns a result and the remaining part of the string. +type P func(string) (N, string) + +// A sequence of parsers. Matches when all of p match. +func Seq(p ...P) P { + return func(s string) (N, string) { + result := N{Matched: true, Nodes: make([]N, 0, len(p))} + for _, parser := range p { + n, r := parser(s) + result.Nodes = append(result.Nodes, n) + if !n.Matched { + result.Matched = false + break + } + result.Content = result.Content + n.Content + s = r + } + return result, s + } +} + +// Matches and returns on the first match of p. +func Any(p ...P) P { + return func(s string) (N, string) { + result := N{Matched: false, Nodes: nil} + for _, parser := range p { + n, r := parser(s) + if n.Matched { + return n, r + } + result.Nodes, s = append(result.Nodes, n), r + } + return result, s + } +} + +// Kleene star (zero or more). Always matches. +func K(p P) P { + return func(s string) (N, string) { + result := N{Matched: true, Nodes: nil} + for n, r := p(s); n.Matched; n, r = p(r) { + result.Content = result.Content + n.Content + result.Nodes = append(result.Nodes, n) + s = r + } + return result, s + } +} + +// Returns a delegating parser whose delegate can be set on later. +// Useful for recursive definitions. +func Defer() (P, *P) { + var deferred P + return func(s string) (N, string) { + return deferred(s) + }, &deferred +} + +// Returns a parser that accepts the specified number of bytes +func Accept(count int) P { + return func(s string) (N, string) { + if len(s) < count { + return N{Matched: false, Content: "", Nodes: nil}, s + } + return N{Matched: true, Content: s[:count], Nodes: nil}, s[count:] + } +} ADDED src/0dev.org/parse/parse_test.go Index: src/0dev.org/parse/parse_test.go ================================================================== --- /dev/null +++ src/0dev.org/parse/parse_test.go @@ -0,0 +1,57 @@ +package parse + +import ( + "testing" +) + +func TestAccept(t *testing.T) { + // Accept(0) will always match :) + p0 := Accept(0) + if r, s := p0(""); !r.Matched || r.Content != "" || r.Nodes != nil || s != "" { + t.Error("Invalid result for Accept's negative test", r) + } + // Test with a non-zero amount of bytes + p := Accept(1) + if r, s := p(""); r.Matched || r.Content != "" || r.Nodes != nil || s != "" { + t.Error("Invalid result for Accept's negative test", r) + } + if r, s := p("a"); !r.Matched || r.Content != "a" || r.Nodes != nil || s != "" { + t.Error("Invalid result for Accept's positive test", r) + } +} + +func TestK(t *testing.T) { + p := Accept(1) + k := K(p) + if r, s := k("aaa"); !r.Matched || r.Content != "aaa" || r.Nodes == nil || s != "" { + t.Error("Invalid result for K* match test", r) + } + if r, s := k(""); !r.Matched || r.Content != "" || r.Nodes != nil || s != "" { + t.Error("Invalid result for K* no-match test", r) + } +} + +func TestSeq(t *testing.T) { + p1, p2 := Accept(1), Accept(1) + s := Seq(p1, p2) + if r, s := s("aa"); !r.Matched || r.Content != "aa" || r.Nodes == nil || s != "" { + t.Error("Invalid result for Seq positive test", r) + } + if r, s := s("a"); r.Matched || r.Content != "a" || r.Nodes == nil || s != "" { + t.Error("Invalid result for Seq partial match test", r) + } + if r, s := s(""); r.Matched || r.Content != "" || r.Nodes == nil || s != "" { + t.Error("Invalid result for Seq no-match test", r) + } +} + +func TestAny(t *testing.T) { + p, p0 := Accept(1), Accept(0) + a := Any(p, p0) + if r, s := a(""); !r.Matched || r.Content != "" || r.Nodes != nil || s != "" { + t.Error("Invalid result for Any match test", r) + } + if r, s := a("aa"); !r.Matched || r.Content != "a" || r.Nodes != nil || s != "a" { + t.Error("Invalid result for Any match test", r) + } +}