Added a nice example that shows how a []string-based type can be turned into a parser that fills its own slice elements during parsing.
This commit is contained in:
parent
2d851103e5
commit
7aff3fc43e
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
// <calculation> = <expr> <EOF>
|
||||
// <expr> = (<term> | <term> (ADD|SUB) <term>)
|
||||
// <term> = (<factor> | <factor> (MUL|DIV) <factor>)
|
||||
// <space> = (<space> (SPACE|TAB) | "")
|
||||
// <factor> = <space> (FLOAT | LPAREN <expr> RPAREN) <space>
|
||||
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) {
|
||||
// <calculation> = <expr> <EOF>
|
||||
func (c *calculator) calculation(p *parsekit.ParseAPI) {
|
||||
if p.Handle(c.expr) {
|
||||
p.ExpectEndOfFile()
|
||||
c.result = c.interpreter.result
|
||||
}
|
||||
}
|
||||
|
||||
// expr : term ((ADD|SUB) term)*
|
||||
// <expr> = (<term> | <term> (ADD|SUB) <term>)
|
||||
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)*
|
||||
// <term> = (<factor> | <factor> (MUL|DIV) <factor>)
|
||||
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> = (<space> (SPACE|TAB) | "")
|
||||
// <factor> = <space> (FLOAT | LPAREN <expr> RPAREN) <space>
|
||||
func (c *calculator) factor(p *parsekit.ParseAPI) {
|
||||
var pc, a = parsekit.C, parsekit.A
|
||||
p.On(a.Whitespace).Skip()
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 = <nil>
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue