[ruby/strscan] jruby: Check if len++ walked off the end
[ruby.git] / ast.rb
blob6380621780a0bf2065c51a6aecfad64bf4ca73a3
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 _prism_ gem, which is the official
17 # Ruby API to parse Ruby code.
19 module RubyVM::AbstractSyntaxTree
21   #  call-seq:
22   #     RubyVM::AbstractSyntaxTree.parse(string, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node
23   #
24   #  Parses the given _string_ into an abstract syntax tree,
25   #  returning the root node of that tree.
26   #
27   #    RubyVM::AbstractSyntaxTree.parse("x = 1 + 2")
28   #    # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9>
29   #
30   #  If <tt>keep_script_lines: true</tt> option is provided, the text of the parsed
31   #  source is associated with nodes and is available via Node#script_lines.
32   #
33   #  If <tt>keep_tokens: true</tt> option is provided, Node#tokens are populated.
34   #
35   #  SyntaxError is raised if the given _string_ is invalid syntax. To overwrite this
36   #  behavior, <tt>error_tolerant: true</tt> can be provided. In this case, the parser
37   #  will produce a tree where expressions with syntax errors would be represented by
38   #  Node with <tt>type=:ERROR</tt>.
39   #
40   #     root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2")
41   #     # <internal:ast>:33:in `parse': syntax error, unexpected ';', expecting ')' (SyntaxError)
42   #     # x = 1; p(x; y=2
43   #     #           ^
44   #
45   #     root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2", error_tolerant: true)
46   #     # (SCOPE@1:0-1:15
47   #     #  tbl: [:x, :y]
48   #     #  args: nil
49   #     #  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))))
50   #     root.children.last.children
51   #     # [(LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)),
52   #     #  (ERROR@1:7-1:11),
53   #     #  (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2))]
54   #
55   #  Note that parsing continues even after the errored expression.
56   #
57   def self.parse string, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false
58     Primitive.ast_s_parse string, keep_script_lines, error_tolerant, keep_tokens
59   end
61   #  call-seq:
62   #     RubyVM::AbstractSyntaxTree.parse_file(pathname, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node
63   #
64   #   Reads the file from _pathname_, then parses it like ::parse,
65   #   returning the root node of the abstract syntax tree.
66   #
67   #   SyntaxError is raised if _pathname_'s contents are not
68   #   valid Ruby syntax.
69   #
70   #     RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb")
71   #     # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3>
72   #
73   #   See ::parse for explanation of keyword argument meaning and usage.
74   def self.parse_file pathname, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false
75     Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant, keep_tokens
76   end
78   #  call-seq:
79   #     RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false)   -> RubyVM::AbstractSyntaxTree::Node
80   #     RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node
81   #
82   #   Returns AST nodes of the given _proc_ or _method_.
83   #
84   #     RubyVM::AbstractSyntaxTree.of(proc {1 + 2})
85   #     # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:35-1:42>
86   #
87   #     def hello
88   #       puts "hello, world"
89   #     end
90   #
91   #     RubyVM::AbstractSyntaxTree.of(method(:hello))
92   #     # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3>
93   #
94   #   See ::parse for explanation of keyword argument meaning and usage.
95   def self.of body, keep_script_lines: RubyVM.keep_script_lines, error_tolerant: false, keep_tokens: false
96     Primitive.ast_s_of body, keep_script_lines, error_tolerant, keep_tokens
97   end
99   #  call-seq:
100   #     RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(backtrace_location)   -> integer
101   #
102   #   Returns the node id for the given backtrace location.
103   #
104   #     begin
105   #       raise
106   #     rescue =>  e
107   #       loc = e.backtrace_locations.first
108   #       RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
109   #     end # => 0
110   def self.node_id_for_backtrace_location backtrace_location
111     Primitive.node_id_for_backtrace_location backtrace_location
112   end
114   # RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in
115   # RubyVM::AbstractSyntaxTree.
116   #
117   # This class is MRI specific.
118   #
119   class Node
121     #  call-seq:
122     #     node.type -> symbol
123     #
124     #  Returns the type of this node as a symbol.
125     #
126     #    root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2")
127     #    root.type # => :SCOPE
128     #    lasgn = root.children[2]
129     #    lasgn.type # => :LASGN
130     #    call = lasgn.children[1]
131     #    call.type # => :OPCALL
132     def type
133       Primitive.ast_node_type
134     end
136     #  call-seq:
137     #     node.first_lineno -> integer
138     #
139     #  The line number in the source code where this AST's text began.
140     def first_lineno
141       Primitive.ast_node_first_lineno
142     end
144     #  call-seq:
145     #     node.first_column -> integer
146     #
147     #  The column number in the source code where this AST's text began.
148     def first_column
149       Primitive.ast_node_first_column
150     end
152     #  call-seq:
153     #     node.last_lineno -> integer
154     #
155     #  The line number in the source code where this AST's text ended.
156     def last_lineno
157       Primitive.ast_node_last_lineno
158     end
160     #  call-seq:
161     #     node.last_column -> integer
162     #
163     #  The column number in the source code where this AST's text ended.
164     def last_column
165       Primitive.ast_node_last_column
166     end
168     #  call-seq:
169     #     node.tokens -> array
170     #
171     #  Returns tokens corresponding to the location of the node.
172     #  Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called.
173     #
174     #    root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
175     #    root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
176     #    root.tokens.map{_1[2]}.join # => "x = 1 + 2"
177     #
178     #  Token is an array of:
179     #
180     #  - id
181     #  - token type
182     #  - source code text
183     #  - location [ first_lineno, first_column, last_lineno, last_column ]
184     def tokens
185       return nil unless all_tokens
187       all_tokens.each_with_object([]) do |token, a|
188         loc = token.last
189         if ([first_lineno, first_column] <=> [loc[0], loc[1]]) <= 0 &&
190            ([last_lineno, last_column]   <=> [loc[2], loc[3]]) >= 0
191            a << token
192         end
193       end
194     end
196     #  call-seq:
197     #     node.all_tokens -> array
198     #
199     #  Returns all tokens for the input script regardless the receiver node.
200     #  Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called.
201     #
202     #    root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
203     #    root.all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
204     #    root.children[-1].all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
205     def all_tokens
206       Primitive.ast_node_all_tokens
207     end
209     #  call-seq:
210     #     node.children -> array
211     #
212     #  Returns AST nodes under this one.  Each kind of node
213     #  has different children, depending on what kind of node it is.
214     #
215     #  The returned array may contain other nodes or <code>nil</code>.
216     def children
217       Primitive.ast_node_children
218     end
220     #  call-seq:
221     #     node.inspect -> string
222     #
223     #  Returns debugging information about this node as a string.
224     def inspect
225       Primitive.ast_node_inspect
226     end
228     #  call-seq:
229     #     node.node_id -> integer
230     #
231     #  Returns an internal node_id number.
232     #  Note that this is an API for ruby internal use, debugging,
233     #  and research. Do not use this for any other purpose.
234     #  The compatibility is not guaranteed.
235     def node_id
236       Primitive.ast_node_node_id
237     end
239     #  call-seq:
240     #     node.script_lines -> array
241     #
242     #  Returns the original source code as an array of lines.
243     #
244     #  Note that this is an API for ruby internal use, debugging,
245     #  and research. Do not use this for any other purpose.
246     #  The compatibility is not guaranteed.
247     def script_lines
248       Primitive.ast_node_script_lines
249     end
251     #  call-seq:
252     #     node.source -> string
253     #
254     #  Returns the code fragment that corresponds to this AST.
255     #
256     #  Note that this is an API for ruby internal use, debugging,
257     #  and research. Do not use this for any other purpose.
258     #  The compatibility is not guaranteed.
259     #
260     #  Also note that this API may return an incomplete code fragment
261     #  that does not parse; for example, a here document following
262     #  an expression may be dropped.
263     def source
264       lines = script_lines
265       if lines
266         lines = lines[first_lineno - 1 .. last_lineno - 1]
267         lines[-1] = lines[-1].byteslice(0...last_column)
268         lines[0] = lines[0].byteslice(first_column..-1)
269         lines.join
270       else
271         nil
272       end
273     end
275     #  call-seq:
276     #     node.locations -> array
277     #
278     #  Returns location objects associated with the AST node.
279     #  The returned array contains RubyVM::AbstractSyntaxTree::Location.
280     def locations
281       Primitive.ast_node_locations
282     end
283   end
285   # RubyVM::AbstractSyntaxTree::Location instances are created by
286   # RubyVM::AbstractSyntaxTree::Node#locations.
287   #
288   # This class is MRI specific.
289   #
290   class Location
292     #  call-seq:
293     #     location.first_lineno -> integer
294     #
295     #  The line number in the source code where this AST's text began.
296     def first_lineno
297       Primitive.ast_location_first_lineno
298     end
300     #  call-seq:
301     #     location.first_column -> integer
302     #
303     #  The column number in the source code where this AST's text began.
304     def first_column
305       Primitive.ast_location_first_column
306     end
308     #  call-seq:
309     #     location.last_lineno -> integer
310     #
311     #  The line number in the source code where this AST's text ended.
312     def last_lineno
313       Primitive.ast_location_last_lineno
314     end
316     #  call-seq:
317     #     location.last_column -> integer
318     #
319     #  The column number in the source code where this AST's text ended.
320     def last_column
321       Primitive.ast_location_last_column
322     end
324     #  call-seq:
325     #     location.inspect -> string
326     #
327     #  Returns debugging information about this location as a string.
328     def inspect
329       Primitive.ast_location_inspect
330     end
331   end