Check-in [061baeefcb]
Overview
Comment:A simple parser combinator library
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 061baeefcb27371e64a70378d3deb69946a13db3
User & Date: spaskalev on 2015-03-19 20:17:55
Other Links: manifest | tags
Context
2015-03-19
20:36
Added a string matching parser. check-in: 893c36d683 user: spaskalev tags: trunk
20:17
A simple parser combinator library check-in: 061baeefcb user: spaskalev tags: trunk
2015-01-26
23:02
full dep traversal in the collecting handler check-in: bca8ee3e12 user: spaskalev tags: trunk
Changes

Added src/0dev.org/parse/parse.go version [b38eeb1414].


















































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 version [72baa0d593].


























































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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)
	}
}