package parsekit_test import ( "fmt" "testing" "git.makaay.nl/mauricem/go-parsekit" ) func ExampleParser_usingAcceptedRunes() { // Easy access to the parsekit definitions. a := parsekit.A matches := []string{} parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { for p.On(a.AnyRune).Accept() { matches = append(matches, p.Result().String()) } p.ExpectEndOfFile() }) err := parser.Execute("¡Any will dö!") fmt.Printf("Matches = %q, Error = %s\n", matches, err) // Output: // Matches = ["¡" "A" "n" "y" " " "w" "i" "l" "l" " " "d" "ö" "!"], Error = } func ExampleParser_usingTokens() { // Easy access to the parsekit definitions. c, a, tok := parsekit.C, parsekit.A, parsekit.T parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { if p.On(c.OneOrMore(tok.Rune("RUNE", a.AnyRune))).Accept() { fmt.Printf("Runes accepted: %q\n", p.Result().String()) fmt.Printf("Token values: %s\n", p.Result().Tokens()) } p.ExpectEndOfFile() }) parser.Execute("¡ök!") // Output: // Runes accepted: "¡ök!" // Token values: RUNE("¡", value = (int32)161) RUNE("ö", value = (int32)246) RUNE("k", value = (int32)107) RUNE("!", value = (int32)33) } func ExampleParseAPI_UnexpectedInput() { parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { p.Expects("a thing") p.UnexpectedInput() }) err := parser.Execute("Whatever, this parser will never be happy...") fmt.Println(err.Full()) // Output: // unexpected input (expected a thing) at start of file } func ExampleParseAPIOnAction_Accept() { parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { // When a case-insensitive match on "Yowza!" is found by the // tokenizer, then Accept() will make the result available // through ParseAPI.Result() if p.On(parsekit.A.StrNoCase("Yowza!")).Accept() { // Result.String() returns a string containing all // accepted runes that were matched against. fmt.Println(p.Result().String()) } }) parser.Execute("YOWZA!") // Output: // YOWZA! } func ExampleParseAPIOnAction_Skip() { var result string parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { for loop := true; loop; { switch { case p.On(parsekit.A.Rune('X')).Skip(): // NOOP, skip this rune case p.On(parsekit.A.AnyRune).Accept(): result += p.Result().String() default: loop = false } } }) parser.Execute("HXeXllXoXX, XXwoXrlXXXd!") fmt.Println(result) // Output: // Hello, world! } func ExampleParseAPI_Stop() { C, A := parsekit.C, parsekit.A parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { fmt.Printf("First word: ") for p.On(C.Not(A.Space)).Accept() { fmt.Printf("%s", p.Result()) } p.Stop() }) parser.Execute("Input with spaces") // Output: // First word: Input } func ExampleParseAPI_Stop_notCalledAndNoInputPending() { C, A := parsekit.C, parsekit.A parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { fmt.Printf("Word: ") for p.On(C.Not(A.Space)).Accept() { fmt.Printf("%s", p.Result()) } fmt.Printf("\n") }) err := parser.Execute("Troglodyte") fmt.Printf("Error is nil: %t\n", err == nil) // Output: // Word: Troglodyte // Error is nil: true } func ExampleParseAPI_Stop_notCalledButInputPending() { C, A := parsekit.C, parsekit.A parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { fmt.Printf("First word: ") for p.On(C.Not(A.Space)).Accept() { fmt.Printf("%s", p.Result()) } fmt.Printf("\n") }) err := parser.Execute("Input with spaces") fmt.Printf("Error: %s\n", err.Full()) // Output: // First word: Input // Error: unexpected input (expected end of file) at line 1, column 6 } func ExampleParseAPIOnAction_Stay() { // Definition of a fantasy serial number format. C, A := parsekit.C, parsekit.A serialnr := C.Seq(A.Asterisk, A.ASCIIUpper, A.ASCIIUpper, A.Digits) // This handler is able to handle serial numbers. serialnrHandler := func(p *parsekit.ParseAPI) { if p.On(serialnr).Accept() { fmt.Println(p.Result().String()) } } // Start could function as a sort of dispatcher, handing over // control to the correct ParseHandler function, based on the input. start := func(p *parsekit.ParseAPI) { if p.On(parsekit.A.Asterisk).Stay() { p.Handle(serialnrHandler) return } // ... other cases could go here ... } parser := parsekit.NewParser(start) parser.Execute("#XX1234") parser.Execute("*ay432566") parser.Execute("*ZD987112") // Output: // *ZD987112 } func TestGivenNullHandler_NewParserPanics(t *testing.T) { parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { parsekit.NewParser(nil) }, Regexp: true, Expect: `parsekit\.NewParser\(\): NewParser\(\) called ` + `with nil input at /.*/parser_test\.go:\d+`}) } func TestGivenNullHandler_HandlePanics(t *testing.T) { brokenParseHandler := func(p *parsekit.ParseAPI) { p.Handle(nil) } parser := parsekit.NewParser(brokenParseHandler) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { parser.Execute("") }, Regexp: true, Expect: `parsekit\.ParseAPI\.Handle\(\): Handle\(\) called with nil input ` + `at /.*/parser_test\.go:\d+`}) } func TestGivenNilTokenHandler_OnPanics(t *testing.T) { p := parsekit.NewParser(func(p *parsekit.ParseAPI) { p.On(nil) }) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { p.Execute("") }, Regexp: true, Expect: `parsekit\.ParseAPI\.On\(\): On\(\) called with nil ` + `tokenHandler argument at /.*/parser_test\.go:\d+`}) } func TestGivenStoppedParser_HandlePanics(t *testing.T) { otherHandler := func(p *parsekit.ParseAPI) { panic("This is not the handler you're looking for") } p := parsekit.NewParser(func(p *parsekit.ParseAPI) { p.Stop() p.Handle(otherHandler) }) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { p.Execute("") }, Regexp: true, Expect: `parsekit\.ParseAPI\.Handle\(\): Illegal call to Handle\(\) ` + `at /.*/parser_test\.go:\d+: no calls allowed after ParseAPI\.Stop\(\)`}) } func TestGivenParserWithErrorSet_HandlePanics(t *testing.T) { otherHandler := func(p *parsekit.ParseAPI) { panic("This is not the handler you're looking for") } p := parsekit.NewParser(func(p *parsekit.ParseAPI) { p.Error("It ends here") p.Handle(otherHandler) }) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { p.Execute("") }, Regexp: true, Expect: `parsekit\.ParseAPI\.Handle\(\): Illegal call to Handle\(\) ` + `at /.*/parser_test\.go:\d+: no calls allowed after ParseAPI\.Error\(\)`}) } func TestGivenParserWithoutCallToAccept_ResultPanics(t *testing.T) { p := parsekit.NewParser(func(p *parsekit.ParseAPI) { p.Result() }) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { p.Execute("") }, Regexp: true, Expect: `parsekit\.ParseAPI\.TokenHandlerResult\(\): TokenHandlerResult\(\) called at ` + `/.*/parser_test.go:\d+ without calling ParseAPI.Accept\(\) on beforehand`}) } func TestGivenParserWhichIsNotStopped_WithNoMoreInput_FallbackExpectEndOfFileKicksIn(t *testing.T) { p := parsekit.NewParser(func(p *parsekit.ParseAPI) {}) err := p.Execute("") parsekit.AssertTrue(t, err == nil, "err") } func TestGivenParserWhichIsNotStopped_WithMoreInput_ProducesError(t *testing.T) { p := parsekit.NewParser(func(p *parsekit.ParseAPI) {}) err := p.Execute("x") parsekit.AssertEqual(t, "unexpected input (expected end of file) at start of file", err.Full(), "err") } type parserWithLoop struct { loopCounter int } func (l *parserWithLoop) first(p *parsekit.ParseAPI) { p.On(parsekit.A.ASCII).Accept() p.Handle(l.second) } func (l *parserWithLoop) second(p *parsekit.ParseAPI) { p.On(parsekit.A.ASCII).Accept() p.Handle(l.third) } func (l *parserWithLoop) third(p *parsekit.ParseAPI) { if l.loopCounter++; l.loopCounter > 100 { p.Error("Loop not detected by parsekit") return } p.On(parsekit.A.ASCII).Accept() p.Handle(l.first) } func TestGivenLoopingParserDefinition_ParserPanics(t *testing.T) { looper := &parserWithLoop{} parser := parsekit.NewParser(looper.first) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { parser.Execute("Het houdt niet op, niet vanzelf") }, Regexp: true, Expect: `parsekit\.ParseAPI: Loop detected in parser at /.*/parser_test.go:\d+`}) } // This test incorporates an actual loop bug that I dropped on myself and // that I could not easily spot in my code. It sounded so logical: // I want to get chunks of 5 chars from the input, so I simply loop on: // // p.On(c.Max(5, a.AnyRune)) // // The problem here is that Max(5, ...) will also match when there is // no more input, since Max(5, ---) is actually MinMax(0, 5, ...). // Therefore the loop will never stop. Solving the loop was simple: // // p.On(c.MinMax(1, 5, a.AnyRune)) // // Now the loop stops when the parser finds no more matching input data. func TestGivenLoopingParserDefinition2_ParserPanics(t *testing.T) { var c, a = parsekit.C, parsekit.A parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { for p.On(c.Max(5, a.AnyRune)).Accept() { } p.Stop() }) parsekit.AssertPanic(t, parsekit.PanicT{ Function: func() { parser.Execute("This will end soon") }, Regexp: true, Expect: `parsekit\.ParseAPI: Loop detected in parser at .*/parser_test.go:\d+`}) }