prism 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -3
- data/config.yml +4 -4
- data/docs/ripper_translation.md +8 -17
- data/ext/prism/extension.h +1 -1
- data/include/prism/ast.h +4 -4
- data/include/prism/version.h +2 -2
- data/lib/prism/lex_compat.rb +135 -94
- data/lib/prism/node.rb +27 -19
- data/lib/prism/parse_result.rb +9 -0
- data/lib/prism/serialize.rb +1 -1
- data/lib/prism/translation/ripper/filter.rb +53 -0
- data/lib/prism/translation/ripper/lexer.rb +90 -1
- data/lib/prism/translation/ripper.rb +59 -36
- data/lib/prism.rb +1 -14
- data/prism.gemspec +2 -2
- data/rbi/prism/node.rbi +3 -0
- data/rbi/prism.rbi +0 -3
- data/sig/prism/node.rbs +3 -0
- data/sig/prism/parse_result.rbs +1 -0
- data/sig/prism.rbs +54 -40
- data/src/prism.c +1 -1
- metadata +2 -2
- data/lib/prism/lex_ripper.rb +0 -64
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f7205a73da36d10903c1495fd475e014a30c875568c932568cac22351eb3059
|
|
4
|
+
data.tar.gz: d59eac5f6e8e8955ea9b259b63eec423a0af06c79a748edaa9f64f24463dc874
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59571b78dba19ec01a6f52094cc6d30e5648e633cd24f3abfbbc765145bed778f88fea75eada071d780ecb3dbe77a1bebab563d676289fed0ea6d21e50056290
|
|
7
|
+
data.tar.gz: 3c5b40536f98839fefb0afcefd49d5e78a3a31f9a55bd33b78b80665b2ca3bd1c2262c1836f9c6f1d3ddc7ad2d9b7dd12f06b3dff2b4aea9475a7171340d95b4
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.9.0] - 2026-01-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Lots of work on the Ripper translation layer to make it more compatible and efficient.
|
|
14
|
+
- Alias `Prism::Node#breadth_first_search` to `Prism::Node#find`.
|
|
15
|
+
- Add `Prism::Node#breadth_first_search_all`/`Prism::Node#find_all` for finding all nodes matching a condition.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Fixed location of opening tokens when invalid syntax is parsed.
|
|
20
|
+
- Fix RBI for parsing options.
|
|
21
|
+
|
|
7
22
|
## [1.8.0] - 2026-01-12
|
|
8
23
|
|
|
9
24
|
### Added
|
|
@@ -19,8 +34,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
|
19
34
|
- Decouple ripper translator from ripper library.
|
|
20
35
|
- Sync Prism::Translation::ParserCurrent with Ruby 4.0.
|
|
21
36
|
|
|
22
|
-
## [Unreleased]
|
|
23
|
-
|
|
24
37
|
## [1.7.0] - 2025-12-18
|
|
25
38
|
|
|
26
39
|
### Added
|
|
@@ -731,7 +744,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
|
731
744
|
|
|
732
745
|
- 🎉 Initial release! 🎉
|
|
733
746
|
|
|
734
|
-
[unreleased]: https://github.com/ruby/prism/compare/v1.
|
|
747
|
+
[unreleased]: https://github.com/ruby/prism/compare/v1.9.0...HEAD
|
|
748
|
+
[1.9.0]: https://github.com/ruby/prism/compare/v1.8.0...v1.9.0
|
|
735
749
|
[1.8.0]: https://github.com/ruby/prism/compare/v1.7.0...v1.8.0
|
|
736
750
|
[1.7.0]: https://github.com/ruby/prism/compare/v1.6.0...v1.7.0
|
|
737
751
|
[1.6.0]: https://github.com/ruby/prism/compare/v1.5.2...v1.6.0
|
data/config.yml
CHANGED
|
@@ -1269,17 +1269,17 @@ nodes:
|
|
|
1269
1269
|
- name: opening_loc
|
|
1270
1270
|
type: location
|
|
1271
1271
|
comment: |
|
|
1272
|
-
Represents the location of the opening
|
|
1272
|
+
Represents the location of the opening `{` or `do`.
|
|
1273
1273
|
|
|
1274
1274
|
[1, 2, 3].each { |i| puts x }
|
|
1275
|
-
|
|
1275
|
+
^
|
|
1276
1276
|
- name: closing_loc
|
|
1277
1277
|
type: location
|
|
1278
1278
|
comment: |
|
|
1279
|
-
Represents the location of the closing
|
|
1279
|
+
Represents the location of the closing `}` or `end`.
|
|
1280
1280
|
|
|
1281
1281
|
[1, 2, 3].each { |i| puts x }
|
|
1282
|
-
|
|
1282
|
+
^
|
|
1283
1283
|
comment: |
|
|
1284
1284
|
Represents a block of ruby code.
|
|
1285
1285
|
|
data/docs/ripper_translation.md
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
# Ripper translation
|
|
2
2
|
|
|
3
|
-
Prism provides the ability to mirror the `Ripper` standard library. You can
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
require "prism/translation/ripper/shim"
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
This provides the APIs like:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
Ripper.lex
|
|
13
|
-
Ripper.parse
|
|
14
|
-
Ripper.sexp_raw
|
|
15
|
-
Ripper.sexp
|
|
16
|
-
|
|
17
|
-
Ripper::SexpBuilder
|
|
18
|
-
Ripper::SexpBuilderPP
|
|
19
|
-
```
|
|
3
|
+
Prism provides the ability to mirror the `Ripper` standard library. It is available under `Prism::Translation::Ripper`. You can use the entire public API, and also some undocumented features that are commonly used.
|
|
20
4
|
|
|
21
5
|
Briefly, `Ripper` is a streaming parser that allows you to construct your own syntax tree. As an example:
|
|
22
6
|
|
|
@@ -49,6 +33,13 @@ ArithmeticRipper.new("1 + 2 - 3").parse # => [0]
|
|
|
49
33
|
|
|
50
34
|
The exact names of the `on_*` methods are listed in the `Ripper` source.
|
|
51
35
|
|
|
36
|
+
You can can also automatically use the ripper translation in places that don't explicitly use the translation layer by doing the following:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# Will redirect access of the `Ripper` constant to `Prism::Translation::Ripper`.
|
|
40
|
+
require "prism/translation/ripper/shim"
|
|
41
|
+
```
|
|
42
|
+
|
|
52
43
|
## Background
|
|
53
44
|
|
|
54
45
|
It is helpful to understand the differences between the `Ripper` library and the `Prism` library. Both libraries perform parsing and provide you with APIs to manipulate and understand the resulting syntax tree. However, there are a few key differences.
|
data/ext/prism/extension.h
CHANGED
data/include/prism/ast.h
CHANGED
|
@@ -1826,20 +1826,20 @@ typedef struct pm_block_node {
|
|
|
1826
1826
|
/**
|
|
1827
1827
|
* BlockNode#opening_loc
|
|
1828
1828
|
*
|
|
1829
|
-
* Represents the location of the opening
|
|
1829
|
+
* Represents the location of the opening `{` or `do`.
|
|
1830
1830
|
*
|
|
1831
1831
|
* [1, 2, 3].each { |i| puts x }
|
|
1832
|
-
*
|
|
1832
|
+
* ^
|
|
1833
1833
|
*/
|
|
1834
1834
|
pm_location_t opening_loc;
|
|
1835
1835
|
|
|
1836
1836
|
/**
|
|
1837
1837
|
* BlockNode#closing_loc
|
|
1838
1838
|
*
|
|
1839
|
-
* Represents the location of the closing
|
|
1839
|
+
* Represents the location of the closing `}` or `end`.
|
|
1840
1840
|
*
|
|
1841
1841
|
* [1, 2, 3].each { |i| puts x }
|
|
1842
|
-
*
|
|
1842
|
+
* ^
|
|
1843
1843
|
*/
|
|
1844
1844
|
pm_location_t closing_loc;
|
|
1845
1845
|
} pm_block_node_t;
|
data/include/prism/version.h
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* The minor version of the Prism library as an int.
|
|
16
16
|
*/
|
|
17
|
-
#define PRISM_VERSION_MINOR
|
|
17
|
+
#define PRISM_VERSION_MINOR 9
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* The patch version of the Prism library as an int.
|
|
@@ -24,6 +24,6 @@
|
|
|
24
24
|
/**
|
|
25
25
|
* The version of the Prism library as a constant string.
|
|
26
26
|
*/
|
|
27
|
-
#define PRISM_VERSION "1.
|
|
27
|
+
#define PRISM_VERSION "1.9.0"
|
|
28
28
|
|
|
29
29
|
#endif
|
data/lib/prism/lex_compat.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# :markup: markdown
|
|
3
3
|
|
|
4
|
-
require "delegate"
|
|
5
|
-
|
|
6
4
|
module Prism
|
|
7
5
|
# This class is responsible for lexing the source using prism and then
|
|
8
6
|
# converting those tokens to be compatible with Ripper. In the vast majority
|
|
@@ -201,87 +199,51 @@ module Prism
|
|
|
201
199
|
# When we produce tokens, we produce the same arrays that Ripper does.
|
|
202
200
|
# However, we add a couple of convenience methods onto them to make them a
|
|
203
201
|
# little easier to work with. We delegate all other methods to the array.
|
|
204
|
-
class Token <
|
|
205
|
-
#
|
|
202
|
+
class Token < BasicObject
|
|
203
|
+
# Create a new token object with the given ripper-compatible array.
|
|
204
|
+
def initialize(array)
|
|
205
|
+
@array = array
|
|
206
|
+
end
|
|
206
207
|
|
|
207
208
|
# The location of the token in the source.
|
|
208
209
|
def location
|
|
209
|
-
|
|
210
|
+
@array[0]
|
|
210
211
|
end
|
|
211
212
|
|
|
212
213
|
# The type of the token.
|
|
213
214
|
def event
|
|
214
|
-
|
|
215
|
+
@array[1]
|
|
215
216
|
end
|
|
216
217
|
|
|
217
218
|
# The slice of the source that this token represents.
|
|
218
219
|
def value
|
|
219
|
-
|
|
220
|
+
@array[2]
|
|
220
221
|
end
|
|
221
222
|
|
|
222
223
|
# The state of the lexer when this token was produced.
|
|
223
224
|
def state
|
|
224
|
-
|
|
225
|
+
@array[3]
|
|
225
226
|
end
|
|
226
|
-
end
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
# trim it down to just the content on the first line when comparing.
|
|
230
|
-
class EndContentToken < Token
|
|
228
|
+
# We want to pretend that this is just an Array.
|
|
231
229
|
def ==(other) # :nodoc:
|
|
232
|
-
|
|
230
|
+
@array == other
|
|
233
231
|
end
|
|
234
|
-
end
|
|
235
232
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
class IgnoreStateToken < Token
|
|
239
|
-
def ==(other) # :nodoc:
|
|
240
|
-
self[0...-1] == other[0...-1]
|
|
233
|
+
def respond_to_missing?(name, include_private = false) # :nodoc:
|
|
234
|
+
@array.respond_to?(name, include_private)
|
|
241
235
|
end
|
|
242
|
-
end
|
|
243
236
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
# through named captures in regular expressions). In that case we don't
|
|
247
|
-
# compare the state.
|
|
248
|
-
class IdentToken < Token
|
|
249
|
-
def ==(other) # :nodoc:
|
|
250
|
-
(self[0...-1] == other[0...-1]) && (
|
|
251
|
-
(other[3] == Translation::Ripper::EXPR_LABEL | Translation::Ripper::EXPR_END) ||
|
|
252
|
-
(other[3] & (Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_CMDARG) != 0)
|
|
253
|
-
)
|
|
237
|
+
def method_missing(name, ...) # :nodoc:
|
|
238
|
+
@array.send(name, ...)
|
|
254
239
|
end
|
|
255
240
|
end
|
|
256
241
|
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
class
|
|
260
|
-
def ==(other) # :nodoc:
|
|
261
|
-
return false unless self[0...-1] == other[0...-1]
|
|
262
|
-
|
|
263
|
-
if self[3] == Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED
|
|
264
|
-
other[3] & Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED != 0
|
|
265
|
-
else
|
|
266
|
-
self[3] == other[3]
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# If we have an identifier that follows a method name like:
|
|
272
|
-
#
|
|
273
|
-
# def foo bar
|
|
274
|
-
#
|
|
275
|
-
# then Ripper will mark bar as END|LABEL if there is a local in a parent
|
|
276
|
-
# scope named bar because it hasn't pushed the local table yet. We do this
|
|
277
|
-
# more accurately, so we need to allow comparing against both END and
|
|
278
|
-
# END|LABEL.
|
|
279
|
-
class ParamToken < Token
|
|
242
|
+
# Tokens where state should be ignored
|
|
243
|
+
# used for :on_sp, :on_comment, :on_heredoc_end, :on_embexpr_end
|
|
244
|
+
class IgnoreStateToken < Token
|
|
280
245
|
def ==(other) # :nodoc:
|
|
281
|
-
|
|
282
|
-
(other[3] == Translation::Ripper::EXPR_END) ||
|
|
283
|
-
(other[3] == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL)
|
|
284
|
-
)
|
|
246
|
+
self[0...-1] == other[0...-1]
|
|
285
247
|
end
|
|
286
248
|
end
|
|
287
249
|
|
|
@@ -619,10 +581,10 @@ module Prism
|
|
|
619
581
|
BOM_FLUSHED = RUBY_VERSION >= "3.3.0"
|
|
620
582
|
private_constant :BOM_FLUSHED
|
|
621
583
|
|
|
622
|
-
attr_reader :
|
|
584
|
+
attr_reader :options
|
|
623
585
|
|
|
624
|
-
def initialize(
|
|
625
|
-
@
|
|
586
|
+
def initialize(code, **options)
|
|
587
|
+
@code = code
|
|
626
588
|
@options = options
|
|
627
589
|
end
|
|
628
590
|
|
|
@@ -632,12 +594,14 @@ module Prism
|
|
|
632
594
|
state = :default
|
|
633
595
|
heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]]
|
|
634
596
|
|
|
635
|
-
result = Prism.lex(
|
|
597
|
+
result = Prism.lex(@code, **options)
|
|
598
|
+
source = result.source
|
|
636
599
|
result_value = result.value
|
|
637
600
|
previous_state = nil #: State?
|
|
638
601
|
last_heredoc_end = nil #: Integer?
|
|
602
|
+
eof_token = nil
|
|
639
603
|
|
|
640
|
-
bom = source.
|
|
604
|
+
bom = source.slice(0, 3) == "\xEF\xBB\xBF"
|
|
641
605
|
|
|
642
606
|
result_value.each_with_index do |(token, lex_state), index|
|
|
643
607
|
lineno = token.location.start_line
|
|
@@ -675,12 +639,15 @@ module Prism
|
|
|
675
639
|
|
|
676
640
|
event = RIPPER.fetch(token.type)
|
|
677
641
|
value = token.value
|
|
678
|
-
lex_state = Translation::Ripper::Lexer::State.
|
|
642
|
+
lex_state = Translation::Ripper::Lexer::State.cached(lex_state)
|
|
679
643
|
|
|
680
644
|
token =
|
|
681
645
|
case event
|
|
682
646
|
when :on___end__
|
|
683
|
-
|
|
647
|
+
# Ripper doesn't include the rest of the token in the event, so we need to
|
|
648
|
+
# trim it down to just the content on the first line.
|
|
649
|
+
value = value[0..value.index("\n")]
|
|
650
|
+
Token.new([[lineno, column], event, value, lex_state])
|
|
684
651
|
when :on_comment
|
|
685
652
|
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
|
|
686
653
|
when :on_heredoc_end
|
|
@@ -688,33 +655,18 @@ module Prism
|
|
|
688
655
|
# want to bother comparing the state on them.
|
|
689
656
|
last_heredoc_end = token.location.end_offset
|
|
690
657
|
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
|
|
691
|
-
when :on_ident
|
|
692
|
-
if lex_state == Translation::Ripper::EXPR_END
|
|
693
|
-
# If we have an identifier that follows a method name like:
|
|
694
|
-
#
|
|
695
|
-
# def foo bar
|
|
696
|
-
#
|
|
697
|
-
# then Ripper will mark bar as END|LABEL if there is a local in a
|
|
698
|
-
# parent scope named bar because it hasn't pushed the local table
|
|
699
|
-
# yet. We do this more accurately, so we need to allow comparing
|
|
700
|
-
# against both END and END|LABEL.
|
|
701
|
-
ParamToken.new([[lineno, column], event, value, lex_state])
|
|
702
|
-
elsif lex_state == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL
|
|
703
|
-
# In the event that we're comparing identifiers, we're going to
|
|
704
|
-
# allow a little divergence. Ripper doesn't account for local
|
|
705
|
-
# variables introduced through named captures in regexes, and we
|
|
706
|
-
# do, which accounts for this difference.
|
|
707
|
-
IdentToken.new([[lineno, column], event, value, lex_state])
|
|
708
|
-
else
|
|
709
|
-
Token.new([[lineno, column], event, value, lex_state])
|
|
710
|
-
end
|
|
711
658
|
when :on_embexpr_end
|
|
712
659
|
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
|
|
713
|
-
when :
|
|
714
|
-
#
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
660
|
+
when :on_words_sep
|
|
661
|
+
# Ripper emits one token each per line.
|
|
662
|
+
value.each_line.with_index do |line, index|
|
|
663
|
+
if index > 0
|
|
664
|
+
lineno += 1
|
|
665
|
+
column = 0
|
|
666
|
+
end
|
|
667
|
+
tokens << Token.new([[lineno, column], event, line, lex_state])
|
|
668
|
+
end
|
|
669
|
+
tokens.pop
|
|
718
670
|
when :on_regexp_end
|
|
719
671
|
# On regex end, Ripper scans and then sets end state, so the ripper
|
|
720
672
|
# lexed output is begin, when it should be end. prism sets lex state
|
|
@@ -739,13 +691,14 @@ module Prism
|
|
|
739
691
|
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
|
|
740
692
|
end
|
|
741
693
|
|
|
742
|
-
Translation::Ripper::Lexer::State.
|
|
694
|
+
Translation::Ripper::Lexer::State.cached(result_value[current_index][1])
|
|
743
695
|
else
|
|
744
696
|
previous_state
|
|
745
697
|
end
|
|
746
698
|
|
|
747
699
|
Token.new([[lineno, column], event, value, lex_state])
|
|
748
700
|
when :on_eof
|
|
701
|
+
eof_token = token
|
|
749
702
|
previous_token = result_value[index - 1][0]
|
|
750
703
|
|
|
751
704
|
# If we're at the end of the file and the previous token was a
|
|
@@ -768,7 +721,7 @@ module Prism
|
|
|
768
721
|
end_offset += 3
|
|
769
722
|
end
|
|
770
723
|
|
|
771
|
-
tokens << Token.new([[lineno, 0], :on_nl, source.
|
|
724
|
+
tokens << Token.new([[lineno, 0], :on_nl, source.slice(start_offset, end_offset - start_offset), lex_state])
|
|
772
725
|
end
|
|
773
726
|
end
|
|
774
727
|
|
|
@@ -859,10 +812,98 @@ module Prism
|
|
|
859
812
|
# Drop the EOF token from the list
|
|
860
813
|
tokens = tokens[0...-1]
|
|
861
814
|
|
|
862
|
-
# We sort by location
|
|
863
|
-
|
|
815
|
+
# We sort by location because Ripper.lex sorts.
|
|
816
|
+
# Manually implemented instead of `sort_by!(&:location)` for performance.
|
|
817
|
+
tokens.sort_by! do |token|
|
|
818
|
+
line, column = token.location
|
|
819
|
+
source.byte_offset(line, column)
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
# Add :on_sp tokens
|
|
823
|
+
tokens = add_on_sp_tokens(tokens, source, result.data_loc, bom, eof_token)
|
|
824
|
+
|
|
825
|
+
Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, source)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
def add_on_sp_tokens(tokens, source, data_loc, bom, eof_token)
|
|
829
|
+
new_tokens = []
|
|
830
|
+
|
|
831
|
+
prev_token_state = Translation::Ripper::Lexer::State.cached(Translation::Ripper::EXPR_BEG)
|
|
832
|
+
prev_token_end = bom ? 3 : 0
|
|
833
|
+
|
|
834
|
+
tokens.each do |token|
|
|
835
|
+
line, column = token.location
|
|
836
|
+
start_offset = source.byte_offset(line, column)
|
|
837
|
+
|
|
838
|
+
# Ripper reports columns on line 1 without counting the BOM, so we
|
|
839
|
+
# adjust to get the real offset
|
|
840
|
+
start_offset += 3 if line == 1 && bom
|
|
841
|
+
|
|
842
|
+
if start_offset > prev_token_end
|
|
843
|
+
sp_value = source.slice(prev_token_end, start_offset - prev_token_end)
|
|
844
|
+
sp_line = source.line(prev_token_end)
|
|
845
|
+
sp_column = source.column(prev_token_end)
|
|
846
|
+
# Ripper reports columns on line 1 without counting the BOM
|
|
847
|
+
sp_column -= 3 if sp_line == 1 && bom
|
|
848
|
+
continuation_index = sp_value.byteindex("\\")
|
|
849
|
+
|
|
850
|
+
# ripper emits up to three :on_sp tokens when line continuations are used
|
|
851
|
+
if continuation_index
|
|
852
|
+
next_whitespace_index = continuation_index + 1
|
|
853
|
+
next_whitespace_index += 1 if sp_value.byteslice(next_whitespace_index) == "\r"
|
|
854
|
+
next_whitespace_index += 1
|
|
855
|
+
first_whitespace = sp_value[0...continuation_index]
|
|
856
|
+
continuation = sp_value[continuation_index...next_whitespace_index]
|
|
857
|
+
second_whitespace = sp_value[next_whitespace_index..]
|
|
858
|
+
|
|
859
|
+
new_tokens << IgnoreStateToken.new([
|
|
860
|
+
[sp_line, sp_column],
|
|
861
|
+
:on_sp,
|
|
862
|
+
first_whitespace,
|
|
863
|
+
prev_token_state
|
|
864
|
+
]) unless first_whitespace.empty?
|
|
865
|
+
|
|
866
|
+
new_tokens << IgnoreStateToken.new([
|
|
867
|
+
[sp_line, sp_column + continuation_index],
|
|
868
|
+
:on_sp,
|
|
869
|
+
continuation,
|
|
870
|
+
prev_token_state
|
|
871
|
+
])
|
|
872
|
+
|
|
873
|
+
new_tokens << IgnoreStateToken.new([
|
|
874
|
+
[sp_line + 1, 0],
|
|
875
|
+
:on_sp,
|
|
876
|
+
second_whitespace,
|
|
877
|
+
prev_token_state
|
|
878
|
+
]) unless second_whitespace.empty?
|
|
879
|
+
else
|
|
880
|
+
new_tokens << IgnoreStateToken.new([
|
|
881
|
+
[sp_line, sp_column],
|
|
882
|
+
:on_sp,
|
|
883
|
+
sp_value,
|
|
884
|
+
prev_token_state
|
|
885
|
+
])
|
|
886
|
+
end
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
new_tokens << token
|
|
890
|
+
prev_token_state = token.state
|
|
891
|
+
prev_token_end = start_offset + token.value.bytesize
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
unless data_loc # no trailing :on_sp with __END__ as it is always preceded by :on_nl
|
|
895
|
+
end_offset = eof_token.location.end_offset
|
|
896
|
+
if prev_token_end < end_offset
|
|
897
|
+
new_tokens << IgnoreStateToken.new([
|
|
898
|
+
[source.line(prev_token_end), source.column(prev_token_end)],
|
|
899
|
+
:on_sp,
|
|
900
|
+
source.slice(prev_token_end, end_offset - prev_token_end),
|
|
901
|
+
prev_token_state
|
|
902
|
+
])
|
|
903
|
+
end
|
|
904
|
+
end
|
|
864
905
|
|
|
865
|
-
|
|
906
|
+
new_tokens
|
|
866
907
|
end
|
|
867
908
|
end
|
|
868
909
|
|
data/lib/prism/node.rb
CHANGED
|
@@ -194,25 +194,13 @@ module Prism
|
|
|
194
194
|
def tunnel(line, column)
|
|
195
195
|
queue = [self] #: Array[Prism::node]
|
|
196
196
|
result = [] #: Array[Prism::node]
|
|
197
|
+
offset = source.byte_offset(line, column)
|
|
197
198
|
|
|
198
199
|
while (node = queue.shift)
|
|
199
200
|
result << node
|
|
200
201
|
|
|
201
202
|
node.each_child_node do |child_node|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
start_line = child_location.start_line
|
|
205
|
-
end_line = child_location.end_line
|
|
206
|
-
|
|
207
|
-
if start_line == end_line
|
|
208
|
-
if line == start_line && column >= child_location.start_column && column < child_location.end_column
|
|
209
|
-
queue << child_node
|
|
210
|
-
break
|
|
211
|
-
end
|
|
212
|
-
elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column)
|
|
213
|
-
queue << child_node
|
|
214
|
-
break
|
|
215
|
-
elsif line > start_line && line < end_line
|
|
203
|
+
if child_node.start_offset <= offset && offset < child_node.end_offset
|
|
216
204
|
queue << child_node
|
|
217
205
|
break
|
|
218
206
|
end
|
|
@@ -223,7 +211,7 @@ module Prism
|
|
|
223
211
|
end
|
|
224
212
|
|
|
225
213
|
# Returns the first node that matches the given block when visited in a
|
|
226
|
-
#
|
|
214
|
+
# breadth-first search. This is useful for finding a node that matches a
|
|
227
215
|
# particular condition.
|
|
228
216
|
#
|
|
229
217
|
# node.breadth_first_search { |node| node.node_id == node_id }
|
|
@@ -238,6 +226,26 @@ module Prism
|
|
|
238
226
|
|
|
239
227
|
nil
|
|
240
228
|
end
|
|
229
|
+
alias find breadth_first_search
|
|
230
|
+
|
|
231
|
+
# Returns all of the nodes that match the given block when visited in a
|
|
232
|
+
# breadth-first search. This is useful for finding all nodes that match a
|
|
233
|
+
# particular condition.
|
|
234
|
+
#
|
|
235
|
+
# node.breadth_first_search_all { |node| node.is_a?(Prism::CallNode) }
|
|
236
|
+
#
|
|
237
|
+
def breadth_first_search_all(&block)
|
|
238
|
+
queue = [self] #: Array[Prism::node]
|
|
239
|
+
results = [] #: Array[Prism::node]
|
|
240
|
+
|
|
241
|
+
while (node = queue.shift)
|
|
242
|
+
results << node if yield node
|
|
243
|
+
queue.concat(node.compact_child_nodes)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
results
|
|
247
|
+
end
|
|
248
|
+
alias find_all breadth_first_search_all
|
|
241
249
|
|
|
242
250
|
# Returns a list of the fields that exist for this node class. Fields
|
|
243
251
|
# describe the structure of the node. This kind of reflection is useful for
|
|
@@ -2025,10 +2033,10 @@ module Prism
|
|
|
2025
2033
|
# ^^^^^^
|
|
2026
2034
|
attr_reader :body
|
|
2027
2035
|
|
|
2028
|
-
# Represents the location of the opening
|
|
2036
|
+
# Represents the location of the opening `{` or `do`.
|
|
2029
2037
|
#
|
|
2030
2038
|
# [1, 2, 3].each { |i| puts x }
|
|
2031
|
-
#
|
|
2039
|
+
# ^
|
|
2032
2040
|
def opening_loc
|
|
2033
2041
|
location = @opening_loc
|
|
2034
2042
|
return location if location.is_a?(Location)
|
|
@@ -2041,10 +2049,10 @@ module Prism
|
|
|
2041
2049
|
repository.enter(node_id, :opening_loc)
|
|
2042
2050
|
end
|
|
2043
2051
|
|
|
2044
|
-
# Represents the location of the closing
|
|
2052
|
+
# Represents the location of the closing `}` or `end`.
|
|
2045
2053
|
#
|
|
2046
2054
|
# [1, 2, 3].each { |i| puts x }
|
|
2047
|
-
#
|
|
2055
|
+
# ^
|
|
2048
2056
|
def closing_loc
|
|
2049
2057
|
location = @closing_loc
|
|
2050
2058
|
return location if location.is_a?(Location)
|
data/lib/prism/parse_result.rb
CHANGED
|
@@ -76,6 +76,15 @@ module Prism
|
|
|
76
76
|
source.byteslice(byte_offset, length) or raise
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
# Converts the line number and column in bytes to a byte offset.
|
|
80
|
+
def byte_offset(line, column)
|
|
81
|
+
normal = line - @start_line
|
|
82
|
+
raise IndexError if normal < 0
|
|
83
|
+
offsets.fetch(normal) + column
|
|
84
|
+
rescue IndexError
|
|
85
|
+
raise ArgumentError, "line #{line} is out of range"
|
|
86
|
+
end
|
|
87
|
+
|
|
79
88
|
# Binary search through the offsets to find the line number for the given
|
|
80
89
|
# byte offset.
|
|
81
90
|
def line(byte_offset)
|
data/lib/prism/serialize.rb
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prism
|
|
4
|
+
module Translation
|
|
5
|
+
class Ripper
|
|
6
|
+
class Filter # :nodoc:
|
|
7
|
+
# :stopdoc:
|
|
8
|
+
def initialize(src, filename = '-', lineno = 1)
|
|
9
|
+
@__lexer = Lexer.new(src, filename, lineno)
|
|
10
|
+
@__line = nil
|
|
11
|
+
@__col = nil
|
|
12
|
+
@__state = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def filename
|
|
16
|
+
@__lexer.filename
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def lineno
|
|
20
|
+
@__line
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def column
|
|
24
|
+
@__col
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def state
|
|
28
|
+
@__state
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse(init = nil)
|
|
32
|
+
data = init
|
|
33
|
+
@__lexer.lex.each do |pos, event, tok, state|
|
|
34
|
+
@__line, @__col = *pos
|
|
35
|
+
@__state = state
|
|
36
|
+
data = if respond_to?(event, true)
|
|
37
|
+
then __send__(event, tok, data)
|
|
38
|
+
else on_default(event, tok, data)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
data
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def on_default(event, token, data)
|
|
47
|
+
data
|
|
48
|
+
end
|
|
49
|
+
# :startdoc:
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|