Module: TireSwing::NodeDefinition::ModuleMethods
- Defined in:
- lib/tire_swing/node_definition.rb
Instance Method Summary collapse
-
#array_of(kind, recursive = false, &blk) ⇒ Object
Returns a lambda to select only child nodes of the given kind.
-
#create_node(name) ⇒ Object
Returns a NodeCreator to act as a stand-in for a normal node class definition.
-
#extract(node_name) ⇒ Object
Returns a lambda which takes a node and calls node_name on each in turn if possible, returning the result.
-
#node(name, *attribute_names, &blk) ⇒ Object
Define a node.
Instance Method Details
#array_of(kind, recursive = false, &blk) ⇒ Object
Returns a lambda to select only child nodes of the given kind. This is best used for rules that can return arrays of different kind of nodes, some of which you want to ignore, e.g.:
rule assignments
assignment* <node(:assignments)>
end
node :assignments, :assignments => array_of(:assignment)
When parsed, this will give you a recursive set of nodes:
[assignment, [assignment, [assignment]]]
If you specify that the array is recursive, it will retrieve all nested child nodes, no matter how deep, which provide the kind of node you want.
If you provide a block, the filtered result will be yielded to the block and returned as the final result.
136 137 138 139 140 141 |
# File 'lib/tire_swing/node_definition.rb', line 136 def array_of(kind, recursive = false, &blk) lambda do |node| result = NodeFilters.filter(node, kind, recursive) blk ? result.map(&blk) : result end end |
#create_node(name) ⇒ Object
Returns a NodeCreator to act as a stand-in for a normal node class definition. According to the treetop metagrammar, node class definitions can have more than just a class in them. For example:
rule variable
[a-z]+ <Variable>
end
Instead, use this method to use AST node auto-build functionality. Given
node :variable, :value => :text_value
You can define the grammar as
rule variable
[a-z]+ <node(:variable)>
end
Also note that you can specify alternate namespaces:
module AST
node :variable
end
<AST.create_node(:variable)>
When you’re using the parser extension:
TireSwing.parses_grammar(Grammar, AST)
you can use
<node(...)>
Which is an instance method wrapper for the create_node class method.
41 42 43 |
# File 'lib/tire_swing/node_definition.rb', line 41 def create_node(name) TireSwing::NodeCreator.new(name, const_get(name.to_s.camelize)) end |
#extract(node_name) ⇒ Object
Returns a lambda which takes a node and calls node_name on each in turn if possible, returning the result. This is useful for nested rules, such as:
rule assignments
(assignment [\n])+
end
This is a subtle difference from array_of. The rule given here provides a syntax node with multiple elements, each one corresponding to the grouping, not to an individual child node – that is, it’s a list of
- [assignment node, newline node], [assignment, newline], …
-
instead of being a flattened list of
[assignment, newline, assignment, newline, …].
To extract these:
node :assignments, :assignments => extract(:assignment)
Which will extract just the assignments out of those “nested children”, and then do what you expect from there.
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/tire_swing/node_definition.rb', line 161 def extract(node_name) lambda do |node| results = [] results << node.send(node_name) if node.respond_to?(node_name) results.push *node.elements.select { |elem| elem.respond_to?(node_name) }.map { |elem| elem.send(node_name) } results end end |
#node(name, *attribute_names, &blk) ⇒ Object
Define a node.
This creates a new class (a subclass of TireSwing::Node) with the given attribute names.
Attribute names can be lists of symbols (simple attributes) and/or a hash of name-value pairs (mapped attributes). The new class will have attributes matching the names and hash keys given. More on the hash values in a minute.
node :foo
Creates a Foo class in the local scope.
node :calculation, :left, :right, :operator => lambda { |node| node.elements[1] }
Creates a Calculation class with left, right, and operator attributes.
There are two ways to instantiate a class generated by this method.
The first method is to provide a hash with name/value pairs for the attributes:
c = Calculation.new(:left => "lhs", :right => "rhs", :operator => "=")
c.left # => "lhs"
If an attribute isn’t initialized in this way, you will get an exception if you try to access it.
This simple way of instantiating a node can be used in a grammar to build an AST with these nodes manually.
The second method takes a treetop syntax node and auto-builds the node using the syntax node as a basis. The auto-build functionality uses the attribute names and mapped attributes in the following way:
-
Simple attributes: calls the method by that name on the syntax node, e.g.
node :assignment, :lhs, :rhs
-
Mapped attributes:
-
symbol/string - call that method on either the node, if it responds, or on its text value (e.g. #to_i)
node :number, :value => :to_i # calls to_i on the number node’s text value
-
lambda - yields the parsed node to the lambda
Whatever the value (or array of values) is returned by the mapped call will then be built. Any item returned that responds to the build method will have it called. If there’s any bare syntax nodes left over, they are converted into their text value, and anything else will be returned as-is (numbers, strings, etc.)
Note that you can use the array_of and extract helpers defined below to define lambdas for doing more advanced filtering on the node and it’s children.
-
Simple example:
rule assignment
variable:lhs "=" variable:rhs <node(:assignment)>
end
rule variable
[a-z]+
end
node :assignment, :lhs, :rhs
Assignment.new(syntax_node) will return an instance with an lhs and rhs set from that node.
If a block is given, the block is evaluated in the context of the new class (for method definitions, etc.) e.g.
node :foo do
def name; "hello!"; end
end
Foo.new.name #=> "hello!"
112 113 114 115 116 |
# File 'lib/tire_swing/node_definition.rb', line 112 def node(name, *attribute_names, &blk) klass = TireSwing::Node.create *attribute_names const_set name.to_s.camelize, klass klass.class_eval &blk if block_given? end |