Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 433 lines (337 sloc) 8.979 kb
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
1 # A simple processor to create a list of dependencies for a given source code
2 # file based on the #include files in the source file.
3 #
4 # The process involves three phases:
5 #
6 # 1. Parsing the source file for all C/C++ preprocessor control directives
7 # (#include, #ifdef, #ifndef, #else, #endif, #define, #undef, #if, #elif)
8 # 2. Executing the tree derived from step 1 in the context of a set of
9 # macro definitions.
10 # 3. Collecting the dependency list for each source file.
11 #
12 # In step 1, the tree resulting from parsing each included file is cached. If
13 # a later file of the same expanded relative path is included, the parse tree
14 # from the cache is returned.
15 #
16 # The system level macro definitions are retrieved using `cpp -dM`. This
17 # system set may be updated with new definitions or removed definitions while
18 # the parse tree is executed.
19
20 require 'set'
21
22 class DependencyGrapher
32cf036 Brian Shirai Abstract /dev/null for Windows (luislavena).
brixen authored
23 DEV_NULL = RUBY_PLATFORM =~ /mingw|mswin/ ? 'NUL' : '/dev/null'
24
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
25 class ExpressionEvaluator
26 def initialize(expression)
27 @expression = expression
28 end
29
30 # TODO: when melbourne is running in MRI, use it to parse
31 # the expression and then evaluate it.
32 def evaluate(defines)
33 @expression == "0" ? false : true
34 end
35 end
36
37 class Node
38 def initialize(parser)
39 @parser = parser
40 end
41
42 def close
43 message = "unbalanced \#endif for #{@parser.stack_top.class} at line #{@parser.line}"
44 raise ParseError, message
45 end
46
47 def add_else
48 message = "invalid \#else for #{@parser.stack_top.class} at line #{@parser.line}"
49 end
50
51 # TODO: remove
52 def execute(defines, node)
53 puts "#execute not implemented for #{self.class}"
54 end
55 end
56
57 module Container
58 attr_accessor :body
59
60 def initialize(parser)
61 super parser
62 @body = []
63 end
64
65 def close
66 @parser.stack_pop
67 end
68
69 def execute_body(defines, node)
70 body.each { |x| x.execute(defines, node) }
71 end
72 end
73
74 module Conditional
75 def initialize(parser)
76 super parser
77 @else = nil
78 end
79
80 def add_else(node)
81 @else = node
82 end
83 end
84
85
86 class SourceFile < Node
87 include Container
88
89 attr_reader :name, :object_name, :includes, :dependencies
90
91 def initialize(name, parser)
92 super parser
93 @name = name
94 @object_name = name.sub(/((c(pp)?)|S)$/, 'o')
95 @includes = []
96 end
97
98 def execute(defines)
99 execute_body defines, self
100 end
101
102 def collect_dependencies
103 set = Set.new
104
105 set << @name
106 @includes.each { |x| x.collect_dependencies(set) }
107
108 @dependencies = set.to_a.sort
109 end
110
111 def print_dependencies(out, max)
112 str = "#{@object_name}:"
113 out.print str
114
115 width = str.size
116 @dependencies.each do |name|
117 size = name.size + 1
118 if width + size > max
119 width = 0
120 out.print " \\\n "
121 end
122
123 out.print " ", name
124 width += size
125 end
126
127 out.print "\n"
128 end
129 end
130
131 class IncludedFile < Node
132 include Container
133
134 attr_reader :name, :includes
135
136 def self.cache
137 @cache ||= {}
138 end
139
140 def initialize(name, parser)
141 super parser
142 @name = name
143 @includes = []
144 end
145
4e59bdd Report the file has a bad include
Evan Phoenix authored
146 def expand_filename(node)
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
147 return if File.exist? @name
148
149 @parser.directories.each do |dir|
150 path = File.join dir, @name
151 return @name = path if File.file? path
152 end
153
4e59bdd Report the file has a bad include
Evan Phoenix authored
154 raise Errno::ENOENT, "unable to find file to include: #{@name} from #{node.name}"
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
155 end
156
157 def execute(defines, node)
4e59bdd Report the file has a bad include
Evan Phoenix authored
158 expand_filename(node)
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
159
160 if cached = self.class.cache[@name.to_sym]
161 @body = cached.body
162 else
163 parser = FileParser.new self, @parser.directories
164 parser.parse_file @name
165 self.class.cache[@name.to_sym] = self
166 end
167
168 execute_body defines, self
169
170 node.includes << self
171 end
172
173 def collect_dependencies(set)
174 return if set.include? @name
175
176 set << @name
177 @includes.each { |x| x.collect_dependencies(set) }
178 end
179 end
180
181 class IfDefined < Node
182 include Conditional, Container
183
184 def initialize(macro, parser)
185 super parser
186 @macro = macro.strip
187 end
188
189 def execute(defines, node)
190 if defines.key? @macro
191 execute_body defines, node
192 elsif @else
193 @else.execute(defines, node)
194 end
195 end
196 end
197
198 class IfNotDefined < Node
199 include Conditional, Container
200
201 def initialize(macro, parser)
202 super parser
203 @macro = macro.strip
204 end
205
206 def execute(defines, node)
207 if !defines.key? @macro
208 execute_body defines, node
209 elsif @else
210 @else.execute(defines, node)
211 end
212 end
213 end
214
215 class If < Node
216 include Conditional, Container
217
218 def initialize(expression, parser)
219 super parser
220 @value = nil
221 @expression = expression.strip
222 end
223
224 def execute(defines, node)
225 @value = ExpressionEvaluator.new(@expression).evaluate defines
226
227 if @value
228 execute_body(defines, node)
229 elsif @else
230 @else.execute(defines, node)
231 end
232 end
233 end
234
235 class Else < Node
236 include Container
237
238 def execute(defines, node)
239 execute_body defines, node
240 end
241 end
242
243 class ElseIf < If; end
244
245 class Define < Node
246 def initialize(macro, parser)
247 super parser
248
249 macro.strip!
250 if index = macro.index(" ")
251 @name = macro[0..index-1]
252 @value = macro[index+1..-1]
253 @name, @value = macro.strip.split
254 else
255 @name = macro
256 @value = "1"
257 end
258 end
259
260 def execute(defines, node)
261 defines[@name] = @value
262 end
263 end
264
265 class Undefine < Node
266 def initialize(macro, parser)
267 super parser
268 @macro = macro.strip
269 end
270
271 def execute(defines, node)
272 defines.delete @macro
273 end
274 end
275
276 class ParseError < Exception; end
277
278 # Parses a file for all preprocessor control directives into a tree of Node
279 # objects. The parser can operate on a file or an array of lines.
280 class FileParser
281 attr_reader :line, :directories
282
283 def initialize(root, directories)
284 @stack = [root]
285 @directories = directories
286 end
287
288 # Parser operations
289
290 def add_body(node)
291 @stack.last.body << node
292 end
293
294 def add_else(node)
295 @stack.last.add_else node
296 end
297
298 def stack_push(node)
299 @stack.push node
300 end
301
302 def stack_pop
303 @stack.pop
304 end
305
306 def stack_top
307 @stack.last
308 end
309
310 def close
311 @stack.last.close
312 end
313
314 # Events
315
316 def process_include(name)
317 # We do not process any <files>. This could be enabled as
318 # an option, but we don't need it or want it now.
319 name =~ /\s*"([^"]+)".*$/
320 return unless $1
321
322 node = IncludedFile.new $1, self
323 add_body node
324 end
325
326 def process_ifdef(macro)
327 node = IfDefined.new macro, self
328 add_body node
329 stack_push node
330 end
331
332 def process_ifndef(macro)
333 node = IfNotDefined.new macro, self
334 add_body node
335 stack_push node
336 end
337
338 def process_endif(rest)
339 close
340 end
341
342 def process_else(rest)
343 node = Else.new self
344 add_else node
345 end
346
347 def process_define(macro)
348 node = Define.new macro, self
349 add_body node
350 end
351
352 def process_undef(macro)
353 node = Undefine.new macro, self
354 add_body node
355 end
356
357 def process_if(expression)
358 node = If.new expression, self
359 add_body node
360 stack_push node
361 end
362
363 def process_elif(expression)
364 node = ElseIf.new expression, self
365 add_else node
366 add_body node
367 stack_push node
368 end
369
370 # Parse methods
371
372 def parse_file(name)
373 parse IO.readlines(name)
374 end
375
376 def parse(lines)
377 @line = 0
378
379 lines.each do |line|
380 @line += 1
381 m = line.match(/^\s*#(include|ifdef|ifndef|endif|else|define|undef|if|elif)(.*)$/)
382
383 # TODO: continue reading if line ends in \
384
385 send :"process_#{m[1]}", m[2] if m
386 end
387 end
388 end
389
390 attr_accessor :file_names, :directories, :defines, :system_defines
391 attr_reader :sources
392
393 def initialize(files, directories=[], defines=nil)
394 @file_names = files
395 @directories = directories
396 @defines = defines
397
398 @system_defines = {}
399 @sources = []
400 end
401
402 def get_system_defines
32cf036 Brian Shirai Abstract /dev/null for Windows (luislavena).
brixen authored
403 lines = `cpp -dM #{@defines} #{DEV_NULL}`.split("\n")
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
404
405 source = SourceFile.new "sytem_defines", self
406 parser = FileParser.new source, @directories
407 parser.parse lines
408
409 source.execute @system_defines
410 end
411
412 def process
413 get_system_defines
414
415 @file_names.each do |name|
416 source = SourceFile.new name, self
417 parser = FileParser.new source, @directories
418
419 parser.parse_file name
420 source.execute @system_defines.dup
421 source.collect_dependencies
422
423 @sources << source
424 end
425 end
426
427 def print_dependencies(out, max=72)
428 @sources.each do |source|
429 source.print_dependencies out, max
430 end
431 end
432 end
Something went wrong with that request. Please try again.