Documentation
¶
Overview ¶
Package rat implements a PEG packrat parser that takes advantage of Go's unique ability to switch on type to create an interpreted meta-language consisting entirely of Go types. These types serve as tokens that any lexer would create when lexing a higher level meta language such as PEG, PEGN, or regular expressions. Passing these types to rat.Pack compiles (memoizes) them into a grammar of parsing functions not unlike compilation of regular expressions. The string representations of these structs consists of entirely of valid, compilable Go code suitable for parser code generation for parsers of any type, in any language. Simply printing a Grammar instance to a file is suitable to generate such a parser.
Prefer Pack over Make*
Although the individual Make* methods for each of the supported types have been exported publicly allowing developers to call them directly from within their own Rule implementations, most should use Pack instead. Consider it the equivalent of compiling a regular expression.
Index ¶
- Constants
- Variables
- func Walk(root Result, do VisitFunc)
- func WalkBy(flatten FlatFunc, root Result, do VisitFunc)
- type CheckFunc
- type ErrArgs
- type ErrExpected
- type ErrIsZero
- type ErrNoCheckFunc
- type ErrNotFound
- type FlatFunc
- type Grammar
- func (g *Grammar) AddRule(rule *Rule) *Rule
- func (g *Grammar) Check(r []rune, i int) Result
- func (g *Grammar) Init() *Grammar
- func (g *Grammar) MakeAny(in x.Any) *Rule
- func (g *Grammar) MakeEnd(in x.End) *Rule
- func (g *Grammar) MakeIs(in x.Is) *Rule
- func (g *Grammar) MakeMmx(in x.Mmx) *Rule
- func (g *Grammar) MakeNamed(in x.N) *Rule
- func (g *Grammar) MakeNot(in x.Not) *Rule
- func (g *Grammar) MakeOne(one x.One) *Rule
- func (g *Grammar) MakeRef(in x.Ref) *Rule
- func (g *Grammar) MakeRng(in x.Rng) *Rule
- func (g *Grammar) MakeRule(in any) *Rule
- func (g *Grammar) MakeSave(in x.Sav) *Rule
- func (g *Grammar) MakeSee(in x.See) *Rule
- func (g *Grammar) MakeSeq(seq x.Seq) *Rule
- func (g *Grammar) MakeStr(in any) *Rule
- func (g *Grammar) MakeTo(in x.To) *Rule
- func (g *Grammar) MakeVal(in x.Val) *Rule
- func (g *Grammar) NewRule() *Rule
- func (g *Grammar) Pack(in ...any) *Grammar
- func (g Grammar) Print()
- func (g *Grammar) Scan(in any) Result
- func (g Grammar) String() string
- type IsFunc
- type Result
- type Rule
- type RuleMaker
- type VisitFunc
Examples ¶
Constants ¶
const ( ErrIsZeroT = `zero value: %T` ErrNotExistT = `does not exist: %v` ErrExpectedT = `expected: %v` ErrBadTypeT = `unknown type: %v (%[1]T)` ErrArgsT = `missing or incorrect arguments: %v (%[1]T)` ErrPackTypeT = `invalid type` ErrNoCheckFuncT = `no check function assigned: %v` )
Variables ¶
var DefaultFlatFunc = ByDepth
DefaultFlatFunc is the default FlatFunc to use when filtering for results using With* methods.
var DefaultRuleName = `Rule`
DefaultRuleName is used by NewRule and AddRule as the prefix for new, unnamed rules and is combined with the internal integer number that increments for each new such rule added.
var MaxGoroutines int
MaxGoroutines set the maximum number of goroutines by any method or function in this package (WalkByAsync, for example). By default, there is no limit (0).
var Trace int
Trace enables tracing parsing and checks while they happen. Output is with the log package.
Functions ¶
func Walk ¶
Walk calls WalkBy(DefaultFlatFunc, root, do). Use this when the order of processing matters more than speed (ASTs, etc.). Also see WalkAsync.
func WalkBy ¶
WalkBy takes a function to flatten a rooted node tree (FlatFunc), creates a flattened slice of Results starting from root Result, and then passes each synchronously to the VisitFunc waiting for it to complete before doing the next. Walk calls WalkBy(DefaultFlatFunc, root, do). Use this when the order of processing matters more than speed (ASTs, etc.). Also see
Types ¶
type CheckFunc ¶
CheckFunc examines the []rune buffer at a specific position for a specific grammar rule and should generally only be used from an encapsulating Rule so that it has a Text identifier associated with it. One or more Rules may, however, encapsulate the same CheckFunc function.
CheckFunc MUST return a Result indicating success or failure by setting Result.X (error) for failure. (Note that this is unlike many packrat designs that return nil to indicate rule failure.)
CheckFunc MUST set Result.X (error) if unable to match the entire rule and MUST advance to the E (end) to the farthest possible position in the []rune slice before failure occurred. This allows for better recovery and specific user-facing error messages while promoting succinct rule development.
When a CheckFunc is composed of multiple sub-rules, each MUST be added to the Result.C (children) slice including any that generated errors. Some functions may opt to continue even if the result contained an error allowing recovery. Usually, however, a CheckFunc should stop on the first error and include it with the children. Usually, a CheckFunc should also set its error Result.X to that of the final Result that failed.
type ErrExpected ¶
type ErrExpected struct{ V any }
func (ErrExpected) Error ¶
func (e ErrExpected) Error() string
type ErrNoCheckFunc ¶
type ErrNoCheckFunc struct{ V any }
func (ErrNoCheckFunc) Error ¶
func (e ErrNoCheckFunc) Error() string
type ErrNotFound ¶
type ErrNotFound struct {
// contains filtered or unexported fields
}
type Grammar ¶
type Grammar struct { Trace int // activate logs for debug visibility Rules map[string]*Rule // keyed to Rule.Name (not Text) Saved map[string]*Rule // dynamically created literals from Sav Main *Rule // entry point for Check or Scan // contains filtered or unexported fields }
Grammar is an aggregation of cached (memoized) rules with methods to add rules and check data against them. The Main rule is the entry point. Grammars may have multiple unrelated rules added and change the Main entry point dynamically as needed, but most will use the sequence rule created by calling the Pack method or it's rat.Pack equivalent constructor. Trace may be incremented during debugging to gain performant visibility into grammar construction and scanning.
Memoization ¶
All Make* methods check the Rules map/cache for a match for the String form of the rat/x expression and return it directly if found rather than create a new Rule with an identical CheckFunc. The MakeNamed creates an additional entry (pointing to the same *Rule) for the specified name.
func Pack ¶
Pack interprets a sequence of any valid Go types into a Grammar suitable for checking and parsing any UTF-8 input. All arguments passed are assumed to rat/x expressions or literal forms (%q). These have special meaning corresponding to the fundamentals expressions of an PEG or regular expression. Consider these the tokens created by any lexer when processing any meta-language. Any grammar or structured data format that uses UTF-8 encoding can be fully expressed as compilable Go code using this method of interpretation.
Alternative Call ¶
Pack is actually just shorthand equivalent to the following:
g := new(Grammar) rule := g.MakeRule()
Memoization ¶
Memoization is a fundamental requirement for any PEG packrat parser. Pack automatically memoizes all expressions using closure functions and map entries matching the specific arguments to a specific expression. Results are always integer pointers to specific positions within the data passed so there is never wasteful redundancy. This maximizes performance and minimizes memory utilization.
Example (End) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.Any{2}, x.End{}) g.Print() g.Scan(`fo`).PrintText() g.Scan(`fo`).Print() g.Scan(`foo`).Print() }
Output: x.Seq{x.Any{2}, x.End{}} fo {"B":0,"E":2,"C":[{"B":0,"E":2},{"B":2,"E":2}],"R":"fo"} {"B":0,"E":2,"X":"expected: x.End{}","C":[{"B":0,"E":2},{"B":2,"E":2,"X":"expected: x.End{}"}],"R":"foo"}
Example (Is) ¶
package main import ( "unicode" "github.com/rwxrob/rat" ) func main() { IsPrint := unicode.IsPrint g := rat.Pack(IsPrint) g.Print() g.Scan(`foo`).PrintText() g.Scan(`foo`).Print() g.Scan("\x00foo").Print() }
Output: x.Is{IsPrint} f {"B":0,"E":1,"R":"foo"} {"B":0,"E":0,"X":"expected: x.Is{IsPrint}","R":"\x00foo"}
Example (Mmx) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.Mmx{1, 3, `foo`}) g.Print() g.Scan(`foo`).PrintText() g.Scan(`foo`).Print() g.Scan(`foofoo`).PrintText() g.Scan(`foofoo`).Print() g.Scan(`foofoofoo`).PrintText() g.Scan(`foofoofoo`).Print() g.Scan(`foofoofoofoo`).PrintText() g.Scan(`foofoofoofoo`).Print() g.Scan(`barfoofoo`).Print() }
Output: x.Mmx{1, 3, x.Str{"foo"}} foo {"B":0,"E":3,"C":[{"B":0,"E":3}],"R":"foo"} foofoo {"B":0,"E":6,"C":[{"B":0,"E":3},{"B":3,"E":6}],"R":"foofoo"} foofoofoo {"B":0,"E":9,"C":[{"B":0,"E":3},{"B":3,"E":6},{"B":6,"E":9}],"R":"foofoofoo"} foofoofoo {"B":0,"E":9,"C":[{"B":0,"E":3},{"B":3,"E":6},{"B":6,"E":9},{"B":9,"E":12}],"R":"foofoofoofoo"} {"B":0,"E":0,"X":"expected: x.Mmx{1, 3, x.Str{\"foo\"}}","R":"barfoofoo"}
Example (Named) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.N{`foo`, true}) g.Print() g.Scan(`true`).Print() }
Output: x.N{"foo", x.Str{"true"}} {"N":"foo","B":0,"E":4,"R":"true"}
Example (Not) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.Not{`foo`}) g.Print() g.Scan(`fo`).PrintText() g.Scan(`fo`).Print() g.Scan(`bar`).PrintText() g.Scan(`bar`).Print() g.Scan(`fooooo`).Print() }
Output: x.Not{x.Str{"foo"}} {"B":0,"E":0,"R":"fo"} {"B":0,"E":0,"R":"bar"} {"B":0,"E":0,"X":"expected: x.Not{x.Str{\"foo\"}}","R":"fooooo"}
Example (One) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.One{`foo`, `bar`}) g.Print() g.Scan(`foobar`).PrintText() g.Scan(`foobar`).Print() g.Scan(`barfoo`).PrintText() g.Scan(`barfoo`).Print() g.Scan(`baz`).Print() }
Output: x.One{x.Str{"foo"}, x.Str{"bar"}} foo {"B":0,"E":3,"C":[{"B":0,"E":3}],"R":"foobar"} bar {"B":0,"E":3,"C":[{"B":0,"E":3}],"R":"barfoo"} {"B":0,"E":0,"X":"expected: x.One{x.Str{\"foo\"}, x.Str{\"bar\"}}","R":"baz"}
Example (One_Named) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { one := x.One{`foo`, `bar`} Foo := x.N{`Foo`, one} g := rat.Pack(Foo) g.Print() // foo g.Scan(`foobar`).Print() g.Scan(`foobar`).PrintText() // bar g.Scan(`barrr`).Print() g.Scan(`barrr`).PrintText() // bork g.Scan(`fobar`).Print() }
Output: x.N{"Foo", x.One{x.Str{"foo"}, x.Str{"bar"}}} {"N":"Foo","B":0,"E":3,"C":[{"B":0,"E":3}],"R":"foobar"} foo {"N":"Foo","B":0,"E":3,"C":[{"B":0,"E":3}],"R":"barrr"} bar {"N":"Foo","B":0,"E":0,"X":"expected: x.One{x.Str{\"foo\"}, x.Str{\"bar\"}}","R":"fobar"}
Example (Ref) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.Ref{`Foo`}) g.MakeRule(x.N{`Foo`, `foo`}) g.Print() g.Rules[`x.Str{"foo"}`].Print() g.Rules[`Foo`].Print() g.Scan(`foo`).Print() g.Rules[`x.Str{"foo"}`].Scan(`foo`).Print() }
Output: x.Ref{"Foo"} x.Str{"foo"} x.N{"Foo", x.Str{"foo"}} {"N":"Foo","B":0,"E":3,"R":"foo"} {"B":0,"E":3,"R":"foo"}
Example (Rng) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.Rng{'😀', '🙏'}) g.Print() g.Scan(`🙉`).PrintText() g.Scan(`🙉`).Print() g.Scan(`🚆`).Print() }
Output: x.Rng{'😀', '🙏'} 🙉 {"B":0,"E":1,"R":"🙉"} {"B":0,"E":0,"X":"expected: x.Rng{'😀', '🙏'}","R":"🚆"}
Example (Save) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := new(rat.Grammar).Init() g.MakeRule(x.N{`Post`, x.Mmx{3, 8, '`'}}) g.Pack(x.N{`Fenced`, x.Seq{x.Sav{`Post`}, x.To{x.Val{`Post`}}, x.Val{`Post`}}}) g.Print() // one step at a time g.Rules[`x.Sav{"Post"}`].Scan("````").Print() g.Rules[`Post`].Scan("````").Print() g.Rules[`x.Val{"Post"}`].Scan("````````").Print() g.Rules[`x.To{x.Val{"Post"}}`].Scan("....``````").Print() // combined g.Scan("```.......`````").PrintText() g.Scan("```.......`````").Print() }
Output: x.N{"Fenced", x.Seq{x.Sav{"Post"}, x.To{x.Val{"Post"}}, x.Val{"Post"}}} {"N":"Post","B":0,"E":4,"C":[{"B":0,"E":1},{"B":1,"E":2},{"B":2,"E":3},{"B":3,"E":4}],"R":"````"} {"N":"Post","B":0,"E":4,"C":[{"B":0,"E":1},{"B":1,"E":2},{"B":2,"E":3},{"B":3,"E":4}],"R":"````"} {"B":0,"E":4,"R":"````````"} {"B":0,"E":4,"R":"....``````"} ```.......``` {"N":"Fenced","B":0,"E":13,"C":[{"N":"Post","B":0,"E":3,"C":[{"B":0,"E":1},{"B":1,"E":2},{"B":2,"E":3}]},{"B":3,"E":10},{"B":10,"E":13}],"R":"```.......`````"}
Example (See) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.See{`foo`}) g.Print() g.Scan(`fooooo`).PrintText() g.Scan(`fooooo`).Print() g.Scan(`fo`).Print() g.Scan(`bar`).Print() }
Output: x.See{x.Str{"foo"}} {"B":0,"E":0,"R":"fooooo"} {"B":0,"E":0,"X":"expected: x.See{x.Str{\"foo\"}}","R":"fo"} {"B":0,"E":0,"X":"expected: x.See{x.Str{\"foo\"}}","R":"bar"}
Example (Seq) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(`foo`, `bar`, x.One{true, false}, x.Mmx{0, 1, `baz`}) g.Print() g.Scan(`foobartrue`).PrintText() g.Scan(`foobartruebaz`).PrintText() g.Scan(`foobarfalsebaz`).PrintText() g.Scan(`foo`).Print() g.Scan(`foobarbaz`).PrintError() }
Output: x.Seq{x.Str{"foobar"}, x.One{x.Str{"true"}, x.Str{"false"}}, x.Mmx{0, 1, x.Str{"baz"}}} foobartrue foobartruebaz foobarfalsebaz {"B":0,"E":3,"X":"expected: b","C":[{"B":0,"E":3,"X":"expected: b"}],"R":"foo"} expected: x.One{x.Str{"true"}, x.Str{"false"}}
Example (To) ¶
package main import ( "github.com/rwxrob/rat" "github.com/rwxrob/rat/x" ) func main() { g := rat.Pack(x.To{`foo`}) g.Print() g.Scan(`...foo`).PrintText() g.Scan(`...foo`).Print() g.Scan(`foofoo`).PrintText() g.Scan(`foofoo`).Print() g.Scan(`.foofo`).PrintText() g.Scan(`.foofo`).Print() g.Scan(`...fo`).Print() g.Scan(`...bar`).Print() }
Output: x.To{x.Str{"foo"}} ... {"B":0,"E":3,"R":"...foo"} {"B":0,"E":0,"R":"foofoo"} . {"B":0,"E":1,"R":".foofo"} {"B":0,"E":5,"X":"expected: x.To{x.Str{\"foo\"}}","R":"...fo"} {"B":0,"E":6,"X":"expected: x.To{x.Str{\"foo\"}}","R":"...bar"}
func (*Grammar) AddRule ¶
AddRule adds a new rule to the grammar cache keyed to the rule.Name. If a rule was already keyed to that name it is overwritten. If rule.Name is empty a new incremental name is created with the DefaultRuleName prefix. Avoid changing the rule.Name values after added since the key in the grammar cache is hard-coded to the rule.Name when called. If the rule.Name is not important consider NewRule instead (which uses these defaults and requires no argument). Returns self for convenience.
func (*Grammar) Init ¶
Init initializes the Grammar emptying the Rules if any or creating a new Rules map and setting internal rule ID to 0 and disabling Trace, and emptying Main.
func (*Grammar) MakeIs ¶
MakeIs takes an x.IsFunc (which is just a func(r rune) bool) or x.Is type and calls that function in its Check.
func (*Grammar) MakeNamed ¶
MakeNamed makes two rules pointing to the same CheckFunc, one unnamed and other named (first argument). Both produce results that have the Name field set.
func (*Grammar) MakeRule ¶
MakeRule fulfills the MakeRule interface. The input argument is usually a rat/x ("ratex") expression type including x.IsFunc functions. Anything else is interpreted as a literal string by using its String method or converting it into a string using the %v (string, []rune, [] byte, rune) or %q representation. Note that MakeRule itself does not check the Rules cache for existing Rules not does it add the rule to that cache. This work is left to the Make* methods themselves or to the AddRule method. The result, however, is the same since MakeRule delegates to those Make* methods.
func (*Grammar) NewRule ¶
NewRule creates a new rule in the grammar cache using the defaults. It is a convenience when a Name is not needed. See AddRule for details.
func (*Grammar) Pack ¶
Pack allows multiple rules to be passed (unlike MakeRule). If one argument, returns MakeRule for it. If more than one argument, delegates to MakeSeq. Pack is called from the package function of the same name, which describes the valid argument types. As a convenience, a self-reference is returned.
type IsFunc ¶
IsFunc functions return true if the passed rune is contained in a set of runes. The unicode package contains several examples.
type Result ¶
type Result struct { N string // string name (x.Name) I int // integer identifier (x.ID) B int // beginning (inclusive) E int // ending (non-inclusive) X error // error, eXpected something else C []Result // children, results within this result R []rune // reference data (underlying slice array shared) }
Result contains the result of an evaluated Rule function along with its own shared []rune slice (R).
N (for "Name") is a string name for a result mapped to the x.Name rule. This makes for easy reading and walking of results trees since the string name is included in the output JSON (see String). Normally, mixing x.ID and x.Name rules is avoided.
I (for "ID") is an integer mapped to the x.ID rule. Integer IDs are preferable to names (N) in cases where the use of names would increase the parse tree output JSON (see String) beyond acceptable levels since integer identifiers rarely take more than 2 runes each. Normally, mixing x.ID and x.Name rules is avoided.
B (for "beginning") is the inclusive beginning position index of the first rune in Buf that matches.
E (for "ending") is the exclusive ending position index of the end of the match (and Beg of next match). End must be advanced to the farthest possible point even if the rule fails (and an Err set). This allows recovery attempts from that position.
Note that B == E does not indicate failure. E is usually greater than B, but not necessarily, for example, for lookahead rules are successful without advancing the position at all. E is also greater than B if a partial match was made that still resulted in an error. Only checking X can absolutely confirm a rule failure.
X (for "expected") contains any error encountered while parsing.
C (for "children") contains results within this result, sub-matches, equivalent to parenthesized patterns of a regular expression.
R (for "result" or "runes" or "buffer") contains the slice with all the data in it. Since Go uses the same underlying array for any slice, no matter how many times it is referenced, there is no loss of memory efficiency even though marshaling a Result would produce duplicate output. For this reason [MarshalJSON] omits this field from any children ([C]) when marshaling.
Avoid taking reference to Result ¶
A Result is already made up of references so no further dereferencing is required. The buffer (R) is a slice and therefore all slices point to the same underlying array in memory. And no actual string data is saved in any Result. Rather, the beginning and ending positions within the buffer data are stored and retrieved when needed with methods such as Text().
func ByDepth ¶
ByDepth flattens a rooted node tree of Result structs by traversing in a synchronous, depth-first, preorder way.
func (Result) MarshalJSON ¶
MarshalJSON fulfills the encoding.JSONMarshaler interface. The begin (B), end (E) are always included. The name (N), id (I), buffer (R), error (X) and child sub-matches (C) are only included if not empty. Child sub-matches omit the buffer (R). The order of fields is guaranteed not to change. Output is always a single line. There is no dependency on the reflect package. The buffer (R) is rendered as a quoted string (%q) with no further escaping (unlike built-in Go JSON marshaling which escapes things unnecessarily producing unreadable output). The buffer (R) is never included for children (which is the same). An error is never returned.
func (Result) PrintError ¶
func (m Result) PrintError()
PrintError is short for fmt.Println(m.X) but adds position.
func (Result) String ¶
String fulfills the fmt.Stringer interface as JSON by calling MarshalJSON. If JSON marshaling fails for any reason a "null" string is returned.
func (Result) Text ¶
Text returns the text between beginning (B) and ending (E) (non-inclusively) It is a shortcut for string(res.R[res.B:res.E]).
func (Result) WithName ¶
WithName returns all results with any of the passed names. Returns zero length slice if no results. As a convenience, multiple names may be passed and all matches for each will be grouped together in the order provided. See WalkDefault for details on the algorithm used.
Example ¶
package main import ( "github.com/rwxrob/rat" ) func main() { foo := rat.Result{N: `foo`, I: 1, B: 2, E: 3} r1 := rat.Result{N: `r1`, B: 1, E: 3} r2 := r1 r1a := rat.Result{N: `r1a`, B: 1, E: 2} r1b := rat.Result{N: `r1b`, B: 2, E: 3, C: []rat.Result{foo}} foo.I = 2 r1.C = []rat.Result{r1a, r1b} r2.N = `r2` r2.C = []rat.Result{foo} foo.I = 3 root := rat.Result{ N: `Root`, B: 1, E: 3, C: []rat.Result{r1, r2, foo}, } for _, result := range root.WithName(`foo`) { result.Print() } }
Output: {"N":"foo","I":1,"B":2,"E":3} {"N":"foo","I":2,"B":2,"E":3} {"N":"foo","I":3,"B":2,"E":3}
type Rule ¶
type Rule struct { Name string // uniquely identifying name (sometimes dynamically assigned) Text string // prefer rat/x compatible expression (ex: x.Seq{"foo", "bar"}) Check CheckFunc // closure created with a RuleMaker }
Rule encapsulates a CheckFunc with a Name and Text representation. The Name is use as the unique key in the Grammar.Rules cache. Text can be anything, but it is strongly recommended that it contain rat/x compatible expression so that it can be used directly for code generation.
Rules are created by implementations of RuleMaker the most important of which is Grammar. Almost every Rule encapsulates a different set of arguments enclosed in its CheckFunc. Once created, a Rule should be considered immutable. Field values must not change so that they correspond with the enclosed values within the CheckFunc closure and so that the Name can be used to uniquely identify the Rule.
type RuleMaker ¶
RuleMaker implementations must return a new Rule created from any input (but usually from rat/x expressions and other Go types). Implementations may choose to cache the newly created rule and simply return a previously cached rule if the input arguments are identified as representing an identical previous rule. This fulfills the PEG packrat parsing requirement for functional memoization.
type VisitFunc ¶
type VisitFunc func(a Result)
VisitFunc is a first-class function passed one result. Typically these functions will enclose variables, contexts, or a channel outside of its own scope to be updated for each visit. Functional recursion is usually used, which may present some limitations depending on the depth required.