More documentation and examples.

This commit is contained in:
Maurice Makaay 2019-06-12 16:17:13 +00:00
parent 1a280233b0
commit cdfc4ce52c
10 changed files with 148 additions and 34 deletions

View File

@ -57,8 +57,8 @@ func Example_basicCalculator1() {
// An error is returned in case the calculation failed. // An error is returned in case the calculation failed.
func ComputeSimple(calculation string) (int64, error) { func ComputeSimple(calculation string) (int64, error) {
calculator := &simpleCalculator{op: +1} calculator := &simpleCalculator{op: +1}
parser := parse.New(calculator.number) parseCalculation := parse.New(calculator.number)
err := parser(calculation) err := parseCalculation(calculation)
return calculator.Result, err return calculator.Result, err
} }

View File

@ -78,8 +78,8 @@ type calculator struct {
// calculation fails for some reason. // calculation fails for some reason.
func Compute(input string) (float64, error) { func Compute(input string) (float64, error) {
calc := &calculator{} calc := &calculator{}
parser := parse.New(calc.calculation) parseCalculation := parse.New(calc.calculation)
err := parser(input) err := parseCalculation(input)
return calc.result, err return calc.result, err
} }

View File

@ -9,7 +9,7 @@
// //
// One big difference between the parser/combinator-based example and this one, // One big difference between the parser/combinator-based example and this one,
// is that this parser reports errors much more fine-grained. This might or // is that this parser reports errors much more fine-grained. This might or
// might not be useful for your specific use case. If you need error reporting // might not be useful for your specific use case. If you need error reporting
// like this, then also take a look at the helloSingleState example, which does // like this, then also take a look at the helloSingleState example, which does
// the same thing as this version, only more concise. // the same thing as this version, only more concise.
@ -76,8 +76,8 @@ type helloparser1 struct {
} }
func (h *helloparser1) Parse(input string) (string, error) { func (h *helloparser1) Parse(input string) (string, error) {
parser := parse.New(h.start) parseHello := parse.New(h.start)
err := parser(input) err := parseHello(input)
return h.greetee, err return h.greetee, err
} }
@ -92,12 +92,10 @@ func (h *helloparser1) start(p *parse.API) {
func (h *helloparser1) comma(p *parse.API) { func (h *helloparser1) comma(p *parse.API) {
a := tokenize.A a := tokenize.A
switch { p.Accept(a.Blanks)
case p.Accept(a.Blanks): if p.Accept(a.Comma) {
p.Handle(h.comma)
case p.Accept(a.Comma):
p.Handle(h.startName) p.Handle(h.startName)
default: } else {
p.Expected("comma") p.Expected("comma")
} }
} }

View File

@ -74,8 +74,8 @@ type helloparser2 struct {
} }
func (h *helloparser2) Parse(input string) (string, error) { func (h *helloparser2) Parse(input string) (string, error) {
parser := parse.New(h.start) parseHello := parse.New(h.start)
err := parser(input) err := parseHello(input)
return h.greetee, err return h.greetee, err
} }

View File

@ -3,7 +3,7 @@
// //
// Here, we create a custom type 'Chunks', which is an alias // Here, we create a custom type 'Chunks', which is an alias
// for []string. We add a Handler method directly to that type // for []string. We add a Handler method directly to that type
// and let the parsing code fill the slice with strings during parsing. // and let the parsing code append items to the slice during parsing.
package examples package examples
@ -20,12 +20,12 @@ func (l *Chunks) AddChopped(s string, chunkSize int) error {
c, a := tokenize.C, tokenize.A c, a := tokenize.C, tokenize.A
chunkOfRunes := c.MinMax(1, chunkSize, a.AnyRune) chunkOfRunes := c.MinMax(1, chunkSize, a.AnyRune)
parser := parse.New(func(p *parse.API) { parseChunks := parse.New(func(p *parse.API) {
for p.Accept(chunkOfRunes) { for p.Accept(chunkOfRunes) {
*l = append(*l, p.Result().String()) *l = append(*l, p.Result().String())
} }
}) })
return parser(s) return parseChunks(s)
} }
func Example_usingSliceAsParserState() { func Example_usingSliceAsParserState() {

2
go.mod
View File

@ -1,3 +1,3 @@
module git.makaay.nl/mauricem/go-parsekit module git.makaay.nl/mauricem/go-parsekit
go 1.12 go 1.12

View File

@ -65,7 +65,7 @@ func (p *API) invokeHandler(name string, tokenHandler tokenize.Handler) (*tokeni
p.panicWhenStoppedOrInError(name) p.panicWhenStoppedOrInError(name)
p.checkForLoops() p.checkForLoops()
if tokenHandler == nil { if tokenHandler == nil {
callerPanic(2, "parse.API.%s(): %s() called with nil tokenHandler argument at {caller}", name, name) callerPanic(2, "parsekit.parse.API.%s(): %s() called with nil tokenHandler argument at {caller}", name, name)
} }
p.result = nil p.result = nil
@ -93,7 +93,7 @@ func (p *API) panicWhenStoppedOrInError(name string) {
after = "Stop()" after = "Stop()"
} }
callerPanic(2, "parse.API.%s(): Illegal call to %s() at {caller}: "+ callerPanic(2, "parsekit.parse.API.%s(): Illegal call to %s() at {caller}: "+
"no calls allowed after API.%s", name, name, after) "no calls allowed after API.%s", name, name, after)
} }
@ -115,7 +115,7 @@ func (p *API) initLoopCheck() {
func (p *API) checkForLoops() { func (p *API) checkForLoops() {
filepos := callerFilepos(3) filepos := callerFilepos(3)
if _, ok := p.loopCheck[filepos]; ok { if _, ok := p.loopCheck[filepos]; ok {
callerPanic(3, "parse.API: Loop detected in parser at {caller}") callerPanic(3, "parsekit.parse.API: Loop detected in parser at {caller}")
} }
p.loopCheck[filepos] = true p.loopCheck[filepos] = true
} }
@ -128,7 +128,7 @@ func (p *API) checkForLoops() {
func (p *API) Result() *tokenize.Result { func (p *API) Result() *tokenize.Result {
result := p.result result := p.result
if p.result == nil { if p.result == nil {
callerPanic(1, "parse.API.Result(): Result() called "+ callerPanic(1, "parsekit.parse.API.Result(): Result() called "+
"at {caller} without calling API.Peek() or API.Accept() on beforehand") "at {caller} without calling API.Peek() or API.Accept() on beforehand")
} }
return result return result
@ -154,7 +154,7 @@ func (p *API) Handle(parseHandler Handler) bool {
func (p *API) panicWhenHandlerNil(parseHandler Handler) { func (p *API) panicWhenHandlerNil(parseHandler Handler) {
if parseHandler == nil { if parseHandler == nil {
callerPanic(2, "parse.API.Handle(): Handle() called with nil input at {caller}") callerPanic(2, "parsekit.parse.API.Handle(): Handle() called with nil input at {caller}")
} }
} }

View File

@ -200,7 +200,7 @@ func TestGivenNullHandler_HandlePanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { parser("") }, Function: func() { parser("") },
Regexp: true, Regexp: true,
Expect: `parse\.API\.Handle\(\): Handle\(\) called with nil input ` + Expect: `parsekit\.parse\.API\.Handle\(\): Handle\(\) called with nil input ` +
`at /.*/parse_test\.go:\d+`}) `at /.*/parse_test\.go:\d+`})
} }
func TestGivenNilHandler_AcceptPanics(t *testing.T) { func TestGivenNilHandler_AcceptPanics(t *testing.T) {
@ -210,7 +210,7 @@ func TestGivenNilHandler_AcceptPanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") }, Function: func() { p("") },
Regexp: true, Regexp: true,
Expect: `parse\.API\.Accept\(\): Accept\(\) called with nil ` + Expect: `parsekit\.parse\.API\.Accept\(\): Accept\(\) called with nil ` +
`tokenHandler argument at /.*/parse_test\.go:\d+`}) `tokenHandler argument at /.*/parse_test\.go:\d+`})
} }
@ -221,7 +221,7 @@ func TestGivenNilHandler_PeekPanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") }, Function: func() { p("") },
Regexp: true, Regexp: true,
Expect: `parse\.API\.Peek\(\): Peek\(\) called with nil ` + Expect: `parsekit\.parse\.API\.Peek\(\): Peek\(\) called with nil ` +
`tokenHandler argument at /.*/parse_test\.go:\d+`}) `tokenHandler argument at /.*/parse_test\.go:\d+`})
} }
@ -236,7 +236,7 @@ func TestGivenStoppedParser_HandlePanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") }, Function: func() { p("") },
Regexp: true, Regexp: true,
Expect: `parse\.API\.Handle\(\): Illegal call to Handle\(\) ` + Expect: `parsekit\.parse\.API\.Handle\(\): Illegal call to Handle\(\) ` +
`at /.*/parse_test\.go:\d+: no calls allowed after API\.Stop\(\)`}) `at /.*/parse_test\.go:\d+: no calls allowed after API\.Stop\(\)`})
} }
@ -251,7 +251,7 @@ func TestGivenParserWithErrorSet_HandlePanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") }, Function: func() { p("") },
Regexp: true, Regexp: true,
Expect: `parse\.API\.Handle\(\): Illegal call to Handle\(\) ` + Expect: `parsekit\.parse\.API\.Handle\(\): Illegal call to Handle\(\) ` +
`at /.*/parse_test\.go:\d+: no calls allowed after API\.Error\(\)`}) `at /.*/parse_test\.go:\d+: no calls allowed after API\.Error\(\)`})
} }
@ -262,7 +262,7 @@ func TestGivenParserWithoutCallToPeekOrAccept_ResultPanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { p("") }, Function: func() { p("") },
Regexp: true, Regexp: true,
Expect: `parse\.API\.Result\(\): Result\(\) called at ` + Expect: `parsekit\.parse\.API\.Result\(\): Result\(\) called at ` +
`/.*/parse_test.go:\d+ without calling API.Peek\(\) or API.Accept\(\) on beforehand`}) `/.*/parse_test.go:\d+ without calling API.Peek\(\) or API.Accept\(\) on beforehand`})
} }
@ -307,7 +307,7 @@ func TestGivenLoopingParserDefinition_ParserPanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { parser("Het houdt niet op, niet vanzelf") }, Function: func() { parser("Het houdt niet op, niet vanzelf") },
Regexp: true, Regexp: true,
Expect: `parse\.API: Loop detected in parser at /.*/parse_test.go:\d+`}) Expect: `parsekit\.parse\.API: Loop detected in parser at /.*/parse_test.go:\d+`})
} }
// This test incorporates an actual loop bug that I dropped on myself and // This test incorporates an actual loop bug that I dropped on myself and
@ -333,5 +333,5 @@ func TestGivenLoopingParserDefinition2_ParserPanics(t *testing.T) {
parse.AssertPanic(t, parse.PanicT{ parse.AssertPanic(t, parse.PanicT{
Function: func() { parser("This will end soon") }, Function: func() { parser("This will end soon") },
Regexp: true, Regexp: true,
Expect: `parse\.API: Loop detected in parser at .*/parse_test.go:\d+`}) Expect: `parsekit\.parse\.API: Loop detected in parser at .*/parse_test.go:\d+`})
} }

View File

@ -6,6 +6,102 @@ import (
"git.makaay.nl/mauricem/go-parsekit/tokenize" "git.makaay.nl/mauricem/go-parsekit/tokenize"
) )
func ExampleNewAPI() {
tokenize.NewAPI("The input that the API will handle")
// Output:
}
func ExampleAPI_NextRune() {
api := tokenize.NewAPI("The input that the API will handle")
r, err := api.NextRune()
fmt.Printf("Rune read from input; %c\n", r)
fmt.Printf("The error: %v\n", err)
fmt.Printf("API results: %q\n", api.Result().String())
// Output:
// Rune read from input; T
// The error: <nil>
// API results: ""
}
func ExampleAPI_Accept() {
api := tokenize.NewAPI("The input that the API will handle")
api.NextRune() // reads 'T'
api.Accept() // adds 'T' to the API results
api.NextRune() // reads 'h'
api.Accept() // adds 'h' to the API results
api.NextRune() // reads 'e', but it is not added to the API results
fmt.Printf("API results: %q\n", api.Result().String())
// Output:
// API results: "Th"
}
func ExampleAPI_Result() {
api := tokenize.NewAPI("")
result := api.Result()
result.AddRunes("Some runes")
result.AddRunes([]rune{' ', 'a', 'd', 'd', 'e', 'd'})
result.AddRunes(' ', 'i', 'n', ' ', "various ways")
fmt.Printf("API result first 10 runes: %q\n", api.Result().Runes()[0:10])
fmt.Printf("API result runes as string: %q\n", api.Result().String())
result.SetRunes("new ", "set ", "of ", 'r', 'u', 'n', 'e', 's')
fmt.Printf("API result runes as string: %q\n", api.Result().String())
fmt.Printf("API result runes: %q\n", api.Result().Runes())
fmt.Printf("API third rune: %q\n", api.Result().Rune(2))
result.AddTokens(&tokenize.Token{
Runes: []rune("demo 1"),
Type: 42,
Value: "towel"})
result.AddTokens(&tokenize.Token{
Runes: []rune("demo 2"),
Type: 73,
Value: "Zaphod"})
fmt.Printf("API result tokens: %v\n", api.Result().Tokens())
fmt.Printf("API second result token: %v\n", api.Result().Token(1))
// Output:
// API result first 10 runes: ['S' 'o' 'm' 'e' ' ' 'r' 'u' 'n' 'e' 's']
// API result runes as string: "Some runes added in various ways"
// API result runes as string: "new set of runes"
// API result runes: ['n' 'e' 'w' ' ' 's' 'e' 't' ' ' 'o' 'f' ' ' 'r' 'u' 'n' 'e' 's']
// API third rune: 'w'
// API result tokens: [42("demo 1", value = (string)towel) 73("demo 2", value = (string)Zaphod)]
// API second result token: 73("demo 2", value = (string)Zaphod)
}
func ExampleAPI_Reset() {
api := tokenize.NewAPI("Very important input!")
api.NextRune()
api.Accept()
api.NextRune()
api.Accept()
fmt.Printf("API results: %q at %s\n", api.Result().String(), api.Result().Cursor())
// Reset clears the results, but keeps the cursor position.
api.Reset()
fmt.Printf("API results: %q at %s\n", api.Result().String(), api.Result().Cursor())
api.NextRune()
api.Accept()
api.NextRune()
api.Accept()
fmt.Printf("API results: %q at %s\n", api.Result().String(), api.Result().Cursor())
// Output:
// API results: "Ve" at line 1, column 3
// API results: "" at line 1, column 3
// API results: "ry" at line 1, column 5
}
func ExampleAPI_Fork() { func ExampleAPI_Fork() {
// This custom Handler checks for input 'a', 'b' or 'c'. // This custom Handler checks for input 'a', 'b' or 'c'.
abcHandler := func(t *tokenize.API) bool { abcHandler := func(t *tokenize.API) bool {
@ -44,6 +140,26 @@ func ExampleAPI_Fork() {
// <nil> mismatch at start of file // <nil> mismatch at start of file
} }
func ExampleAPI_Dispose() {
api := tokenize.NewAPI("My uninspired piece of input")
child := api.Fork()
// ... do stuff with child ...
child.NextRune()
child.Accept()
child.NextRune()
child.Accept()
// ... dispose of the child results ...
child.Dispose()
// The parent still reads from the start of the input.
r, _ := api.NextRune()
fmt.Printf("Rune read from parent: %c\n", r)
// Output:
// Rune read from parent: M
}
func ExampleAPI_Merge() { func ExampleAPI_Merge() {
tokenHandler := func(t *tokenize.API) bool { tokenHandler := func(t *tokenize.API) bool {
child1 := t.Fork() child1 := t.Fork()

View File

@ -68,9 +68,9 @@ func (r *Result) ClearRunes() {
} }
// SetRunes replaces the Runes from the Result with the provided input. // SetRunes replaces the Runes from the Result with the provided input.
func (r *Result) SetRunes(s interface{}) { func (r *Result) SetRunes(s ...interface{}) {
r.ClearRunes() r.ClearRunes()
r.addRunes(s) r.addRunes(s...)
} }
// AddRunes is used to add runes to the Result. // AddRunes is used to add runes to the Result.
@ -114,7 +114,7 @@ func (r *Result) ClearTokens() {
} }
// SetTokens replaces the Tokens from the Result with the provided tokens. // SetTokens replaces the Tokens from the Result with the provided tokens.
func (r *Result) SetTokens(tokens []*Token) { func (r *Result) SetTokens(tokens ...*Token) {
r.ClearTokens() r.ClearTokens()
for _, t := range tokens { for _, t := range tokens {
r.AddTokens(t) r.AddTokens(t)