File Coverage

blib/lib/Asm/Preproc.pm
Criterion Covered Total %
statement 114 115 99.1
branch 41 44 93.1
condition 3 9 33.3
subroutine 21 21 100.0
pod 8 8 100.0
total 187 197 94.9


line stmt bran cond sub pod time code
1             # $Id: Preproc.pm,v 1.15 2013/07/26 01:57:26 Paulo Exp $
2            
3             package Asm::Preproc;
4            
5             #------------------------------------------------------------------------------
6            
7             =head1 NAME
8            
9             Asm::Preproc - Preprocessor to be called from an assembler
10            
11             =cut
12            
13             #------------------------------------------------------------------------------
14            
15 6     6   35728 use warnings;
  6         16  
  6         206  
16 6     6   35 use strict;
  6         12  
  6         142  
17            
18 6     6   32 use File::Spec;
  6         17  
  6         166  
19 6     6   3811 use Asm::Preproc::Line;
  6         14  
  6         166  
20 6     6   11218 use Iterator::Simple::Lookahead;
  6         34434  
  6         403  
21            
22             our $VERSION = '1.02';
23            
24             #------------------------------------------------------------------------------
25            
26             =head1 SYNOPSIS
27            
28             use Asm::Preproc;
29             my $pp = Asm::Preproc->new();
30             my $pp = Asm::Preproc->new(@files);
31            
32             $pp->add_path(@path); @path = $pp->path;
33             $pp->include($file); $pp->include($file, $from_line);
34             my $full_path = $pp->path_search($file);
35            
36             $pp->include_list(@input);
37            
38             my $iter = sub {return scalar };
39             $pp->include_list($iter);
40            
41             my $line = $pp->getline; # isa Asm::Preproc::Line
42             my $strm = $pp->line_stream; # isa Iterator::Simple::Lookahead
43            
44             =head1 DESCRIPTION
45            
46             This module implements a preprocessor that reads source files
47             and handles recursive file includes.
48             It is intended to be called from inside an assembler or compiler.
49            
50             =cut
51            
52             # TODO: does conditional text expansion and macro substitution.
53            
54             =head1 METHODS
55            
56             =head2 new
57            
58             Creates a new object. If an argument list is given, calls C
59             for each of the file starting from the last, so that the files are
60             read in the given order.
61            
62             =cut
63            
64             #------------------------------------------------------------------------------
65             # Asm::Preproc::File : current file being read
66             use Class::XSAccessor::Array {
67 6         67 class => 'Asm::Preproc::File',
68             accessors => {
69             iter => 0, # iter() to read each line
70             file => 1, # file name
71             line_nr => 2, # current line number
72             line_inc => 3, # line number increment
73             },
74 6     6   44 };
  6         23  
75             sub Asm::Preproc::File::new {
76             #my($class, $iter, $file) = @_;
77 30     30   54 my $class = shift;
78 30         173 bless [@_, 0, 1], $class;
79             }
80            
81             #------------------------------------------------------------------------------
82             # Asm::Preproc : stack of stuff to read
83             use Class::XSAccessor::Array {
84 6         42 accessors => {
85             _stack => 0, # stack of Asm::Preproc::File
86             _path => 1, # path of search directories
87             },
88 6     6   1654 };
  6         14  
89            
90 6     6   880 use constant TOP => -1; # top of stack, i.e. current input file
  6         11  
  6         8287  
91            
92             sub new {
93 20     20 1 11005 my($class, @files) = @_;
94 20         74 my $self = bless [
95             [], # stack
96             [], # path
97             ], $class;
98 20         100 $self->include($_) for reverse @files;
99 19         115 return $self;
100             }
101             #------------------------------------------------------------------------------
102            
103             =head2 path
104            
105             Returns the list of directories to search in sequence for source files.
106            
107             =cut
108            
109             #------------------------------------------------------------------------------
110 3     3 1 7 sub path { @{$_[0]->_path} }
  3         22  
111             #------------------------------------------------------------------------------
112            
113             =head2 add_path
114            
115             Adds the given directories to the path searched for include files.
116            
117             =cut
118            
119             #------------------------------------------------------------------------------
120             sub add_path {
121 2     2 1 7 my($self, @dirs) = @_;
122 2         4 push @{$self->_path}, @dirs;
  2         10  
123             }
124             #------------------------------------------------------------------------------
125            
126             =head2 path_search
127            
128             Searches for the given file in the C created by C, returns
129             the first full path name where the file can be found.
130            
131             Returns the input file name if the file is found in the current directory,
132             or if it is not found in any of the C directories.
133            
134             =cut
135            
136             #------------------------------------------------------------------------------
137             sub path_search {
138 27     27 1 49 my($self, $file) = @_;
139            
140 27 100       534 return $file if -f $file; # found
141            
142 7         15 for my $dir (@{$self->_path}) {
  7         31  
143 8         111 my $full_path = File::Spec->catfile($dir, $file);
144 8 100       171 return $full_path if -f $full_path;
145             }
146            
147 2         32 return $file; # not found
148             }
149             #------------------------------------------------------------------------------
150            
151             =head2 include
152            
153             Open the input file and sets-up the object to read each line in sequence.
154            
155             The optional second argument is a L
156             object pointing at the C<%include> line that included the file, to be used
157             in error messages.
158            
159             An exception is raised if the input file cannot be read, or if a file is
160             included recursively, to avoid an infinite include loop.
161            
162             =cut
163            
164             #------------------------------------------------------------------------------
165             sub include {
166 23     23 1 47 my($self, $file, $from_line) = @_;
167            
168             # search include path
169 23         62 my $full_path = $self->path_search($file);
170            
171             # check for include loop
172 23 100       47 if (grep {$_->file eq $full_path} @{$self->_stack}) {
  16         79  
  23         114  
173 1   33     15 ($from_line || Asm::Preproc::Line->new)
174             ->error("%include loop")
175             }
176            
177             # open the file
178 22 100 33     772 open(my $fh, $full_path) or
179             ($from_line || Asm::Preproc::Line->new)
180             ->error("unable to open input file '$full_path'");
181            
182             # create a new iterator to read file lines
183             my $iter = sub {
184 48 50   48   113 return undef unless $fh;
185 48         424 my $text = <$fh>;
186 48 100       169 defined($text) and return $text;
187            
188 18         200 undef $fh; # close fh at end of file
189 18         41 return undef;
190 21         103 };
191 21         65 $self->_push_iter($iter, $full_path);
192             }
193             #------------------------------------------------------------------------------
194            
195             =head2 include_list
196            
197             Sets-up the object to read each element of the passed input
198             list one line at a time.
199            
200             Each element of the list is either a text string
201             or a code reference of an iterator.
202             The iterator may return text strings, or other iterators that will be
203             called recursively. The iterator returns C at the end of input.
204            
205             The text strings are split by lines, so that each C calls returns
206             one complete line.
207            
208             As the text lines are scanned for pre-processor directives, the following two
209             lines are equivalent:
210            
211             $pp->include('file.asm');
212             $pp->include_list('%include ');
213            
214             =cut
215            
216             #------------------------------------------------------------------------------
217             sub include_list {
218 9     9 1 37 my($self, @input) = @_;
219            
220             # create a new iterator to read text lines from iterators or strings
221             my $iter = sub {
222 66     66   106 while (1) {
223 106 100       252 return undef unless @input;
224            
225             # call iterator to get first string, if any
226 96 100       256 if (ref $input[0]) {
227 32         80 my $text = $input[0]->(); # get first from iterator
228 32 100       169 if (defined $text) {
229 29         58 unshift @input, $text; # insert line at head
230             }
231             else {
232 3         8 shift @input; # iter exhausted, drop it
233             }
234 32         59 next; # need to test list again
235             }
236            
237             # line is a string, return each complete line
238 64         139 for ($input[0]) {
239 64 100       157 last unless defined $_; # skip undef lines
240 60 100       359 if (/ \G ( .*? \n | .+ ) /gcx) {
241 56 100       153 shift @input if pos == length; # consumed all, drop it
242 56         236 return $1;
243             }
244             }
245 8         14 shift @input; # end of input
246             }
247 9         58 };
248 9         32 $self->_push_iter($iter, "-");
249             }
250            
251             #------------------------------------------------------------------------------
252             # prepare the object to read the given iterator and file name
253             sub _push_iter {
254 30     30   66 my($self, $iter, $file) = @_;
255            
256             # new file in the stack
257 30         105 push @{$self->_stack}, Asm::Preproc::File->new($iter, $file);
  30         232  
258             }
259             #------------------------------------------------------------------------------
260            
261             =head2 getline
262            
263             Returns the next line from the input, after doing all the pre-processing.
264             The line is returned as a L object
265             containing the actual text, and the file and line number where the text
266             was found.
267            
268             Returns C at the end of the input.
269            
270             =cut
271            
272             #------------------------------------------------------------------------------
273             # return next line as a Asm::Preproc::Line object
274             sub getline {
275 99     99 1 8951 my($self) = @_;
276            
277 99         153 while (1) {
278 142 100       180 return undef unless @{$self->_stack}; # no more files
  142         625  
279 109         234 my $top = $self->_stack->[TOP];
280            
281             # read line
282 109         273 my $text = $top->iter->();
283 109 100       283 if (! defined $text) { # file finished, read next
284 27         41 pop @{$self->_stack};
  27         68  
285 27         225 next;
286             }
287            
288             # inc line number, save it to use as the line_nr of a multi-line
289             # continuation
290 82         254 my $line_nr = $top->line_nr( $top->line_nr + $top->line_inc );
291            
292             # while line ends in \\, remove all blanks before it and \r \n after
293             # the line contains at most one \n, due to include_list() iterator
294 82         307 while ($text =~ s/ \s* \\ [\r\n]* \z / /x) {
295 5         16 my $next = $top->iter->();
296 5         19 $top->line_nr( $top->line_nr + $top->line_inc );
297            
298 5 100       13 defined($next) or last; # no more input, ignore last \\
299 4         21 $text .= $next;
300             }
301            
302             # normalize eol
303 82         485 $text =~ s/ \s* \z /\n/x; # any ending blanks replaced by \n
304            
305             # line to be returned, is used in %include below
306 82         487 my $line = Asm::Preproc::Line->new($text, $top->file, $line_nr);
307            
308             # check for pre-processor directives
309 82 100       321 if ($text =~ /^ \s* [\#\%] /gcix) {
310 18 100       80 if ($text =~ / \G line /gcix) {
    50          
311             # %line n+m file
312             # #line n "file"
313 6 50       22 if ($text =~ / \G \s+ (\d+) /gcix) { # line_nr
314 6         17 $top->line_nr( $1 );
315            
316 6 100       16 if ($text =~ / \G \+ (\d+) /gcix) { # optional line_inc
317 3         8 $top->line_inc( $1 );
318             }
319             else {
320 3         8 $top->line_inc( 1 );
321             }
322            
323 6 100       19 if ($text =~ / \G \s+ \"? ([^\"\s]+) \"? /gcix) { # file
324 4         12 $top->file( $1 );
325             }
326            
327             # next line in nr+inc
328 6         68 $top->line_nr( $top->line_nr - $top->line_inc );
329 6         17 next; # get next line
330             }
331             }
332             elsif ($text =~ / \G include /gcix) {
333             # %include
334             # #include 'file'
335             # %include "file"
336             # #include file
337 12 100       56 if ($text =~ / \G \s+ (?: \< ([^\>]+) \> |
338             \' ([^\']+) \' |
339             \" ([^\"]+) \" |
340             (\S+)
341             ) /gcix) {
342 11   33     84 my $file = $1 || $2 || $3 || $4;
343 11         28 $self->include($file, $line);
344 10         38 next; # get next line
345             }
346             else {
347 1         6 $line->error("%include expects a file name\n");
348             }
349             }
350             else {
351             # ignore other unknown directives
352 0         0 next; # get next line
353             }
354             }
355             else {
356             # TODO: macro expansion
357             }
358            
359             # return complete line
360 64         195 return $line;
361             }
362             }
363             #------------------------------------------------------------------------------
364            
365             =head2 line_stream
366            
367             Returns a L object that will
368             return the result of C on each C call.
369            
370             =cut
371            
372             #------------------------------------------------------------------------------
373             sub line_stream {
374 1     1 1 2 my($self) = @_;
375 1     4   6 return Iterator::Simple::Lookahead->new(sub {$self->getline});
  4         524  
376             }
377             #------------------------------------------------------------------------------
378            
379             =head1 PREPROCESSING
380            
381             The following preprocessor-like lines are processed:
382            
383             %line N+M FILE
384            
385             nasm-like line directive, telling that the next input line is
386             line N from file FILE,
387             followed by lines N+M, N+2*M, ...
388             This information is used to generate error messages.
389             Usefull to parse a file preprocessed by nasm.
390            
391             #line N "FILE"
392            
393             cpp-like line directive, telling that the next input line is
394             line N from file FILE,
395             followed by lines N+1, N+2, ...
396             This information is used to generate error messages.
397             Usefull to parse a file preprocessed by cpp.
398            
399             %include 'FILE'
400             %include "FILE"
401             %include
402             %include FILE
403             #include 'FILE'
404             #include "FILE"
405             #include
406             #include FILE
407            
408             nasm/cpp-like include directive, asking to insert the contents
409             of the given file in the input stream.
410            
411             All the other preprocessor-like lines are ignored, i.e. lines starting with '#' or '%'.
412            
413             =head1 AUTHOR
414            
415             Paulo Custodio, C<< >>
416            
417             =head1 BUGS and FEEDBACK
418            
419             Please report any bugs or feature requests through the web interface at
420             L.
421            
422             =head1 ACKNOWLEDGEMENTS
423            
424             Inspired in the Netwide Assembler, L
425            
426             =head1 LICENSE and COPYRIGHT
427            
428             Copyright (c) 2010 Paulo Custodio.
429            
430             This program is free software; you can redistribute it and/or modify it
431             under the terms of either: the GNU General Public License as published
432             by the Free Software Foundation; or the Artistic License.
433            
434             See http://dev.perl.org/licenses/ for more information.
435            
436             =cut
437            
438             1; # End of Asm::Preproc