ast-2.3.0/0000755000175000017500000000000013031222122011615 5ustar miguelmiguelast-2.3.0/.gitignore0000644000175000017500000000010413031222122013600 0ustar miguelmiguel.bundle Gemfile.lock pkg/* .rbx/ *.sublime-* doc/ .yardoc/ coverage/ast-2.3.0/README.YARD.md0000644000175000017500000000074213031222122013635 0ustar miguelmiguel{AST} is a library for manipulating abstract syntax trees. It embraces immutability; each AST node is inherently frozen at creation, and updating a child node requires recreating that node and its every parent, recursively. This is a design choice. It does create some pressure on garbage collector, but completely eliminates all concurrency and aliasing problems. See also {AST::Node}, {AST::Processor::Mixin} and {AST::Sexp} for additional recommendations and design patterns. ast-2.3.0/Rakefile0000644000175000017500000000103513031222122013261 0ustar miguelmiguelrequire 'bundler/gem_tasks' require 'bundler/setup' task :default => :test desc "Run test suite" task :test do sh "bacon -Itest -a" end PAGES_REPO = 'git@github.com:whitequark/ast' desc "Build and deploy documentation to GitHub pages" task :pages do system "git clone #{PAGES_REPO} gh-temp/ -b gh-pages; rm gh-temp/* -rf; touch gh-temp/.nojekyll" or abort system "yardoc -o gh-temp/;" or abort system "cd gh-temp/; git add -A; git commit -m 'Updated pages.'; git push -f origin gh-pages" or abort FileUtils.rm_rf 'gh-temp' end ast-2.3.0/ast.gemspec0000644000175000017500000000216413031222122013754 0ustar miguelmiguelGem::Specification.new do |s| s.name = 'ast' s.version = '2.3.0' s.license = 'MIT' s.authors = ["whitequark"] s.email = ["whitequark@whitequark.org"] s.homepage = "https://whitequark.github.io/ast/" s.summary = %q{A library for working with Abstract Syntax Trees.} s.description = s.summary s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_development_dependency 'rake', '~> 10.0' s.add_development_dependency 'bacon', '~> 1.2' s.add_development_dependency 'bacon-colored_output' s.add_development_dependency 'simplecov' s.add_development_dependency 'coveralls' s.add_development_dependency 'json_pure' # for coveralls on 1.9.2 s.add_development_dependency 'mime-types', '~> 1.25' # for coveralls on 1.8.7 s.add_development_dependency 'rest-client', '~> 1.6.7' # 1.8.7 s.add_development_dependency 'yard' s.add_development_dependency 'kramdown' end ast-2.3.0/test/0000755000175000017500000000000013031222122012574 5ustar miguelmiguelast-2.3.0/test/helper.rb0000644000175000017500000000050213031222122014375 0ustar miguelmiguelrequire 'bacon' require 'bacon/colored_output' require 'simplecov' require 'coveralls' SimpleCov.start do self.formatter = SimpleCov::Formatter::MultiFormatter[ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ] # Exclude the testsuite itself. add_filter "/test/" end require 'ast' ast-2.3.0/test/test_ast.rb0000644000175000017500000001544413031222122014757 0ustar miguelmiguelrequire 'helper' describe AST::Node do extend AST::Sexp class MetaNode < AST::Node attr_reader :meta end before do @node = AST::Node.new(:node, [ 0, 1 ]) @metanode = MetaNode.new(:node, [ 0, 1 ], :meta => 'value') end it 'should have accessors for type and children' do @node.type.should.equal :node @node.children.should.equal [0, 1] end it 'should set metadata' do @metanode.meta.should.equal 'value' end it 'should be frozen' do @node.frozen?.should.be.true @node.children.frozen?.should.be.true end it 'should return self when duping' do @node.dup.should.equal? @node end it 'should return self when cloning' do @node.clone.should.equal? @node end it 'should return an updated node, but only if needed' do @node.updated().should.be.identical_to @node @node.updated(:node).should.be.identical_to @node @node.updated(nil, [0, 1]).should.be.identical_to @node updated = @node.updated(:other_node) updated.should.not.be.identical_to @node updated.type.should.equal :other_node updated.children.should.equal @node.children updated.frozen?.should.be.true updated = @node.updated(nil, [1, 1]) updated.should.not.be.identical_to @node updated.type.should.equal @node.type updated.children.should.equal [1, 1] updated = @metanode.updated(nil, nil, :meta => 'other_value') updated.meta.should.equal 'other_value' end it 'should format to_sexp correctly' do AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_sexp.should.equal '(a :sym [1, 2])' AST::Node.new(:a, [ :sym, @node ]).to_sexp.should.equal "(a :sym\n (node 0 1))" AST::Node.new(:a, [ :sym, AST::Node.new(:b, [ @node, @node ]) ]).to_sexp.should.equal "(a :sym\n (b\n (node 0 1)\n (node 0 1)))" end it 'should format to_s correctly' do AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_s.should.equal '(a :sym [1, 2])' AST::Node.new(:a, [ :sym, @node ]).to_s.should.equal "(a :sym\n (node 0 1))" AST::Node.new(:a, [ :sym, AST::Node.new(:b, [ @node, @node ]) ]).to_s.should.equal "(a :sym\n (b\n (node 0 1)\n (node 0 1)))" end it 'should format inspect correctly' do AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).inspect.should.equal "s(:a, :sym, [1, 2])" AST::Node.new(:a, [ :sym, AST::Node.new(:b, [ @node, @node ]) ]).inspect.should.equal "s(:a, :sym,\n s(:b,\n s(:node, 0, 1),\n s(:node, 0, 1)))" end it 'should recreate inspect output' do simple_node = AST::Node.new(:a, [ :sym, [ 1, 2 ] ]) eval(simple_node.inspect).should.equal simple_node complex_node = s(:a , :sym, s(:b, s(:node, 0, 1), s(:node, 0, 1))) eval(complex_node.inspect).should.equal complex_node end it 'should return self in to_ast' do @node.to_ast.should.be.identical_to @node end it 'should only use type and children to compute #hash' do @node.hash.should.equal([@node.type, @node.children, @node.class].hash) end it 'should only use type and children in #eql? comparisons' do # Not identical but equivalent @node.eql?(AST::Node.new(:node, [0, 1])).should.be.true # Not identical and not equivalent @node.eql?(AST::Node.new(:other, [0, 1])).should.be.false # Not identical and not equivalent because of differend class @node.eql?(@metanode).should.be.false end it 'should only use type and children in #== comparisons' do @node.should.equal @node @node.should.equal @metanode @node.should.not.equal :foo mock_node = Object.new.tap do |obj| def obj.to_ast self end def obj.type :node end def obj.children [ 0, 1 ] end end @node.should.equal mock_node end it 'should allow to decompose nodes with a, b = *node' do node = s(:gasgn, :$foo, s(:integer, 1)) var_name, value = *node var_name.should.equal :$foo value.should.equal s(:integer, 1) end it 'should concatenate with arrays' do node = s(:gasgn, :$foo) (node + [s(:integer, 1)]). should.equal s(:gasgn, :$foo, s(:integer, 1)) end it 'should append elements' do node = s(:array) (node << s(:integer, 1) << s(:string, "foo")). should.equal s(:array, s(:integer, 1), s(:string, "foo")) end begin eval <<-CODE it 'should not trigger a rubinius bug' do bar = [ s(:bar, 1) ] baz = s(:baz, 2) s(:foo, *bar, baz).should.equal s(:foo, s(:bar, 1), s(:baz, 2)) end CODE rescue SyntaxError # Running on 1.8, ignore. end end describe AST::Processor do extend AST::Sexp def have_sexp(text) text = text.lines.map { |line| line.sub /^ +\|(.+)/, '\1' }.join.rstrip lambda { |ast| ast.to_sexp == text } end class MockProcessor < AST::Processor attr_reader :counts def initialize @counts = Hash.new(0) end def on_root(node) count_node(node) node.updated(nil, process_all(node.children)) end alias on_body on_root def on_def(node) count_node(node) name, arglist, body = node.children node.updated(:def, [ name, process(arglist), process(body) ]) end def handler_missing(node) count_node(node) end def count_node(node) @counts[node.type] += 1; nil end end before do @ast = AST::Node.new(:root, [ AST::Node.new(:def, [ :func, AST::Node.new(:arglist, [ :foo, :bar ]), AST::Node.new(:body, [ AST::Node.new(:invoke, [ :puts, "Hello world" ]) ]) ]), AST::Node.new(:invoke, [ :func ]) ]) @processor = MockProcessor.new end it 'should visit every node' do @processor.process(@ast).should.equal @ast @processor.counts.should.equal({ :root => 1, :def => 1, :arglist => 1, :body => 1, :invoke => 2, }) end it 'should be able to replace inner nodes' do def @processor.on_arglist(node) node.updated(:new_fancy_arglist) end @processor.process(@ast).should have_sexp(<<-SEXP) |(root | (def :func | (new-fancy-arglist :foo :bar) | (body | (invoke :puts "Hello world"))) | (invoke :func)) SEXP end it 'should build sexps' do s(:add, s(:integer, 1), s(:multiply, s(:integer, 2), s(:integer, 3))).should have_sexp(<<-SEXP) |(add | (integer 1) | (multiply | (integer 2) | (integer 3))) SEXP end it 'should return nil if passed nil' do @processor.process(nil).should == nil end it 'should refuse to process non-nodes' do lambda { @processor.process([]) }.should.raise NoMethodError, %r|to_ast| end it 'should allow to visit nodes with process_all(node)' do @processor.process_all s(:foo, s(:bar), s(:integer, 1)) @processor.counts.should.equal({ :bar => 1, :integer => 1, }) end end ast-2.3.0/.travis.yml0000644000175000017500000000023713031222122013730 0ustar miguelmiguellanguage: ruby rvm: - 1.8.7 - 1.9.2 - 1.9.3 - 2.0 - 2.1 - 2.2 - jruby-18mode - jruby-19mode - rbx-2 before_install: - gem install bundler ast-2.3.0/LICENSE.MIT0000644000175000017500000000210113031222122013244 0ustar miguelmiguelCopyright (c) 2011-2013 Peter Zotov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ast-2.3.0/README.md0000644000175000017500000000152113031222122013073 0ustar miguelmiguel# AST [![Build Status](https://travis-ci.org/whitequark/ast.svg?branch=master)](https://travis-ci.org/whitequark/ast) [![Code Climate](https://codeclimate.com/github/whitequark/ast.svg)](https://codeclimate.com/github/whitequark/ast) [![Coverage Status](https://coveralls.io/repos/whitequark/ast/badge.svg?branch=master)](https://coveralls.io/r/whitequark/ast) AST is a small library for working with immutable abstract syntax trees. ## Installation $ gem install ast ## Usage See the documentation at [GitHub](http://whitequark.github.com/ast/frames.html) or [rdoc.info](http://rdoc.info/gems/ast). ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ast-2.3.0/CHANGELOG.md0000644000175000017500000000025013031222122013423 0ustar miguelmiguelChangelog ========= v1.1.0 (2013-06-17) ------------------- API changes: * AST::Processor#process will return nil if passed nil, instead of raising NoMethodError. ast-2.3.0/Gemfile0000644000175000017500000000013213031222122013104 0ustar miguelmiguelsource "http://rubygems.org" # Specify your gem's dependencies in furnace.gemspec gemspecast-2.3.0/lib/0000755000175000017500000000000013031222122012363 5ustar miguelmiguelast-2.3.0/lib/ast.rb0000644000175000017500000000111213031222122013472 0ustar miguelmiguel# {AST} is a library for manipulating abstract syntax trees. # # It embraces immutability; each AST node is inherently frozen at # creation, and updating a child node requires recreating that node # and its every parent, recursively. # This is a design choice. It does create some pressure on # garbage collector, but completely eliminates all concurrency # and aliasing problems. # # See also {AST::Node}, {AST::Processor::Mixin} and {AST::Sexp} for # additional recommendations and design patterns. # module AST require 'ast/node' require 'ast/processor' require 'ast/sexp' end ast-2.3.0/lib/ast/0000755000175000017500000000000013031222122013152 5ustar miguelmiguelast-2.3.0/lib/ast/sexp.rb0000644000175000017500000000166313031222122014464 0ustar miguelmiguelmodule AST # This simple module is very useful in the cases where one needs # to define deeply nested ASTs from Ruby code, for example, in # tests. It should be used like this: # # describe YourLanguage::AST do # include Sexp # # it "should correctly parse expressions" do # YourLanguage.parse("1 + 2 * 3").should == # s(:add, # s(:integer, 1), # s(:multiply, # s(:integer, 2), # s(:integer, 3))) # end # end # # This way the amount of boilerplate code is greatly reduced. module Sexp # Creates a {Node} with type `type` and children `children`. # Note that the resulting node is of the type AST::Node and not a # subclass. # This would not pose a problem with comparisons, as {Node#==} # ignores metadata. def s(type, *children) Node.new(type, children) end end end ast-2.3.0/lib/ast/processor/0000755000175000017500000000000013031222122015171 5ustar miguelmiguelast-2.3.0/lib/ast/processor/mixin.rb0000644000175000017500000002404713031222122016651 0ustar miguelmiguelmodule AST class Processor # The processor module is a module which helps transforming one # AST into another. In a nutshell, the {#process} method accepts # a {Node} and dispatches it to a handler corresponding to its # type, and returns a (possibly) updated variant of the node. # # The processor module has a set of associated design patterns. # They are best explained with a concrete example. Let's define a # simple arithmetic language and an AST format for it: # # Terminals (AST nodes which do not have other AST nodes inside): # # * `(integer )`, # # Nonterminals (AST nodes with other nodes as children): # # * `(add )`, # * `(multiply )`, # * `(divide )`, # * `(negate )`, # * `(store )`: stores value of `` # into a variable named ``, # * `(load )`: loads value of a variable named # ``, # * `(each ...): computes each of the ``s and # prints the result. # # All AST nodes have the same Ruby class, and therefore they don't # know how to traverse themselves. (A solution which dynamically # checks the type of children is possible, but is slow and # error-prone.) So, a class including the module which knows how # to traverse the entire tree should be defined. Such classes # have a handler for each nonterminal node which recursively # processes children nodes: # # require 'ast' # # class ArithmeticsProcessor # include AST::Processor::Mixin # # This method traverses any binary operators such as (add) # # or (multiply). # def process_binary_op(node) # # Children aren't decomposed automatically; it is # # suggested to use Ruby multiple assignment expansion, # # as it is very convenient here. # left_expr, right_expr = *node # # # AST::Node#updated won't change node type if nil is # # passed as a first argument, which allows to reuse the # # same handler for multiple node types using `alias' # # (below). # node.updated(nil, [ # process(left_expr), # process(right_expr) # ]) # end # alias_method :on_add, :process_binary_op # alias_method :on_multiply, :process_binary_op # alias_method :on_divide, :process_binary_op # # def on_negate(node) # # It is also possible to use #process_all for more # # compact code if every child is a Node. # node.updated(nil, process_all(node)) # end # # def on_store(node) # expr, variable_name = *node # # # Note that variable_name is not a Node and thus isn't # # passed to #process. # node.updated(nil, [ # process(expr), # variable_name # ]) # end # # # (load) is effectively a terminal node, and so it does # # not need an explicit handler, as the following is the # # default behavior. Essentially, for any nodes that don't # # have a defined handler, the node remains unchanged. # def on_load(node) # nil # end # # def on_each(node) # node.updated(nil, process_all(node)) # end # end # # Let's test our ArithmeticsProcessor: # # include AST::Sexp # expr = s(:add, s(:integer, 2), s(:integer, 2)) # # p ArithmeticsProcessor.new.process(expr) == expr # => true # # As expected, it does not change anything at all. This isn't # actually very useful, so let's now define a Calculator, which # will compute the expression values: # # # This Processor folds nonterminal nodes and returns an # # (integer) terminal node. # class ArithmeticsCalculator < ArithmeticsProcessor # def compute_op(node) # # First, node children are processed and then unpacked # # to local variables. # nodes = process_all(node) # # if nodes.all? { |node| node.type == :integer } # # If each of those nodes represents a literal, we can # # fold this node! # values = nodes.map { |node| node.children.first } # AST::Node.new(:integer, [ # yield(values) # ]) # else # # Otherwise, we can just leave the current node in the # # tree and only update it with processed children # # nodes, which can be partially folded. # node.updated(nil, nodes) # end # end # # def on_add(node) # compute_op(node) { |left, right| left + right } # end # # def on_multiply(node) # compute_op(node) { |left, right| left * right } # end # end # # Let's check: # # p ArithmeticsCalculator.new.process(expr) # => (integer 4) # # Excellent, the calculator works! Now, a careful reader could # notice that the ArithmeticsCalculator does not know how to # divide numbers. What if we pass an expression with division to # it? # # expr_with_division = \ # s(:add, # s(:integer, 1), # s(:divide, # s(:add, s(:integer, 8), s(:integer, 4)), # s(:integer, 3))) # 1 + (8 + 4) / 3 # # folded_expr_with_division = ArithmeticsCalculator.new.process(expr_with_division) # p folded_expr_with_division # # => (add # # (integer 1) # # (divide # # (integer 12) # # (integer 3))) # # As you can see, the expression was folded _partially_: the inner # `(add)` node which could be computed was folded to # `(integer 12)`, the `(divide)` node is left as-is because there # is no computing handler for it, and the root `(add)` node was # also left as it is because some of its children were not # literals. # # Note that this partial folding is only possible because the # _data_ format, i.e. the format in which the computed values of # the nodes are represented, is the same as the AST itself. # # Let's extend our ArithmeticsCalculator class further. # # class ArithmeticsCalculator # def on_divide(node) # compute_op(node) { |left, right| left / right } # end # # def on_negate(node) # # Note how #compute_op works regardless of the operator # # arity. # compute_op(node) { |value| -value } # end # end # # Now, let's apply our renewed ArithmeticsCalculator to a partial # result of previous evaluation: # # p ArithmeticsCalculator.new.process(expr_with_division) # => (integer 5) # # Five! Excellent. This is also pretty much how CRuby 1.8 executed # its programs. # # Now, let's do some automated bug searching. Division by zero is # an error, right? So if we could detect that someone has divided # by zero before the program is even run, that could save some # debugging time. # # class DivisionByZeroVerifier < ArithmeticsProcessor # class VerificationFailure < Exception; end # # def on_divide(node) # # You need to process the children to handle nested divisions # # such as: # # (divide # # (integer 1) # # (divide (integer 1) (integer 0)) # left, right = process_all(node) # # if right.type == :integer && # right.children.first == 0 # raise VerificationFailure, "Ouch! This code divides by zero." # end # end # # def divides_by_zero?(ast) # process(ast) # false # rescue VerificationFailure # true # end # end # # nice_expr = \ # s(:divide, # s(:add, s(:integer, 10), s(:integer, 2)), # s(:integer, 4)) # # p DivisionByZeroVerifier.new.divides_by_zero?(nice_expr) # # => false. Good. # # bad_expr = \ # s(:add, s(:integer, 10), # s(:divide, s(:integer, 1), s(:integer, 0))) # # p DivisionByZeroVerifier.new.divides_by_zero?(bad_expr) # # => true. WHOOPS. DO NOT RUN THIS. # # Of course, this won't detect more complex cases... unless you # use some partial evaluation before! The possibilites are # endless. Have fun. module Mixin # Dispatches `node`. If a node has type `:foo`, then a handler # named `on_foo` is invoked with one argument, the `node`; if # there isn't such a handler, {#handler_missing} is invoked # with the same argument. # # If the handler returns `nil`, `node` is returned; otherwise, # the return value of the handler is passed along. # # @param [AST::Node, nil] node # @return [AST::Node, nil] def process(node) return if node.nil? node = node.to_ast # Invoke a specific handler on_handler = :"on_#{node.type}" if respond_to? on_handler new_node = send on_handler, node else new_node = handler_missing(node) end node = new_node if new_node node end # {#process}es each node from `nodes` and returns an array of # results. # # @param [Array] nodes # @return [Array] def process_all(nodes) nodes.to_a.map do |node| process node end end # Default handler. Does nothing. # # @param [AST::Node] node # @return [AST::Node, nil] def handler_missing(node) end end end end ast-2.3.0/lib/ast/processor.rb0000644000175000017500000000061213031222122015515 0ustar miguelmiguelmodule AST # This class includes {AST::Processor::Mixin}; however, it is # deprecated, since the module defines all of the behaviors that # the processor includes. Any new libraries should use # {AST::Processor::Mixin} instead of subclassing this. # # @deprecated Use {AST::Processor::Mixin} instead. class Processor require 'ast/processor/mixin' include Mixin end end ast-2.3.0/lib/ast/node.rb0000644000175000017500000001722513031222122014433 0ustar miguelmiguelmodule AST # Node is an immutable class, instances of which represent abstract # syntax tree nodes. It combines semantic information (i.e. anything # that affects the algorithmic properties of a program) with # meta-information (line numbers or compiler intermediates). # # Notes on inheritance # ==================== # # The distinction between semantics and metadata is important. Complete # semantic information should be contained within just the {#type} and # {#children} of a Node instance; in other words, if an AST was to be # stripped of all meta-information, it should remain a valid AST which # could be successfully processed to yield a result with the same # algorithmic properties. # # Thus, Node should never be inherited in order to define methods which # affect or return semantic information, such as getters for `class_name`, # `superclass` and `body` in the case of a hypothetical `ClassNode`. The # correct solution is to use a generic Node with a {#type} of `:class` # and three children. See also {Processor} for tips on working with such # ASTs. # # On the other hand, Node can and should be inherited to define # application-specific metadata (see also {#initialize}) or customize the # printing format. It is expected that an application would have one or two # such classes and use them across the entire codebase. # # The rationale for this pattern is extensibility and maintainability. # Unlike static ones, dynamic languages do not require the presence of a # predefined, rigid structure, nor does it improve dispatch efficiency, # and while such a structure can certainly be defined, it does not add # any value but incurs a maintaining cost. # For example, extending the AST even with a transformation-local # temporary node type requires making globally visible changes to # the codebase. # class Node # Returns the type of this node. # @return [Symbol] attr_reader :type # Returns the children of this node. # The returned value is frozen. # @return [Array] attr_reader :children # Returns the precomputed hash value for this node # @return [Fixnum] attr_reader :hash # Constructs a new instance of Node. # # The arguments `type` and `children` are converted with `to_sym` and # `to_a` respectively. Additionally, the result of converting `children` # is frozen. While mutating the arguments is generally considered harmful, # the most common case is to pass an array literal to the constructor. If # your code does not expect the argument to be frozen, use `#dup`. # # The `properties` hash is passed to {#assign_properties}. def initialize(type, children=[], properties={}) @type, @children = type.to_sym, children.to_a.freeze assign_properties(properties) @hash = [@type, @children, self.class].hash freeze end # Test if other object is equal to # @param [Object] other # @return [Boolean] def eql?(other) self.class.eql?(other.class) && @type.eql?(other.type) && @children.eql?(other.children) end # By default, each entry in the `properties` hash is assigned to # an instance variable in this instance of Node. A subclass should define # attribute readers for such variables. The values passed in the hash # are not frozen or whitelisted; such behavior can also be implemented # by subclassing Node and overriding this method. # # @return [nil] def assign_properties(properties) properties.each do |name, value| instance_variable_set :"@#{name}", value end nil end protected :assign_properties alias :original_dup :dup private :original_dup # Nodes are already frozen, so there is no harm in returning the # current node as opposed to initializing from scratch and freezing # another one. # # @return self def dup self end alias :clone :dup # Returns a new instance of Node where non-nil arguments replace the # corresponding fields of `self`. # # For example, `Node.new(:foo, [ 1, 2 ]).updated(:bar)` would yield # `(bar 1 2)`, and `Node.new(:foo, [ 1, 2 ]).updated(nil, [])` would # yield `(foo)`. # # If the resulting node would be identical to `self`, does nothing. # # @param [Symbol, nil] type # @param [Array, nil] children # @param [Hash, nil] properties # @return [AST::Node] def updated(type=nil, children=nil, properties=nil) new_type = type || @type new_children = children || @children new_properties = properties || {} if @type == new_type && @children == new_children && properties.nil? self else original_dup.send :initialize, new_type, new_children, new_properties end end # Compares `self` to `other`, possibly converting with `to_ast`. Only # `type` and `children` are compared; metadata is deliberately ignored. # # @return [Boolean] def ==(other) if equal?(other) true elsif other.respond_to? :to_ast other = other.to_ast other.type == self.type && other.children == self.children else false end end # Concatenates `array` with `children` and returns the resulting node. # # @return [AST::Node] def concat(array) updated(nil, @children + array.to_a) end alias + concat # Appends `element` to `children` and returns the resulting node. # # @return [AST::Node] def append(element) updated(nil, @children + [element]) end alias << append # Returns {#children}. This is very useful in order to decompose nodes # concisely. For example: # # node = s(:gasgn, :$foo, s(:integer, 1)) # s # var_name, value = *node # p var_name # => :$foo # p value # => (integer 1) # # @return [Array] def to_a children end # Converts `self` to a pretty-printed s-expression. # # @param [Integer] indent Base indentation level. # @return [String] def to_sexp(indent=0) indented = " " * indent sexp = "#{indented}(#{fancy_type}" first_node_child = children.index do |child| child.is_a?(Node) || child.is_a?(Array) end || children.count children.each_with_index do |child, idx| if child.is_a?(Node) && idx >= first_node_child sexp += "\n#{child.to_sexp(indent + 1)}" else sexp += " #{child.inspect}" end end sexp += ")" sexp end alias to_s to_sexp # Converts `self` to a s-expression ruby string. # The code return will recreate the node, using the sexp module s() # # @param [Integer] indent Base indentation level. # @return [String] def inspect(indent=0) indented = " " * indent sexp = "#{indented}s(:#{@type}" first_node_child = children.index do |child| child.is_a?(Node) || child.is_a?(Array) end || children.count children.each_with_index do |child, idx| if child.is_a?(Node) && idx >= first_node_child sexp += ",\n#{child.inspect(indent + 1)}" else sexp += ", #{child.inspect}" end end sexp += ")" sexp end # @return [AST::Node] self def to_ast self end protected # Returns `@type` with all underscores replaced by dashes. This allows # to write symbol literals without quotes in Ruby sources and yet have # nicely looking s-expressions. # # @return [String] def fancy_type @type.to_s.gsub('_', '-') end end end ast-2.3.0/.yardopts0000644000175000017500000000005213031222122013460 0ustar miguelmiguel-r README.YARD.md -m markdown --protected