riml 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  [![Build Status](https://secure.travis-ci.org/luke-gru/riml.png?branch=master)](https://travis-ci.org/luke-gru/riml)
2
2
 
3
- Riml, a relaxed Vimscript
3
+ Riml, a relaxed Vim script
4
4
  ====================================
5
5
 
6
- Riml is a subset of Vimscript with some added features, and it compiles to
7
- plain Vimscript. Some of the added features include classes, string interpolation,
6
+ Riml is a subset of Vim script with some added features, and it compiles to
7
+ plain Vim script. Some of the added features include classes, string interpolation,
8
8
  heredocs, default case-sensitive string comparison, default parameters
9
9
  in functions, and other things programmers tend to take for granted.
10
10
  To see how Riml is compiled to VimL, just take a look at this README. The left
@@ -70,7 +70,7 @@ Notice in the last line of Riml there's string interpolation. This works
70
70
  in double-quoted strings and heredocs, which we'll encounter later.
71
71
 
72
72
  In Riml, you can choose to end any block with 'end', or with whatever you used
73
- to do in Vimscript ('endif', 'endfunction', etc...). Also, 'if' and 'unless' can
73
+ to do in Vim script ('endif', 'endfunction', etc...). Also, 'if' and 'unless' can
74
74
  now be used as statement modifiers:
75
75
 
76
76
  callcount = 0 unless callcount?
@@ -260,9 +260,9 @@ global, this is not a problem. Simply:
260
260
  end
261
261
 
262
262
  If you look at the last line of Riml in the previous example, you'll see that
263
- it doesn't use Vimscript's builtin 'call' function for calling the 'translate'
263
+ it doesn't use Vim script's builtin 'call' function for calling the 'translate'
264
264
  method on the translation object. Riml can figure out when 'call' is necessary,
265
- and will add it to the compiled Vimscript.
265
+ and will add it to the compiled Vim script.
266
266
 
267
267
  ###Using 'super'
268
268
 
data/bin/riml CHANGED
@@ -8,36 +8,38 @@ module Riml
8
8
  require 'riml'
9
9
 
10
10
  require 'optparse'
11
+ require 'ostruct'
11
12
 
12
13
  class Options
13
- def self.parse(argv)
14
+ def self.parse(argv = ARGV)
14
15
  argv << '--help' if argv.size.zero?
15
16
 
16
17
  # defaults
17
- options = {}
18
- options[:compile_files] = []
19
- options[:check_syntax_files] = []
20
- options[:repl] = false
21
- options[:vi_readline] = false
22
- options[:allow_undefined_global_classes] = DEFAULT_PARSE_OPTIONS[:allow_undefined_global_classes]
23
- options[:readable] = DEFAULT_COMPILE_OPTIONS[:readable]
24
- options[:output_dir] = DEFAULT_COMPILE_FILES_OPTIONS[:output_dir]
25
-
26
- OptionParser.new do |opts|
18
+ options = OpenStruct.new
19
+ options.compile_files = []
20
+ options.check_syntax_files = []
21
+ options.repl = false
22
+ options.vi_readline = false
23
+ options.allow_undefined_global_classes = DEFAULT_PARSE_OPTIONS[:allow_undefined_global_classes]
24
+ options.include_reordering = DEFAULT_PARSE_OPTIONS[:include_reordering]
25
+ options.readable = DEFAULT_COMPILE_OPTIONS[:readable]
26
+ options.output_dir = DEFAULT_COMPILE_FILES_OPTIONS[:output_dir]
27
+
28
+ opts_parser = OptionParser.new do |opts|
27
29
  opts.banner = "Usage: riml [options] [file1][,file2]..."
28
30
  opts.separator ""
29
31
  opts.separator "Specific options:"
30
32
 
31
33
  opts.on("-c", "--compile FILES", Array, "Compiles riml file(s) to VimL.") do |filenames|
32
- append_filenames_to_list_if_valid(options[:compile_files], *filenames)
34
+ append_filenames_to_list_if_valid(options.compile_files, *filenames)
33
35
  end
34
36
 
35
37
  opts.on("-s", "--stdio", "Takes riml from stdin and outputs VimL to stdout.") do
36
- options[:stdio] = true
38
+ options.stdio = true
37
39
  end
38
40
 
39
41
  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
- append_filenames_to_list_if_valid(options[:check_syntax_files], *filenames)
42
+ append_filenames_to_list_if_valid(options.check_syntax_files, *filenames)
41
43
  end
42
44
 
43
45
  opts.on("-S", "--source-path PATH", "Colon-separated path riml uses to find files for `riml_source`. Defaults to pwd.") do |path|
@@ -57,23 +59,27 @@ module Riml
57
59
  end
58
60
 
59
61
  opts.on("-a", "--allow-undef-global-classes", "Continue compilation when encountering undefined global class(es).") do
60
- options[:allow_undefined_global_classes] = true
62
+ options.allow_undefined_global_classes = true
63
+ end
64
+
65
+ opts.on("-n", "--no-include-reordering", "Turns off default feature of reordering `riml_include`s based on class dependencies.") do
66
+ options.include_reordering = false
61
67
  end
62
68
 
63
69
  opts.on("-o", "--output-dir DIR", "Output all .vim files in specified directory.") do |dir|
64
- options[:output_dir] = dir
70
+ options.output_dir = dir
65
71
  end
66
72
 
67
73
  opts.on("-d", "--condensed", "Omit readability improvements such as blank lines.") do
68
- options[:readable] = false
74
+ options.readable = false
69
75
  end
70
76
 
71
77
  opts.on("-i", "--interactive", "Start an interactive riml session (REPL).") do
72
- options[:repl] = true
78
+ options.repl = true
73
79
  end
74
80
 
75
81
  opts.on("--vi", "Use vi readline settings during interactive session.") do
76
- options[:vi_readline] = options[:repl] = true
82
+ options.vi_readline = options.repl = true
77
83
  end
78
84
 
79
85
  opts.on_tail("-v", "--version", "Show riml version.") do
@@ -85,8 +91,13 @@ module Riml
85
91
  puts opts
86
92
  exit
87
93
  end
88
- end.parse!(argv)
94
+ end
89
95
 
96
+ begin
97
+ opts_parser.parse!(argv)
98
+ rescue OptionParser::ParseError => e
99
+ abort e.message
100
+ end
90
101
  options
91
102
  end
92
103
 
@@ -110,34 +121,38 @@ module Riml
110
121
  class Runner
111
122
  class << self
112
123
  def start
113
- options = Options.parse(ARGV)
124
+ options = Options.parse
114
125
  compile_options = {
115
- :readable => options[:readable],
116
- :allow_undefined_global_classes => options[:allow_undefined_global_classes]
126
+ :readable => options.readable,
127
+ :allow_undefined_global_classes => options.allow_undefined_global_classes,
128
+ :include_reordering => options.include_reordering
117
129
  }
118
130
  compile_files_options = compile_options.merge(
119
- :output_dir => options[:output_dir]
131
+ :output_dir => options.output_dir
120
132
  )
121
- if options[:stdio]
133
+ if options.stdio
122
134
  puts Riml.compile($stdin.read, compile_options)
123
- elsif options[:compile_files].any?
135
+ elsif options.compile_files.any?
124
136
  FileRollback.trap(:INT, :QUIT, :KILL) { print("\n"); exit 1 }
125
- Riml.compile_files(*options[:compile_files], compile_files_options)
126
- elsif options[:check_syntax_files].any?
127
- files = options[:check_syntax_files].uniq
137
+ Riml.compile_files(*options.compile_files, compile_files_options)
138
+ elsif options.check_syntax_files.any?
139
+ files = options.check_syntax_files.uniq
128
140
  Riml.check_syntax_files(*files)
129
141
  size = files.size
130
142
  # "ok (1 file)" OR "ok (2 files)"
131
143
  puts "ok (#{size} file#{'s' if size > 1})"
132
- elsif options[:repl]
144
+ elsif options.repl
133
145
  require 'riml/repl'
134
- Riml::Repl.new(options[:vi_readline], compile_options).run
146
+ Riml::Repl.new(options.vi_readline, compile_options).run
135
147
  else
136
- ARGV.replace ['--help']
137
- start
148
+ abort "Invalid arguments. See valid arguments with 'riml --help'"
138
149
  end
139
150
  end
140
151
  end
141
152
  end
142
- Runner.start
153
+ begin
154
+ Runner.start
155
+ rescue RimlError => e
156
+ abort e.message
157
+ end
143
158
  end
@@ -10,6 +10,7 @@ require 'riml/warning_buffer'
10
10
  require 'riml/include_cache'
11
11
  require 'riml/path_cache'
12
12
  require 'riml/rewritten_ast_cache'
13
+ require 'riml/backtrace_filter'
13
14
  require 'riml/file_rollback'
14
15
 
15
16
  module Riml
@@ -18,7 +19,10 @@ module Riml
18
19
  DEFAULT_COMPILE_FILES_OPTIONS = DEFAULT_COMPILE_OPTIONS.merge(
19
20
  :output_dir => nil
20
21
  )
21
- DEFAULT_PARSE_OPTIONS = { :allow_undefined_global_classes => false }
22
+ DEFAULT_PARSE_OPTIONS = {
23
+ :allow_undefined_global_classes => false,
24
+ :include_reordering => true
25
+ }
22
26
 
23
27
  EXTRACT_PARSE_OPTIONS = lambda { |k,_| DEFAULT_PARSE_OPTIONS.keys.include?(k.to_sym) }
24
28
  EXTRACT_COMPILE_OPTIONS = lambda { |k,_| DEFAULT_COMPILE_OPTIONS.keys.include?(k.to_sym) }
@@ -1,6 +1,7 @@
1
1
  require File.expand_path("../constants", __FILE__)
2
2
  require File.expand_path("../imported_class", __FILE__)
3
3
  require File.expand_path("../class_map", __FILE__)
4
+ require File.expand_path("../class_dependency_graph", __FILE__)
4
5
  require File.expand_path("../walker", __FILE__)
5
6
 
6
7
  module Riml
@@ -10,7 +11,7 @@ module Riml
10
11
  attr_accessor :ast, :options
11
12
  attr_reader :classes, :rewritten_included_and_sourced_files
12
13
 
13
- def initialize(ast = nil, classes = nil)
14
+ def initialize(ast = nil, classes = nil, class_dependency_graph = nil)
14
15
  @ast = ast
15
16
  @classes = classes || ClassMap.new
16
17
  # AST_Rewriter shares options with Parser. Parser set AST_Rewriter's
@@ -23,6 +24,9 @@ module Riml
23
24
  # ex: { nil => ["main.riml"], "main.riml" => ["lib1.riml", "lib2.riml"],
24
25
  # "lib1.riml" => [], "lib2.riml" => [] }
25
26
  @included_and_sourced_file_refs = Hash.new { |h, k| h[k] = [] }
27
+ @class_dependency_graph = class_dependency_graph || ClassDependencyGraph.new
28
+ @class_dependency_graph.classes = @classes
29
+ @resolving_class_dependencies = nil
26
30
  end
27
31
 
28
32
  def rewrite(filename = nil, included = false)
@@ -30,12 +34,17 @@ module Riml
30
34
  rewrite_included_and_sourced_files!(filename)
31
35
  return rewritten_ast
32
36
  end
37
+ establish_parents(ast)
33
38
  if @options && @options[:allow_undefined_global_classes] && !@classes.has_global_import?
34
39
  @classes.globbed_imports.unshift(ImportedClass.new('*'))
40
+ else
41
+ class_imports = RegisterImportedClasses.new(ast, classes)
42
+ class_imports.rewrite_on_match
43
+ end
44
+ if resolve_class_dependencies?
45
+ resolve_class_dependencies!(filename)
46
+ return if @resolving_class_dependencies == true
35
47
  end
36
- establish_parents(ast)
37
- class_imports = RegisterImportedClasses.new(ast, classes)
38
- class_imports.rewrite_on_match
39
48
  class_registry = RegisterDefinedClasses.new(ast, classes)
40
49
  class_registry.rewrite_on_match
41
50
  rewrite_included_and_sourced_files!(filename)
@@ -58,6 +67,114 @@ module Riml
58
67
  ast
59
68
  end
60
69
 
70
+ def resolve_class_dependencies?
71
+ @resolving_class_dependencies = false if @options[:include_reordering] != true
72
+ @resolving_class_dependencies != false
73
+ end
74
+
75
+ def resolve_class_dependencies!(filename)
76
+ if @resolving_class_dependencies.nil?
77
+ start_resolving = @resolving_class_dependencies = true
78
+ @included_ASTs_by_include_file = {}
79
+ end
80
+ old_ast = ast
81
+ RegisterClassDependencies.new(ast, classes, @class_dependency_graph, filename).rewrite_on_match
82
+ ast.children.grep(RimlFileCommandNode).each do |node|
83
+ next unless node.name == 'riml_include'
84
+ node.each_existing_file! do |file, fullpath|
85
+ if filename && @included_and_sourced_file_refs[file].include?(filename)
86
+ msg = "#{filename.inspect} can't include #{file.inspect}, as " \
87
+ " #{file.inspect} already included #{filename.inspect}"
88
+ raise IncludeFileLoop, msg
89
+ elsif filename == file
90
+ raise UserArgumentError, "#{file.inspect} can't include itself"
91
+ end
92
+ @included_and_sourced_file_refs[filename] << file
93
+ riml_src = File.read(fullpath)
94
+ Parser.new.tap { |p| p.options = @options }.parse(riml_src, self, file, true)
95
+ @included_ASTs_by_include_file[file] = Parser.ast_cache[file]
96
+ end
97
+ end
98
+ ensure
99
+ self.ast = old_ast
100
+ if start_resolving == true
101
+ @resolving_class_dependencies = false
102
+ @included_and_sourced_file_refs.clear
103
+ reorder_includes_based_on_class_dependencies!
104
+ end
105
+ end
106
+
107
+ def reorder_includes_based_on_class_dependencies!
108
+ global_included_filename_order = @class_dependency_graph.filename_order
109
+ asts = [ast]
110
+ while (ast = asts.shift)
111
+ include_nodes =
112
+ ast.children.grep(RimlFileCommandNode).select do |n|
113
+ n.name == 'riml_include'
114
+ end
115
+ included_filenames = include_nodes.map { |n| n.arguments.map(&:value) }.flatten
116
+ new_order_filenames = global_included_filename_order & included_filenames
117
+ add_to_head = included_filenames - new_order_filenames
118
+ new_order_filenames = add_to_head + new_order_filenames
119
+ include_nodes.each do |node|
120
+ node.arguments.each do |arg|
121
+ if (included_file_ast = @included_ASTs_by_include_file[arg.value])
122
+ asts << included_file_ast
123
+ end
124
+ if new_order_filenames.first
125
+ arg.value = new_order_filenames.shift
126
+ else
127
+ # for now, just to be cautious
128
+ raise "Internal error in AST rewriting process. Please report bug!"
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ class RegisterClassDependencies < AST_Rewriter
136
+ def initialize(ast, classes, class_dependency_graph, filename)
137
+ super(ast, classes, class_dependency_graph)
138
+ @filename = filename
139
+ end
140
+
141
+ def match?(node)
142
+ ClassDefinitionNode === node || ObjectInstantiationNode === node
143
+ end
144
+
145
+ def replace(node)
146
+ if ClassDefinitionNode === node
147
+ @class_dependency_graph.class_defined(
148
+ @filename, class_node_full_name(node),
149
+ class_name_full_name(node.superclass_name)
150
+ )
151
+ else
152
+ @class_dependency_graph.class_encountered(
153
+ @filename,
154
+ class_node_full_name(node.call_node)
155
+ )
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def class_node_full_name(node)
162
+ (
163
+ node.scope_modifier ||
164
+ ClassDefinitionNode::DEFAULT_SCOPE_MODIFIER
165
+ ) + node.name
166
+ end
167
+
168
+ def class_name_full_name(class_name)
169
+ return nil if class_name.nil?
170
+ if class_name[1] == ':'
171
+ class_name
172
+ else
173
+ ClassDefinitionNode::DEFAULT_SCOPE_MODIFIER + class_name
174
+ end
175
+ end
176
+ end
177
+
61
178
  def establish_parents(node)
62
179
  Walker.walk_node(node, method(:do_establish_parents))
63
180
  end
@@ -1,13 +1,32 @@
1
+ require File.expand_path('../environment', __FILE__)
2
+
1
3
  module Riml
2
4
  class BacktraceFilter
5
+ include Riml::Environment
6
+
7
+ RIML_INTERNAL_FILE_REGEX = /#{ROOTDIR}/
8
+
3
9
  attr_reader :error
4
10
 
5
11
  def initialize(error)
6
12
  @error = error
7
13
  end
8
14
 
9
- def filter!
10
- error.backtrace.clear
15
+ def filter!(first_i = 0, last_i = -1)
16
+ if first_i < 0
17
+ raise ArgumentError, "first argument must be >= 0"
18
+ end
19
+ if last_i > 0 && first_i > last_i
20
+ raise ArgumentError, "first index must come before (or be equal to) last index"
21
+ end
22
+
23
+ unless $DEBUG
24
+ add_to_head = @error.backtrace[0...first_i] || []
25
+ add_to_tail = @error.backtrace[last_i...-1] || []
26
+ backtrace = @error.backtrace[first_i..last_i] || []
27
+ backtrace.delete_if { |loc| RIML_INTERNAL_FILE_REGEX =~ loc }
28
+ @error.set_backtrace(add_to_head + backtrace + add_to_tail)
29
+ end
11
30
  end
12
31
 
13
32
  end
@@ -0,0 +1,88 @@
1
+ # topological sorting module from stdlib
2
+ # uses Tarjan's algorithm for strongly connected components
3
+ require 'tsort'
4
+
5
+ module Riml
6
+ # Used for reordering `riml_include`s based on class dependencies.
7
+ class ClassDependencyGraph
8
+ include TSort
9
+
10
+ attr_reader :definition_graph, :encountered_graph
11
+ # for checking imported classes
12
+ attr_accessor :classes
13
+
14
+ # definition_graph: { "faster_car.riml" => { "s:FasterCar" => "s:Car" }, "car.riml" => { "s:Car" => nil } }
15
+ # encountered_graph: { "faster_car.riml" => ["s:FasterCar", "s:Car"], "car.riml" => ["s:Car"] }
16
+ def initialize(definition_graph = {}, encountered_graph = {})
17
+ @definition_graph = definition_graph
18
+ @encountered_graph = encountered_graph
19
+ @filename_graph = nil
20
+ end
21
+
22
+ def class_defined(filename, class_name, superclass_name)
23
+ @definition_graph[filename] ||= {}
24
+ @definition_graph[filename][class_name] = superclass_name
25
+ class_encountered(filename, class_name)
26
+ class_encountered(filename, superclass_name) if superclass_name
27
+ end
28
+
29
+ def class_encountered(filename, class_name)
30
+ @encountered_graph[filename] ||= []
31
+ unless @encountered_graph[filename].include?(class_name)
32
+ @encountered_graph[filename] << class_name
33
+ end
34
+ end
35
+
36
+ # order in which filenames need to be included based off internal
37
+ # `@definition_graph` and `@encountered_graph`
38
+ # @return Array filenames
39
+ def tsort
40
+ prepare_filename_graph! if @filename_graph.nil?
41
+ super
42
+ end
43
+
44
+ alias filename_order tsort
45
+
46
+ # Computes `@filename_graph` from `@encountered_graph` and `@definition_graph`.
47
+ # This graph is used by `tsort` to sort the filenames for inclusion.
48
+ def prepare_filename_graph!
49
+ @filename_graph = {}
50
+ @encountered_graph.each do |filename, encountered_classes|
51
+ # must be imported class or undefined class
52
+ if @definition_graph[filename].nil?
53
+ encountered_classes.each do |enc|
54
+ # raises Riml::ClassNotFound if not found
55
+ @classes[enc]
56
+ end
57
+ next
58
+ end
59
+ defined_in_filename = @definition_graph[filename].keys
60
+ dependent_by_superclass = @definition_graph[filename].values.compact - defined_in_filename
61
+ dependent_by_use = encountered_classes - (defined_in_filename + dependent_by_superclass)
62
+ dependents = dependent_by_superclass + dependent_by_use
63
+ dependents.each do |dep|
64
+ dependent_definition_fname = @definition_graph.detect { |fname, hash| hash.has_key?(dep) }.first rescue nil
65
+ if dependent_definition_fname
66
+ @filename_graph[filename] ||= []
67
+ unless @filename_graph[filename].include?(dependent_definition_fname)
68
+ @filename_graph[filename] << dependent_definition_fname
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def tsort_each_node(&block)
76
+ @filename_graph.each_key(&block)
77
+ end
78
+
79
+ def tsort_each_child(node, &block)
80
+ if @filename_graph[node]
81
+ @filename_graph[node].each(&block)
82
+ else
83
+ []
84
+ end
85
+ end
86
+
87
+ end
88
+ end