Backup work.

This commit is contained in:
Maurice Makaay 2019-06-08 22:48:56 +00:00
parent 05ae55c487
commit 9f5caa2024
11 changed files with 103 additions and 47 deletions

View File

@ -79,7 +79,7 @@ func (c *simpleCalculator) number(p *parsekit.ParseAPI) {
c.Result += c.op * p.Result().Value(0).(int64) c.Result += c.op * p.Result().Value(0).(int64)
p.Handle(c.operatorOrEndOfFile) p.Handle(c.operatorOrEndOfFile)
} else { } else {
p.UnexpectedInput("integer number") p.Expected("integer number")
} }
} }
@ -93,7 +93,7 @@ func (c *simpleCalculator) operatorOrEndOfFile(p *parsekit.ParseAPI) {
c.op = -1 c.op = -1
p.Handle(c.number) p.Handle(c.number)
case !p.On(A.EndOfFile).Stay(): case !p.On(A.EndOfFile).Stay():
p.UnexpectedInput("operator, '+' or '-'") p.Expected("operator, '+' or '-'")
default: default:
p.ExpectEndOfFile() p.ExpectEndOfFile()
} }

View File

@ -140,11 +140,11 @@ func (calc *calculator) factor(p *parsekit.ParseAPI) {
return return
} }
if !p.On(A.RightParen).Skip() { if !p.On(A.RightParen).Skip() {
p.UnexpectedInput("')'") p.Expected("')'")
return return
} }
default: default:
p.UnexpectedInput("factor or parenthesized expression") p.Expected("factor or parenthesized expression")
return return
} }
p.On(A.Blanks).Skip() p.On(A.Blanks).Skip()

View File

@ -85,7 +85,7 @@ func (h *helloparser1) start(p *parsekit.ParseAPI) {
if p.On(a.StrNoCase("hello")).Skip() { if p.On(a.StrNoCase("hello")).Skip() {
p.Handle(h.comma) p.Handle(h.comma)
} else { } else {
p.UnexpectedInput("hello") p.Expected("hello")
} }
} }
@ -97,7 +97,7 @@ func (h *helloparser1) comma(p *parsekit.ParseAPI) {
case p.On(a.Comma).Skip(): case p.On(a.Comma).Skip():
p.Handle(h.startName) p.Handle(h.startName)
default: default:
p.UnexpectedInput("comma") p.Expected("comma")
} }
} }
@ -107,7 +107,7 @@ func (h *helloparser1) startName(p *parsekit.ParseAPI) {
if p.On(a.AnyRune).Stay() { if p.On(a.AnyRune).Stay() {
p.Handle(h.name) p.Handle(h.name)
} else { } else {
p.UnexpectedInput("name") p.Expected("name")
} }
} }
@ -120,7 +120,7 @@ func (h *helloparser1) name(p *parsekit.ParseAPI) {
h.greetee += p.Result().String() h.greetee += p.Result().String()
p.Handle(h.name) p.Handle(h.name)
default: default:
p.UnexpectedInput("exclamation mark") p.Expected("exclamation mark")
} }
} }
@ -129,7 +129,7 @@ func (h *helloparser1) exclamation(p *parsekit.ParseAPI) {
if p.On(a.Excl).Accept() { if p.On(a.Excl).Accept() {
p.Handle(h.end) p.Handle(h.end)
} else { } else {
p.UnexpectedInput("exclamation") p.Expected("exclamation")
} }
} }
@ -139,7 +139,7 @@ func (h *helloparser1) exclamation(p *parsekit.ParseAPI) {
func (h *helloparser1) end(p *parsekit.ParseAPI) { func (h *helloparser1) end(p *parsekit.ParseAPI) {
var a = parsekit.A var a = parsekit.A
if !p.On(a.EndOfFile).Stay() { if !p.On(a.EndOfFile).Stay() {
p.UnexpectedInput("end of greeting") p.Expected("end of greeting")
return return
} }

View File

@ -5,10 +5,10 @@ import (
"io" "io"
) )
// ParseAPI holds the internal state of a parse run and provides an API to // ParseAPI holds the internal state of a parse run and provides an API that
// ParseHandler methods to communicate with the parser. // ParseHandler methods can use to communicate with the parser.
type ParseAPI struct { type ParseAPI struct {
tokenAPI *TokenAPI // the input reader tokenAPI *TokenAPI // the TokenAPI, used for communicating with TokenHandler functions
loopCheck map[string]bool // used for parser loop detection loopCheck map[string]bool // used for parser loop detection
result *TokenHandlerResult // Last TokenHandler result as produced by On(...).Accept() result *TokenHandlerResult // Last TokenHandler result as produced by On(...).Accept()
err *Error // error during parsing, retrieved by Error(), further ParseAPI calls are ignored err *Error // error during parsing, retrieved by Error(), further ParseAPI calls are ignored
@ -123,8 +123,9 @@ type ParseAPIOnAction struct {
// found by a TokenHandler, and to make the TokenHandlerResult from the // found by a TokenHandler, and to make the TokenHandlerResult from the
// TokenAPI available in the ParseAPI through the ParseAPI.Result() method. // TokenAPI available in the ParseAPI through the ParseAPI.Result() method.
// //
// Returns true in case a match was found. // Returns true in case a match was found by On().
// When no match was found, then no action is taken and false is returned. // When no match was found, then no action is taken, no results are
// exposed and false is returned.
func (a *ParseAPIOnAction) Accept() bool { func (a *ParseAPIOnAction) Accept() bool {
if a.ok { if a.ok {
a.forkedTokenAPI.Merge() a.forkedTokenAPI.Merge()
@ -145,7 +146,7 @@ func (a *ParseAPIOnAction) Accept() bool {
// than the Accept() call and (more important if you ask me) the code // than the Accept() call and (more important if you ask me) the code
// expresses more clearly that your intent is to skip the match. // expresses more clearly that your intent is to skip the match.
// //
// Returns true in case a match was found. // Returns true in case a match was found by On().
// When no match was found, then no action is taken and false is returned. // When no match was found, then no action is taken and false is returned.
func (a *ParseAPIOnAction) Skip() bool { func (a *ParseAPIOnAction) Skip() bool {
if a.ok { if a.ok {
@ -167,7 +168,7 @@ func (a *ParseAPIOnAction) Skip() bool {
// When a match is found, it hands off control to another ParseHandler // When a match is found, it hands off control to another ParseHandler
// to take care of the actual token parsing. // to take care of the actual token parsing.
// //
// Returns true in case a match was found, false otherwise. // Returns true in case a match was found by On(), false otherwise.
func (a *ParseAPIOnAction) Stay() bool { func (a *ParseAPIOnAction) Stay() bool {
if a.ok { if a.ok {
a.parseAPI.result = nil a.parseAPI.result = nil
@ -179,6 +180,9 @@ func (a *ParseAPIOnAction) Stay() bool {
// Result returns a TokenHandlerResult struct, containing results as produced by the // Result returns a TokenHandlerResult struct, containing results as produced by the
// last ParseAPI.On().Accept() call. // last ParseAPI.On().Accept() call.
//
// When Result() is called without first doing a ParsAPI.On().Accept(), then no
// result will be available and the method will panic.
func (p *ParseAPI) Result() *TokenHandlerResult { func (p *ParseAPI) Result() *TokenHandlerResult {
result := p.result result := p.result
if p.result == nil { if p.result == nil {
@ -213,7 +217,7 @@ func (p *ParseAPI) panicWhenParseHandlerNil(parseHandler ParseHandler) {
// When the parser implementation returns without stopping first (and // When the parser implementation returns without stopping first (and
// without running into an error), the Parser.Execute() will call // without running into an error), the Parser.Execute() will call
// ParserAPI.ExpectEndOfFile() to check if the end of the file was reached. // ParserAPI.ExpectEndOfFile() to check if the end of the file was reached.
// If not, then things will end in an UnexpectedError(). // If not, then things will end in an unexpected input error.
// Even though this fallback mechanism will work in a lot of cases, try to make // Even though this fallback mechanism will work in a lot of cases, try to make
// your parser explicit about things and call Stop() actively yourself. // your parser explicit about things and call Stop() actively yourself.
// //
@ -238,25 +242,28 @@ func (p *ParseAPI) Error(format string, args ...interface{}) {
// //
// When it finds that the end of the file was indeed reached, then the // When it finds that the end of the file was indeed reached, then the
// parser will be stopped through ParseAPI.Stop(). Otherwise unexpected // parser will be stopped through ParseAPI.Stop(). Otherwise unexpected
// input is reported through ParseAPI.UnexpectedInput() with "end of file" // input is reported through ParseAPI.Expected() with "end of file"
// as the expectation. // as the expectation.
func (p *ParseAPI) ExpectEndOfFile() { func (p *ParseAPI) ExpectEndOfFile() {
p.panicWhenStoppedOrInError() p.panicWhenStoppedOrInError()
if p.On(A.EndOfFile).Stay() { if p.On(A.EndOfFile).Stay() {
p.Stop() p.Stop()
} else { } else {
p.UnexpectedInput("end of file") p.Expected("end of file")
} }
} }
// UnexpectedInput is used to set an error that tells the user that some // Expected is used to set an error that tells the user that some
// unexpected input was encountered. // unexpected input was encountered, and that input was expected.
//
// The 'expected' argument can be an empty string. In that case the error
// message will not contain a description of the expected input.
// //
// It automatically produces an error message for a couple of situations: // It automatically produces an error message for a couple of situations:
// 1) the input simply didn't match the expectation // 1) the input simply didn't match the expectation
// 2) the end of the input was reached // 2) the end of the input was reached
// 3) there was an error while reading the input. // 3) there was an error while reading the input.
func (p *ParseAPI) UnexpectedInput(expected string) { func (p *ParseAPI) Expected(expected string) {
p.panicWhenStoppedOrInError() p.panicWhenStoppedOrInError()
_, err := p.tokenAPI.NextRune() _, err := p.tokenAPI.NextRune()
switch { switch {

View File

@ -1,9 +1,5 @@
package parsekit package parsekit
import (
"strings"
)
// Parser is the top-level struct that holds the configuration for a parser. // Parser is the top-level struct that holds the configuration for a parser.
// The Parser can be instantiated using the parsekit.NewParser() method. // The Parser can be instantiated using the parsekit.NewParser() method.
type Parser struct { type Parser struct {
@ -34,10 +30,12 @@ func NewParser(startHandler ParseHandler) *Parser {
} }
// Execute starts the parser for the provided input. // Execute starts the parser for the provided input.
// When an error occurs during parsing, then this error is returned. Nil otherwise. // For an overview of allowed inputs, take a look at the documentation for parsekit.reader.New().
func (p *Parser) Execute(input string) *Error { //
// When an error occurs during parsing, then this error is returned, nil otherwise.
func (p *Parser) Execute(input interface{}) *Error {
api := &ParseAPI{ api := &ParseAPI{
tokenAPI: NewTokenAPI(strings.NewReader(input)), tokenAPI: NewTokenAPI(input),
loopCheck: map[string]bool{}, loopCheck: map[string]bool{},
} }
if api.Handle(p.startHandler) { if api.Handle(p.startHandler) {

View File

@ -44,9 +44,9 @@ func ExampleParser_usingTokens() {
// Token values: RUNE("¡", value = (int32)161) RUNE("ö", value = (int32)246) RUNE("k", value = (int32)107) RUNE("!", value = (int32)33) // Token values: RUNE("¡", value = (int32)161) RUNE("ö", value = (int32)246) RUNE("k", value = (int32)107) RUNE("!", value = (int32)33)
} }
func ExampleParseAPI_UnexpectedInput() { func ExampleParseAPI_Expected() {
parser := parsekit.NewParser(func(p *parsekit.ParseAPI) { parser := parsekit.NewParser(func(p *parsekit.ParseAPI) {
p.UnexpectedInput("a thing") p.Expected("a thing")
}) })
err := parser.Execute("Whatever, this parser will never be happy...") err := parser.Execute("Whatever, this parser will never be happy...")
fmt.Println(err.Full()) fmt.Println(err.Full())

View File

@ -43,13 +43,15 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"strings"
"unicode/utf8" "unicode/utf8"
) )
// Reader wraps around an io.Reader and provides buffering that allows us to read // Reader wraps around a bufio.Reader and provides an additional layer of
// the same runes over and over again. This is useful for implementing a parser // buffering that allows us to read the same runes over and over again.
// that must be able to do lookahead on the input, returning to the original // This is useful for implementing a parser that must be able to do lookahead
// input position after finishing that lookahead). // on the input, returning to the original input position after finishing
// that lookahead).
// //
// To minimze memory use, it is also possible to flush the read buffer when there is // To minimze memory use, it is also possible to flush the read buffer when there is
// no more need to go back to previously read runes. // no more need to go back to previously read runes.
@ -62,14 +64,34 @@ type Reader struct {
bufferLen int // Input size, the number of runes in the buffer bufferLen int // Input size, the number of runes in the buffer
} }
// New initializes a new reader struct, wrapped around the provided io.Reader. // New initializes a new reader struct, wrapped around the provided input.
func New(r io.Reader) *Reader { //
// The input can be any one of the following types:
// - string
// - type implementing io.Reader
// - bufio.Reader
func New(input interface{}) *Reader {
return &Reader{ return &Reader{
bufio: bufio.NewReader(r), bufio: makeBufioReader(input),
buffer: []rune{}, buffer: []rune{},
} }
} }
func makeBufioReader(input interface{}) *bufio.Reader {
switch input := input.(type) {
case bufio.Reader:
return &input
case *bufio.Reader:
return input
case io.Reader:
return bufio.NewReader(input)
case string:
return bufio.NewReader(strings.NewReader(input))
default:
panic(fmt.Sprintf("parsekit.reader.New(): no support for input of type %T", input))
}
}
// RuneAt reads the rune at the provided rune offset. // RuneAt reads the rune at the provided rune offset.
// //
// This offset is relative to the current starting position of the buffer in // This offset is relative to the current starting position of the buffer in

View File

@ -1,6 +1,7 @@
package reader_test package reader_test
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"strings" "strings"
@ -21,6 +22,34 @@ func ExampleNew() {
// H! // H!
} }
func TestNew_VariousInputTypesCanBeUsed(t *testing.T) {
for _, test := range []struct {
name string
input interface{}
}{
{"string", "Hello, world!"},
{"io.Reader", strings.NewReader("Hello, world!")},
{"*bufio.Reader", bufio.NewReader(strings.NewReader("Hello, world!"))},
{"bufio.Reader", *(bufio.NewReader(strings.NewReader("Hello, world!")))},
} {
r := reader.New(test.input)
firstRune, _ := r.RuneAt(0)
if firstRune != 'H' {
t.Errorf("[%s] first rune not 'H'", test.name)
}
lastRune, _ := r.RuneAt(12)
if lastRune != '!' {
t.Errorf("[%s] last rune not '!'", test.name)
}
}
}
func TestNew_UnhandledInputType_Panics(t *testing.T) {
assert.PanicsWithValue(t,
"parsekit.reader.New(): no support for input of type int",
func() { reader.New(12345) })
}
func TestReader_RuneAt(t *testing.T) { func TestReader_RuneAt(t *testing.T) {
r := reader.New(strings.NewReader("Hello, world!")) r := reader.New(strings.NewReader("Hello, world!"))
at := func(i int) rune { r, _ := r.RuneAt(i); return r } at := func(i int) rune { r, _ := r.RuneAt(i); return r }

View File

@ -2,7 +2,6 @@ package parsekit
import ( import (
"fmt" "fmt"
"io"
"git.makaay.nl/mauricem/go-parsekit/reader" "git.makaay.nl/mauricem/go-parsekit/reader"
) )
@ -69,12 +68,11 @@ type TokenAPI struct {
} }
// NewTokenAPI initializes a new TokenAPI struct, wrapped around the provided io.Reader. // NewTokenAPI initializes a new TokenAPI struct, wrapped around the provided io.Reader.
func NewTokenAPI(r io.Reader) *TokenAPI { func NewTokenAPI(input interface{}) *TokenAPI {
input := &TokenAPI{ return &TokenAPI{
reader: reader.New(r), reader: reader.New(input),
result: newTokenHandlerResult(), result: newTokenHandlerResult(),
} }
return input
} }
// NextRune returns the rune at the current read offset. // NextRune returns the rune at the current read offset.

View File

@ -394,7 +394,7 @@ func TestSequenceOfRunes(t *testing.T) {
output = p.Result().String() output = p.Result().String()
p.Stop() p.Stop()
} else { } else {
p.UnexpectedInput("sequence of runes") p.Expected("sequence of runes")
} }
}) })
err := parser.Execute(input) err := parser.Execute(input)

View File

@ -30,16 +30,18 @@ func NewTokenizer(tokenHandler TokenHandler) *Tokenizer {
tokenizer.result = p.Result() tokenizer.result = p.Result()
p.Stop() p.Stop()
} else { } else {
p.UnexpectedInput("") p.Expected("")
} }
}) })
return tokenizer return tokenizer
} }
// Execute feeds the input to the wrapped TokenHandler function. // Execute feeds the input to the wrapped TokenHandler function.
// For an overview of allowed inputs, take a look at the documentation for parsekit.reader.New().
//
// It returns the TokenHandler's TokenHandlerResult. When an error occurred // It returns the TokenHandler's TokenHandlerResult. When an error occurred
// during parsing, the error will be set, nil otherwise. // during parsing, the error will be set, nil otherwise.
func (t *Tokenizer) Execute(input string) (*TokenHandlerResult, *Error) { func (t *Tokenizer) Execute(input interface{}) (*TokenHandlerResult, *Error) {
err := t.parser.Execute(input) err := t.parser.Execute(input)
return t.result, err return t.result, err
} }