module TireSwing::NodeDefinition module ModuleMethods # 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]+ # 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]+ # end # def create_node(name) TireSwing::NodeCreator.new(const_get(name.to_s.camelize)) end # 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 # * Mapped attributes: if the attribute value is a symbol or a string, it calls that method on the syntax node. # If the attribute value is a lambda, it yields the syntax node to the lambda. # # If the value (or array of values) returned by one of these methods is another syntax node, the auto-build code # will call the build method on each syntax node or array item (assuming each syntax node has a build method, # usually auto-defined using create_node). Any value not responding to the build method is left alone. # # Simple example: # # rule assignment # variable:lhs "=" variable:rhs # 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!" # 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 end # Include this module to get access to the node and create_node methods. # A good place to include this is in a separate AST module, e.g. # # module AST # include NodeDefinition # node :foo # end # # And then in your grammar: # # rule foo # "foo" # end # def self.included(base) base.extend ModuleMethods end end