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