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 CHANGED
@@ -1,3 +1,7 @@
1
+ if RUBY_VERSION < '1.9'
2
+ require 'rubygems'
3
+ end
4
+
1
5
  require File.expand_path('../lib/riml/environment', __FILE__)
2
6
  require 'rake/testtask'
3
7
  require 'bundler/setup'
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, compile_files_options)
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
- abort e.message
162
+ if Riml.debug
163
+ raise
164
+ else
165
+ abort e.verbose_message
166
+ end
157
167
  end
158
168
  end
@@ -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 Dir.exists?(dir)
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
- end
312
+ # ruby-1.8.7 guard condition
313
+ end unless defined?(Riml) && defined?(Riml::DEFAULT_COMPILE_OPTIONS)
@@ -9,7 +9,7 @@ module Riml
9
9
  include Riml::Constants
10
10
 
11
11
  attr_accessor :ast, :options
12
- attr_reader :classes, :rewritten_included_and_sourced_files
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
- raise IncludeFileLoop, msg
106
+ error = IncludeFileLoop.new(msg, node)
107
+ raise error
89
108
  elsif filename == file
90
- raise UserArgumentError, "#{file.inspect} can't include itself"
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
- raise Riml.const_get("#{action.capitalize}FileLoop"), msg
214
+ error = Riml.const_get("#{action.capitalize}FileLoop").new(msg, node)
215
+ raise error
214
216
  elsif filename == file
215
- raise UserArgumentError, "#{file.inspect} can't #{action} itself"
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
- # TODO: raise error instead of aborting
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
- raise Riml::UserFunctionNotFoundError,
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
- raise UserArgumentError, "can't have regular parameter after default parameter in function #{def_node.name.inspect}"
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 $DEBUG
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] || []
@@ -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
- raise CompileError, "unexpected construct at #{node.location_info}"
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
- node.value.each {|n| n.parent_node = node}
173
- '[' <<
174
- node.value.map do |n|
175
- n.accept(visitor_for_node(n))
176
- n.compiled_output
177
- end.join(', ') << ']'
178
- when Hash
179
- node.value.each {|k_n, v_n| k_n.parent_node, v_n.parent_node = node, node}
180
- '{' <<
181
- node.value.map do |k,v|
182
- k.accept(visitor_for_node(k))
183
- v.accept(visitor_for_node(v))
184
- k.compiled_output << ': ' << v.compiled_output
185
- end.join(', ') << '}'
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
- raise IncludeNotTopLevel, error_msg
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)
@@ -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 =
@@ -1,19 +1,64 @@
1
+ require File.expand_path('../constants', __FILE__)
2
+
1
3
  module Riml
2
- RimlError = Class.new(StandardError) do
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
- UserFunctionNotFoundError = Class.new(RimlError)
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)