Adds an exploratory stab at a grammar grammar.
Memoizing visitors correctly symbolize recursive graphs.
Recasts visitors as an object algebra.

Object algebras are probably more useful in a language that supports the parameterization of interfaces by types, but with the addition of lazy representations of children, they provide a natural and effective way to implement visiting with decisions about whether to recurse. This allows them to conveniently handle the memoization and symbolization of nodes in a cyclic graph, composably. HammerMemoizationVisitor can therefore be used to make an arbitrary visitor implementing the HammerParserVisitor protocol terminate when visiting a recursive parser.

This is a lot tidier than the visit/leave implementations and more directly specifies the arity and types of the parsers and their data.

One issue with this formulation is that the visiting algorithm doesn’t have access to the identities of the parsers (as distinct from the parts from which they are composed). This means that HammerMemoizationVisitor has to synthesize an identity for a parser from the identities of its class, any children it might have, and any other associated data it has. Thus, the identity symbolizer is a little less useful in this formulation since the “identity” is (with HammerMemoizationVisitor) instead this tuple-like construct, encoded as an array, and rather difficult to predict. Reifying the identity of a parser (perhaps internally to HammerMemoizationVisitor, or perhaps via an identifying token passed to the visitor, like a unique name or ID) could improve this situation somewhat.

Additionally, the lazy visitation of children means that the identities of the lazy blocks must be persistent between -acceptAlgebra: calls on the same object (at least during the same overall invocation of the visit). The extant parsers accomplish this by use of a memoizing block at initialization, which, when called, evaluates their own lazy reference to their children—and memoizes it on the parser. Thus, the lazy references won’t be evaluated more frequently than necessary, and the memoization visitor has consistent identities with which to detect cycles.

Visitors made with this formulation must explicitly recurse into and process parsers’ children (should they need to); this appears to externalize the formerly internal iteration of the visitor, but it doesn’t break encapsulation: the visitor only knows what the parser tells it, so the parser is ultimately in control of what state it exposes. Similarly the visitor gains control of what children should be recursed into; the ability to iterate the children is thus held by the parser, and the ability to choose the path actually taken is held by the visitor. A second drawback arises here: since the visitor must explicitly cause the children to be visited, composing this behaviour requires an intermediary to redirect the -acceptAlgebra: message to use the correct visitor. HammerMemoizationVisitor uses the (currently private) HammerRedirectingVisitable for this purpose, so that recursive visits step through the HammerMemoizingVisitor rather than short circuiting directly to the targeted visitor.

This commit also takes the opportunity to remove the obsolete visitor protocol, the obsolete patterns, and their tests.