riml 0.3.3 → 0.3.4

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.
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