riml 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path('../lib/environment', __FILE__)
1
+ require File.expand_path('../lib/riml/environment', __FILE__)
2
2
  require 'rake/testtask'
3
3
  require 'bundler/setup'
4
4
  require 'bundler/gem_tasks'
@@ -22,7 +22,7 @@ task :debug_parser do
22
22
  end
23
23
 
24
24
  def in_libdir
25
- Dir.chdir(File.expand_path("../lib", __FILE__)) do
25
+ Dir.chdir(File.expand_path("../lib/riml", __FILE__)) do
26
26
  yield
27
27
  end
28
28
  end
data/bin/riml CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # vim: syntax=ruby
3
3
 
4
- require File.expand_path("../../lib/environment", __FILE__)
4
+ require File.expand_path("../../lib/riml/environment", __FILE__)
5
5
 
6
6
  module Riml
7
7
  include Environment
@@ -20,6 +20,7 @@ module Riml
20
20
  options.check_syntax_files = []
21
21
  options.repl = false
22
22
  options.vi_readline = false
23
+ options.allow_undefined_global_classes = DEFAULT_PARSE_OPTIONS[:allow_undefined_global_classes]
23
24
  options.readable = DEFAULT_COMPILE_OPTIONS[:readable]
24
25
  options.output_dir = DEFAULT_COMPILE_FILES_OPTIONS[:output_dir]
25
26
 
@@ -36,7 +37,7 @@ module Riml
36
37
  options.stdio = true
37
38
  end
38
39
 
39
- opts.on("-k", "--check FILES", Array, "Checks syntax of file(s). Because Riml is (mostly) compatible with VimL, this can be used to check VimL's syntax as well.") do |filenames|
40
+ opts.on("-k", "--check FILES", Array, "Checks syntax of file(s). Because Riml is (mostly) compatible with VimL, this can also be used to check VimL syntax.") do |filenames|
40
41
  append_filenames_to_list_if_valid(options.check_syntax_files, *filenames)
41
42
  end
42
43
 
@@ -56,6 +57,10 @@ module Riml
56
57
  end
57
58
  end
58
59
 
60
+ opts.on("-a", "--allow-undef-global-classes", "Continue compilation when encountering undefined global class(es).") do
61
+ options.allow_undefined_global_classes = true
62
+ end
63
+
59
64
  opts.on("-o", "--output-dir DIR", "Output all .vim files in specified directory.") do |dir|
60
65
  options.output_dir = dir
61
66
  end
@@ -108,7 +113,8 @@ module Riml
108
113
  def start
109
114
  options = Options.parse(ARGV)
110
115
  compile_options = {
111
- :readable => options.readable
116
+ :readable => options.readable,
117
+ :allow_undefined_global_classes => options.allow_undefined_global_classes
112
118
  }
113
119
  compile_files_options = compile_options.merge(
114
120
  :output_dir => options.output_dir
@@ -124,7 +130,7 @@ module Riml
124
130
  # "ok (1 file)" OR "ok (2 files)"
125
131
  puts "ok (#{size} file#{'s' if size > 1})"
126
132
  elsif options.repl
127
- require 'repl'
133
+ require 'riml/repl'
128
134
  Riml::Repl.new(options.vi_readline, compile_options).run
129
135
  else
130
136
  ARGV.replace ['--help']
data/lib/riml.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  require 'pathname'
2
2
  require 'fileutils'
3
3
 
4
- require File.expand_path('../environment', __FILE__)
5
- require 'nodes'
6
- require 'lexer'
7
- require 'parser'
8
- require 'compiler'
9
- require 'warning_buffer'
10
- require 'include_cache'
4
+ require File.expand_path('../riml/environment', __FILE__)
5
+ require 'riml/nodes'
6
+ require 'riml/lexer'
7
+ require 'riml/parser'
8
+ require 'riml/compiler'
9
+ require 'riml/warning_buffer'
10
+ require 'riml/include_cache'
11
+ require 'riml/path_cache'
12
+ require 'riml/rewritten_ast_cache'
11
13
 
12
14
  module Riml
13
15
 
@@ -15,6 +17,11 @@ module Riml
15
17
  DEFAULT_COMPILE_FILES_OPTIONS = DEFAULT_COMPILE_OPTIONS.merge(
16
18
  :output_dir => nil
17
19
  )
20
+ DEFAULT_PARSE_OPTIONS = { :allow_undefined_global_classes => false }
21
+
22
+ EXTRACT_PARSE_OPTIONS = lambda { |k,_| DEFAULT_PARSE_OPTIONS.keys.include?(k.to_sym) }
23
+ EXTRACT_COMPILE_OPTIONS = lambda { |k,_| DEFAULT_COMPILE_OPTIONS.keys.include?(k.to_sym) }
24
+ EXTRACT_COMPILE_FILES_OPTIONS = lambda { |k,_| DEFAULT_COMPILE_FILES_OPTIONS.keys.include?(k.to_sym) }
18
25
 
19
26
  # lex code (String) into tokens (Array)
20
27
  def self.lex(code)
@@ -31,9 +38,12 @@ module Riml
31
38
  end
32
39
 
33
40
  def self.compile(input, options = {})
41
+ parse_options = options.select(&EXTRACT_PARSE_OPTIONS)
42
+ compile_options = options.select(&EXTRACT_COMPILE_OPTIONS)
34
43
  parser = Parser.new
44
+ parser.options = DEFAULT_PARSE_OPTIONS.merge(parse_options)
35
45
  compiler = Compiler.new
36
- compiler.options = DEFAULT_COMPILE_OPTIONS.merge(options)
46
+ compiler.options = DEFAULT_COMPILE_OPTIONS.merge(compile_options)
37
47
  do_compile(input, parser, compiler)
38
48
  end
39
49
 
@@ -79,10 +89,17 @@ module Riml
79
89
  filenames = filenames.dup
80
90
  parser, compiler = Parser.new, Compiler.new
81
91
 
92
+ # extract parser and compiler options from last argument, or use default
93
+ # options
82
94
  if filenames.last.is_a?(Hash)
83
- compiler.options = DEFAULT_COMPILE_FILES_OPTIONS.merge(filenames.pop)
95
+ options = filenames.pop
96
+ compile_options = options.select(&EXTRACT_COMPILE_FILES_OPTIONS)
97
+ parse_options = options.select(&EXTRACT_PARSE_OPTIONS)
98
+ compiler.options = DEFAULT_COMPILE_FILES_OPTIONS.merge(compile_options)
99
+ parser.options = DEFAULT_PARSE_OPTIONS.merge(parse_options)
84
100
  else
85
101
  compiler.options = DEFAULT_COMPILE_FILES_OPTIONS.dup
102
+ parser.options = DEFAULT_PARSE_OPTIONS.dup
86
103
  end
87
104
 
88
105
  filenames.uniq!
@@ -93,7 +110,8 @@ module Riml
93
110
  to_compile = filenames.shift(4)
94
111
  to_compile.each do |fname|
95
112
  _parser, _compiler = Parser.new, Compiler.new
96
- _compiler.options = compiler.options
113
+ _compiler.options = compiler.options.dup
114
+ _parser.options = parser.options.dup
97
115
  threads << Thread.new do
98
116
  f = File.open(fname)
99
117
  # `do_compile` will close file handle
@@ -134,17 +152,15 @@ module Riml
134
152
  def self.source_path
135
153
  get_path(:source_path)
136
154
  end
137
-
138
- def self.source_path=(path)
139
- set_path(:source_path, path)
155
+ def self.source_path=(path, force_cache_bust = false)
156
+ set_path(:source_path, path, force_cache_bust)
140
157
  end
141
158
 
142
159
  def self.include_path
143
160
  get_path(:include_path)
144
161
  end
145
-
146
- def self.include_path=(path)
147
- set_path(:include_path, path)
162
+ def self.include_path=(path, force_cache_bust = false)
163
+ set_path(:include_path, path, force_cache_bust)
148
164
  end
149
165
 
150
166
  def self.warn(warning)
@@ -154,9 +170,20 @@ module Riml
154
170
  def self.include_cache
155
171
  @include_cache
156
172
  end
157
- # initialize non-lazily because ||= isn't thread-safe
173
+ # initialize non-lazily because ||= isn't thread-safe and
174
+ # this is used across threads
158
175
  @include_cache = IncludeCache.new
159
176
 
177
+ def self.path_cache
178
+ @path_cache
179
+ end
180
+ @path_cache = PathCache.new
181
+
182
+ def self.rewritten_ast_cache
183
+ @rewritten_ast_cache
184
+ end
185
+ @rewritten_ast_cache = RewrittenASTCache.new
186
+
160
187
  class << self
161
188
  attr_accessor :warnings
162
189
  end
@@ -175,10 +202,11 @@ module Riml
175
202
  def self.warning_buffer
176
203
  @warning_buffer
177
204
  end
178
- # initialize non-lazily because ||= isn't thread-safe
205
+ # initialize non-lazily because ||= isn't thread-safe and
206
+ # this is used across threads
179
207
  @warning_buffer = WarningBuffer.new
180
208
 
181
- def self.set_path(name, path)
209
+ def self.set_path(name, path, force_cache_bust = false)
182
210
  return instance_variable_set("@#{name}", nil) if path.nil?
183
211
  path = path.split(':') if path.is_a?(String)
184
212
  path.each do |dir|
@@ -188,10 +216,17 @@ module Riml
188
216
  end
189
217
  end
190
218
  instance_variable_set("@#{name}", path)
219
+ cache_files_in_path(path, force_cache_bust)
220
+ path
191
221
  end
192
222
  self.source_path = nil # eliminate ivar warnings
193
223
  self.include_path = nil # eliminate ivar warnings
194
224
 
225
+ def self.cache_files_in_path(path, force_cache_bust = false)
226
+ @path_cache[path] = nil if force_cache_bust
227
+ @path_cache[path] || @path_cache.cache(path)
228
+ end
229
+
195
230
  def self.get_path(name)
196
231
  ivar = instance_variable_get("@#{name}")
197
232
  return ivar if ivar
@@ -214,9 +249,9 @@ module Riml
214
249
  end
215
250
  end
216
251
 
217
- FILE_HEADER = File.read(File.expand_path("../header.vim", __FILE__)) % VERSION.join('.')
218
- INCLUDE_COMMENT_FMT = File.read(File.expand_path("../included.vim", __FILE__))
219
- GET_SID_FUNCTION_SRC = File.read(File.expand_path("../get_sid_function.vim", __FILE__))
252
+ FILE_HEADER = File.read(File.expand_path("../riml/header.vim", __FILE__)) % VERSION.join('.')
253
+ INCLUDE_COMMENT_FMT = File.read(File.expand_path("../riml/included.vim", __FILE__))
254
+ GET_SID_FUNCTION_SRC = File.read(File.expand_path("../riml/get_sid_function.vim", __FILE__))
220
255
 
221
256
  def self.write_file(compiler, output, fname, cmdline_file = true)
222
257
  # writing out a file that's compiled from cmdline, output into output_dir
@@ -236,6 +271,8 @@ module Riml
236
271
  basename_without_riml_ext = File.basename(fname).sub(/\.riml\Z/i, '')
237
272
  FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
238
273
  full_path = File.join(output_dir, "#{basename_without_riml_ext}.vim")
274
+ # if a function definition is at the end of a file and the :readable compiler
275
+ # option is `true`, there will be 2 NL at EOF
239
276
  if output[-2..-1] == "\n\n"
240
277
  output.chomp!
241
278
  end
@@ -1,4 +1,5 @@
1
1
  require File.expand_path("../constants", __FILE__)
2
+ require File.expand_path("../imported_class", __FILE__)
2
3
  require File.expand_path("../class_map", __FILE__)
3
4
  require File.expand_path("../walker", __FILE__)
4
5
 
@@ -6,12 +7,15 @@ module Riml
6
7
  class AST_Rewriter
7
8
  include Riml::Constants
8
9
 
9
- attr_accessor :ast
10
- attr_reader :classes, :rewritten_included_and_sourced_files
10
+ attr_accessor :ast, :options
11
+ attr_reader :classes, :rewritten_included_and_sourced_files
11
12
 
12
13
  def initialize(ast = nil, classes = nil)
13
14
  @ast = ast
14
15
  @classes = classes || ClassMap.new
16
+ # AST_Rewriter shares options with Parser. Parser set AST_Rewriter's
17
+ # options before call to `rewrite`.
18
+ @options = nil
15
19
  # Keeps track of filenames with their rewritten ASTs, to prevent rewriting
16
20
  # the same AST more than once.
17
21
  @rewritten_included_and_sourced_files = {}
@@ -22,10 +26,16 @@ module Riml
22
26
  end
23
27
 
24
28
  def rewrite(filename = nil, included = false)
25
- if filename && (rewritten_ast = rewritten_included_and_sourced_files[filename])
26
- return rewritten_ast
29
+ if filename && (rewritten_ast = Riml.rewritten_ast_cache[filename])
30
+ rewrite_included_and_sourced_files!(filename)
31
+ rewritten_ast
32
+ end
33
+ if @options && @options[:allow_undefined_global_classes] && !@classes.has_global_import?
34
+ @classes.globbed_imports.unshift(ImportedClass.new('*'))
27
35
  end
28
36
  establish_parents(ast)
37
+ class_imports = RegisterImportedClasses.new(ast, classes)
38
+ class_imports.rewrite_on_match
29
39
  class_registry = RegisterDefinedClasses.new(ast, classes)
30
40
  class_registry.rewrite_on_match
31
41
  rewrite_included_and_sourced_files!(filename)
@@ -76,7 +86,7 @@ module Riml
76
86
  def rewrite_included_and_sourced_files!(filename)
77
87
  old_ast = ast
78
88
  ast.children.each do |node|
79
- next unless RimlCommandNode === node
89
+ next unless RimlFileCommandNode === node
80
90
  action = node.name == 'riml_include' ? 'include' : 'source'
81
91
 
82
92
  node.each_existing_file! do |file, fullpath|
@@ -86,20 +96,49 @@ module Riml
86
96
  # IncludeFileLoop/SourceFileLoop
87
97
  raise Riml.const_get("#{action.capitalize}FileLoop"), msg
88
98
  elsif filename == file
89
- raise UserArgumentError, "#{file.inspect} can't include itself"
99
+ raise UserArgumentError, "#{file.inspect} can't #{action} itself"
90
100
  end
91
101
  @included_and_sourced_file_refs[filename] << file
92
- riml_src = File.read(fullpath)
93
102
  # recursively parse included files with this ast_rewriter in order
94
103
  # to pick up any classes that are defined there
95
- rewritten_ast = Parser.new.parse(riml_src, self, file, action == 'include')
96
- rewritten_included_and_sourced_files[file] = rewritten_ast
104
+ rewritten_ast = nil
105
+ watch_for_class_pickup do
106
+ rewritten_ast = Riml.rewritten_ast_cache.fetch(file) do
107
+ riml_src = File.read(fullpath)
108
+ Parser.new.tap { |p| p.options = @options }.
109
+ parse(riml_src, self, file, action == 'include')
110
+ end
111
+ end
112
+ @rewritten_included_and_sourced_files[file] ||= rewritten_ast
97
113
  end
98
114
  end
99
115
  ensure
100
116
  self.ast = old_ast
101
117
  end
102
118
 
119
+ def watch_for_class_pickup
120
+ before_class_names = classes.class_names
121
+ ast = yield
122
+ after_class_names = classes.class_names
123
+ diff_class_names = after_class_names - before_class_names
124
+ class_diff = diff_class_names.inject({}) do |hash, class_name|
125
+ hash[class_name] = classes[class_name]
126
+ hash
127
+ end
128
+ # no classes were picked up, it could be that the cache was hit. Let's
129
+ # register the cached classes for this ast, if there are any
130
+ if class_diff.empty?
131
+ real_diff = Riml.rewritten_ast_cache.fetch_classes_registered(ast)
132
+ return if real_diff.empty?
133
+ real_diff.each do |k,v|
134
+ classes[k] = v unless classes.safe_fetch(k)
135
+ end
136
+ # new classes were picked up, let's save them with this ast as the key
137
+ else
138
+ Riml.rewritten_ast_cache.save_classes_registered(ast, class_diff)
139
+ end
140
+ end
141
+
103
142
  def recursive?
104
143
  true
105
144
  end
@@ -112,7 +151,7 @@ module Riml
112
151
  while included_files.any?
113
152
  incs = []
114
153
  included_files.each do |included_file|
115
- if (ast = rewritten_included_and_sourced_files[included_file])
154
+ if (ast = @rewritten_included_and_sourced_files[included_file])
116
155
  return true if ast.children.grep(ClassDefinitionNode).any?
117
156
  end
118
157
  incs.concat @included_and_sourced_file_refs[included_file]
@@ -139,6 +178,26 @@ module Riml
139
178
  ast.nodes.unshift fn
140
179
  end
141
180
 
181
+ class RegisterImportedClasses < AST_Rewriter
182
+ def match?(node)
183
+ RimlClassCommandNode === node
184
+ end
185
+
186
+ def replace(node)
187
+ node.class_names_without_modifiers.each do |class_name|
188
+ # TODO: check for wrong scope modifier
189
+ imported_class = ImportedClass.new(class_name)
190
+ if imported_class.globbed?
191
+ classes.globbed_imports << imported_class
192
+ else
193
+ imported_class.instance_variable_set("@registered_state", true)
194
+ classes["g:#{class_name}"] = imported_class
195
+ end
196
+ end
197
+ node.remove
198
+ end
199
+ end
200
+
142
201
  class RegisterDefinedClasses < AST_Rewriter
143
202
  def match?(node)
144
203
  ClassDefinitionNode === node
@@ -216,6 +275,7 @@ module Riml
216
275
  SelfToDictName.new(dict_name).rewrite_on_match(constructor)
217
276
  SuperToSuperclassFunction.new(node, classes).rewrite_on_match
218
277
  PrivateFunctionCallToPassObjExplicitly.new(node, classes).rewrite_on_match
278
+ SplatsToExecuteInCallingContext.new(node, classes).rewrite_on_match
219
279
 
220
280
  constructor.expressions.push(
221
281
  ReturnNode.new(GetVariableNode.new(nil, dict_name))
@@ -233,6 +293,150 @@ module Riml
233
293
  end
234
294
  end
235
295
 
296
+ # Rewrite constructs like:
297
+ #
298
+ # let animalObj = s:AnimalConstructor(*a:000)
299
+ #
300
+ # to:
301
+ #
302
+ # let __riml_splat_list = a:000
303
+ # let __riml_splat_size = len(__riml_splat_list)
304
+ # let __riml_splat_str_vars = []
305
+ # let __riml_splat_idx = 1
306
+ # while __riml_splat_idx <=# __riml_splat_size
307
+ # let __riml_splat_var_{__riml_splat_idx} = get(__riml_splat_list, __riml_splat_idx - 1)
308
+ # call add(__riml_splat_str_vars, __riml_splat_var_{__riml_splat_idx})
309
+ # let __riml_splat_idx += 1
310
+ # endwhile
311
+ # execute 'let l:animalObj = s:AnimalConstructor(' . join(__riml_splat_str_vars, ', ') . ')'
312
+ #
313
+ # Basically, mimic Ruby's approach to expanding lists to their
314
+ # constituent argument parts with '*' in calling context.
315
+ # NOTE: currently only works with `super`.
316
+ class SplatsToExecuteInCallingContext < AST_Rewriter
317
+
318
+ def match?(node)
319
+ if SplatNode === node && CallNode === node.parent
320
+ @splat_node = node
321
+ end
322
+ end
323
+
324
+ def replace(node)
325
+ construct_splat_str_vars_node = build_construct_splat_str_vars_node
326
+ call_node_args =
327
+ CallNode.new(
328
+ nil,
329
+ 'join',
330
+ [
331
+ GetVariableNode.new('n:', '__riml_splat_str_vars'),
332
+ StringNode.new(', ', :s)
333
+ ]
334
+ )
335
+ call_node = node.parent
336
+ node_to_execute = if AssignNode === call_node.parent
337
+ assign_node = call_node.parent
338
+ # This is necessary because this node is getting put into a new
339
+ # compiler where it's not wrapped in a function context, therefore
340
+ # variables will be script-local there unless their scope_modifier
341
+ # is set
342
+ assign_node.lhs.scope_modifier = 'l:'
343
+ assign_node
344
+ else
345
+ call_node
346
+ end
347
+ call_node.arguments.clear
348
+ compiler = Compiler.new
349
+ # have to dup node_to_execute here because, if not, its parent will
350
+ # get reset during this next compilation step
351
+ output = compiler.compile(Nodes.new([node_to_execute.dup]))
352
+ execute_string_node = StringNode.new(output.chomp[0..-2], :s)
353
+ execute_string_node.value.insert(0, 'call ') if CallNode === node_to_execute
354
+ execute_arg = BinaryOperatorNode.new(
355
+ '.',
356
+ [
357
+ execute_string_node,
358
+ BinaryOperatorNode.new(
359
+ '.',
360
+ [
361
+ call_node_args,
362
+ StringNode.new(')', :s)
363
+ ]
364
+ )
365
+ ]
366
+ )
367
+ execute_node = CallNode.new(nil, 'execute', [execute_arg])
368
+ establish_parents(execute_node)
369
+ node.remove
370
+ node_to_execute.replace_with(construct_splat_str_vars_node)
371
+ execute_node.parent = construct_splat_str_vars_node.parent
372
+ construct_splat_str_vars_node.parent.insert_after(construct_splat_str_vars_node, execute_node)
373
+ end
374
+
375
+ private
376
+
377
+ def build_construct_splat_str_vars_node
378
+ nodes = Nodes.new([])
379
+ splat_list_init = AssignNode.new(
380
+ '=',
381
+ GetVariableNode.new('n:', '__riml_splat_list'),
382
+ splat_value
383
+ )
384
+ splat_size = AssignNode.new(
385
+ '=',
386
+ GetVariableNode.new('n:', '__riml_splat_size'),
387
+ CallNode.new(nil, 'len', [GetVariableNode.new('n:', '__riml_splat_list')])
388
+ )
389
+ splat_string_vars_init = AssignNode.new(
390
+ '=',
391
+ GetVariableNode.new('n:', '__riml_splat_str_vars'),
392
+ ListNode.new([])
393
+ )
394
+ splat_list_idx_init = AssignNode.new(
395
+ '=',
396
+ GetVariableNode.new('n:', '__riml_splat_idx'),
397
+ NumberNode.new('1')
398
+ )
399
+ while_loop = WhileNode.new(
400
+ # condition
401
+ BinaryOperatorNode.new('<=', [GetVariableNode.new('n:', '__riml_splat_idx'), GetVariableNode.new('n:', '__riml_splat_size')]),
402
+ # body
403
+ Nodes.new([
404
+ AssignNode.new(
405
+ '=',
406
+ GetCurlyBraceNameNode.new('n:', CurlyBraceVariable.new([CurlyBracePart.new('__riml_splat_var_'), CurlyBraceVariable.new([CurlyBracePart.new(GetVariableNode.new('n:', '__riml_splat_idx'))])])),
407
+ CallNode.new(nil, 'get', [
408
+ GetVariableNode.new('n:', '__riml_splat_list'),
409
+ BinaryOperatorNode.new('-', [
410
+ GetVariableNode.new('n:', '__riml_splat_idx'),
411
+ NumberNode.new('1')
412
+ ])
413
+ ])
414
+ ),
415
+ ExplicitCallNode.new(nil, 'add', [
416
+ GetVariableNode.new('n:', '__riml_splat_str_vars'),
417
+ BinaryOperatorNode.new('.', [StringNode.new('__riml_splat_var_', :s), GetVariableNode.new('n:', '__riml_splat_idx')])
418
+ ]),
419
+ AssignNode.new('+=', GetVariableNode.new('n:', '__riml_splat_idx'), NumberNode.new('1'))
420
+ ])
421
+ )
422
+ nodes << splat_list_init << splat_size << splat_string_vars_init <<
423
+ splat_list_idx_init << while_loop
424
+ establish_parents(nodes)
425
+ nodes
426
+ end
427
+
428
+ def splat_value
429
+ n = @splat_node
430
+ until DefNode === n || n.nil?
431
+ n = n.parent
432
+ end
433
+ var_str_without_star = @splat_node.value[1..-1]
434
+ var_without_star = GetVariableNode.new(nil, var_str_without_star)
435
+ return var_without_star if n.nil? || !n.splat || (n.splat != @splat_node.value)
436
+ GetVariableNode.new('a:', '000')
437
+ end
438
+ end
439
+
236
440
  class SelfToObjArgumentInPrivateFunction < AST_Rewriter
237
441
  def initialize(ast, classes, class_node)
238
442
  super(ast, classes)
@@ -267,6 +471,7 @@ module Riml
267
471
  class_node = ast
268
472
  class_name = class_node.name
269
473
  node.scope_modifier = 's:'
474
+ node.original_name = node.name
270
475
  node.name = "#{class_name}_#{node.name}"
271
476
  node.sid = nil
272
477
  node.keywords -= ['dict']
@@ -306,7 +511,7 @@ module Riml
306
511
 
307
512
  class ExtendObjectWithMethods < AST_Rewriter
308
513
  def match?(node)
309
- DefMethodNode === node
514
+ node.instance_of?(DefMethodNode)
310
515
  end
311
516
 
312
517
  def replace(node)
@@ -379,10 +584,16 @@ module Riml
379
584
  end
380
585
 
381
586
  def replace(class_node)
382
- if class_node.superclass?
587
+ if class_node.superclass? && !imported_superclass?
383
588
  def_node = DefNode.new(
384
589
  '!', nil, nil, "initialize", superclass_params, nil, Nodes.new([SuperNode.new([], false)])
385
590
  )
591
+ # has imported superclass and no initialize method. Must create
592
+ # initialize method taking *splat parameter and call super with it
593
+ elsif class_node.superclass?
594
+ def_node = DefNode.new(
595
+ '!', nil, nil, "initialize", ['...'], nil, Nodes.new([SuperNode.new([SplatNode.new('*a:000')], false)])
596
+ )
386
597
  else
387
598
  def_node = DefNode.new(
388
599
  '!', nil, nil, "initialize", [], nil, Nodes.new([])
@@ -396,6 +607,10 @@ module Riml
396
607
  classes.superclass(ast.full_name).constructor.parameters
397
608
  end
398
609
 
610
+ def imported_superclass?
611
+ classes.superclass(ast.full_name).imported?
612
+ end
613
+
399
614
  def recursive?
400
615
  false
401
616
  end
@@ -458,7 +673,7 @@ module Riml
458
673
  end
459
674
  end
460
675
 
461
- # rewrites calls to 'super' in non-initialize function
676
+ # rewrites calls to 'super' in public/private non-initialize functions
462
677
  class SuperToSuperclassFunction < AST_Rewriter
463
678
  def match?(node)
464
679
  return false unless SuperNode === node
@@ -469,29 +684,55 @@ module Riml
469
684
  end
470
685
 
471
686
  def replace(node)
472
- # TODO: check if class even has superclass before all this
473
687
  func_scope = 's:'
474
688
  superclass = classes[ast.superclass_full_name]
475
689
  while superclass && !superclass.has_function?(func_scope, superclass_func_name(superclass)) && superclass.superclass?
476
690
  superclass = classes[superclass.superclass_full_name]
477
691
  end
478
- if superclass.nil? || !superclass.has_function?(func_scope, superclass_func_name(superclass))
692
+ superclass_function = superclass.find_function(func_scope, superclass_func_name(superclass))
693
+ if superclass.nil? || !superclass_function
479
694
  raise Riml::UserFunctionNotFoundError,
480
695
  "super was called in class #{ast.full_name} in " \
481
696
  "function #{@function_node.original_name}, but there are no " \
482
697
  "functions with this name in that class's superclass hierarchy."
483
698
  end
484
- call_node = CallNode.new(
485
- nil,
486
- DictGetDotNode.new(
699
+ node_args = if node.arguments.empty? && !node.with_parens && superclass_function.splat
700
+ [SplatNode.new('*a:000')]
701
+ else
702
+ if @function_node.private_function?
703
+ node.arguments.unshift GetVariableNode.new(nil, @function_node.parameters.first)
704
+ end
705
+ node.arguments
706
+ end
707
+ # check if SplatNode is in node_args. If it is, check if the splat
708
+ # value is equal to splat param. If it is, and we're inside a
709
+ # private function, we have to add the explicit object (first
710
+ # parameter to the function we're in) to the splat arg
711
+ if @function_node.private_function?
712
+ if (splat_node = node_args.detect { |arg| SplatNode === arg }) &&
713
+ @function_node.splat == splat_node.value
714
+ splat_node.value = "*[a:#{@function_node.parameters.first}] + a:000"
715
+ end
716
+ # call s.ClassA_private_func(args)
717
+ call_node_name = superclass_func_name(superclass)
718
+ else
719
+ # call self.ClassA_public_func(args)
720
+ call_node_name = DictGetDotNode.new(
487
721
  GetVariableNode.new(nil, 'self'),
488
722
  [superclass_func_name(superclass)]
489
- ),
490
- node.arguments
723
+ )
724
+ end
725
+ call_node = CallNode.new(
726
+ nil,
727
+ call_node_name,
728
+ node_args
491
729
  )
492
730
 
493
731
  node.replace_with(call_node)
494
- add_superclass_func_ref_to_constructor(superclass)
732
+ # private functions are NOT extended in constructor function
733
+ unless @function_node.private_function?
734
+ add_superclass_func_ref_to_constructor(superclass)
735
+ end
495
736
  reestablish_parents(@function_node)
496
737
  end
497
738