[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / ast.rb
blob51ee5b3d59a9fce967e9567e8a4f34d943c867c8
1 # for ast.c
3 # AbstractSyntaxTree provides methods to parse Ruby code into
4 # abstract syntax trees. The nodes in the tree
5 # are instances of RubyVM::AbstractSyntaxTree::Node.
7 # This module is MRI specific as it exposes implementation details
8 # of the MRI abstract syntax tree.
10 # This module is experimental and its API is not stable, therefore it might
11 # change without notice. As examples, the order of children nodes is not
12 # guaranteed, the number of children nodes might change, there is no way to
13 # access children nodes by name, etc.
15 # If you are looking for a stable API or an API working under multiple Ruby
16 # implementations, consider using the _parser_ gem or Ripper. If you would
17 # like to make RubyVM::AbstractSyntaxTree stable, please join the discussion
18 # at https://bugs.ruby-lang.org/issues/14844.
20 module RubyVM::AbstractSyntaxTree
22   #  call-seq:
23   #     RubyVM::AbstractSyntaxTree.parse(string, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node
24   #
25   #  Parses the given _string_ into an abstract syntax tree,
26   #  returning the root node of that tree.
27   #
28   #    RubyVM::AbstractSyntaxTree.parse("x = 1 + 2")
29   #    # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9>
30   #
31   #  If <tt>keep_script_lines: true</tt> option is provided, the text of the parsed
32   #  source is associated with nodes and is available via Node#script_lines.
33   #
34   #  If <tt>keep_tokens: true</tt> option is provided, Node#tokens are populated.
35   #
36   #  SyntaxError is raised if the given _string_ is invalid syntax. To overwrite this
37   #  behavior, <tt>error_tolerant: true</tt> can be provided. In this case, the parser
38   #  will produce a tree where expressions with syntax errors would be represented by
39   #  Node with <tt>type=:ERROR</tt>.
40   #
41   #     root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2")
42   #     # <internal:ast>:33:in `parse': syntax error, unexpected ';', expecting ')' (SyntaxError)
43   #     # x = 1; p(x; y=2
44   #     #           ^
45   #
46   #     root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2", error_tolerant: true)
47   #     # (SCOPE@1:0-1:15
48   #     #  tbl: [:x, :y]
49   #     #  args: nil
50   #     #  body: (BLOCK@1:0-1:15 (LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)) (ERROR@1:7-1:11) (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2))))
51   #     root.children.last.children
52   #     # [(LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)),
53   #     #  (ERROR@1:7-1:11),
54   #     #  (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2))]
55   #
56   #  Note that parsing continues even after the errored expression.
57   #
58   def self.parse string, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false
59     Primitive.ast_s_parse string, keep_script_lines, error_tolerant, keep_tokens
60   end
62   #  call-seq:
63   #     RubyVM::AbstractSyntaxTree.parse_file(pathname, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node
64   #
65   #   Reads the file from _pathname_, then parses it like ::parse,
66   #   returning the root node of the abstract syntax tree.
67   #
68   #   SyntaxError is raised if _pathname_'s contents are not
69   #   valid Ruby syntax.
70   #
71   #     RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb")
72   #     # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3>
73   #
74   #   See ::parse for explanation of keyword argument meaning and usage.
75   def self.parse_file pathname, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false
76     Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant, keep_tokens
77   end
79   #  call-seq:
80   #     RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false)   -> RubyVM::AbstractSyntaxTree::Node
81   #     RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node
82   #
83   #   Returns AST nodes of the given _proc_ or _method_.
84   #
85   #     RubyVM::AbstractSyntaxTree.of(proc {1 + 2})
86   #     # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:35-1:42>
87   #
88   #     def hello
89   #       puts "hello, world"
90   #     end
91   #
92   #     RubyVM::AbstractSyntaxTree.of(method(:hello))
93   #     # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3>
94   #
95   #   See ::parse for explanation of keyword argument meaning and usage.
96   def self.of body, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false
97     Primitive.ast_s_of body, keep_script_lines, error_tolerant, keep_tokens
98   end
100   #  call-seq:
101   #     RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(backtrace_location)   -> integer
102   #
103   #   Returns the node id for the given backtrace location.
104   #
105   #     begin
106   #       raise
107   #     rescue =>  e
108   #       loc = e.backtrace_locations.first
109   #       RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
110   #     end # => 0
111   def self.node_id_for_backtrace_location backtrace_location
112     Primitive.node_id_for_backtrace_location backtrace_location
113   end
115   # RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in
116   # RubyVM::AbstractSyntaxTree.
117   #
118   # This class is MRI specific.
119   #
120   class Node
122     #  call-seq:
123     #     node.type -> symbol
124     #
125     #  Returns the type of this node as a symbol.
126     #
127     #    root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2")
128     #    root.type # => :SCOPE
129     #    lasgn = root.children[2]
130     #    lasgn.type # => :LASGN
131     #    call = lasgn.children[1]
132     #    call.type # => :OPCALL
133     def type
134       Primitive.ast_node_type
135     end
137     #  call-seq:
138     #     node.first_lineno -> integer
139     #
140     #  The line number in the source code where this AST's text began.
141     def first_lineno
142       Primitive.ast_node_first_lineno
143     end
145     #  call-seq:
146     #     node.first_column -> integer
147     #
148     #  The column number in the source code where this AST's text began.
149     def first_column
150       Primitive.ast_node_first_column
151     end
153     #  call-seq:
154     #     node.last_lineno -> integer
155     #
156     #  The line number in the source code where this AST's text ended.
157     def last_lineno
158       Primitive.ast_node_last_lineno
159     end
161     #  call-seq:
162     #     node.last_column -> integer
163     #
164     #  The column number in the source code where this AST's text ended.
165     def last_column
166       Primitive.ast_node_last_column
167     end
169     #  call-seq:
170     #     node.tokens -> array
171     #
172     #  Returns tokens corresponding to the location of the node.
173     #  Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called.
174     #
175     #    root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
176     #    root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
177     #    root.tokens.map{_1[2]}.join # => "x = 1 + 2"
178     #
179     #  Token is an array of:
180     #
181     #  - id
182     #  - token type
183     #  - source code text
184     #  - location [ first_lineno, first_column, last_lineno, last_column ]
185     def tokens
186       return nil unless all_tokens
188       all_tokens.each_with_object([]) do |token, a|
189         loc = token.last
190         if ([first_lineno, first_column] <=> [loc[0], loc[1]]) <= 0 &&
191            ([last_lineno, last_column]   <=> [loc[2], loc[3]]) >= 0
192            a << token
193         end
194       end
195     end
197     #  call-seq:
198     #     node.all_tokens -> array
199     #
200     #  Returns all tokens for the input script regardless the receiver node.
201     #  Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called.
202     #
203     #    root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
204     #    root.all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
205     #    root.children[-1].all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
206     def all_tokens
207       Primitive.ast_node_all_tokens
208     end
210     #  call-seq:
211     #     node.children -> array
212     #
213     #  Returns AST nodes under this one.  Each kind of node
214     #  has different children, depending on what kind of node it is.
215     #
216     #  The returned array may contain other nodes or <code>nil</code>.
217     def children
218       Primitive.ast_node_children
219     end
221     #  call-seq:
222     #     node.inspect -> string
223     #
224     #  Returns debugging information about this node as a string.
225     def inspect
226       Primitive.ast_node_inspect
227     end
229     #  call-seq:
230     #     node.node_id -> integer
231     #
232     #  Returns an internal node_id number.
233     #  Note that this is an API for ruby internal use, debugging,
234     #  and research. Do not use this for any other purpose.
235     #  The compatibility is not guaranteed.
236     def node_id
237       Primitive.ast_node_node_id
238     end
240     #  call-seq:
241     #     node.script_lines -> array
242     #
243     #  Returns the original source code as an array of lines.
244     #
245     #  Note that this is an API for ruby internal use, debugging,
246     #  and research. Do not use this for any other purpose.
247     #  The compatibility is not guaranteed.
248     def script_lines
249       Primitive.ast_node_script_lines
250     end
252     #  call-seq:
253     #     node.source -> string
254     #
255     #  Returns the code fragment that corresponds to this AST.
256     #
257     #  Note that this is an API for ruby internal use, debugging,
258     #  and research. Do not use this for any other purpose.
259     #  The compatibility is not guaranteed.
260     #
261     #  Also note that this API may return an incomplete code fragment
262     #  that does not parse; for example, a here document following
263     #  an expression may be dropped.
264     def source
265       lines = script_lines
266       if lines
267         lines = lines[first_lineno - 1 .. last_lineno - 1]
268         lines[-1] = lines[-1].byteslice(0...last_column)
269         lines[0] = lines[0].byteslice(first_column..-1)
270         lines.join
271       else
272         nil
273       end
274     end
275   end