ripper2ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+