Backup work.
This commit is contained in:
parent
05ae55c487
commit
9f5caa2024
|
@ -79,7 +79,7 @@ func (c *simpleCalculator) number(p *parsekit.ParseAPI) {
|
|||
c.Result += c.op * p.Result().Value(0).(int64)
|
||||
p.Handle(c.operatorOrEndOfFile)
|
||||
} else {
|
||||
p.UnexpectedInput("integer number")
|
||||
p.Expected("integer number")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ func (c *simpleCalculator) operatorOrEndOfFile(p *parsekit.ParseAPI) {
|
|||
c.op = -1
|
||||
p.Handle(c.number)
|
||||
case !p.On(A.EndOfFile).Stay():
|
||||
p.UnexpectedInput("operator, '+' or '-'")
|
||||
p.Expected("operator, '+' or '-'")
|
||||
default:
|
||||
p.ExpectEndOfFile()
|
||||
}
|
||||
|
|
|
@ -140,11 +140,11 @@ func (calc *calculator) factor(p *parsekit.ParseAPI) {
|
|||
return
|
||||
}
|
||||
if !p.On(A.RightParen).Skip() {
|
||||
p.UnexpectedInput("')'")
|
||||
p.Expected("')'")
|
||||
return
|
||||
}
|
||||
default:
|
||||
p.UnexpectedInput("factor or parenthesized expression")
|
||||
p.Expected("factor or parenthesized expression")
|
||||
return
|
||||
}
|
||||
p.On(A.Blanks).Skip()
|
||||
|
|
|
@ -85,7 +85,7 @@ func (h *helloparser1) start(p *parsekit.ParseAPI) {
|
|||
if p.On(a.StrNoCase("hello")).Skip() {
|
||||
p.Handle(h.comma)
|
||||
} else {
|
||||
p.UnexpectedInput("hello")
|
||||
p.Expected("hello")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ func (h *helloparser1) comma(p *parsekit.ParseAPI) {
|
|||
case p.On(a.Comma).Skip():
|
||||
p.Handle(h.startName)
|
||||
default:
|
||||
p.UnexpectedInput("comma")
|
||||
p.Expected("comma")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ func (h *helloparser1) startName(p *parsekit.ParseAPI) {
|
|||
if p.On(a.AnyRune).Stay() {
|
||||
p.Handle(h.name)
|
||||
} else {
|
||||
p.UnexpectedInput("name")
|
||||
p.Expected("name")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ func (h *helloparser1) name(p *parsekit.ParseAPI) {
|
|||
h.greetee += p.Result().String()
|
||||
p.Handle(h.name)
|
||||
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() {
|
||||
p.Handle(h.end)
|
||||
} 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) {
|
||||
var a = parsekit.A
|
||||
if !p.On(a.EndOfFile).Stay() {
|
||||
p.UnexpectedInput("end of greeting")
|
||||
p.Expected("end of greeting")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
33
parseapi.go
33
parseapi.go
|
@ -5,10 +5,10 @@ import (
|
|||
"io"
|
||||
)
|
||||
|
||||
// ParseAPI holds the internal state of a parse run and provides an API to
|
||||
// ParseHandler methods to communicate with the parser.
|
||||
// ParseAPI holds the internal state of a parse run and provides an API that
|
||||
// ParseHandler methods can use to communicate with the parser.
|
||||
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
|
||||
result *TokenHandlerResult // Last TokenHandler result as produced by On(...).Accept()
|
||||
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
|
||||
// TokenAPI available in the ParseAPI through the ParseAPI.Result() method.
|
||||
//
|
||||
// Returns true in case a match was found.
|
||||
// When no match was found, then no action is taken and false is returned.
|
||||
// Returns true in case a match was found by On().
|
||||
// When no match was found, then no action is taken, no results are
|
||||
// exposed and false is returned.
|
||||
func (a *ParseAPIOnAction) Accept() bool {
|
||||
if a.ok {
|
||||
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
|
||||
// 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.
|
||||
func (a *ParseAPIOnAction) Skip() bool {
|
||||
if a.ok {
|
||||
|
@ -167,7 +168,7 @@ func (a *ParseAPIOnAction) Skip() bool {
|
|||
// When a match is found, it hands off control to another ParseHandler
|
||||
// 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 {
|
||||
if a.ok {
|
||||
a.parseAPI.result = nil
|
||||
|
@ -179,6 +180,9 @@ func (a *ParseAPIOnAction) Stay() bool {
|
|||
|
||||
// Result returns a TokenHandlerResult struct, containing results as produced by the
|
||||
// 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 {
|
||||
result := p.result
|
||||
if p.result == nil {
|
||||
|
@ -213,7 +217,7 @@ func (p *ParseAPI) panicWhenParseHandlerNil(parseHandler ParseHandler) {
|
|||
// When the parser implementation returns without stopping first (and
|
||||
// without running into an error), the Parser.Execute() will call
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
func (p *ParseAPI) ExpectEndOfFile() {
|
||||
p.panicWhenStoppedOrInError()
|
||||
if p.On(A.EndOfFile).Stay() {
|
||||
p.Stop()
|
||||
} else {
|
||||
p.UnexpectedInput("end of file")
|
||||
p.Expected("end of file")
|
||||
}
|
||||
}
|
||||
|
||||
// UnexpectedInput is used to set an error that tells the user that some
|
||||
// unexpected input was encountered.
|
||||
// Expected is used to set an error that tells the user that some
|
||||
// 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:
|
||||
// 1) the input simply didn't match the expectation
|
||||
// 2) the end of the input was reached
|
||||
// 3) there was an error while reading the input.
|
||||
func (p *ParseAPI) UnexpectedInput(expected string) {
|
||||
func (p *ParseAPI) Expected(expected string) {
|
||||
p.panicWhenStoppedOrInError()
|
||||
_, err := p.tokenAPI.NextRune()
|
||||
switch {
|
||||
|
|
12
parser.go
12
parser.go
|
@ -1,9 +1,5 @@
|
|||
package parsekit
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parser is the top-level struct that holds the configuration for a parser.
|
||||
// The Parser can be instantiated using the parsekit.NewParser() method.
|
||||
type Parser struct {
|
||||
|
@ -34,10 +30,12 @@ func NewParser(startHandler ParseHandler) *Parser {
|
|||
}
|
||||
|
||||
// Execute starts the parser for the provided input.
|
||||
// When an error occurs during parsing, then this error is returned. Nil otherwise.
|
||||
func (p *Parser) Execute(input string) *Error {
|
||||
// For an overview of allowed inputs, take a look at the documentation for parsekit.reader.New().
|
||||
//
|
||||
// When an error occurs during parsing, then this error is returned, nil otherwise.
|
||||
func (p *Parser) Execute(input interface{}) *Error {
|
||||
api := &ParseAPI{
|
||||
tokenAPI: NewTokenAPI(strings.NewReader(input)),
|
||||
tokenAPI: NewTokenAPI(input),
|
||||
loopCheck: map[string]bool{},
|
||||
}
|
||||
if api.Handle(p.startHandler) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
func ExampleParseAPI_UnexpectedInput() {
|
||||
func ExampleParseAPI_Expected() {
|
||||
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...")
|
||||
fmt.Println(err.Full())
|
||||
|
|
|
@ -43,13 +43,15 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Reader wraps around an io.Reader and provides buffering that allows us to read
|
||||
// the same runes over and over again. This is useful for implementing a parser
|
||||
// that must be able to do lookahead on the input, returning to the original
|
||||
// input position after finishing that lookahead).
|
||||
// Reader wraps around a bufio.Reader and provides an additional layer of
|
||||
// buffering that allows us to read the same runes over and over again.
|
||||
// This is useful for implementing a parser that must be able to do 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
|
||||
// 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
|
||||
}
|
||||
|
||||
// New initializes a new reader struct, wrapped around the provided io.Reader.
|
||||
func New(r io.Reader) *Reader {
|
||||
// New initializes a new reader struct, wrapped around the provided input.
|
||||
//
|
||||
// The input can be any one of the following types:
|
||||
// - string
|
||||
// - type implementing io.Reader
|
||||
// - bufio.Reader
|
||||
func New(input interface{}) *Reader {
|
||||
return &Reader{
|
||||
bufio: bufio.NewReader(r),
|
||||
bufio: makeBufioReader(input),
|
||||
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.
|
||||
//
|
||||
// This offset is relative to the current starting position of the buffer in
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package reader_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
@ -21,6 +22,34 @@ func ExampleNew() {
|
|||
// 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) {
|
||||
r := reader.New(strings.NewReader("Hello, world!"))
|
||||
at := func(i int) rune { r, _ := r.RuneAt(i); return r }
|
||||
|
|
|
@ -2,7 +2,6 @@ package parsekit
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"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.
|
||||
func NewTokenAPI(r io.Reader) *TokenAPI {
|
||||
input := &TokenAPI{
|
||||
reader: reader.New(r),
|
||||
func NewTokenAPI(input interface{}) *TokenAPI {
|
||||
return &TokenAPI{
|
||||
reader: reader.New(input),
|
||||
result: newTokenHandlerResult(),
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// NextRune returns the rune at the current read offset.
|
||||
|
|
|
@ -394,7 +394,7 @@ func TestSequenceOfRunes(t *testing.T) {
|
|||
output = p.Result().String()
|
||||
p.Stop()
|
||||
} else {
|
||||
p.UnexpectedInput("sequence of runes")
|
||||
p.Expected("sequence of runes")
|
||||
}
|
||||
})
|
||||
err := parser.Execute(input)
|
||||
|
|
|
@ -30,16 +30,18 @@ func NewTokenizer(tokenHandler TokenHandler) *Tokenizer {
|
|||
tokenizer.result = p.Result()
|
||||
p.Stop()
|
||||
} else {
|
||||
p.UnexpectedInput("")
|
||||
p.Expected("")
|
||||
}
|
||||
})
|
||||
return tokenizer
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
return t.result, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue