go-parsekit/parse/parse_test.go

270 lines
6.7 KiB
Go

package parse_test
import (
"fmt"
"testing"
"git.makaay.nl/mauricem/go-parsekit/parse"
"git.makaay.nl/mauricem/go-parsekit/tokenize"
)
func ExampleNew_usingAcceptedRunes() {
// Easy access to the tokenize definitions.
a := tokenize.A
matches := []string{}
parser := parse.New(func(p *parse.API) {
for p.Accept(a.AnyRune) {
matches = append(matches, p.Result.String())
}
p.ExpectEndOfFile()
})
err := parser("¡Any will dö!")
fmt.Printf("Matches = %q, Error = %v\n", matches, err)
// Output:
// Matches = ["¡" "A" "n" "y" " " "w" "i" "l" "l" " " "d" "ö" "!"], Error = <nil>
}
func ExampleNew_usingTokens() {
// Easy access to the tokenize definitions.
c, a, tok := tokenize.C, tokenize.A, tokenize.T
parser := parse.New(func(p *parse.API) {
if p.Accept(c.OneOrMore(tok.Rune("RUNE", a.AnyRune))) {
fmt.Printf("Runes accepted: %q\n", p.Result.String())
fmt.Printf("Tokens:\n")
tokens := p.Result.Tokens()
for i, token := range tokens {
fmt.Printf("[%d] %s\n", i, token)
}
}
p.ExpectEndOfFile()
})
parser("¡ök!")
// Output:
// Runes accepted: "¡ök!"
// Tokens:
// [0] RUNE('¡')
// [1] RUNE('ö')
// [2] RUNE('k')
// [3] RUNE('!')
}
func ExampleAPI_Expected() {
parser := parse.New(func(p *parse.API) {
p.Expected("a thing")
})
err := parser("Whatever, this parser will never be happy...")
fmt.Printf("Error: %s\n", err)
// Output:
// Error: unexpected input (expected a thing) at start of file
}
func ExampleAPI_Accept_inIfStatement() {
parser := parse.New(func(p *parse.API) {
// When a case-insensitive match on "Yowza!" is found by the
// tokenizer, then Accept() will make the result available
// through API.Result()
if p.Accept(tokenize.A.StrNoCase("Yowza!")) {
// Result.String() returns a string containing all
// accepted runes that were matched against.
fmt.Println(p.Result.String())
}
})
parser("YOWZA!")
// Output:
// YOWZA!
}
func ExampleAPI_Accept_inSwitchStatement() {
var result string
parser := parse.New(func(p *parse.API) {
for loop := true; loop; {
switch {
case p.Accept(tokenize.A.Rune('X')):
// NOOP, skip this rune
case p.Accept(tokenize.A.AnyRune):
result += p.Result.String()
default:
loop = false
}
}
})
parser("HXeXllXoXX, XXwoXrlXXXd!")
fmt.Println(result)
// Output:
// Hello, world!
}
func ExampleAPI_Stop() {
c, a := tokenize.C, tokenize.A
parser := parse.New(func(p *parse.API) {
fmt.Printf("First word: ")
for p.Accept(c.Not(a.Space)) {
fmt.Printf("%s", p.Result.String())
}
p.Stop()
})
parser("Input with spaces")
// Output:
// First word: Input
}
func ExampleAPI_Stop_notCalledAndNoInputPending() {
c, a := tokenize.C, tokenize.A
parser := parse.New(func(p *parse.API) {
fmt.Printf("Word: ")
for p.Accept(c.Not(a.Space)) {
fmt.Printf("%s", p.Result.String())
}
fmt.Printf("\n")
})
err := parser("Troglodyte")
fmt.Printf("Error is nil: %t\n", err == nil)
// Output:
// Word: Troglodyte
// Error is nil: true
}
func ExampleAPI_Stop_notCalledButInputPending() {
c, a := tokenize.C, tokenize.A
parser := parse.New(func(p *parse.API) {
fmt.Printf("First word: ")
for p.Accept(c.Not(a.Space)) {
fmt.Printf("%s", p.Result.String())
}
fmt.Printf("\n")
})
err := parser("Input with spaces")
fmt.Printf("Error: %s\n", err)
// Output:
// First word: Input
// Error: unexpected input (expected end of file) at line 1, column 6
}
func ExampleAPI_Peek() {
// Definition of a fantasy serial number format.
c, a := tokenize.C, tokenize.A
serialnr := c.Seq(a.Asterisk, a.ASCIIUpper, a.ASCIIUpper, a.Digits)
// This handler is able to handle serial numbers.
serialnrHandler := func(p *parse.API) {
if p.Accept(serialnr) {
fmt.Println(p.Result.String())
}
}
// Start could function as a sort of dispatcher, handing over
// control to the correct Handler function, based on the input.
start := func(p *parse.API) {
if p.Peek(tokenize.A.Asterisk) {
p.Handle(serialnrHandler)
return
}
// ... other cases could go here ...
}
parser := parse.New(start)
parser("#XX1234")
parser("*ay432566")
parser("*ZD987112")
// Output:
// *ZD987112
}
func TestGivenNullHandler_NewPanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{
Function: func() { parse.New(nil) },
Regexp: true,
Expect: `parsekit\.parse\.New\(\): New\(\) called ` +
`with nil input at /.*/parse_test\.go:\d+`})
}
func TestGivenNullHandler_HandlePanics(t *testing.T) {
brokenHandler := func(p *parse.API) {
p.Handle(nil)
}
parser := parse.New(brokenHandler)
parse.AssertPanic(t, parse.PanicT{
Function: func() { parser("") },
Regexp: true,
Expect: `parsekit\.parse\.API\.Handle\(\): Handle\(\) called with nil input ` +
`at /.*/parse_test\.go:\d+`})
}
func TestGivenNilHandler_AcceptPanics(t *testing.T) {
p := parse.New(func(p *parse.API) {
p.Accept(nil)
})
parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") },
Regexp: true,
Expect: `parsekit\.parse\.API\.Accept\(\): Accept\(\) called with nil ` +
`tokenHandler argument at /.*/parse_test\.go:\d+`})
}
func TestGivenNilHandler_PeekPanics(t *testing.T) {
p := parse.New(func(p *parse.API) {
p.Peek(nil)
})
parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") },
Regexp: true,
Expect: `parsekit\.parse\.API\.Peek\(\): Peek\(\) called with nil ` +
`tokenHandler argument at /.*/parse_test\.go:\d+`})
}
func TestGivenStoppedParser_HandlePanics(t *testing.T) {
otherHandler := func(p *parse.API) {
panic("This is not the handler you're looking for")
}
p := parse.New(func(p *parse.API) {
p.Stop()
p.Handle(otherHandler)
})
parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") },
Regexp: true,
Expect: `parsekit\.parse\.API\.Handle\(\): Illegal call to Handle\(\) ` +
`at /.*/parse_test\.go:\d+: no calls allowed after API\.Stop\(\)`})
}
func TestGivenParserWithErrorSet_HandlePanics(t *testing.T) {
otherHandler := func(p *parse.API) {
panic("This is not the handler you're looking for")
}
p := parse.New(func(p *parse.API) {
p.SetError("It ends here")
p.Handle(otherHandler)
})
parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") },
Regexp: true,
Expect: `parsekit\.parse\.API\.Handle\(\): Illegal call to Handle\(\) ` +
`at /.*/parse_test\.go:\d+: no calls allowed after API\.Error\(\)`})
}
func TestGivenParserWhichIsNotStopped_WithNoMoreInput_FallbackExpectEndOfFileKicksIn(t *testing.T) {
p := parse.New(func(p *parse.API) {})
err := p("")
parse.AssertTrue(t, err == nil, "err")
}
func TestGivenParserWhichIsNotStopped_WithMoreInput_ProducesError(t *testing.T) {
p := parse.New(func(p *parse.API) {})
err := p("x")
parse.AssertEqual(t, "unexpected input (expected end of file) at start of file", err.Error(), "err")
}