ripper2ruby 0.0.1

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.
Files changed (111) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.markdown +10 -0
  3. data/lib/core_ext/array/flush.rb +5 -0
  4. data/lib/core_ext/hash/delete_at.rb +5 -0
  5. data/lib/core_ext/object/meta_class.rb +5 -0
  6. data/lib/core_ext/object/try.rb +12 -0
  7. data/lib/erb/stripper.rb +48 -0
  8. data/lib/highlighters/ansi.rb +29 -0
  9. data/lib/ripper/event_log.rb +45 -0
  10. data/lib/ripper/ruby_builder.rb +168 -0
  11. data/lib/ripper/ruby_builder/buffer.rb +34 -0
  12. data/lib/ripper/ruby_builder/events/args.rb +40 -0
  13. data/lib/ripper/ruby_builder/events/array.rb +71 -0
  14. data/lib/ripper/ruby_builder/events/assignment.rb +55 -0
  15. data/lib/ripper/ruby_builder/events/block.rb +80 -0
  16. data/lib/ripper/ruby_builder/events/call.rb +123 -0
  17. data/lib/ripper/ruby_builder/events/case.rb +17 -0
  18. data/lib/ripper/ruby_builder/events/const.rb +47 -0
  19. data/lib/ripper/ruby_builder/events/for.rb +13 -0
  20. data/lib/ripper/ruby_builder/events/hash.rb +24 -0
  21. data/lib/ripper/ruby_builder/events/identifier.rb +41 -0
  22. data/lib/ripper/ruby_builder/events/if.rb +37 -0
  23. data/lib/ripper/ruby_builder/events/lexer.rb +159 -0
  24. data/lib/ripper/ruby_builder/events/literal.rb +47 -0
  25. data/lib/ripper/ruby_builder/events/method.rb +21 -0
  26. data/lib/ripper/ruby_builder/events/operator.rb +23 -0
  27. data/lib/ripper/ruby_builder/events/statements.rb +50 -0
  28. data/lib/ripper/ruby_builder/events/string.rb +117 -0
  29. data/lib/ripper/ruby_builder/events/symbol.rb +22 -0
  30. data/lib/ripper/ruby_builder/events/while.rb +27 -0
  31. data/lib/ripper/ruby_builder/queue.rb +33 -0
  32. data/lib/ripper/ruby_builder/stack.rb +125 -0
  33. data/lib/ripper/ruby_builder/token.rb +91 -0
  34. data/lib/ripper2ruby.rb +1 -0
  35. data/lib/ruby.rb +28 -0
  36. data/lib/ruby/aggregate.rb +71 -0
  37. data/lib/ruby/alternation/args.rb +25 -0
  38. data/lib/ruby/alternation/hash.rb +25 -0
  39. data/lib/ruby/alternation/list.rb +19 -0
  40. data/lib/ruby/args.rb +36 -0
  41. data/lib/ruby/array.rb +27 -0
  42. data/lib/ruby/assignment.rb +32 -0
  43. data/lib/ruby/assoc.rb +17 -0
  44. data/lib/ruby/block.rb +42 -0
  45. data/lib/ruby/call.rb +34 -0
  46. data/lib/ruby/case.rb +30 -0
  47. data/lib/ruby/const.rb +49 -0
  48. data/lib/ruby/for.rb +18 -0
  49. data/lib/ruby/hash.rb +14 -0
  50. data/lib/ruby/if.rb +35 -0
  51. data/lib/ruby/list.rb +40 -0
  52. data/lib/ruby/literal.rb +45 -0
  53. data/lib/ruby/method.rb +19 -0
  54. data/lib/ruby/node.rb +47 -0
  55. data/lib/ruby/node/composite.rb +68 -0
  56. data/lib/ruby/node/conversions.rb +66 -0
  57. data/lib/ruby/node/position.rb +35 -0
  58. data/lib/ruby/node/source.rb +29 -0
  59. data/lib/ruby/node/text.rb +121 -0
  60. data/lib/ruby/node/traversal.rb +82 -0
  61. data/lib/ruby/operator.rb +49 -0
  62. data/lib/ruby/params.rb +41 -0
  63. data/lib/ruby/statements.rb +45 -0
  64. data/lib/ruby/string.rb +40 -0
  65. data/lib/ruby/symbol.rb +27 -0
  66. data/lib/ruby/token.rb +51 -0
  67. data/lib/ruby/while.rb +32 -0
  68. data/test/all.rb +3 -0
  69. data/test/builder/stack_test.rb +67 -0
  70. data/test/builder/text_test.rb +118 -0
  71. data/test/context_test.rb +54 -0
  72. data/test/erb_stripper_test.rb +29 -0
  73. data/test/fixtures/all.rb.src +150 -0
  74. data/test/fixtures/source_1.rb +16 -0
  75. data/test/fixtures/source_2.rb +1 -0
  76. data/test/fixtures/stuff.rb +371 -0
  77. data/test/fixtures/template.html.erb +22 -0
  78. data/test/fixtures/tmp.rb +6 -0
  79. data/test/lib_test.rb +92 -0
  80. data/test/lib_test_helper.rb +103 -0
  81. data/test/libs.txt +227 -0
  82. data/test/nodes/args_test.rb +100 -0
  83. data/test/nodes/array_test.rb +141 -0
  84. data/test/nodes/assignment_test.rb +49 -0
  85. data/test/nodes/block_test.rb +125 -0
  86. data/test/nodes/call_test.rb +229 -0
  87. data/test/nodes/case_test.rb +68 -0
  88. data/test/nodes/comments_test.rb +25 -0
  89. data/test/nodes/const_test.rb +46 -0
  90. data/test/nodes/conversions_test.rb +9 -0
  91. data/test/nodes/for_test.rb +34 -0
  92. data/test/nodes/hash_test.rb +71 -0
  93. data/test/nodes/heredoc_test.rb +202 -0
  94. data/test/nodes/identifier_test.rb +51 -0
  95. data/test/nodes/if_test.rb +100 -0
  96. data/test/nodes/literals_test.rb +63 -0
  97. data/test/nodes/method_test.rb +92 -0
  98. data/test/nodes/namespaces_test.rb +65 -0
  99. data/test/nodes/node_test.rb +74 -0
  100. data/test/nodes/nodes_test.rb +23 -0
  101. data/test/nodes/operator_test.rb +241 -0
  102. data/test/nodes/separators_test.rb +97 -0
  103. data/test/nodes/statements_test.rb +70 -0
  104. data/test/nodes/string_test.rb +92 -0
  105. data/test/nodes/symbol_test.rb +57 -0
  106. data/test/nodes/unless_test.rb +42 -0
  107. data/test/nodes/until_test.rb +61 -0
  108. data/test/nodes/while_test.rb +71 -0
  109. data/test/test_helper.rb +51 -0
  110. data/test/traversal_test.rb +53 -0
  111. metadata +163 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Sven Fuchs <svenfuchs@artweb-design.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,10 @@
1
+ Ripper2Ruby
2
+ ===========
3
+
4
+ Similar to ruby2ruby this library allows to parse Ruby code, modify and
5
+ recompile it back to Ruby.
6
+
7
+ Differences:
8
+
9
+ * uses Ripper for parsing (shipped with Ruby 1.9)
10
+ * produces a full object-oriented representation of the Ruby code
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def flush
3
+ self.dup.tap { self.clear }
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def delete_at(*keys)
3
+ keys.map { |key| delete(key) }
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def meta_class
3
+ (class << self; self; end)
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ class Object
2
+ # alias_method :try, :__send__
3
+ def try(method, *args, &block)
4
+ send(method, *args, &block) if respond_to?(method)
5
+ end
6
+ end
7
+
8
+ class NilClass
9
+ def try(*args)
10
+ nil
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ # replaces html and erb tags with whitespace so that we can parse the result
2
+ # as pure ruby preserving the exact positions of tokens in the original erb
3
+ # source code
4
+
5
+ require 'erb'
6
+ $KCODE = 'u' if RUBY_VERSION < '1.9'
7
+
8
+ module Erb
9
+ class Scanner < ERB::Compiler::Scanner
10
+ def scan
11
+ stag_reg = /(.*?)(^[ \t]*<%%|<%%=|<%=|<%#|<%-|<%|\z)/m
12
+ etag_reg = /(.*?)(%%>|\-%>|%>|\z)/m
13
+ scanner = StringScanner.new(@src)
14
+ while !scanner.eos?
15
+ scanner.scan(@stag ? etag_reg : stag_reg)
16
+ yield(scanner[1]) unless scanner[1].nil?
17
+ yield(scanner[2]) unless scanner[2].nil?
18
+ end
19
+ end
20
+ end
21
+ ERB::Compiler::Scanner.regist_scanner(Scanner, nil, false)
22
+
23
+ class Stripper
24
+ def to_ruby(source)
25
+ result = ''
26
+ comment = false
27
+ scanner = ERB::Compiler.new(nil).make_scanner(source)
28
+ scanner.scan do |token|
29
+ comment = true if token == '<%#'
30
+ if scanner.stag.nil?
31
+ result << to_whitespace(token)
32
+ scanner.stag = token if ['<%', '<%%', '<%-', '<%=', '<%%=', '<%#'].include?(token.strip)
33
+ elsif ['%>', '%%>', '-%>'].include?(token.strip)
34
+ result << to_whitespace(token.gsub(/>/, ';'))
35
+ scanner.stag = nil
36
+ else
37
+ result << (comment ? to_whitespace(token) : token)
38
+ comment = false
39
+ end
40
+ end
41
+ result
42
+ end
43
+
44
+ def to_whitespace(str)
45
+ str.gsub(/[^\s;]/, ' ')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ module Highlighters
2
+ class Ansi
3
+ COLORS = { :red =>";31", :yellow => ";33", :green => ";32" }
4
+ STYLES = { :bold => ";1", :underline => ";4" }
5
+
6
+ def initialize(*formats)
7
+ @formats = formats
8
+ end
9
+
10
+ def highlight(text)
11
+ ansi_format(text, @formats)
12
+ end
13
+
14
+ def ansi_format(text, formats)
15
+ res = "\e[0"
16
+ if formats.is_a?(Array) || formats.is_a?(Hash)
17
+ COLORS.each { |k,v| res += v if formats.include?(k) }
18
+ STYLES.each { |k,v| res += v if formats.include?(k) }
19
+ elsif formats.is_a?(Symbol)
20
+ COLORS.each { |k,v| res += v if formats == k }
21
+ STYLES.each { |k,v| res += v if formats == k }
22
+ elsif formats.respond_to?(:to_sym)
23
+ COLORS.each { |k,v| res += v if formats.to_sym == k }
24
+ STYLES.each { |k,v| res += v if formats.to_sym == k }
25
+ end
26
+ res += "m" + text.to_s + "\e[0m"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ require 'ripper'
2
+ require 'highlighters/ansi'
3
+
4
+ class Ripper
5
+ class EventLog < Ripper::SexpBuilder
6
+ class << self
7
+ def out(src)
8
+ parser = new(src)
9
+ parser.parse
10
+ parser.out
11
+ end
12
+ end
13
+
14
+ attr_reader :log
15
+
16
+ def initialize(src)
17
+ @log = []
18
+ super
19
+ end
20
+
21
+ def out
22
+ log.each do |type, sexp|
23
+ arg = sexp[1] =~ /\s/ ? sexp[1].inspect : sexp[1]
24
+ line = (sexp[0].to_s).ljust(20)
25
+ if type == :scanner
26
+ puts line + arg[0..30]
27
+ else
28
+ puts highlight(line)
29
+ end
30
+ end
31
+ end
32
+
33
+ def highlight(str)
34
+ Highlighters::Ansi.new(:bold, :green).highlight(str)
35
+ end
36
+
37
+ { :scanner => SCANNER_EVENTS, :parser => PARSER_EVENTS }.each do |type, events|
38
+ events.each do |event|
39
+ define_method :"on_#{event}" do |*args|
40
+ log << [type, super(*args)]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,168 @@
1
+ require 'ripper'
2
+ require 'ruby'
3
+
4
+ require 'core_ext/hash/delete_at'
5
+ require 'core_ext/array/flush'
6
+
7
+ require 'ripper/ruby_builder/token'
8
+ require 'ripper/ruby_builder/stack'
9
+
10
+ Dir[File.dirname(__FILE__) + '/ruby_builder/events/*.rb'].each { |file| require file }
11
+
12
+ # Ripper::RubyBuilder extends Ripper's SexpBuilder and builds a rich, object
13
+ # oriented representation of Ruby code.
14
+ #
15
+ # code = Ripper::RubyBuilder.build("foo(1, :bar, %w(baz)"), filename)
16
+ # code.to_ruby # => "foo(1, :bar, %w(baz)"
17
+ #
18
+ # RubyBuilder uses SexpBuilder's lexing and parsing event callbacks (see
19
+ # ruby_builder/events) and builds up Ruby::Nodes which can then be used.
20
+ # See RubyBuilder::Stack, RubyBuilder::Queue, RubyBuilder::Buffer and
21
+ # RubyBuilder::Token for more details about the parsing process.
22
+
23
+ class Ripper
24
+ class RubyBuilder < Ripper::SexpBuilder
25
+ class ParseError < RuntimeError
26
+ end
27
+
28
+ class << self
29
+ def build(src, filename = nil)
30
+ new(src, filename).parse
31
+ end
32
+ end
33
+
34
+ NEWLINE = [:@nl, :@ignored_nl]
35
+ WHITESPACE = [:@sp, :@comment] + NEWLINE
36
+ OPENERS = [:@lparen, :@lbracket, :@lbrace, :@class, :@module, :@def, :@begin, :@while, :@until,
37
+ :@for, :@if, :@elsif, :@else, :@unless, :@case, :@when, :@embexpr_beg, :@do, :@rescue,
38
+ :'@=', :'@::']
39
+ KEYWORDS = [:@alias, :@and, :@BEGIN, :@begin, :@break, :@case, :@class, :@def, :@defined,
40
+ :@do, :@else, :@elsif, :@END, :@end, :@ensure, :@false, :@for, :@if, :@in,
41
+ :@module, :@next, :@nil, :@not, :@or, :@redo, :@rescue, :@retry, :@return,
42
+ :@self, :@super, :@then, :@true, :@undef, :@unless, :@until, :@when, :@while,
43
+ :@yield]
44
+
45
+ SEPARATORS = [:@semicolon, :@comma]
46
+
47
+ UNARY_OPERATORS = [:'@+', :'@-', :'@!', :'@~', :@not, :'@+@', :'@-@']
48
+ BINARY_OPERATORS = [:'@**', :'@*', :'@/', :'@%', :'@+', :'@-', :'@<<', :'@>>', :'@&', :'@|', :'@^',
49
+ :'@>', :'@>=', :'@<', :'@<=', :'@<=>', :'@==', :'@===', :'@!=', :'@=~', :'@!~',
50
+ :'@&&', :'@||', :@and, :@or]
51
+ TERNARY_OPERATORS = [:'@?', :'@:']
52
+ ASSIGN_OPERATORS = [:'@=', :'@+=', :'@-=', :'@*=', :'@**=', :'@%=', :'@/=', :'@|=', :'@&=', :'@^=',
53
+ :'@[]=', :'@||=', :'@&&=', :'@<<=', :'@>>=']
54
+ ACCESS_OPERATORS = [:'@[]']
55
+
56
+ OPERATORS = UNARY_OPERATORS + BINARY_OPERATORS + TERNARY_OPERATORS + ASSIGN_OPERATORS + ACCESS_OPERATORS
57
+
58
+
59
+ include Lexer, Statements, Const, Method, Call, Block, Args, Assignment, Operator,
60
+ If, Case, For, While, Identifier, Literal, String, Symbol, Array, Hash
61
+
62
+ attr_reader :src, :filename, :stack, :string_stack, :trailing_whitespace
63
+
64
+ def initialize(src, filename = nil, lineno = nil)
65
+ @src = src ||= filename && File.read(filename) || ''
66
+ @src.gsub!(/([\s\n]*)\Z/) { |s| @trailing_whitespace = Ruby::Whitespace.new(s) and nil }
67
+
68
+ @filename = filename
69
+ @stack = []
70
+ @stack = Stack.new
71
+ @string_stack = []
72
+
73
+ super
74
+ end
75
+
76
+ protected
77
+ def position
78
+ Ruby::Node::Position.new(lineno.to_i - 1, column)
79
+ end
80
+
81
+ def prolog
82
+ Ruby::Prolog.new(stack.buffer.flush)
83
+ end
84
+
85
+ def push(sexp = nil)
86
+ token = Token.new(sexp[0], sexp[1], position) if sexp.is_a?(::Array)
87
+ stack.push(token)
88
+ token
89
+ end
90
+
91
+ def pop(*args)
92
+ stack.pop(*args)
93
+ end
94
+
95
+ def pop_token(*types)
96
+ options = types.last.is_a?(::Hash) ? types.pop : {}
97
+ options[:max] = 1
98
+ pop_tokens(*types << options).first
99
+ end
100
+
101
+ def pop_tokens(*types)
102
+ pop(*types).map { |token| build_token(token) }.flatten.compact
103
+ end
104
+
105
+ def pop_identifier(type, options = {})
106
+ pop_token(type, options).to_identifier
107
+ end
108
+
109
+ def pop_string_content
110
+ pop_token(:@tstring_content).to_string_content
111
+ end
112
+
113
+ def pop_operator(options = {})
114
+ pop_token(*OPERATORS, options)
115
+ end
116
+
117
+ def pop_unary_operator(options = {})
118
+ pop_token(*UNARY_OPERATORS, options)
119
+ end
120
+
121
+ def pop_binary_operator(options = {})
122
+ pop_token(*BINARY_OPERATORS, options)
123
+ end
124
+
125
+ def pop_ternary_operator(options = {})
126
+ pop_token(*TERNARY_OPERATORS, options)
127
+ end
128
+
129
+ def pop_assignment_operator(options = {})
130
+ pop_token(*ASSIGN_OPERATORS, options)
131
+ end
132
+
133
+ def pop_end_data
134
+ token = pop_token(:@__end__)
135
+ token.token = Ruby::Node::Text::Clip.new(src, token.position).to_s if token # TODO clean up
136
+ token
137
+ end
138
+
139
+ def build_token(token)
140
+ Ruby::Token.new(token.token, token.position, token.prolog) if token
141
+ end
142
+
143
+ def build_keyword(token)
144
+ klass = Ruby.const_get(token.token[0].upcase + token.token[1..-1])
145
+ klass.new(token, token.position, token.prolog)
146
+ rescue NameError
147
+ Ruby::Keyword.new(token, token.position, token.prolog)
148
+ end
149
+
150
+ def build_xstring(token)
151
+ case token.type
152
+ when :@symbeg
153
+ Ruby::DynaSymbol.new(nil, build_token(token))
154
+ when :@regexp_beg
155
+ Ruby::Regexp.new(nil, build_token(token))
156
+ else
157
+ Ruby::ExecutableString.new(nil, build_token(token))
158
+ end
159
+ end
160
+
161
+ # def extract_src(from, to)
162
+ # lines = Ruby::Node::Text.split(src)
163
+ # lines[from.row] = lines[from.row][from.col..-1] # || ''
164
+ # lines[to.row] = lines[to.row][0, to.col]
165
+ # lines[from.row..to.row].join
166
+ # end
167
+ end
168
+ end
@@ -0,0 +1,34 @@
1
+ # When tokens are pushed to the stack they may first be buffered when they
2
+ # belong to the Prolog part of a Ruby::Node. Buffered tokens will then be
3
+ # aggregated to the Prolog of the next token that is not buffered. Tokens
4
+ # belonging to the Prolog part of a Ruby::Node are whitespace, separator and
5
+ # heredoc tokens.
6
+ #
7
+ # E.g. when a whitespace char (" ") is pushed to the stack it will be buffered.
8
+ # Then when an :@ident token is pushed to the stack the contents of the buffer
9
+ # will be assigned to the Prolog of the :@ident token. Thus the whitespace char
10
+ # ends up in the Prolog of the :@ident token.
11
+
12
+ class Ripper
13
+ class RubyBuilder < Ripper::SexpBuilder
14
+ class Buffer < Array
15
+ def aggregate(token)
16
+ if token.nil?
17
+ false
18
+ elsif token.whitespace?
19
+ self << Ruby::Whitespace.new(token.token, token.position)
20
+ true
21
+ elsif token.separator?
22
+ self << Ruby::Token.new(token.token, token.position)
23
+ true
24
+ elsif token.heredoc?
25
+ self << token.token
26
+ true
27
+ else
28
+ token.prolog = Ruby::Prolog.new(flush) unless empty?
29
+ false
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ class Ripper
2
+ class RubyBuilder < Ripper::SexpBuilder
3
+ module Args
4
+ def on_method_add_arg(call, args)
5
+ call.arguments = args
6
+ call
7
+ end
8
+
9
+ def on_arg_paren(args)
10
+ args ||= Ruby::ArgsList.new
11
+ args.rdelim ||= pop_token(:@rparen)
12
+ args.ldelim ||= pop_token(:@lparen)
13
+ args
14
+ end
15
+
16
+ def on_args_add_block(args, block)
17
+ args << Ruby::Arg.new(block, pop_token(:'@&')) if block
18
+ args
19
+ end
20
+
21
+ def on_args_add_star(args, arg)
22
+ args << Ruby::Arg.new(arg, pop_token(:'@*', :pass => true))
23
+ args
24
+ end
25
+
26
+ def on_args_add(args, arg)
27
+ args << arg
28
+ args
29
+ end
30
+
31
+ def on_blockarg(identifier)
32
+ Ruby::Arg.new(identifier, pop_token(:'@&'))
33
+ end
34
+
35
+ def on_args_new
36
+ Ruby::ArgsList.new
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,71 @@
1
+ class Ripper
2
+ class RubyBuilder < Ripper::SexpBuilder
3
+ module Array
4
+ def on_array(args)
5
+ rdelim = pop_token(:@rbracket)
6
+ ldelim = pop_token(:@lbracket)
7
+ args ? args.to_array(ldelim, rdelim) : Ruby::Array.new(nil, ldelim, rdelim)
8
+ end
9
+
10
+ def on_words_new(*args)
11
+ rdelim = pop_token(:@words_end)
12
+ ldelim = pop_token(:@words_beg)
13
+ words = Ruby::Array.new(nil, ldelim, rdelim)
14
+ string_stack << words
15
+ words
16
+ end
17
+
18
+ def on_words_add(array, word)
19
+ array.rdelim ||= pop_token(:@words_end)
20
+ array << word
21
+ array
22
+ end
23
+
24
+ def on_qwords_new(*args)
25
+ rdelim = pop_token(:@words_end)
26
+ ldelim = pop_token(:@qwords_beg)
27
+ words = Ruby::Array.new(nil, ldelim, rdelim)
28
+ string_stack << words unless rdelim
29
+ words
30
+ end
31
+
32
+ def on_qwords_add(array, word)
33
+ word = on_word_add(on_word_new, word) # simulating missing on_word_new and on_word_add events
34
+ array.rdelim ||= pop_token(:@words_end)
35
+ array << word
36
+ array
37
+ end
38
+
39
+ def on_words_end(rdelim = nil)
40
+ words = string_stack.pop
41
+ words.rdelim ||= pop_token(:@tstring_end, :@words_sep)
42
+ words
43
+ end
44
+
45
+ def on_aref(target, args)
46
+ args ||= Ruby::ArgsList.new
47
+ args.ldelim ||= pop_token(:@lbracket, :left => target)
48
+ args.rdelim ||= pop_token(:@rbracket, :reverse => true, :pass => true, :left => args.ldelim)
49
+ Ruby::Call.new(target, nil, nil, args)
50
+ end
51
+
52
+ def on_aref_field(target, args)
53
+ args ||= Ruby::ArgsList.new
54
+ args.ldelim ||= pop_token(:@lbracket, :left => target)
55
+ args.rdelim ||= pop_token(:@rbracket, :reverse => true, :pass => true, :left => args.ldelim)
56
+ Ruby::Call.new(target, nil, nil, args)
57
+ end
58
+
59
+ protected
60
+
61
+ WORD_DELIMITER_MAP = { '(' => ')', '[' => ']', '{' => '}' }
62
+
63
+ def closes_words?(token)
64
+ return false unless string_stack.last.is_a?(Ruby::Array)
65
+ return false unless string_stack.last.ldelim.token =~ /^%w\s*([^\s]*)/i
66
+ (WORD_DELIMITER_MAP[$1] || $1) == token.gsub(/[%w\s]/i, '')
67
+ end
68
+ end
69
+ end
70
+ end
71
+