~mna/siberian

9587361f7eed880d33866e981d24a988d7d9d887 — Martin Angers 4 years ago 15ed568
add helper funcs for common repetition, EOF matcher
6 files changed, 119 insertions(+), 15 deletions(-)

M .golangci.toml
M ebnf.go
M ebnf_test.go
A grammar_test.go
M matcher.go
M matcher_test.go
M .golangci.toml => .golangci.toml +4 -0
@@ 23,6 23,10 @@

[issues]
  exclude-use-default = false
  exclude = [
    "`EOF` is a global variable",
  ]


[issues.exclude-rules]
  path = "_test.go"

M ebnf.go => ebnf.go +42 -0
@@ 19,6 19,48 @@ func (a *Alt) Match(b []byte) int {
	return -1
}

// ZeroOrOne returns a Repeat matcher that matches m zero or one
// time.
func ZeroOrOne(m Matcher) Matcher {
	return AtMost(m, 1)
}

// ZeroOrMore returns a Repeat matcher that matches m zero or more
// times.
func ZeroOrMore(m Matcher) Matcher {
	return &Repeat{
		M:   m,
		Min: 0,
		Max: -1,
	}
}

// OneOrMore returns a Repeat matcher that matches m one or more
// times.
func OneOrMore(m Matcher) Matcher {
	return AtLeast(m, 1)
}

// AtLeast returns a Repeat matcher that matches m at least min
// times.
func AtLeast(m Matcher, min int) Matcher {
	return &Repeat{
		M:   m,
		Min: min,
		Max: -1,
	}
}

// AtMost returns a Repeat matcher that matches m at most max
// times.
func AtMost(m Matcher, max int) Matcher {
	return &Repeat{
		M:   m,
		Min: 0,
		Max: max,
	}
}

// Repeat is a Matcher that matches M at least Min times and at most
// Max times. Min can be zero, in which case Repeat always matches,
// and Max can be -1 for no limit.

M ebnf_test.go => ebnf_test.go +3 -15
@@ 43,11 43,7 @@ func TestRepeat_ZeroOrOne(t *testing.T) {
		{"ababab", 2}, // one is max
		{"cabab", 0},
	}
	rep := Repeat{
		M:   Equal("ab"),
		Min: 0,
		Max: 1,
	}
	rep := ZeroOrOne(Equal("ab"))
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			n := rep.Match([]byte(c.in))


@@ 68,11 64,7 @@ func TestRepeat_OneOrMore(t *testing.T) {
		{"ababab", 6},
		{"cabab", -1},
	}
	rep := Repeat{
		M:   Equal("ab"),
		Min: 1,
		Max: -1,
	}
	rep := OneOrMore(Equal("ab"))
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			n := rep.Match([]byte(c.in))


@@ 93,11 85,7 @@ func TestRepeat_ZeroOrMore(t *testing.T) {
		{"ababab", 6},
		{"cabab", 0},
	}
	rep := Repeat{
		M:   Equal("ab"),
		Min: 0,
		Max: -1,
	}
	rep := ZeroOrMore(Equal("ab"))
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			n := rep.Match([]byte(c.in))

A grammar_test.go => grammar_test.go +43 -0
@@ 0,0 1,43 @@
package siberian

import "testing"

func TestGrammar_AB(t *testing.T) {
	// Start = { As | B } .
	// As = A+ .
	// A = "a" .
	// B = "b" .
	g := &Repeat{
		Min: 0,
		Max: -1,
		M: &Alt{
			Ms: []Matcher{
				&Repeat{
					Min: 1,
					Max: -1,
					M:   Equal("a"),
				},
				Equal("b"),
			},
		},
	}
	cases := []matcherTest{
		{"", 0},
		{"a", 1},
		{"b", 1},
		{"c", 0},
		{"aa", 2},
		{"aaab", 4},
		{"bb", 2},
		{"bbaa", 4},
		{"aaabc", 4},
	}
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			n := g.Match([]byte(c.in))
			if n != c.len {
				t.Errorf("want match of length %d, got %d", c.len, n)
			}
		})
	}
}

M matcher.go => matcher.go +10 -0
@@ 131,3 131,13 @@ func Runes(n int) MatcherFunc {
		return total
	}
}

// EOF is a MatcherFunc that matches if the input is at EOF.
var EOF = MatcherFunc(eof)

func eof(b []byte) int {
	if len(b) == 0 {
		return 0
	}
	return -1
}

M matcher_test.go => matcher_test.go +17 -0
@@ 163,3 163,20 @@ func TestRunes(t *testing.T) {
		})
	}
}

func TestEOF(t *testing.T) {
	cases := []matcherTest{
		{"", 0},
		{"a", -1},
		{"ab", -1},
	}
	var m Matcher = EOF
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			n := m.Match([]byte(c.in))
			if n != c.len {
				t.Errorf("want match of length %d, got %d", c.len, n)
			}
		})
	}
}