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 +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
|
[![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
|
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
|