270 lines
6.7 KiB
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")
|
|
}
|