riml 0.3.6 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +4 -0
- data/bin/riml +12 -2
- data/lib/riml.rb +16 -10
- data/lib/riml/ast_rewriter.rb +46 -39
- data/lib/riml/backtrace_filter.rb +1 -1
- data/lib/riml/compiler.rb +21 -18
- data/lib/riml/constants.rb +5 -1
- data/lib/riml/errors.rb +49 -4
- data/lib/riml/file_rollback.rb +4 -2
- data/lib/riml/grammar.y +16 -11
- data/lib/riml/imported_class.rb +2 -2
- data/lib/riml/lexer.rb +116 -114
- data/lib/riml/nodes.rb +27 -12
- data/lib/riml/parser.rb +753 -723
- data/lib/riml/path_cache.rb +4 -0
- data/lib/riml/repl.rb +1 -1
- data/lib/riml/warning_buffer.rb +2 -0
- data/version.rb +2 -2
- metadata +10 -10
data/Rakefile
CHANGED
data/bin/riml
CHANGED
@@ -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.debug = false
|
23
24
|
options.allow_undefined_global_classes = DEFAULT_PARSE_OPTIONS[:allow_undefined_global_classes]
|
24
25
|
options.include_reordering = DEFAULT_PARSE_OPTIONS[:include_reordering]
|
25
26
|
options.readable = DEFAULT_COMPILE_OPTIONS[:readable]
|
@@ -82,6 +83,10 @@ module Riml
|
|
82
83
|
options.vi_readline = options.repl = true
|
83
84
|
end
|
84
85
|
|
86
|
+
opts.on("-D", "--debug", "Run in debug mode. Full stacktraces are shown on error.") do
|
87
|
+
options.debug = true
|
88
|
+
end
|
89
|
+
|
85
90
|
opts.on_tail("-v", "--version", "Show riml version.") do
|
86
91
|
puts VERSION.join('.')
|
87
92
|
exit
|
@@ -130,11 +135,12 @@ module Riml
|
|
130
135
|
compile_files_options = compile_options.merge(
|
131
136
|
:output_dir => options.output_dir
|
132
137
|
)
|
138
|
+
Riml.debug = options.debug
|
133
139
|
if options.stdio
|
134
140
|
puts Riml.compile($stdin.read, compile_options)
|
135
141
|
elsif options.compile_files.any?
|
136
142
|
FileRollback.trap(:INT, :QUIT, :KILL) { print("\n"); exit 1 }
|
137
|
-
Riml.compile_files(*options.compile_files
|
143
|
+
Riml.compile_files(*(options.compile_files + [compile_files_options]))
|
138
144
|
elsif options.check_syntax_files.any?
|
139
145
|
files = options.check_syntax_files.uniq
|
140
146
|
Riml.check_syntax_files(*files)
|
@@ -153,6 +159,10 @@ module Riml
|
|
153
159
|
begin
|
154
160
|
Runner.start
|
155
161
|
rescue RimlError => e
|
156
|
-
|
162
|
+
if Riml.debug
|
163
|
+
raise
|
164
|
+
else
|
165
|
+
abort e.verbose_message
|
166
|
+
end
|
157
167
|
end
|
158
168
|
end
|
data/lib/riml.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
|
+
if RUBY_VERSION < '1.9'
|
5
|
+
require 'thread'
|
6
|
+
end
|
7
|
+
|
4
8
|
require File.expand_path('../riml/environment', __FILE__)
|
5
|
-
require 'riml/nodes'
|
6
9
|
require 'riml/lexer'
|
10
|
+
require 'riml/nodes'
|
7
11
|
require 'riml/parser'
|
8
12
|
require 'riml/compiler'
|
9
13
|
require 'riml/warning_buffer'
|
@@ -43,8 +47,8 @@ module Riml
|
|
43
47
|
end
|
44
48
|
|
45
49
|
def self.compile(input, options = {})
|
46
|
-
parse_options = options.select(&EXTRACT_PARSE_OPTIONS)
|
47
|
-
compile_options = options.select(&EXTRACT_COMPILE_OPTIONS)
|
50
|
+
parse_options = Hash[options.select(&EXTRACT_PARSE_OPTIONS)]
|
51
|
+
compile_options = Hash[options.select(&EXTRACT_COMPILE_OPTIONS)]
|
48
52
|
parser = Parser.new
|
49
53
|
parser.options = DEFAULT_PARSE_OPTIONS.merge(parse_options)
|
50
54
|
compiler = Compiler.new
|
@@ -98,8 +102,8 @@ module Riml
|
|
98
102
|
# options
|
99
103
|
if filenames.last.is_a?(Hash)
|
100
104
|
options = filenames.pop
|
101
|
-
compile_options = options.select(&EXTRACT_COMPILE_FILES_OPTIONS)
|
102
|
-
parse_options = options.select(&EXTRACT_PARSE_OPTIONS)
|
105
|
+
compile_options = Hash[options.select(&EXTRACT_COMPILE_FILES_OPTIONS)]
|
106
|
+
parse_options = Hash[options.select(&EXTRACT_PARSE_OPTIONS)]
|
103
107
|
compiler.options = DEFAULT_COMPILE_FILES_OPTIONS.merge(compile_options)
|
104
108
|
parser.options = DEFAULT_PARSE_OPTIONS.merge(parse_options)
|
105
109
|
else
|
@@ -195,6 +199,7 @@ module Riml
|
|
195
199
|
@include_cache.clear
|
196
200
|
@path_cache.clear
|
197
201
|
@rewritten_ast_cache.clear
|
202
|
+
Parser.ast_cache.clear
|
198
203
|
end
|
199
204
|
|
200
205
|
# if error is thrown, all files that were created will be rolled back
|
@@ -205,11 +210,11 @@ module Riml
|
|
205
210
|
FileRollback.guard(&block)
|
206
211
|
end
|
207
212
|
|
208
|
-
# turn warnings on/off
|
209
213
|
class << self
|
210
|
-
attr_accessor :warnings
|
214
|
+
attr_accessor :warnings, :debug
|
211
215
|
end
|
212
216
|
self.warnings = true
|
217
|
+
self.debug = false
|
213
218
|
|
214
219
|
private
|
215
220
|
|
@@ -232,7 +237,7 @@ module Riml
|
|
232
237
|
return instance_variable_set("@#{name}", nil) if path.nil?
|
233
238
|
path = path.split(':') if path.is_a?(String)
|
234
239
|
path.each do |dir|
|
235
|
-
unless
|
240
|
+
unless File.directory?(dir)
|
236
241
|
raise UserArgumentError, "Error trying to set #{name.to_s}. " \
|
237
242
|
"Directory #{dir.inspect} doesn't exist"
|
238
243
|
end
|
@@ -283,7 +288,7 @@ module Riml
|
|
283
288
|
else
|
284
289
|
# absolute path for filename sent from cmdline or from riml_sourced files,
|
285
290
|
# output to that same directory if no --output-dir option is set
|
286
|
-
if fname[0] == File::SEPARATOR && !compiler.output_dir
|
291
|
+
if fname[0, 1] == File::SEPARATOR && !compiler.output_dir
|
287
292
|
Pathname.new(fname).parent.to_s
|
288
293
|
# relative path
|
289
294
|
else
|
@@ -304,4 +309,5 @@ module Riml
|
|
304
309
|
end
|
305
310
|
end
|
306
311
|
|
307
|
-
|
312
|
+
# ruby-1.8.7 guard condition
|
313
|
+
end unless defined?(Riml) && defined?(Riml::DEFAULT_COMPILE_OPTIONS)
|
data/lib/riml/ast_rewriter.rb
CHANGED
@@ -9,7 +9,7 @@ module Riml
|
|
9
9
|
include Riml::Constants
|
10
10
|
|
11
11
|
attr_accessor :ast, :options
|
12
|
-
attr_reader :classes
|
12
|
+
attr_reader :classes
|
13
13
|
|
14
14
|
def initialize(ast = nil, classes = nil, class_dependency_graph = nil)
|
15
15
|
@ast = ast
|
@@ -31,16 +31,15 @@ module Riml
|
|
31
31
|
|
32
32
|
def rewrite(filename = nil, included = false)
|
33
33
|
if filename && (rewritten_ast = Riml.rewritten_ast_cache[filename])
|
34
|
-
rewrite_included_and_sourced_files!(filename)
|
35
34
|
return rewritten_ast
|
36
35
|
end
|
36
|
+
|
37
37
|
establish_parents(ast)
|
38
38
|
if @options && @options[:allow_undefined_global_classes] && !@classes.has_global_import?
|
39
39
|
@classes.globbed_imports.unshift(ImportedClass.new('*'))
|
40
|
-
else
|
41
|
-
class_imports = RegisterImportedClasses.new(ast, classes)
|
42
|
-
class_imports.rewrite_on_match
|
43
40
|
end
|
41
|
+
class_imports = RegisterImportedClasses.new(ast, classes)
|
42
|
+
class_imports.rewrite_on_match
|
44
43
|
if resolve_class_dependencies?
|
45
44
|
resolve_class_dependencies!(filename)
|
46
45
|
return if @resolving_class_dependencies == true
|
@@ -67,6 +66,25 @@ module Riml
|
|
67
66
|
ast
|
68
67
|
end
|
69
68
|
|
69
|
+
def establish_parents(node)
|
70
|
+
Walker.walk_node(node, method(:do_establish_parents))
|
71
|
+
end
|
72
|
+
alias reestablish_parents establish_parents
|
73
|
+
|
74
|
+
def do_establish_parents(node)
|
75
|
+
node.children.each do |child|
|
76
|
+
child.parent_node = node if child.respond_to?(:parent_node=)
|
77
|
+
end if node.respond_to?(:children)
|
78
|
+
end
|
79
|
+
|
80
|
+
def rewrite_on_match(node = ast)
|
81
|
+
Walker.walk_node(node, method(:do_rewrite_on_match), max_recursion_lvl)
|
82
|
+
end
|
83
|
+
|
84
|
+
def do_rewrite_on_match(node)
|
85
|
+
replace node if match?(node)
|
86
|
+
end
|
87
|
+
|
70
88
|
def resolve_class_dependencies?
|
71
89
|
@resolving_class_dependencies = false if @options[:include_reordering] != true
|
72
90
|
@resolving_class_dependencies != false
|
@@ -85,9 +103,11 @@ module Riml
|
|
85
103
|
if filename && @included_and_sourced_file_refs[file].include?(filename)
|
86
104
|
msg = "#{filename.inspect} can't include #{file.inspect}, as " \
|
87
105
|
" #{file.inspect} already included #{filename.inspect}"
|
88
|
-
|
106
|
+
error = IncludeFileLoop.new(msg, node)
|
107
|
+
raise error
|
89
108
|
elsif filename == file
|
90
|
-
|
109
|
+
error = UserArgumentError.new("#{file.inspect} can't include itself", node)
|
110
|
+
raise error
|
91
111
|
end
|
92
112
|
@included_and_sourced_file_refs[filename] << file
|
93
113
|
riml_src = File.read(fullpath)
|
@@ -167,7 +187,7 @@ module Riml
|
|
167
187
|
|
168
188
|
def class_name_full_name(class_name)
|
169
189
|
return nil if class_name.nil?
|
170
|
-
if class_name[1] == ':'
|
190
|
+
if class_name[1..1] == ':'
|
171
191
|
class_name
|
172
192
|
else
|
173
193
|
ClassDefinitionNode::DEFAULT_SCOPE_MODIFIER + class_name
|
@@ -175,25 +195,6 @@ module Riml
|
|
175
195
|
end
|
176
196
|
end
|
177
197
|
|
178
|
-
def establish_parents(node)
|
179
|
-
Walker.walk_node(node, method(:do_establish_parents))
|
180
|
-
end
|
181
|
-
alias reestablish_parents establish_parents
|
182
|
-
|
183
|
-
def do_establish_parents(node)
|
184
|
-
node.children.each do |child|
|
185
|
-
child.parent_node = node if child.respond_to?(:parent_node=)
|
186
|
-
end if node.respond_to?(:children)
|
187
|
-
end
|
188
|
-
|
189
|
-
def rewrite_on_match(node = ast)
|
190
|
-
Walker.walk_node(node, method(:do_rewrite_on_match), max_recursion_lvl)
|
191
|
-
end
|
192
|
-
|
193
|
-
def do_rewrite_on_match(node)
|
194
|
-
replace node if match?(node)
|
195
|
-
end
|
196
|
-
|
197
198
|
# We need to rewrite the included/sourced files before anything else. This is in
|
198
199
|
# order to keep track of any classes defined in the included and sourced files (and
|
199
200
|
# files included/sourced in those, etc...). We keep a cache of rewritten asts
|
@@ -210,9 +211,11 @@ module Riml
|
|
210
211
|
msg = "#{filename.inspect} can't #{action} #{file.inspect}, as " \
|
211
212
|
" #{file.inspect} already included/sourced #{filename.inspect}"
|
212
213
|
# IncludeFileLoop/SourceFileLoop
|
213
|
-
|
214
|
+
error = Riml.const_get("#{action.capitalize}FileLoop").new(msg, node)
|
215
|
+
raise error
|
214
216
|
elsif filename == file
|
215
|
-
|
217
|
+
error = UserArgumentError.new("#{file.inspect} can't #{action} itself", node)
|
218
|
+
raise error
|
216
219
|
end
|
217
220
|
@included_and_sourced_file_refs[filename] << file
|
218
221
|
# recursively parse included files with this ast_rewriter in order
|
@@ -697,9 +700,9 @@ module Riml
|
|
697
700
|
]
|
698
701
|
),
|
699
702
|
StringNode.new("_s:#{def_node.name}", :s)
|
700
|
-
]
|
703
|
+
]
|
701
704
|
)
|
702
|
-
]
|
705
|
+
]
|
703
706
|
)
|
704
707
|
)
|
705
708
|
constructor.expressions << extension
|
@@ -779,9 +782,10 @@ module Riml
|
|
779
782
|
|
780
783
|
def replace(constructor)
|
781
784
|
unless class_node.superclass?
|
782
|
-
#
|
783
|
-
abort "class #{class_node.full_name.inspect} called super in its " \
|
785
|
+
error_msg "class #{class_node.full_name.inspect} called super in its " \
|
784
786
|
" initialize function, but it has no superclass."
|
787
|
+
error = InvalidSuper.new(error_msg, constructor)
|
788
|
+
raise error
|
785
789
|
end
|
786
790
|
|
787
791
|
superclass = classes.superclass(class_node.full_name)
|
@@ -841,10 +845,11 @@ module Riml
|
|
841
845
|
end
|
842
846
|
superclass_function = superclass.find_function(func_scope, superclass_func_name(superclass))
|
843
847
|
if superclass.nil? || !superclass_function
|
844
|
-
|
845
|
-
"super was called in class #{ast.full_name} in " \
|
848
|
+
error_msg = "super was called in class #{ast.full_name} in " \
|
846
849
|
"function #{@function_node.original_name}, but there are no " \
|
847
850
|
"functions with this name in that class's superclass hierarchy."
|
851
|
+
error = Riml::InvalidSuper.new(error_msg, node)
|
852
|
+
raise error
|
848
853
|
end
|
849
854
|
node_args = if node.arguments.empty? && !node.with_parens && superclass_function.splat
|
850
855
|
[SplatNode.new('*a:000')]
|
@@ -910,9 +915,9 @@ module Riml
|
|
910
915
|
]
|
911
916
|
),
|
912
917
|
StringNode.new("_s:#{super_func_name}", :s)
|
913
|
-
]
|
918
|
+
]
|
914
919
|
)
|
915
|
-
]
|
920
|
+
]
|
916
921
|
)
|
917
922
|
)
|
918
923
|
ast.constructor.expressions << assign_node
|
@@ -964,7 +969,9 @@ module Riml
|
|
964
969
|
|
965
970
|
while param = def_node.parameters[param_idx += 1]
|
966
971
|
unless param == def_node.splat || DefaultParamNode === param
|
967
|
-
|
972
|
+
error_msg = "can't have regular parameter after default parameter in function #{def_node.name.inspect}"
|
973
|
+
error = UserArgumentError.new(error_msg, def_node)
|
974
|
+
raise error
|
968
975
|
end
|
969
976
|
end
|
970
977
|
|
@@ -1043,5 +1050,5 @@ module Riml
|
|
1043
1050
|
end
|
1044
1051
|
end
|
1045
1052
|
|
1046
|
-
end
|
1053
|
+
end unless defined?(Riml::AST_Rewriter)
|
1047
1054
|
end
|
@@ -20,7 +20,7 @@ module Riml
|
|
20
20
|
raise ArgumentError, "first index must come before (or be equal to) last index"
|
21
21
|
end
|
22
22
|
|
23
|
-
unless
|
23
|
+
unless Riml.debug
|
24
24
|
add_to_head = @error.backtrace[0...first_i] || []
|
25
25
|
add_to_tail = @error.backtrace[last_i...-1] || []
|
26
26
|
backtrace = @error.backtrace[first_i..last_i] || []
|
data/lib/riml/compiler.rb
CHANGED
@@ -31,7 +31,7 @@ module Riml
|
|
31
31
|
|
32
32
|
def visit(node)
|
33
33
|
output = compile(node)
|
34
|
-
output << "\n" if node.force_newline and output[-1] != "\n"
|
34
|
+
output << "\n" if node.force_newline and output[-1, 1] != "\n"
|
35
35
|
propagate_up_tree(node, output)
|
36
36
|
end
|
37
37
|
|
@@ -44,7 +44,8 @@ module Riml
|
|
44
44
|
def visitor_for_node(node, params={})
|
45
45
|
Compiler.const_get("#{node.class.name.split('::').last}Visitor").new(params)
|
46
46
|
rescue NameError
|
47
|
-
|
47
|
+
error = CompileError.new('unexpected construct', node)
|
48
|
+
raise error
|
48
49
|
end
|
49
50
|
|
50
51
|
def root_node(node)
|
@@ -78,7 +79,7 @@ module Riml
|
|
78
79
|
node.compiled_output << node.indent + line
|
79
80
|
end
|
80
81
|
end
|
81
|
-
node.compiled_output << "\n" unless node.compiled_output[-1] == "\n"
|
82
|
+
node.compiled_output << "\n" unless node.compiled_output[-1..-1] == "\n"
|
82
83
|
node.compiled_output << "endif\n"
|
83
84
|
end
|
84
85
|
end
|
@@ -169,20 +170,21 @@ module Riml
|
|
169
170
|
when String
|
170
171
|
StringNode === node ? string_surround(node) : node.value
|
171
172
|
when Array
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
173
|
+
if ListNode === node
|
174
|
+
node.value.each {|n| n.parent_node = node}
|
175
|
+
'[' <<
|
176
|
+
node.value.map do |n|
|
177
|
+
n.accept(visitor_for_node(n))
|
178
|
+
n.compiled_output
|
179
|
+
end.join(', ') << ']'
|
180
|
+
elsif DictionaryNode === node
|
181
|
+
'{' <<
|
182
|
+
node.value.map do |(k, v)|
|
183
|
+
k.accept(visitor_for_node(k))
|
184
|
+
v.accept(visitor_for_node(v))
|
185
|
+
k.compiled_output << ': ' << v.compiled_output
|
186
|
+
end.join(', ') << '}'
|
187
|
+
end
|
186
188
|
end.to_s
|
187
189
|
|
188
190
|
node.compiled_output = value
|
@@ -607,7 +609,8 @@ module Riml
|
|
607
609
|
# riml_include has to be top-level
|
608
610
|
unless node.parent == root_node(node)
|
609
611
|
error_msg = %Q(riml_include error, has to be called at top-level)
|
610
|
-
|
612
|
+
error = IncludeNotTopLevel.new(error_msg, node)
|
613
|
+
raise error
|
611
614
|
end
|
612
615
|
node.each_existing_file! do |basename, full_path|
|
613
616
|
output = current_compiler(node).compile_include(basename, full_path)
|
data/lib/riml/constants.rb
CHANGED
@@ -51,8 +51,12 @@ module Riml
|
|
51
51
|
].flatten
|
52
52
|
|
53
53
|
# For when showing source location (file:lineno) during error
|
54
|
-
# and no file was given
|
54
|
+
# and no file was given, only a string to compile.
|
55
|
+
# Ex: # Riml.compile(source_code) would raise an error like
|
56
|
+
# '<String>:14 riml_include must be top-level'
|
55
57
|
COMPILED_STRING_LOCATION = '<String>'
|
58
|
+
# For when there is no location info associated with a node
|
59
|
+
UNKNOWN_LOCATION_INFO = '<unknown>'
|
56
60
|
|
57
61
|
# :help function-list
|
58
62
|
BUILTIN_FUNCTIONS =
|
data/lib/riml/errors.rb
CHANGED
@@ -1,19 +1,64 @@
|
|
1
|
+
require File.expand_path('../constants', __FILE__)
|
2
|
+
|
1
3
|
module Riml
|
2
|
-
RimlError
|
4
|
+
class RimlError < StandardError
|
3
5
|
attr_accessor :node
|
6
|
+
def initialize(msg = nil, node = nil)
|
7
|
+
super(msg)
|
8
|
+
@node = node
|
9
|
+
end
|
10
|
+
|
11
|
+
def verbose_message
|
12
|
+
"#{self.class}\n" <<
|
13
|
+
"location: #{location_info}\n" <<
|
14
|
+
"message: #{message.to_s.sub(/\A\n/, '')}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def location_info
|
18
|
+
if @node
|
19
|
+
@node.location_info
|
20
|
+
else
|
21
|
+
Constants::UNKNOWN_LOCATION_INFO
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ErrorWithoutNodeAvailable
|
27
|
+
attr_accessor :filename, :lineno
|
28
|
+
def initialize(msg = nil, filename = nil, lineno = nil)
|
29
|
+
super(msg, nil)
|
30
|
+
@filename = filename
|
31
|
+
@lineno = lineno
|
32
|
+
end
|
33
|
+
|
34
|
+
def location_info
|
35
|
+
if @filename || @lineno
|
36
|
+
"#{@filename}:#{@lineno}"
|
37
|
+
else
|
38
|
+
Constants::UNKNOWN_LOCATION_INFO
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class SyntaxError < RimlError
|
44
|
+
include ErrorWithoutNodeAvailable
|
45
|
+
end
|
46
|
+
class ParseError < RimlError
|
47
|
+
include ErrorWithoutNodeAvailable
|
4
48
|
end
|
5
49
|
|
6
|
-
SyntaxError = Class.new(RimlError)
|
7
|
-
ParseError = Class.new(RimlError)
|
8
50
|
CompileError = Class.new(RimlError)
|
9
51
|
|
10
52
|
FileNotFound = Class.new(RimlError)
|
11
53
|
IncludeFileLoop = Class.new(RimlError)
|
12
54
|
SourceFileLoop = Class.new(RimlError)
|
13
55
|
IncludeNotTopLevel = Class.new(RimlError)
|
56
|
+
|
14
57
|
# bad user arguments to Riml functions
|
15
58
|
UserArgumentError = Class.new(RimlError)
|
16
|
-
|
59
|
+
|
60
|
+
# super is called in invalid context
|
61
|
+
InvalidSuper = Class.new(RimlError)
|
17
62
|
|
18
63
|
ClassNotFound = Class.new(RimlError)
|
19
64
|
ClassRedefinitionError = Class.new(RimlError)
|