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 +2 -2
- data/bin/riml +10 -4
- data/lib/riml.rb +59 -22
- data/lib/{ast_rewriter.rb → riml/ast_rewriter.rb} +262 -21
- data/lib/{backtrace_filter.rb → riml/backtrace_filter.rb} +0 -0
- data/lib/{class_map.rb → riml/class_map.rb} +24 -1
- data/lib/{compiler.rb → riml/compiler.rb} +9 -9
- data/lib/{constants.rb → riml/constants.rb} +3 -1
- data/lib/{environment.rb → riml/environment.rb} +1 -1
- data/lib/{errors.rb → riml/errors.rb} +3 -1
- data/lib/{get_sid_function.vim → riml/get_sid_function.vim} +0 -0
- data/lib/{grammar.y → riml/grammar.y} +44 -12
- data/lib/{header.vim → riml/header.vim} +0 -0
- data/lib/riml/imported_class.rb +67 -0
- data/lib/{include_cache.rb → riml/include_cache.rb} +3 -2
- data/lib/{included.vim → riml/included.vim} +0 -0
- data/lib/{lexer.rb → riml/lexer.rb} +13 -8
- data/lib/{nodes.rb → riml/nodes.rb} +57 -18
- data/lib/riml/parser.rb +3231 -0
- data/lib/riml/path_cache.rb +50 -0
- data/lib/{repl.rb → riml/repl.rb} +1 -1
- data/lib/riml/rewritten_ast_cache.rb +31 -0
- data/lib/{walker.rb → riml/walker.rb} +0 -0
- data/lib/{warning_buffer.rb → riml/warning_buffer.rb} +9 -6
- data/version.rb +2 -2
- metadata +24 -22
- data/lib/parser.output +0 -13950
- data/lib/parser.rb +0 -3086
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
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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 =
|
26
|
-
|
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
|
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
|
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 =
|
96
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
485
|
-
|
486
|
-
|
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
|
-
|
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
|
-
|
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
|
|