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