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 +6 -6
- data/bin/riml +49 -34
- data/lib/riml.rb +5 -1
- data/lib/riml/ast_rewriter.rb +121 -4
- data/lib/riml/backtrace_filter.rb +21 -2
- data/lib/riml/class_dependency_graph.rb +88 -0
- data/lib/riml/compiler.rb +23 -17
- data/lib/riml/file_rollback.rb +11 -4
- data/lib/riml/grammar.y +35 -34
- data/lib/riml/nodes.rb +19 -2
- data/lib/riml/parser.output +15000 -0
- data/lib/riml/parser.rb +976 -980
- data/version.rb +2 -2
- metadata +7 -5
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
[](https://travis-ci.org/luke-gru/riml)
|
2
2
|
|
3
|
-
Riml, a relaxed
|
3
|
+
Riml, a relaxed Vim script
|
4
4
|
====================================
|
5
5
|
|
6
|
-
Riml is a subset of
|
7
|
-
plain
|
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
|
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
|
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
|
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
|
19
|
-
options
|
20
|
-
options
|
21
|
-
options
|
22
|
-
options
|
23
|
-
options
|
24
|
-
options
|
25
|
-
|
26
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
74
|
+
options.readable = false
|
69
75
|
end
|
70
76
|
|
71
77
|
opts.on("-i", "--interactive", "Start an interactive riml session (REPL).") do
|
72
|
-
options
|
78
|
+
options.repl = true
|
73
79
|
end
|
74
80
|
|
75
81
|
opts.on("--vi", "Use vi readline settings during interactive session.") do
|
76
|
-
options
|
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
|
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
|
124
|
+
options = Options.parse
|
114
125
|
compile_options = {
|
115
|
-
:readable => options
|
116
|
-
:allow_undefined_global_classes => options
|
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
|
131
|
+
:output_dir => options.output_dir
|
120
132
|
)
|
121
|
-
if options
|
133
|
+
if options.stdio
|
122
134
|
puts Riml.compile($stdin.read, compile_options)
|
123
|
-
elsif options
|
135
|
+
elsif options.compile_files.any?
|
124
136
|
FileRollback.trap(:INT, :QUIT, :KILL) { print("\n"); exit 1 }
|
125
|
-
Riml.compile_files(*options
|
126
|
-
elsif options
|
127
|
-
files = options
|
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
|
144
|
+
elsif options.repl
|
133
145
|
require 'riml/repl'
|
134
|
-
Riml::Repl.new(options
|
146
|
+
Riml::Repl.new(options.vi_readline, compile_options).run
|
135
147
|
else
|
136
|
-
|
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
|
-
|
153
|
+
begin
|
154
|
+
Runner.start
|
155
|
+
rescue RimlError => e
|
156
|
+
abort e.message
|
157
|
+
end
|
143
158
|
end
|
data/lib/riml.rb
CHANGED
@@ -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 = {
|
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) }
|
data/lib/riml/ast_rewriter.rb
CHANGED
@@ -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
|
-
|
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
|