riml 0.3.6 → 0.3.7

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