riml 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDk4OTEyMjkyYTk1YzVjYjEzNzAxNTllZGVmNjY0NGE5YTJjZWZmYQ==
5
+ data.tar.gz: !binary |-
6
+ OTY1ZDRlMDVjNGY3ZjA3YmViZGE2M2RjY2VkODg2ZjJhMDk5NTkxZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTk4MTY0Y2FiMGQ0ZDc4ODNjNDk4NDdmOGViZmFhODUxMmYxY2E1NjY5Njk4
10
+ ZDIxNGI3ZjI3ZjQyZjcxNDNmYWQxYWUyNTNlNzk0ODc1ZmEzZDU1YmUwZGQ1
11
+ YmQ1Y2E0N2M4YzE1OWFhMmZlNjkzYTZlOWQ4OWUyYmFlOWVlZmU=
12
+ data.tar.gz: !binary |-
13
+ MTc4ZjZkZjE1MmU3YTRkNDVlYjA2NTE4YzRhYjk1MGE1NTkwM2FlMjM1NTYw
14
+ OGU2ZWRmZDM2YmM2OTZlZmIwODI0ZmYxNTFjMWNlZWIxM2EyNWMyNDQxYzBk
15
+ MTU3NjJhMjNlM2Q3YTVkNDYxNjljZjZiNDNlMzA2NzQyOTZlODI=
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2013 by Luke Gruber
1
+ Copyright (c) 2012-2014 by Luke Gruber
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  [![Build Status](https://secure.travis-ci.org/luke-gru/riml.png?branch=master)](https://travis-ci.org/luke-gru/riml)
2
2
 
3
- Riml, a relaxed Vim script
4
- ====================================
3
+ Riml, a relaxed VimL
4
+ ====================
5
5
 
6
- Riml is a subset of Vim script with some added features, and it compiles to
7
- plain Vim script. Some of the added features include classes, string interpolation,
6
+ Riml is a subset of VimL with some added features, and it compiles to
7
+ plain VimL. Some of the added features include classes, string interpolation,
8
8
  heredocs, default case-sensitive string comparison, default parameters
9
9
  in functions, and other things programmers tend to take for granted.
10
10
  To see how Riml is compiled to VimL, just take a look at this README. The left
@@ -19,7 +19,7 @@ Variables
19
19
  count += 1 let s:count += 1
20
20
  end endwhile
21
21
 
22
- If you don't specify a scope modifier (or namespace in Vimspeak), it's script local (s:)
22
+ If you don't specify a scope modifier (or 'namespace' in Vimspeak), it's script local (s:)
23
23
  by default in the global scope. Within a function, variables without scope modifiers are plain
24
24
  old local variables.
25
25
 
@@ -70,7 +70,7 @@ Notice in the last line of Riml there's string interpolation. This works
70
70
  in double-quoted strings and heredocs, which we'll encounter later.
71
71
 
72
72
  In Riml, you can choose to end any block with 'end', or with whatever you used
73
- to do in Vim script ('endif', 'endfunction', etc...). Also, 'if' and 'unless' can
73
+ to do in VimL ('endif', 'endfunction', etc...). Also, 'if' and 'unless' can
74
74
  now be used as statement modifiers:
75
75
 
76
76
  callcount = 0 unless callcount?
@@ -82,15 +82,15 @@ Here, the compiled output is the same as the previous example's. Both 'if' and
82
82
 
83
83
  ###True and False
84
84
 
85
- a = true let a = 1
86
- b = false let b = 0
85
+ a = true let s:a = 1
86
+ b = false let s:b = 0
87
87
 
88
88
  Operators And Case Sensitivity
89
89
  ------------------------------
90
90
 
91
- if "hi" == greeting if "hi" ==# s:greeting
92
- echo greeting echo s:greeting
93
- end end
91
+ if "hi" == greeting if "hi" ==# s:greeting
92
+ echo greeting echo s:greeting
93
+ end end
94
94
 
95
95
  Comparisons compile to case-sensitive by default. To get case-insensitive
96
96
  comparisons, you have to explicitly use the form ending in '?' (ex: '==?').
@@ -101,7 +101,7 @@ cousin 'is#', and the same is true of 'isnot'.
101
101
 
102
102
  ###=== Operator
103
103
 
104
- Oh no, not another of THOSE operators! Well, this one is pretty sweet
104
+ Oh no, not another of __those__ operators! Well, this one is pretty sweet
105
105
  actually. In VimL, automatic type conversion can be a pain. For example:
106
106
 
107
107
  echo 4 ==# "4"
@@ -116,7 +116,7 @@ regarding equality:
116
116
  The '===' operator wraps both operands in lists:
117
117
 
118
118
  echo 4 === "4" echo [4] ==# ["4"]
119
-
119
+ => 0
120
120
 
121
121
  Heredocs
122
122
  --------
@@ -126,8 +126,8 @@ Heredocs
126
126
  Hooray!
127
127
  EOS
128
128
 
129
- Riml heredocs must have the ending pattern ('EOS' in this case) start at the
130
- beginning of the line. Interpolated expressions are allowed in heredocs.
129
+ Riml heredocs must have the ending pattern ('EOS' in this case, but can be anything) start
130
+ at the beginning of the line. Interpolated expressions are allowed in heredocs.
131
131
 
132
132
  Functions
133
133
  ---------
@@ -137,15 +137,16 @@ Functions
137
137
  return @/ return @/
138
138
  end endfunction
139
139
 
140
- When defining a function with no parameters, the parens after the function name are optional.
141
-
142
140
  Functions are by default prepended by 's:' unless explicitly prepended with a
143
141
  different scope modifier. Of course, you can use the old form ('function! Name()')
144
142
  for defining functions if you want, as Riml aims to be as compatible as possible
145
143
  with VimL. There are a few exceptions where Riml and VimL aren't compatible, and
146
144
  these differences are explained in the section 'Incompatibilities with VimL'.
147
145
 
148
- ###Default Arguments
146
+ When defining a function with no parameters and no keywords (such as 'dict' or 'abort'),
147
+ the parens after the function name are optional.
148
+
149
+ ###Default Parameters
149
150
 
150
151
  def fillSearchPat(pat = getDefaultSearchPat()) function! s:fillSearchPat(...)
151
152
  @/ = pat if get(a:000, 0, 'rimldefault') !=# 'rimldefault'
@@ -158,13 +159,63 @@ these differences are explained in the section 'Incompatibilities with VimL'.
158
159
  endfunction
159
160
 
160
161
 
161
- Default arguments must be the last arguments given to a function, but there can be more
162
- than one default argument. Also, a splat argument (... or \*argName) can come after default argument(s).
163
- Splats will be explained in the next section.
162
+ Default parameters must be the last parameters given to a function, but there can be more
163
+ than one default parameter. Also, a splat parameter (... or \*argName) can come after default parameters.
164
+ Splats parameters and splat arguments will be explained in the next section.
164
165
 
165
166
  We can now call the function 'fillSearchPat' without any arguments and it will use the default
166
- argument. Also, if we pass the string 'rimldefault', it will use the default argument as well. This
167
- is useful if a function has many default arguments.
167
+ parameter. Also, if we pass the string 'rimldefault', it will use the default parameter as well. This
168
+ is useful if a function has many default parameters.
169
+
170
+ ###Splat Parameters
171
+
172
+ A splat parameter is a formal parameter to a function starting with a '\*'. They must be the last
173
+ parameters given to a function.
174
+
175
+ def my_echo(*words) function! s:my_echo(...)
176
+ echo words echo a:000
177
+ end endfunction
178
+ my_echo("hello", "world") call my_echo("hello", "world")
179
+
180
+ => ["hello", "world"]
181
+
182
+ ###Splat Arguments
183
+
184
+ Splat arguments are splats used in a function call context. With the function `my_echo` defined in the
185
+ previous section, here's an example at work:
186
+
187
+ words = ["hello", "world"] let s:words = ["hello", "world"]
188
+ echo 'without splat:' echo 'without splat:'
189
+ my_echo(words) call my_echo(s:words)
190
+ echo 'with splat:' echo 'with splat:'
191
+ my_echo(*words) let s:__riml_splat_list = s:words
192
+
193
+ let s:__riml_splat_size = len(s:__riml_splat_list)
194
+ let s:__riml_splat_str_vars = []
195
+ let s:__riml_splat_idx = 1
196
+ while s:__riml_splat_idx <=# s:__riml_splat_size
197
+ let s:__riml_splat_var_{s:__riml_splat_idx} = get(s:__riml_splat_list, s:__riml_splat_idx - 1)
198
+ call add(s:__riml_splat_str_vars, '__riml_splat_var_' . s:__riml_splat_idx)
199
+ let s:__riml_splat_idx += 1
200
+ endwhile
201
+ execute 'call s:my_echo(' . join(s:__riml_splat_str_vars, ', ') . ')'
202
+
203
+ => without splat:
204
+ => [["hello", "world"]]
205
+
206
+ => with splat:
207
+ => ["hello", "world"]
208
+
209
+ The way to think about how splat arguments work is that the list variable that's
210
+ splatted is broke into its constituent parts, just like in Ruby. So, `my_echo(*words)`
211
+ is equivalent to `my_echo("hello", "world")` in this example. Notice how you can now
212
+ pass the exact same arguments your function received to another function by splatting a
213
+ parameter and passing it on to another function.
214
+
215
+ def some_func(*args)
216
+ other_func(*args) " passes same arguments along to other function.
217
+ end
218
+
168
219
 
169
220
  Classes
170
221
  -------
@@ -193,6 +244,10 @@ Classes
193
244
  return self.otherData
194
245
  endfunction
195
246
 
247
+ In this basic example of a class, we see a \*splat variable. This is just a
248
+ convenient way to refer to 'a:000' in the body of a function. Splat variables
249
+ are optional parameters and get compiled to '...'.
250
+
196
251
  Classes can only be defined once, and cannot be reopened. Public member
197
252
  functions are defined with 'defm'. If you want to create a non-public function
198
253
  inside a class, use 'def'. To create an instance of this class, simply:
@@ -208,9 +263,6 @@ For example:
208
263
  ...
209
264
  end
210
265
 
211
- In this basic example of a class, we see a \*splat variable. This is just a
212
- convenient way to refer to 'a:000' in the body of a function. Splat variables
213
- are optional parameters and get compiled to '...'.
214
266
 
215
267
  ###Class Inheritance
216
268
 
@@ -251,6 +303,11 @@ instances use the initialize function from 'Translation', and new instances must
251
303
  be provided with an 'input' argument on creation. Basically, if a class doesn't
252
304
  provide an initialize function, it uses its superclass's.
253
305
 
306
+ If you look at the last line of Riml in the previous example, you'll see that
307
+ it doesn't use VimL's builtin 'call' function for calling the 'translate'
308
+ method on the translation object. Riml can figure out when 'call' is necessary,
309
+ and will add it to the compiled VimL.
310
+
254
311
  A base class and inheriting class can have different scope modifiers. For example, if you
255
312
  had a script-local base class and wanted to extend it but have the extending class be
256
313
  global, this is not a problem. Simply:
@@ -259,10 +316,6 @@ global, this is not a problem. Simply:
259
316
  ...
260
317
  end
261
318
 
262
- If you look at the last line of Riml in the previous example, you'll see that
263
- it doesn't use Vim script's builtin 'call' function for calling the 'translate'
264
- method on the translation object. Riml can figure out when 'call' is necessary,
265
- and will add it to the compiled Vim script.
266
319
 
267
320
  ###Using 'super'
268
321
 
data/Rakefile CHANGED
@@ -15,6 +15,11 @@ Rake::TestTask.new(:test) do |t|
15
15
  t.test_files = FileList['test/**/*_test.rb'].to_a
16
16
  end
17
17
 
18
+ desc 'Run benchmarks'
19
+ task :bench => [:parser] do
20
+ load File.expand_path('../benchmarks/run', __FILE__)
21
+ end
22
+
18
23
  desc 'recreate lib/parser.rb from lib/grammar.y using racc'
19
24
  task :parser do
20
25
  in_libdir { sh 'racc -o parser.rb grammar.y' }
data/bin/riml CHANGED
@@ -135,6 +135,7 @@ module Riml
135
135
  compile_files_options = compile_options.merge(
136
136
  :output_dir => options.output_dir
137
137
  )
138
+ Riml.config = options
138
139
  Riml.debug = options.debug
139
140
  if options.stdio
140
141
  puts Riml.compile($stdin.read, compile_options)
@@ -1,5 +1,6 @@
1
1
  require 'pathname'
2
2
  require 'fileutils'
3
+ require 'ostruct'
3
4
 
4
5
  if RUBY_VERSION < '1.9'
5
6
  require 'thread'
@@ -229,6 +230,20 @@ module Riml
229
230
  self.warnings = true
230
231
  self.debug = false
231
232
 
233
+ # @return OpenStruct|nil
234
+ def self.config
235
+ @config
236
+ end
237
+
238
+ # possible values: OpenStruct|nil
239
+ def self.config=(config)
240
+ unless config.nil? || OpenStruct === config
241
+ raise ArgumentError, "config must be OpenStruct or NilClass, is #{config.class}"
242
+ end
243
+ @config = config
244
+ end
245
+ self.config = nil # avoid ivar warnings
246
+
232
247
  private
233
248
 
234
249
  def self.flush_warnings
@@ -259,8 +274,8 @@ module Riml
259
274
  cache_files_in_path(path, force_cache_bust)
260
275
  path
261
276
  end
262
- self.source_path = nil # eliminate ivar warnings
263
- self.include_path = nil # eliminate ivar warnings
277
+ self.source_path = nil # avoid ivar warnings
278
+ self.include_path = nil # avoid ivar warnings
264
279
 
265
280
  def self.cache_files_in_path(path, force_cache_bust = false)
266
281
  @path_cache[path] = nil if force_cache_bust
@@ -13,19 +13,24 @@ module Riml
13
13
 
14
14
  def initialize(ast = nil, classes = nil, class_dependency_graph = nil)
15
15
  @ast = ast
16
- @classes = classes || ClassMap.new
17
- # AST_Rewriter shares options with Parser. Parser set AST_Rewriter's
18
- # options before call to `rewrite`.
19
- @options = nil
20
- # Keeps track of filenames with their rewritten ASTs, to prevent rewriting
21
- # the same AST more than once.
22
- @rewritten_included_and_sourced_files = {}
23
- # Keeps track of which filenames included/sourced which.
24
- # ex: { nil => ["main.riml"], "main.riml" => ["lib1.riml", "lib2.riml"],
25
- # "lib1.riml" => [], "lib2.riml" => [] }
26
- @included_and_sourced_file_refs = Hash.new { |h, k| h[k] = [] }
27
- @class_dependency_graph = class_dependency_graph || ClassDependencyGraph.new
28
- @resolving_class_dependencies = nil
16
+ @classes = classes
17
+ @class_dependency_graph = class_dependency_graph
18
+ # only the top-most rewriter has these properties and defaults
19
+ if self.instance_of?(Riml::AST_Rewriter)
20
+ @classes ||= ClassMap.new
21
+ # AST_Rewriter shares options with Parser. Parser set AST_Rewriter's
22
+ # options before call to `rewrite`.
23
+ @options = nil
24
+ # Keeps track of filenames with their rewritten ASTs, to prevent rewriting
25
+ # the same AST more than once.
26
+ @rewritten_included_and_sourced_files = {}
27
+ # Keeps track of which filenames included/sourced which.
28
+ # ex: { nil => ["main.riml"], "main.riml" => ["lib1.riml", "lib2.riml"],
29
+ # "lib1.riml" => [], "lib2.riml" => [] }
30
+ @included_and_sourced_file_refs = Hash.new { |h, k| h[k] = [] }
31
+ @class_dependency_graph ||= ClassDependencyGraph.new
32
+ @resolving_class_dependencies = nil
33
+ end
29
34
  end
30
35
 
31
36
  def rewrite(filename = nil, included = false)
@@ -57,7 +62,8 @@ module Riml
57
62
  CallToExplicitCall.new(ast, classes),
58
63
  DefaultParamToIfNode.new(ast, classes),
59
64
  DeserializeVarAssignment.new(ast, classes),
60
- TopLevelDefMethodToDef.new(ast, classes)
65
+ TopLevelDefMethodToDef.new(ast, classes),
66
+ SplatsToExecuteInCallingContext.new(ast, classes)
61
67
  ]
62
68
  rewriters.each do |rewriter|
63
69
  rewriter.rewrite_on_match
@@ -72,8 +78,8 @@ module Riml
72
78
 
73
79
  def do_establish_parents(node)
74
80
  node.children.each do |child|
75
- child.parent_node = node if child.respond_to?(:parent_node=)
76
- end if node.respond_to?(:children)
81
+ child.parent_node = node if Visitable === child
82
+ end if Visitable === node
77
83
  end
78
84
 
79
85
  def rewrite_on_match(node = ast)
@@ -437,150 +443,6 @@ module Riml
437
443
  end
438
444
  end
439
445
 
440
- # Rewrite constructs like:
441
- #
442
- # let animalObj = s:AnimalConstructor(*a:000)
443
- #
444
- # to:
445
- #
446
- # let __riml_splat_list = a:000
447
- # let __riml_splat_size = len(__riml_splat_list)
448
- # let __riml_splat_str_vars = []
449
- # let __riml_splat_idx = 1
450
- # while __riml_splat_idx <=# __riml_splat_size
451
- # let __riml_splat_var_{__riml_splat_idx} = get(__riml_splat_list, __riml_splat_idx - 1)
452
- # call add(__riml_splat_str_vars, __riml_splat_var_{__riml_splat_idx})
453
- # let __riml_splat_idx += 1
454
- # endwhile
455
- # execute 'let l:animalObj = s:AnimalConstructor(' . join(__riml_splat_str_vars, ', ') . ')'
456
- #
457
- # Basically, mimic Ruby's approach to expanding lists to their
458
- # constituent argument parts with '*' in calling context.
459
- # NOTE: currently only works with `super`.
460
- class SplatsToExecuteInCallingContext < AST_Rewriter
461
-
462
- def match?(node)
463
- if SplatNode === node && CallNode === node.parent
464
- @splat_node = node
465
- end
466
- end
467
-
468
- def replace(node)
469
- construct_splat_str_vars_node = build_construct_splat_str_vars_node
470
- call_node_args =
471
- CallNode.new(
472
- nil,
473
- 'join',
474
- [
475
- GetVariableNode.new('n:', '__riml_splat_str_vars'),
476
- StringNode.new(', ', :s)
477
- ]
478
- )
479
- call_node = node.parent
480
- node_to_execute = if AssignNode === call_node.parent
481
- assign_node = call_node.parent
482
- # This is necessary because this node is getting put into a new
483
- # compiler where it's not wrapped in a function context, therefore
484
- # variables will be script-local there unless their scope_modifier
485
- # is set
486
- assign_node.lhs.scope_modifier = 'l:'
487
- assign_node
488
- else
489
- call_node
490
- end
491
- call_node.arguments.clear
492
- compiler = Compiler.new
493
- # have to dup node_to_execute here because, if not, its parent will
494
- # get reset during this next compilation step
495
- output = compiler.compile(Nodes.new([node_to_execute.dup]))
496
- execute_string_node = StringNode.new(output.chomp[0..-2], :s)
497
- execute_string_node.value.insert(0, 'call ') if CallNode === node_to_execute
498
- execute_arg = BinaryOperatorNode.new(
499
- '.',
500
- [
501
- execute_string_node,
502
- BinaryOperatorNode.new(
503
- '.',
504
- [
505
- call_node_args,
506
- StringNode.new(')', :s)
507
- ]
508
- )
509
- ]
510
- )
511
- execute_node = CallNode.new(nil, 'execute', [execute_arg])
512
- establish_parents(execute_node)
513
- node.remove
514
- node_to_execute.replace_with(construct_splat_str_vars_node)
515
- execute_node.parent = construct_splat_str_vars_node.parent
516
- construct_splat_str_vars_node.parent.insert_after(construct_splat_str_vars_node, execute_node)
517
- end
518
-
519
- private
520
-
521
- def build_construct_splat_str_vars_node
522
- nodes = Nodes.new([])
523
- splat_list_init = AssignNode.new(
524
- '=',
525
- GetVariableNode.new('n:', '__riml_splat_list'),
526
- splat_value
527
- )
528
- splat_size = AssignNode.new(
529
- '=',
530
- GetVariableNode.new('n:', '__riml_splat_size'),
531
- CallNode.new(nil, 'len', [GetVariableNode.new('n:', '__riml_splat_list')])
532
- )
533
- splat_string_vars_init = AssignNode.new(
534
- '=',
535
- GetVariableNode.new('n:', '__riml_splat_str_vars'),
536
- ListNode.new([])
537
- )
538
- splat_list_idx_init = AssignNode.new(
539
- '=',
540
- GetVariableNode.new('n:', '__riml_splat_idx'),
541
- NumberNode.new('1')
542
- )
543
- while_loop = WhileNode.new(
544
- # condition
545
- BinaryOperatorNode.new('<=', [GetVariableNode.new('n:', '__riml_splat_idx'), GetVariableNode.new('n:', '__riml_splat_size')]),
546
- # body
547
- Nodes.new([
548
- AssignNode.new(
549
- '=',
550
- GetCurlyBraceNameNode.new('n:', CurlyBraceVariable.new([CurlyBracePart.new('__riml_splat_var_'), CurlyBraceVariable.new([CurlyBracePart.new(GetVariableNode.new('n:', '__riml_splat_idx'))])])),
551
- CallNode.new(nil, 'get', [
552
- GetVariableNode.new('n:', '__riml_splat_list'),
553
- BinaryOperatorNode.new('-', [
554
- GetVariableNode.new('n:', '__riml_splat_idx'),
555
- NumberNode.new('1')
556
- ])
557
- ])
558
- ),
559
- ExplicitCallNode.new(nil, 'add', [
560
- GetVariableNode.new('n:', '__riml_splat_str_vars'),
561
- BinaryOperatorNode.new('.', [StringNode.new('__riml_splat_var_', :s), GetVariableNode.new('n:', '__riml_splat_idx')])
562
- ]),
563
- AssignNode.new('+=', GetVariableNode.new('n:', '__riml_splat_idx'), NumberNode.new('1'))
564
- ])
565
- )
566
- nodes << splat_list_init << splat_size << splat_string_vars_init <<
567
- splat_list_idx_init << while_loop
568
- establish_parents(nodes)
569
- nodes
570
- end
571
-
572
- def splat_value
573
- n = @splat_node
574
- until DefNode === n || n.nil?
575
- n = n.parent
576
- end
577
- var_str_without_star = @splat_node.value[1..-1]
578
- var_without_star = GetVariableNode.new(nil, var_str_without_star)
579
- return var_without_star if n.nil? || !n.splat || (n.splat != @splat_node.value)
580
- GetVariableNode.new('a:', '000')
581
- end
582
- end
583
-
584
446
  class SelfToObjArgumentInPrivateFunction < AST_Rewriter
585
447
  def initialize(ast, classes, class_node)
586
448
  super(ast, classes)
@@ -744,7 +606,7 @@ module Riml
744
606
  # initialize method taking *splat parameter and call super with it
745
607
  elsif class_node.superclass?
746
608
  def_node = DefNode.new(
747
- '!', nil, nil, "initialize", ['...'], nil, Nodes.new([SuperNode.new([SplatNode.new('*a:000')], false)])
609
+ '!', nil, nil, "initialize", ['...'], nil, Nodes.new([SuperNode.new([SplatNode.new(GetVariableNode.new('a:', '000'))], false)])
748
610
  )
749
611
  else
750
612
  def_node = DefNode.new(
@@ -851,7 +713,7 @@ module Riml
851
713
  raise error
852
714
  end
853
715
  node_args = if node.arguments.empty? && !node.with_parens && superclass_function.splat
854
- [SplatNode.new('*a:000')]
716
+ [SplatNode.new(GetVariableNode.new('a:', '000'))]
855
717
  else
856
718
  if @function_node.private_function?
857
719
  node.arguments.unshift GetVariableNode.new(nil, @function_node.parameters.first)
@@ -863,9 +725,19 @@ module Riml
863
725
  # private function, we have to add the explicit object (first
864
726
  # parameter to the function we're in) to the splat arg
865
727
  if @function_node.private_function?
866
- if (splat_node = node_args.detect { |arg| SplatNode === arg }) &&
867
- @function_node.splat == splat_node.value
868
- splat_node.value = "*[a:#{@function_node.parameters.first}] + a:000"
728
+ if (splat_node = node_args.detect { |arg| SplatNode === arg })
729
+ splat_node.expression = WrapInParensNode.new(
730
+ BinaryOperatorNode.new(
731
+ '+',
732
+ [
733
+ ListNode.new([
734
+ GetVariableNode.new('a:', @function_node.parameters.first)
735
+ ]),
736
+ GetVariableNode.new('a:', '000')
737
+ ]
738
+ )
739
+ )
740
+ establish_parents(splat_node.expression)
869
741
  end
870
742
  # call s.ClassA_private_func(args)
871
743
  call_node_name = superclass_func_name(superclass)
@@ -925,6 +797,174 @@ module Riml
925
797
  end
926
798
  end # ClassDefinitionToFunctions
927
799
 
800
+ # Rewrite constructs like:
801
+ #
802
+ # let animalObj = s:AnimalConstructor(*a:000)
803
+ #
804
+ # to:
805
+ #
806
+ # let __riml_splat_list = a:000
807
+ # let __riml_splat_size = len(__riml_splat_list)
808
+ # let __riml_splat_str_vars = []
809
+ # let __riml_splat_idx = 1
810
+ # while __riml_splat_idx <=# __riml_splat_size
811
+ # let __riml_splat_var_{__riml_splat_idx} = get(__riml_splat_list, __riml_splat_idx - 1)
812
+ # call add(__riml_splat_str_vars, __riml_splat_var_{__riml_splat_idx})
813
+ # let __riml_splat_idx += 1
814
+ # endwhile
815
+ # execute 'let s:animalObj = s:AnimalConstructor(' . join(__riml_splat_str_vars, ', ') . ')'
816
+ #
817
+ # Basically, mimic Ruby's approach to expanding lists to their
818
+ # constituent argument parts with '*' in calling context.
819
+ class SplatsToExecuteInCallingContext < AST_Rewriter
820
+
821
+ def match?(node)
822
+ if SplatNode === node && CallNode === node.parent
823
+ @splat_node = node
824
+ end
825
+ end
826
+
827
+ def replace(node)
828
+ construct_splat_str_vars_node = build_construct_splat_str_vars_node
829
+ call_node_args =
830
+ CallNode.new(
831
+ nil,
832
+ 'join',
833
+ [
834
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_str_vars'),
835
+ StringNode.new(', ', :s)
836
+ ]
837
+ )
838
+ call_node = node.parent
839
+ node_to_execute = if AssignNode === call_node.parent
840
+ assign_node = call_node.parent
841
+ n = call_node
842
+ until DefNode === n || n.nil?
843
+ n = n.parent
844
+ end
845
+ # This is necessary because this node is getting put into a new
846
+ # compiler where it not wrapped in a function context, therefore
847
+ # variables will be script-local there unless their scope_modifier
848
+ # is set
849
+ if n && assign_node.lhs.scope_modifier.nil?
850
+ if global_scope?
851
+ assign_node.lhs.scope_modifier = 's:'
852
+ else
853
+ assign_node.lhs.scope_modifier = 'l:'
854
+ end
855
+ end
856
+ assign_node
857
+ else
858
+ call_node
859
+ end
860
+ call_node.arguments.clear
861
+ compiler = Compiler.new
862
+ # have to dup node_to_execute here because, if not, its parent will
863
+ # get reset during this next compilation step
864
+ output = compiler.compile(Nodes.new([node_to_execute.clone]))
865
+ execute_string_node = StringNode.new(output.chomp[0..-2], :s)
866
+ if node_to_execute.instance_of?(CallNode)
867
+ execute_string_node.value.insert(0, 'call ')
868
+ end
869
+ execute_arg = BinaryOperatorNode.new(
870
+ '.',
871
+ [
872
+ execute_string_node,
873
+ BinaryOperatorNode.new(
874
+ '.',
875
+ [
876
+ call_node_args,
877
+ StringNode.new(')', :s)
878
+ ]
879
+ )
880
+ ]
881
+ )
882
+ execute_node = CallNode.new(nil, 'execute', [execute_arg])
883
+ establish_parents(execute_node)
884
+ node.remove
885
+ node_to_execute.replace_with(construct_splat_str_vars_node)
886
+ execute_node.parent = construct_splat_str_vars_node.parent
887
+ construct_splat_str_vars_node.parent.insert_after(construct_splat_str_vars_node, execute_node)
888
+ end
889
+
890
+ private
891
+
892
+ def build_construct_splat_str_vars_node
893
+ nodes = Nodes.new([])
894
+ splat_list_init = AssignNode.new(
895
+ '=',
896
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_list'),
897
+ @splat_node.expression
898
+ )
899
+ splat_size = AssignNode.new(
900
+ '=',
901
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_size'),
902
+ CallNode.new(nil, 'len', [GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_list')])
903
+ )
904
+ splat_string_vars_init = AssignNode.new(
905
+ '=',
906
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_str_vars'),
907
+ ListNode.new([])
908
+ )
909
+ splat_list_idx_init = AssignNode.new(
910
+ '=',
911
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_idx'),
912
+ NumberNode.new('1')
913
+ )
914
+ while_loop = WhileNode.new(
915
+ # condition
916
+ BinaryOperatorNode.new('<=', [GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_idx'), GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_size')]),
917
+ # body
918
+ Nodes.new([
919
+ AssignNode.new(
920
+ '=',
921
+ GetCurlyBraceNameNode.new(tmp_var_modifier.dup, CurlyBraceVariable.new([CurlyBracePart.new('__riml_splat_var_'), CurlyBraceVariable.new([CurlyBracePart.new(GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_idx'))])])),
922
+ CallNode.new(nil, 'get', [
923
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_list'),
924
+ BinaryOperatorNode.new('-', [
925
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_idx'),
926
+ NumberNode.new('1')
927
+ ])
928
+ ])
929
+ ),
930
+ ExplicitCallNode.new(nil, 'add', [
931
+ GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_str_vars'),
932
+ BinaryOperatorNode.new('.', [StringNode.new('__riml_splat_var_', :s), GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_idx')])
933
+ ]),
934
+ AssignNode.new('+=', GetVariableNode.new(tmp_var_modifier.dup, '__riml_splat_idx'), NumberNode.new('1'))
935
+ ])
936
+ )
937
+ nodes << splat_list_init << splat_size << splat_string_vars_init <<
938
+ splat_list_idx_init << while_loop
939
+ establish_parents(nodes)
940
+ nodes
941
+ end
942
+
943
+ def tmp_var_modifier
944
+ @tmp_var_modifier ||= begin
945
+ n = @splat_node
946
+ while n != nil && !(DefNode === n)
947
+ n = n.parent
948
+ end
949
+ # n is either `nil` or DefNode
950
+ if n.nil?
951
+ 's:'
952
+ else
953
+ 'n:'
954
+ end
955
+ end
956
+ end
957
+
958
+ def global_scope?
959
+ tmp_var_modifier == 's:'
960
+ end
961
+
962
+ def local_scope?
963
+ not global_scope?
964
+ end
965
+ end
966
+
967
+
928
968
  class ObjectInstantiationToCall < AST_Rewriter
929
969
  def match?(node)
930
970
  ObjectInstantiationNode === node
@@ -1011,7 +1051,7 @@ module Riml
1011
1051
  def replace(node)
1012
1052
  orig_assign = node.dup
1013
1053
  assigns = []
1014
- while assign_node = (node.respond_to?(:rhs) && node.rhs)
1054
+ while assign_node = (AssignNode === node && node.rhs)
1015
1055
  assigns.unshift([node.lhs, node.rhs])
1016
1056
  node = assign_node
1017
1057
  end