diff --git a/example_basiccalculator1_test.go b/example_basiccalculator1_test.go index 4b79f70..a7bb38b 100644 --- a/example_basiccalculator1_test.go +++ b/example_basiccalculator1_test.go @@ -54,7 +54,7 @@ func Example_basicCalculator1() { // Implementation of the parser // --------------------------------------------------------------------------- -// CalculateSimple interprets a simple calculation, consisting of only integers +// ComputeSimple interprets a simple calculation, consisting of only integers // and add or subtract operators. It returns the result of the calculation. // An error is returned in case the calculation failed. func ComputeSimple(calculation string) (int64, *parsekit.Error) { @@ -72,13 +72,12 @@ type simpleCalculator struct { op int64 // represents operation for next term (+1 = add, -1 = subtract) } -func (c *simpleCalculator) number(p *parsekit.ParseAPI) { - // A definition of integer, which conveniently drops surrounding whitespace. - pc, a, m := parsekit.C, parsekit.A, parsekit.M - whitespace := m.Drop(pc.Opt(a.Whitespace)) - integer := pc.Seq(whitespace, a.Integer, whitespace) +// A definition of bareInteger, which conveniently drops surrounding whitespace. +var dropWhitespace = parsekit.M.Drop(parsekit.C.Opt(parsekit.A.Whitespace)) +var bareInteger = parsekit.C.Seq(dropWhitespace, parsekit.A.Integer, dropWhitespace) - if p.On(integer).Accept() { +func (c *simpleCalculator) number(p *parsekit.ParseAPI) { + if p.On(bareInteger).Accept() { value, err := strconv.ParseInt(p.BufLiteral(), 10, 64) p.BufClear() if err != nil { diff --git a/example_basiccalculator2_test.go b/example_basiccalculator2_test.go index d2f9404..de807eb 100644 --- a/example_basiccalculator2_test.go +++ b/example_basiccalculator2_test.go @@ -3,13 +3,13 @@ // // " -10 + (10.8+ (3 *-20-3*(8 +-4.12)) + 10)/5 " // -// More formally, a calculation is defined as: +// In terms of a somewhat formal grammar, a calculation is defined as: // -// calculation : expr EOF -// expr : term ((ADD|SUB) term)* -// term : factor ((MUL|DIV) factor)* -// space : (SPACE|TAB)* -// factor : space (FLOAT | LPAREN expr RPAREN) space +// = +// = ( | (ADD|SUB) ) +// = ( | (MUL|DIV) ) +// = ( (SPACE|TAB) | "") +// = (FLOAT | LPAREN RPAREN) package parsekit_test import ( @@ -66,8 +66,8 @@ func Example_basicCalculator2() { // --------------------------------------------------------------------------- // calculator implements a recursive descent parser that is responsible for parsing -// the input computation string according to the grammar. -// It offloads the actual computation to a separate interpreter. +// the input calculation string according to the grammar. +// It offloads the actual calculation to a separate interpreter. type calculator struct { interpreter interpreter result float64 @@ -75,22 +75,23 @@ type calculator struct { // Compute takes a calculation string as input and returns the interpreted result // value for the calculation. An error can be returned as well, in case the -// computation fails for some reason. +// calculation fails for some reason. func Compute(input string) (float64, *parsekit.Error) { c := &calculator{} - parser := parsekit.NewParser(c.computation) + parser := parsekit.NewParser(c.calculation) err := parser.Execute(input) return c.result, err } -func (c *calculator) computation(p *parsekit.ParseAPI) { +// = +func (c *calculator) calculation(p *parsekit.ParseAPI) { if p.Handle(c.expr) { p.ExpectEndOfFile() c.result = c.interpreter.result } } -// expr : term ((ADD|SUB) term)* +// = ( | (ADD|SUB) ) func (c *calculator) expr(p *parsekit.ParseAPI) { c.interpreter.push() @@ -108,7 +109,7 @@ func (c *calculator) expr(p *parsekit.ParseAPI) { c.interpreter.pop() } -// term : factor ((MUL|DIV) factor)* +// = ( | (MUL|DIV) ) func (c *calculator) term(p *parsekit.ParseAPI) { c.interpreter.push() @@ -126,7 +127,8 @@ func (c *calculator) term(p *parsekit.ParseAPI) { c.interpreter.pop() } -// factor : space (FLOAT | LPAREN expr RPAREN) space +// = ( (SPACE|TAB) | "") +// = (FLOAT | LPAREN RPAREN) func (c *calculator) factor(p *parsekit.ParseAPI) { var pc, a = parsekit.C, parsekit.A p.On(a.Whitespace).Skip() diff --git a/example_helloparser2_test.go b/example_helloparser2_test.go index 5d2e05c..c106835 100644 --- a/example_helloparser2_test.go +++ b/example_helloparser2_test.go @@ -78,16 +78,6 @@ func (h *helloparser2) Parse(input string) (string, *parsekit.Error) { return h.greetee, err } -// Note: -// For efficiency, we could have either: -// -// 1) added a return after every call to p.Error() -// 2) done an 'else if' for every 'if' after the first -// -// For code readability, I omitted these however. The ParseAPI knows it -// should ignore any upcoming call after an error has been set, so after -// an error the p.On() calls will be invoked, however they will always -// return false. func (h *helloparser2) start(p *parsekit.ParseAPI) { c, a, m := parsekit.C, parsekit.A, parsekit.M if !p.On(c.StrNoCase("hello")).Skip() { diff --git a/examples_state_test.go b/examples_state_test.go new file mode 100644 index 0000000..de4eb4e --- /dev/null +++ b/examples_state_test.go @@ -0,0 +1,34 @@ +// In this example, we show that any type can be extended into a parser, +// filling that type with data from the ParseHandler methods. +// +// Here, we create a custom type 'letterCollection', which is an alias +// for []string. We add a ParseHandler method directly to that type +// and let the parsing code fill the slice with strings during parsing. + +package parsekit_test + +import ( + "fmt" + + "git.makaay.nl/mauricem/go-parsekit" +) + +type letterCollection []string + +func (l *letterCollection) parseStart(p *parsekit.ParseAPI) { + for p.On(parsekit.C.MinMax(1, 3, parsekit.A.AnyRune)).Accept() { + *l = append(*l, p.BufLiteral()) + p.BufClear() + } + p.ExpectEndOfFile() +} + +func Example_usingSliceAsParserState() { + letters := &letterCollection{} + parser := parsekit.NewParser(letters.parseStart) + err := parser.Execute("¡Any will dö!") + + fmt.Printf("Matches = %q, Error = %s\n", *letters, err) + // Output: + // Matches = ["¡An" "y w" "ill" " dö" "!"], Error = +} diff --git a/parsehandler_on.go b/parsehandler_on.go index 2062984..0c4feb2 100644 --- a/parsehandler_on.go +++ b/parsehandler_on.go @@ -36,7 +36,7 @@ package parsekit // if p.On(parsekit.C.Str("hi")).Accept() { // p.Emit(SomeItemType, p.BufLiteral()) // } -func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction { +func (p *ParseAPI) On(tokenHandler TokenHandler) *ParseAPIOnAction { p.panicWhenStoppedOrInError() // Perform the matching operation. @@ -54,7 +54,7 @@ func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction { // } p.LastMatch = string(m.input) - return &MatchAction{ + return &ParseAPIOnAction{ p: p, ok: ok, input: m.input, @@ -63,9 +63,9 @@ func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction { } } -// MatchAction is a struct that is used for building the On()-method chain. +// ParseAPIOnAction is a struct that is used for building the On()-method chain. // The On() method will return an initialized struct of this type. -type MatchAction struct { +type ParseAPIOnAction struct { p *ParseAPI ok bool input []rune @@ -79,7 +79,7 @@ type MatchAction struct { // // Returns true in case a match was found. // When no match was found, then no action is taken and false is returned. -func (a *MatchAction) Accept() bool { +func (a *ParseAPIOnAction) Accept() bool { if a.ok { a.p.buffer.writeString(string(a.output)) a.advanceCursor() @@ -92,7 +92,7 @@ func (a *MatchAction) Accept() bool { // // Returns true in case a match was found. // When no match was found, then no action is taken and false is returned. -func (a *MatchAction) Skip() bool { +func (a *ParseAPIOnAction) Skip() bool { if a.ok { a.advanceCursor() } @@ -101,14 +101,14 @@ func (a *MatchAction) Skip() bool { // Stay tells the parser to not move the cursor after finding a match. // Returns true in case a match was found, false otherwise. -func (a *MatchAction) Stay() bool { +func (a *ParseAPIOnAction) Stay() bool { return a.ok } // advanceCursor advances the input position in the input data. // While doing so, it keeps tracks of newlines that are encountered, so we // can report on line + column positions on error. -func (a *MatchAction) advanceCursor() { +func (a *ParseAPIOnAction) advanceCursor() { a.p.inputPos = a.inputPos for _, r := range a.input { if a.p.newline { diff --git a/tokenhandlers_builtin.go b/tokenhandlers_builtin.go index 6f0de4b..0c72634 100644 --- a/tokenhandlers_builtin.go +++ b/tokenhandlers_builtin.go @@ -598,8 +598,8 @@ func modifyTrim(handler TokenHandler, cutset string, trimLeft bool, trimRight bo } // ModifyTrimSpace creates a TokenHandler that checks if the provided TokenHandler applies. -// If it does, then its output is taken and whitespace characters as defined by unicode -// are are trimmed from the left and right of the output. +// If it does, then its output is taken and all leading and trailing whitespace charcters, +// as defined by Unicode (spaces, tabs, carriage returns and newlines) are removed from it. func ModifyTrimSpace(handler TokenHandler) TokenHandler { return ModifyByCallback(handler, strings.TrimSpace) }