riml 0.3.5 → 0.3.6

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