274 lines
11 KiB
Go
274 lines
11 KiB
Go
package parsekit_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"git.makaay.nl/mauricem/go-parsekit"
|
|
)
|
|
|
|
func TestCombinators(t *testing.T) {
|
|
RunTokenHandlerTests(t, []TokenHandlerTest{
|
|
{"xxx", c.Rune('x'), true, "x"},
|
|
{"x ", c.Rune(' '), false, ""},
|
|
{"aa", c.RuneRange('b', 'e'), false, ""},
|
|
{"bb", c.RuneRange('b', 'e'), true, "b"},
|
|
{"cc", c.RuneRange('b', 'e'), true, "c"},
|
|
{"dd", c.RuneRange('b', 'e'), true, "d"},
|
|
{"ee", c.RuneRange('b', 'e'), true, "e"},
|
|
{"ff", c.RuneRange('b', 'e'), false, ""},
|
|
{"Hello, world!", c.Str("Hello"), true, "Hello"},
|
|
{"HellÖ, world!", c.StrNoCase("hellö"), true, "HellÖ"},
|
|
{"+X", c.Runes('+', '-', '*', '/'), true, "+"},
|
|
{"-X", c.Runes('+', '-', '*', '/'), true, "-"},
|
|
{"*X", c.Runes('+', '-', '*', '/'), true, "*"},
|
|
{"/X", c.Runes('+', '-', '*', '/'), true, "/"},
|
|
{"!X", c.Runes('+', '-', '*', '/'), false, ""},
|
|
{"abc", c.Not(c.Rune('b')), true, "a"},
|
|
{"bcd", c.Not(c.Rune('b')), false, ""},
|
|
{"bcd", c.Not(c.Rune('b')), false, ""},
|
|
{"1010", c.Not(c.Seq(c.Rune('2'), c.Rune('0'))), true, "1"},
|
|
{"2020", c.Not(c.Seq(c.Rune('2'), c.Rune('0'))), false, ""},
|
|
{"abc", c.Any(c.Rune('a'), c.Rune('b')), true, "a"},
|
|
{"bcd", c.Any(c.Rune('a'), c.Rune('b')), true, "b"},
|
|
{"cde", c.Any(c.Rune('a'), c.Rune('b')), false, ""},
|
|
{"ababc", c.Rep(4, c.Runes('a', 'b')), true, "abab"},
|
|
{"ababc", c.Rep(5, c.Runes('a', 'b')), false, ""},
|
|
{"", c.Min(0, c.Rune('a')), true, ""},
|
|
{"a", c.Min(0, c.Rune('a')), true, "a"},
|
|
{"aaaaa", c.Min(4, c.Rune('a')), true, "aaaaa"},
|
|
{"aaaaa", c.Min(5, c.Rune('a')), true, "aaaaa"},
|
|
{"aaaaa", c.Min(6, c.Rune('a')), false, ""},
|
|
{"", c.Max(4, c.Rune('b')), true, ""},
|
|
{"X", c.Max(4, c.Rune('b')), true, ""},
|
|
{"bbbbbX", c.Max(4, c.Rune('b')), true, "bbbb"},
|
|
{"bbbbbX", c.Max(5, c.Rune('b')), true, "bbbbb"},
|
|
{"bbbbbX", c.Max(6, c.Rune('b')), true, "bbbbb"},
|
|
{"", c.MinMax(0, 0, c.Rune('c')), true, ""},
|
|
{"X", c.MinMax(0, 0, c.Rune('c')), true, ""},
|
|
{"cccc", c.MinMax(0, 5, c.Rune('c')), true, "cccc"},
|
|
{"ccccc", c.MinMax(0, 5, c.Rune('c')), true, "ccccc"},
|
|
{"cccccc", c.MinMax(0, 5, c.Rune('c')), true, "ccccc"},
|
|
{"cccccX", c.MinMax(0, 0, c.Rune('c')), true, ""},
|
|
{"cccccX", c.MinMax(0, 1, c.Rune('c')), true, "c"},
|
|
{"cccccX", c.MinMax(0, 5, c.Rune('c')), true, "ccccc"},
|
|
{"cccccX", c.MinMax(0, 6, c.Rune('c')), true, "ccccc"},
|
|
{"cccccX", c.MinMax(1, 1, c.Rune('c')), true, "c"},
|
|
{"", c.MinMax(1, 1, c.Rune('c')), false, ""},
|
|
{"X", c.MinMax(1, 1, c.Rune('c')), false, ""},
|
|
{"cccccX", c.MinMax(1, 3, c.Rune('c')), true, "ccc"},
|
|
{"cccccX", c.MinMax(1, 6, c.Rune('c')), true, "ccccc"},
|
|
{"cccccX", c.MinMax(3, 4, c.Rune('c')), true, "cccc"},
|
|
{"", c.OneOrMore(c.Rune('d')), false, ""},
|
|
{"X", c.OneOrMore(c.Rune('d')), false, ""},
|
|
{"dX", c.OneOrMore(c.Rune('d')), true, "d"},
|
|
{"dddddX", c.OneOrMore(c.Rune('d')), true, "ddddd"},
|
|
{"", c.ZeroOrMore(c.Rune('e')), true, ""},
|
|
{"X", c.ZeroOrMore(c.Rune('e')), true, ""},
|
|
{"eX", c.ZeroOrMore(c.Rune('e')), true, "e"},
|
|
{"eeeeeX", c.ZeroOrMore(c.Rune('e')), true, "eeeee"},
|
|
{"Hello, world!X", c.Seq(c.Str("Hello"), a.Comma, a.Space, c.Str("world"), a.Excl), true, "Hello, world!"},
|
|
{"101010123", c.OneOrMore(c.Seq(c.Rune('1'), c.Rune('0'))), true, "101010"},
|
|
{"", c.Opt(c.OneOrMore(c.Rune('f'))), true, ""},
|
|
{"ghijkl", c.Opt(c.Rune('h')), true, ""},
|
|
{"ghijkl", c.Opt(c.Rune('g')), true, "g"},
|
|
{"fffffX", c.Opt(c.OneOrMore(c.Rune('f'))), true, "fffff"},
|
|
{"1,2,3,b,c", c.Separated(a.Comma, a.Digit), true, "1,2,3"},
|
|
{`\x9a\x01\xF0\xfCAndSomeMoreStuff`, c.OneOrMore(c.Seq(a.Backslash, c.Rune('x'), c.Rep(2, a.HexDigit))), true, `\x9a\x01\xF0\xfC`},
|
|
{" ", m.Trim(c.OneOrMore(a.AnyRune), " "), true, ""},
|
|
{" ", m.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, ""},
|
|
{" ", m.TrimRight(c.OneOrMore(a.AnyRune), " "), true, ""},
|
|
})
|
|
}
|
|
|
|
func TestCombinatorPanics(t *testing.T) {
|
|
RunPanicTests(t, []PanicTest{
|
|
{func() { parsekit.C.RuneRange('z', 'a') },
|
|
"internal parser error: MatchRuneRange definition error: start 'z' must not be < end 'a'"},
|
|
{func() { parsekit.C.MinMax(-1, 1, parsekit.A.Space) },
|
|
"internal parser error: MatchMinMax definition error: min must be >= 0"},
|
|
{func() { parsekit.C.MinMax(1, -1, parsekit.A.Space) },
|
|
"internal parser error: MatchMinMax definition error: max must be >= 0"},
|
|
{func() { parsekit.C.MinMax(10, 5, parsekit.A.Space) },
|
|
"internal parser error: MatchMinMax definition error: max 5 must not be < min 10"},
|
|
{func() { parsekit.C.Min(-10, parsekit.A.Space) },
|
|
"internal parser error: MatchMin definition error: min must be >= 0"},
|
|
{func() { parsekit.C.Max(-42, parsekit.A.Space) },
|
|
"internal parser error: MatchMax definition error: max must be >= 0"},
|
|
})
|
|
}
|
|
|
|
func TestAtoms(t *testing.T) {
|
|
RunTokenHandlerTests(t, []TokenHandlerTest{
|
|
{"", a.EndOfFile, true, ""},
|
|
{"⌘", a.AnyRune, true, "⌘"},
|
|
{"\xbc", a.AnyRune, false, ""}, // invalid UTF8 rune
|
|
{"", a.AnyRune, false, ""}, // end of file
|
|
{" ", a.Space, true, " "},
|
|
{"X", a.Space, false, ""},
|
|
{"\t", a.Tab, true, "\t"},
|
|
{"\r", a.CR, true, "\r"},
|
|
{"\n", a.LF, true, "\n"},
|
|
{"!", a.Excl, true, "!"},
|
|
{"\"", a.DoubleQuote, true, "\""},
|
|
{"#", a.Hash, true, "#"},
|
|
{"$", a.Dollar, true, "$"},
|
|
{"%", a.Percent, true, "%"},
|
|
{"&", a.Amp, true, "&"},
|
|
{"'", a.SingleQuote, true, "'"},
|
|
{"(", a.LeftParen, true, "("},
|
|
{"(", a.RoundOpen, true, "("},
|
|
{")", a.RightParen, true, ")"},
|
|
{")", a.RoundClose, true, ")"},
|
|
{"*", a.Asterisk, true, "*"},
|
|
{"*", a.Multiply, true, "*"},
|
|
{"+", a.Plus, true, "+"},
|
|
{"+", a.Add, true, "+"},
|
|
{",", a.Comma, true, ","},
|
|
{"-", a.Minus, true, "-"},
|
|
{"-", a.Subtract, true, "-"},
|
|
{".", a.Dot, true, "."},
|
|
{"/", a.Slash, true, "/"},
|
|
{"/", a.Divide, true, "/"},
|
|
{":", a.Colon, true, ":"},
|
|
{";", a.Semicolon, true, ";"},
|
|
{"<", a.AngleOpen, true, "<"},
|
|
{"<", a.LessThan, true, "<"},
|
|
{"=", a.Equal, true, "="},
|
|
{">", a.AngleClose, true, ">"},
|
|
{">", a.GreaterThan, true, ">"},
|
|
{"?", a.Question, true, "?"},
|
|
{"@", a.At, true, "@"},
|
|
{"[", a.SquareOpen, true, "["},
|
|
{"\\", a.Backslash, true, "\\"},
|
|
{"]", a.SquareClose, true, "]"},
|
|
{"^", a.Caret, true, "^"},
|
|
{"_", a.Underscore, true, "_"},
|
|
{"`", a.Backquote, true, "`"},
|
|
{"{", a.CurlyOpen, true, "{"},
|
|
{"|", a.Pipe, true, "|"},
|
|
{"}", a.CurlyClose, true, "}"},
|
|
{"~", a.Tilde, true, "~"},
|
|
{" \t \t \r\n", a.Whitespace, true, " \t \t "},
|
|
{"\r", a.WhitespaceAndNewlines, false, ""},
|
|
{" \t\r\n \r", a.WhitespaceAndNewlines, true, " \t\r\n "},
|
|
{"", a.EndOfLine, true, ""},
|
|
{"\r\n", a.EndOfLine, true, "\r\n"},
|
|
{"\n", a.EndOfLine, true, "\n"},
|
|
{"0", a.Digit, true, "0"},
|
|
{"1", a.Digit, true, "1"},
|
|
{"2", a.Digit, true, "2"},
|
|
{"3", a.Digit, true, "3"},
|
|
{"4", a.Digit, true, "4"},
|
|
{"5", a.Digit, true, "5"},
|
|
{"6", a.Digit, true, "6"},
|
|
{"7", a.Digit, true, "7"},
|
|
{"8", a.Digit, true, "8"},
|
|
{"9", a.Digit, true, "9"},
|
|
{"X", a.Digit, false, ""},
|
|
{"a", a.ASCIILower, true, "a"},
|
|
{"z", a.ASCIILower, true, "z"},
|
|
{"A", a.ASCIILower, false, ""},
|
|
{"Z", a.ASCIILower, false, ""},
|
|
{"A", a.ASCIIUpper, true, "A"},
|
|
{"Z", a.ASCIIUpper, true, "Z"},
|
|
{"a", a.ASCIIUpper, false, ""},
|
|
{"z", a.ASCIIUpper, false, ""},
|
|
{"0", a.HexDigit, true, "0"},
|
|
{"9", a.HexDigit, true, "9"},
|
|
{"a", a.HexDigit, true, "a"},
|
|
{"f", a.HexDigit, true, "f"},
|
|
{"A", a.HexDigit, true, "A"},
|
|
{"F", a.HexDigit, true, "F"},
|
|
{"g", a.HexDigit, false, "g"},
|
|
{"G", a.HexDigit, false, "G"},
|
|
{"0", a.Integer, true, "0"},
|
|
{"09", a.Integer, true, "0"}, // following Go: 09 is invalid octal, so only 0 is valid for the integer
|
|
{"1", a.Integer, true, "1"},
|
|
{"-10X", a.Integer, false, ""},
|
|
{"+10X", a.Integer, false, ""},
|
|
{"-10X", c.Signed(a.Integer), true, "-10"},
|
|
{"+10X", c.Signed(a.Integer), true, "+10"},
|
|
{"+10.1X", c.Signed(a.Integer), true, "+10"},
|
|
{"0X", a.Float, true, "0"},
|
|
{"0X", a.Float, true, "0"},
|
|
{"1X", a.Float, true, "1"},
|
|
{"1.", a.Float, true, "1"}, // incomplete float, so only the 1 is picked up
|
|
{"123.321X", a.Float, true, "123.321"},
|
|
{"-3.14X", a.Float, false, ""},
|
|
{"-3.14X", c.Signed(a.Float), true, "-3.14"},
|
|
{"-003.0014X", c.Signed(a.Float), true, "-003.0014"},
|
|
})
|
|
}
|
|
|
|
func TestModifiers(t *testing.T) {
|
|
RunTokenHandlerTests(t, []TokenHandlerTest{
|
|
{"--cool", c.Seq(m.Drop(c.OneOrMore(a.Minus)), c.Str("cool")), true, "cool"},
|
|
{" trim ", m.Trim(c.OneOrMore(a.AnyRune), " "), true, "trim"},
|
|
{" \t trim \t ", m.Trim(c.OneOrMore(a.AnyRune), " \t"), true, "trim"},
|
|
{" trim ", m.TrimLeft(c.OneOrMore(a.AnyRune), " "), true, "trim "},
|
|
{" trim ", m.TrimRight(c.OneOrMore(a.AnyRune), " "), true, " trim"},
|
|
{" \t trim \t ", m.TrimRight(c.OneOrMore(a.AnyRune), " \t"), true, " \t trim"},
|
|
{"dirtyword", m.Replace(c.OneOrMore(a.AnyRune), "*******"), true, "*******"},
|
|
{"abcdefghijk", m.ModifyByCallback(c.Str("abc"), func(s string) string { return "X" }), true, "X"},
|
|
{"NoTaLlUpPeR", m.ToUpper(c.StrNoCase("notallUPPER")), true, "NOTALLUPPER"},
|
|
{"NoTaLlLoWeR", m.ToLower(c.StrNoCase("NOTALLlower")), true, "notalllower"},
|
|
})
|
|
}
|
|
|
|
func TestSequenceOfRunes(t *testing.T) {
|
|
sequence := c.Seq(
|
|
a.Hash, a.Dollar, a.Percent, a.Amp, a.SingleQuote, a.LeftParen,
|
|
a.RightParen, a.Asterisk, a.Plus, a.Comma, a.Minus, a.Dot, a.Slash,
|
|
a.Colon, a.Semicolon, a.AngleOpen, a.Equal, a.AngleClose, a.Question,
|
|
a.At, a.SquareOpen, a.Backslash, a.SquareClose, a.Caret, a.Underscore,
|
|
a.Backquote, a.CurlyOpen, a.Pipe, a.CurlyClose, a.Tilde,
|
|
)
|
|
input := "#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
|
output := ""
|
|
parser := parsekit.NewParser(func(p *parsekit.ParseAPI) {
|
|
p.Expects("Sequence of runes")
|
|
if p.On(sequence).Accept() {
|
|
output = p.BufLiteral()
|
|
p.Stop()
|
|
}
|
|
})
|
|
err := parser.Execute(input)
|
|
if err != nil {
|
|
t.Fatalf("Parsing failed: %s", err)
|
|
}
|
|
if output != input {
|
|
t.Fatalf("Unexpected output from parser:\nexpected: %s\nactual: %s\n", input, output)
|
|
}
|
|
}
|
|
|
|
// I know, this is hell, but that's the whole point for this test :->
|
|
func TestCombination(t *testing.T) {
|
|
demonic := c.Seq(
|
|
c.Opt(a.SquareOpen),
|
|
m.Trim(
|
|
c.Seq(
|
|
c.Opt(a.Whitespace),
|
|
c.Rep(3, a.AngleClose),
|
|
m.ModifyByCallback(c.OneOrMore(c.StrNoCase("hello")), func(s string) string {
|
|
return fmt.Sprintf("%d", len(s))
|
|
}),
|
|
m.Replace(c.Separated(a.Comma, c.Opt(a.Whitespace)), ", "),
|
|
m.ToUpper(c.Min(1, a.ASCIILower)),
|
|
m.Drop(a.Excl),
|
|
c.Rep(3, a.AngleOpen),
|
|
c.Opt(a.Whitespace),
|
|
),
|
|
" \t",
|
|
),
|
|
c.Opt(a.SquareClose),
|
|
)
|
|
|
|
RunTokenHandlerTests(t, []TokenHandlerTest{
|
|
{"[ \t >>>Hello, world!<<< ]", demonic, true, "[>>>5, WORLD<<<]"},
|
|
{"[ \t >>>Hello, world!<<< ", demonic, true, "[>>>5, WORLD<<<"},
|
|
{">>>HellohellO, world!<<< ]", demonic, true, ">>>10, WORLD<<<]"},
|
|
{"[ \t >>>HellohellO , , , world!<<< ", demonic, true, "[>>>10, WORLD<<<"},
|
|
})
|
|
}
|