File Coverage

blib/lib/Parse/ALex.pm
Criterion Covered Total %
statement 229 482 47.5
branch 47 144 32.6
condition 5 24 20.8
subroutine 41 70 58.5
pod 0 50 0.0
total 322 770 41.8


line stmt bran cond sub pod time code
1             # Copyright (c) Philippe Verdret, 1995-1999
2            
3             # Architecture:
4             # Parse::ALex - Abstract Lexer
5             # |
6             # +----------+
7             # | |
8             # | Parse::Tokenizer
9             # | | |
10             # LexEvent Lex CLex ... - Concrete lexers
11            
12             require 5.004;
13 10     10   32391 use integer;
  10         106  
  10         49  
14 10     10   317 use strict qw(vars);
  10         18  
  10         303  
15 10     10   61 use strict qw(refs);
  10         14  
  10         239  
16 10     10   47 use strict qw(subs);
  10         17  
  10         454  
17            
18             package Parse::ALex;
19             $Parse::ALex::VERSION = '2.21';
20 10     10   16464 use Parse::Trace;
  10         25  
  10         415  
21             @Parse::ALex::ISA = qw(Parse::Trace);
22            
23 10     10   9264 use Parse::Token;
  10         32  
  10         529  
24 10     10   60 use Parse::Template;
  10         18  
  10         2794  
25            
26             # Default values
27             my $trace = 0; # if true enable the trace mode
28             my $hold = 0; # if true enable data saving
29             my $skip = '[ \t]+'; # strings to skip
30             my $DEFAULT_STREAM = \*STDIN; # Input Filehandle
31             my $eoi = 0; # 1 if end of imput
32            
33             # define constant, use a pseudo-hash???
34             my %_map; # Define a mapping between element names and numbers
35             my @attributes = qw(STREAM FROM_STRING LEXER_SUB BUFFER PENDING_TOKEN
36             LINE RECORD_LENGTH OFFSET POS
37             EOI SKIP HOLD HOLD_TEXT
38             NAME IN_PKG
39             TEMPLATE
40             LEXER_STRING_SUB LEXER_STREAM_SUB LEXER_CLOSURE_ENV
41             LEXER_STRING_CODE LEXER_STREAM_CODE LEXER_CODE
42             STATE_MACHINE_CODE STATES STACK_STATES EXCLUSIVE_COND INCLUSIVE_COND
43             TRACE
44             TOKEN_LIST);
45             my($STREAM, $FROM_STRING, $LEXER_SUB, $BUFFER, $PENDING_TOKEN,
46             $LINE, $RECORD_LENGTH, $OFFSET, $POS,
47             $EOI, $SKIP, $HOLD, $HOLD_TEXT,
48             $NAME, $IN_PKG,
49             $TEMPLATE,
50             $LEXER_STRING_SUB, $LEXER_STREAM_SUB, $LEXER_CLOSURE_ENV,
51             $LEXER_STRING_CODE, $LEXER_STREAM_CODE, $LEXER_CODE,
52             $STATE_MACHINE_CODE, $STATES, $STACK_STATES, $EXCLUSIVE_COND, $INCLUSIVE_COND,
53             $TRACE,
54             $TOKEN_LIST
55             ) = @_map{@attributes} = (0..$#attributes);
56            
57             #sub EOI () { 9 } # trial
58            
59             sub _map {
60 6     6   10 shift;
61 6 50       16 if (@_) {
62 6 50       44 wantarray ? @_map{@_} : $_map{$_[0]};
63             } else {
64 0         0 @attributes;
65             }
66             }
67            
68             my $somevar = '';
69             # Create and instanciate a prototypical instance
70             my $lexer = __PACKAGE__->clone;
71 12 100   12 0 172 sub prototype { $lexer or [] }
72            
73             my $TOKEN_CLASS = 'Parse::Token'; # Root class for the token objects
74             sub tokenClass {
75 10 50   10 0 44 if (defined $_[1]) {
76 10     10   73 no strict qw/refs/;
  10         17  
  10         11067  
77 10         19 ${"$TOKEN_CLASS" . "::PENDING_TOKEN"} = $PENDING_TOKEN;
  10         52  
78 10         25 $TOKEN_CLASS = $_[1];
79             } else {
80 0         0 $_[1]
81             }
82             }
83             my $DEFAULT_TOKEN = $TOKEN_CLASS->new('DEFAULT', '.*'); # default token
84             $lexer->tokenClass($TOKEN_CLASS);
85            
86             $lexer->[$STREAM] = $DEFAULT_STREAM;
87             $lexer->[$FROM_STRING] = 0; # 1 for a string
88             $lexer->[$BUFFER] = \$somevar; # string to tokenize
89             $lexer->[$PENDING_TOKEN] = $DEFAULT_TOKEN;
90             $lexer->[$LINE] = \$somevar; # number of the current record
91             $lexer->[$RECORD_LENGTH] = \$somevar; # length of the current record
92             $lexer->[$OFFSET] = \$somevar; # offset from the beginning of the analysed stream
93             $lexer->[$POS] = \$somevar; # position in the current record
94             $lexer->[$EOI] = $eoi;
95             $lexer->[$SKIP] = $skip; # a pattern to skip
96             $lexer->[$HOLD] = $hold; # save or not what is consumed
97             $lexer->[$HOLD_TEXT] = ''; # saved string
98             $lexer->[$TEMPLATE] = new Parse::Template; # code template
99            
100             # lexer code: [HEADER, BODY, FOOTER]
101             $lexer->[$LEXER_STREAM_CODE] = []; # cached subroutine definition
102             $lexer->[$LEXER_STRING_CODE] = []; # cached subroutine definition
103             $lexer->[$LEXER_CODE] = []; # definition of the current lexer
104             $lexer->[$LEXER_CLOSURE_ENV] = []; # environnement of the lexer closure
105             $lexer->[$LEXER_SUB] = my $DEFAULT_LEXER_SUB = sub {
106             $_[0]->genLex; # lexer autogeneration
107             &{$_[0]->[$LEXER_SUB]}; # lexer execution
108             };
109             $lexer->[$LEXER_STREAM_SUB] = sub {}; # cache for the stream lexer
110             $lexer->[$LEXER_STRING_SUB] = sub {}; # cache for the string lexer
111            
112             # State machine
113             $lexer->[$EXCLUSIVE_COND] = {}; # exclusive conditions
114             $lexer->[$INCLUSIVE_COND] = {}; # inclusive conditions
115             $lexer->[$STATE_MACHINE_CODE] = ''; # definition of the state machine
116             $lexer->[$STATES] = { 'INITIAL' => \$somevar }; # state machine
117             $lexer->[$STACK_STATES] = []; # stack of states, not used
118             $lexer->[$TRACE] = $trace;
119             $lexer->[$TOKEN_LIST] = []; # Token instances
120            
121             sub reset { # reset all lexer's state values
122 8     8 0 16 my $self = shift;
123 8         12 ${$self->[$LINE]} = 0;
  8         18  
124 8         12 ${$self->[$RECORD_LENGTH]} = 0;
  8         15  
125 8         10 ${$self->[$OFFSET]} = 0;
  8         14  
126 8         9 ${$self->[$POS]} = 0;
  8         15  
127 8         13 ${$self->[$BUFFER]} = '';
  8         12  
128 8         16 $self->[$HOLD_TEXT] = '';
129 8         10 $self->[$EOI] = 0;
130 8         40 $self->state('INITIAL'); # initialize the state machine
131 8 100       22 if ($self->[$PENDING_TOKEN]) {
132 5         24 $self->[$PENDING_TOKEN]->setText();
133 5         7 $self->[$PENDING_TOKEN] = 0;
134             }
135 8         15 $self;
136             }
137            
138             sub eoi {
139 0     0 0 0 my $self = shift;
140 0         0 $self->[$EOI];
141             }
142             sub token { # always return a Token object
143 0     0 0 0 my $self = shift;
144 0 0       0 $self->[$PENDING_TOKEN] or $DEFAULT_TOKEN
145             }
146             *getToken = \&token;
147             sub setToken { # force the token
148 0     0 0 0 my $self = shift;
149 0         0 $self->[$PENDING_TOKEN] = $_[0];
150             }
151             sub setBuffer { # not documented
152 0     0 0 0 my $self = shift;
153 0         0 ${$self->[$BUFFER]} = $_[0];
  0         0  
154             }
155             sub getBuffer { # not documented
156 0     0 0 0 my $self = shift;
157 0         0 ${$self->[$BUFFER]};
  0         0  
158             }
159             sub buffer {
160 0     0 0 0 my $self = shift;
161 0 0       0 if (defined $_[0]) {
162 0         0 ${$self->[$BUFFER]} = $_[0]
  0         0  
163             } else {
164 0         0 ${$self->[$BUFFER]};
  0         0  
165             }
166             }
167             sub flush {
168 0     0 0 0 my $self = shift;
169 0         0 my $tmp = $self->[$HOLD_TEXT];
170 0         0 $self->[$HOLD_TEXT] = '';
171 0         0 $tmp;
172             }
173             # returns or sets the number of the current record
174             sub line {
175 0     0 0 0 my $self = shift;
176 0 0       0 if (@_) {
177 0         0 ${$self->[$LINE]} = shift;
  0         0  
178             } else {
179 0         0 ${$self->[$LINE]};
  0         0  
180             }
181             }
182             # return the length of the current record
183             # not documented
184             sub length {
185 0     0 0 0 my $self = shift;
186 0 0       0 if (@_) {
187 0         0 ${$self->[$RECORD_LENGTH]} = $_[0];
  0         0  
188             } else {
189 0         0 ${$self->[$RECORD_LENGTH]};
  0         0  
190             }
191             }
192             # return the end position of last token from the stream beginning
193             sub offset {
194 0     0 0 0 my $self = shift;
195 0         0 ${$self->[$OFFSET]};
  0         0  
196             }
197             # return the end position of the last token
198             # in the current record
199             sub pos {
200 0     0 0 0 my $self = shift;
201 0 0       0 if (defined $_[0]) {
202 0         0 ${$self->[$POS]} = $_[0]
  0         0  
203             } else {
204 0         0 ${$self->[$POS]};
  0         0  
205             }
206             }
207             sub name {
208 0     0 0 0 my $self = shift;
209 0 0       0 if (defined $_[0]) {
210 0         0 $self->[$NAME] = $_[0]
211             } else {
212 0         0 $self->[$NAME];
213             }
214             }
215             # not documented
216             sub inpkg {
217 10     10 0 23 my $self = shift;
218             # if (ref $self) {
219 10 50       24 if (defined $_[0]) {
220 0         0 $self->[$IN_PKG] = $_[0]
221             } else {
222 10         54 $self->[$IN_PKG];
223             }
224             # } else {
225             # if (defined $_[0]) {
226             # $inpkg = $_[0]
227             # } else {
228             # $inpkg;
229             # }
230             # }
231             }
232 10     10   108 use constant TRACE_GEN => 0;
  10         17  
  10         37445  
233             sub tokenList {
234 4     4 0 13 my $self = shift;
235 4 50 33     23 if ($^W and @{$self->[$TOKEN_LIST]} == 0) {
  0         0  
236 0         0 require Carp;
237 0         0 Carp::carp("no token defined");
238             }
239 4         11 @{$self->[$TOKEN_LIST]};
  4         31  
240             }
241             ####
242             # Purpose: define the data input
243             # Parameters: possibilities
244             # 1. filehandle (\*FH, *FH or IO::File instance)
245             # 2. list of strings
246             # 3.
247             # Returns: 1. returns the lexer
248             # 2. returns the lexer
249             # 3. returns the lexer's filehandle if defined
250             # or undef if not
251             sub from {
252 5     5 0 2631 my $self = shift;
253 5         11 my $debug = 0;
254            
255             # check for stream : check only ref($fh) insteaf of fileno()
256             # Filehandles connected to memory objects via new features of
257             # "open" may return undefined even though they are open.)
258 5         16 my $fd = $_[0];
259            
260 5 50       20 print STDERR "arg: $fd\n" if $debug;
261 5 100       22 if (ref($fd)) { # From STREAM
    50          
    0          
262 2         6 $self->[$STREAM] = $fd;
263 2 50       9 print STDERR "From stream\n" if $debug;
264            
265 2 100       4 if (@{$self->[$LEXER_STREAM_CODE]}) { # Code already exists
  2         8  
266 1 50       15 if ($self->[$FROM_STRING]) { # if STREAM definition isn't the current
267 0 0       0 print STDERR "code already exists\n" if $debug;
268 0         0 $self->[$LEXER_SUB] = $self->[$LEXER_STREAM_SUB];
269 0         0 $self->_switchClosureEnv();
270 0         0 $self->[$FROM_STRING] = 0;
271             }
272             }
273             else { # code doesn't exist
274 1 50       4 print STDERR "Analyze STREAM - code generation\n" if $debug;
275 1         2 $self->[$FROM_STRING] = 0;
276             #$self->[$LEXER_SUB] = $DEFAULT_LEXER_SUB; #
277 1         5 $self->genLex; # lexer generation
278             }
279            
280 2         9 $self->reset;
281 2         6 $self;
282             }
283             elsif (defined $_[0]) { # From STRING
284 3 50       8 print STDERR "From string\n" if $debug;
285 3 50       5 if (@{$self->[$LEXER_STRING_CODE]}) { # code already exists
  3         18  
286 0 0       0 unless ($self->[$FROM_STRING]) {
287 0 0       0 print STDERR "code already exists\n" if $debug;
288 0         0 $self->[$LEXER_SUB] = $self->[$LEXER_STRING_SUB];
289 0         0 $self->_switchClosureEnv();
290 0         0 $self->[$FROM_STRING] = 1;
291             }
292             }
293             else { # code doesn't exist
294 3 50       9 print STDERR "Analyze STRING - code generation\n" if $debug;
295 3         6 $self->[$FROM_STRING] = 1;
296             # autogeneration doesn't work,
297             # cause the generation delete the buffer
298             #$self->[$LEXER_SUB] = $DEFAULT_LEXER_SUB; #
299 3         20 $self->genLex; # lexer generation
300             }
301            
302 3         16 $self->reset;
303 3         12 my $buffer = join($", @_); # Data from a list
304 3         5 ${$self->[$BUFFER]} = $buffer;
  3         8  
305 3         8 ${$self->[$RECORD_LENGTH]} = CORE::length($buffer);
  3         5  
306 3         19 $self;
307             }
308             elsif ($self->[$STREAM]) {
309 0         0 $self->[$STREAM];
310             }
311             else {
312 0         0 undef;
313             }
314             }
315            
316            
317             sub readline {
318 0     0 0 0 my $fh = $_[0]->[$STREAM];
319 0         0 my $record = '';
320 0 0       0 if (not defined($record = <$fh>)) {
321 0         0 $_[0]->[$EOI] = 1;
322             } else {
323 0         0 ${$_[0]->[$LINE]}++;
  0         0  
324             }
325 0         0 $record;
326             }
327            
328 6     6 0 31 sub isFromString { $_[0]->[$FROM_STRING] }
329 6     6 0 37 sub isTrace { $_[0]->[$TRACE] }
330            
331             # could be improved
332             # Purpose: Toggle the trace mode
333             # todo: regenerate the lexer if needed
334             sub trace {
335 0     0 0 0 my $self = shift;
336 0         0 my $class = ref($self);
337 0 0       0 if ($class) { # Object method
338 0 0       0 if ($self->[$TRACE]) {
339 0         0 $self->[$TRACE] = 0;
340 0         0 print STDERR qq!trace OFF for a "$class" object\n!;
341             } else {
342 0         0 $self->[$TRACE] = 1;
343 0         0 print STDERR qq!trace ON for a "$class" object\n!;
344             }
345             } else { # Class method
346 0         0 $self->prototype()->[$TRACE] = not $self->prototype->[$TRACE];
347 0         0 $self->SUPER::trace(@_);
348             }
349             }
350 6     6 0 28 sub isHold { $_[0]->[$HOLD] }
351             # hold(EXPR)
352             # hold
353             # Purpose: Toggle method, hold or not consumed strings
354             # Arguments: nothing or EXPR true/false
355             # Returns: value of the hold attribute
356            
357             sub hold {
358 0     0 0 0 my $self = shift;
359 0 0       0 if (ref $self) { # Instance method
360 0         0 $self->[$HOLD] = not $self->[$HOLD];
361            
362             # delete the code already generated
363 0         0 @{$lexer->[$LEXER_STREAM_CODE]} = ();
  0         0  
364 0         0 @{$lexer->[$LEXER_STRING_CODE]} = ();
  0         0  
365 0         0 @{$lexer->[$LEXER_CODE]} = ();
  0         0  
366 0         0 $lexer->[$LEXER_SUB] = $DEFAULT_LEXER_SUB;
367            
368             } else { # Class method
369 0         0 $self->prototype()->[$HOLD] = not $self->prototype()->[$HOLD];
370             }
371             }
372            
373             # skip(EXPR)
374             # skip
375             # Purpose: return or set the value of the regexp used for consuming
376             # inter-token strings.
377             # Arguments: with EXPR change the regexp and regenerate the
378             # lexical analyzer
379             # Returns: see Purpose
380             sub skip {
381 6     6 0 10 my $self = shift;
382            
383 6         7 my $debug = 0;
384 6 50       21 if (ref $self) { # Instance method
385 6 50 33     30 if (defined($_[0]) and $_[0] ne $self->[$SKIP]) {
386 0 0       0 print STDERR "skip value: '$_[0]'\n" if $debug;
387 0         0 $self->[$SKIP] = $_[0];
388            
389             # delete the code already generated
390 0         0 @{$self->[$LEXER_STREAM_CODE]} = (); # or $self->[$LEXER_STREAM_CODE] = []
  0         0  
391 0         0 @{$self->[$LEXER_STRING_CODE]} = ();
  0         0  
392 0         0 @{$self->[$LEXER_CODE]} = ();
  0         0  
393 0         0 $self->[$LEXER_SUB] = $DEFAULT_LEXER_SUB;
394            
395             } else {
396 6         31 $self->[$SKIP];
397             }
398             } else { # Used as a Class method
399 0 0       0 print STDERR "skip value: '$_[0]'\n" if $debug;
400            
401 0 0       0 defined $_[0] ?
402             $self->prototype()->[$SKIP] = $_[0] : $self->prototype()->[$SKIP];
403             }
404             }
405             sub defineTokens {
406 3     3 0 5 my $self = shift;
407 3         21 my @token = $TOKEN_CLASS->factory(@_);
408 3         4 my $token;
409 3         6 foreach $token (@token) {
410 4         22 $token->lexer($self); # Attach each token to its lexer
411 4         19 $token->inpkg($self->inpkg); # Define the package in which the token is defined
412 4         19 $token->exportTo(); # export in the calling package
413             }
414 3         4 print STDERR @token + 0, " tokens\n" if TRACE_GEN;
415 3         9 $self->[$TOKEN_LIST] = [@token];
416             }
417             # From => STRING|FILEHANDLE, Tokens => [], Skip => RE
418             sub configure {
419 0     0 0 0 my $self = shift;
420 0         0 my ($key, $value);
421 0         0 while (@_ >= 2) {
422 0         0 ($key, $value) = (shift, shift);
423 0 0       0 if ($key =~ /^[Ff]rom$/) {
    0          
    0          
424 0         0 $self->from($value);
425             } elsif ($key =~ /^[Ss]kip$/) {
426 0         0 $self->skip($value);
427             } elsif ($key =~ /^[Tt]okens$/) {
428 0 0       0 unless (ref $value eq 'ARRAY') {
429 0         0 require Carp;
430 0         0 Carp::croak "'Tokens' must be associated to an ARRAY reference";
431             }
432 0         0 $self->defineTokens($value);
433             } else {
434 0         0 last;
435             }
436             }
437 0         0 $self;
438             }
439             # not documented
440             # Purpose: returns :
441             # - a copy of the prototypical lexer if used as a class method
442             # - a copy of the message receiver if used as an instance method
443             # naive implementation
444             sub clone {
445 15     15 0 32 my $receiver = shift;
446 15         28 my $class;
447 15 50       61 if ($class = ref $receiver) { # Instance method: clone the current instance
448 0         0 bless [@{$receiver}], $class;
  0         0  
449             } else { # Class method: clone the class prototype
450 15         75 bless [@{$receiver->prototype}], $receiver;
  15         55  
451             }
452             }
453             # Purpose: create the lexical analyzer
454             # Arguments: list of tokens or token specifications
455             # Returns: a lex object
456             sub new {
457 3     3 0 1162 my $receiver = shift;
458 3   33     17 my $class = (ref $receiver or $receiver);
459            
460 3 50       10 if ($class eq __PACKAGE__) {
461 0         0 require Carp;
462 0         0 Carp::croak "can't create an instance of '$class' abstract class"
463             }
464            
465 3         16 my $self = $receiver->clone;
466 3         19 $self->reset;
467 3         13 $self->[$IN_PKG] = caller;
468            
469 3 50       10 if (@_) {
470 3         20 $self->defineTokens(@_);
471             }
472 3         12 $self;
473             }
474            
475             # sub lexerType {
476             # my $self = shift;
477             # if ($self->isa('Parse::Lex')) {
478             # return 'Parse::Lex';
479             # } elsif ($self->isa('Parse::CLex')) {
480             # return 'Parse::CLex';
481             # } else {
482             # return ref $self || $self;
483             # }
484             # }
485            
486             # Put or fetch a template object
487             sub template {
488 14     14 0 109 my $self = shift;
489 14 100       31 if (defined $_[0]) {
490 2         15 $self->[$TEMPLATE] = $_[0];
491             } else {
492 12         101 $self->[$TEMPLATE];
493             }
494             }
495             sub getTemplate {
496 0     0 0 0 my $self = shift;
497 0         0 my $part = shift;
498 0         0 $self->[$TEMPLATE]->{$part};
499             }
500             sub setTemplate {
501 0     0 0 0 my $self = shift;
502 0         0 my $part = shift;
503 0         0 $self->[$TEMPLATE]->{$part} = shift;
504             }
505            
506             # redefine this!!! don't copy, just reference
507             # the LEXER_STRING_CODE!!!
508             # and don't regenerate if code already exists
509             sub genCode {
510 4     4 0 7 my $self = shift;
511 4         5 print STDERR "genCode()\n" if TRACE_GEN;
512 4         24 $self->genHeader();
513 4         2989 $self->genBody($self->tokenList);
514 4         25 $self->genFooter();
515 4 100       677 if ($self->[$FROM_STRING]) { # cache the already generated code
516 3         5 $self->[$LEXER_STRING_CODE] = [@{$self->[$LEXER_CODE]}];
  3         14  
517 3         10 $self->[$LEXER_STRING_SUB] = $self->[$LEXER_SUB];
518             } else {
519 1         4 $self->[$LEXER_STREAM_CODE] = [@{$self->[$LEXER_CODE]}];
  1         4  
520 1         4 $self->[$LEXER_STREAM_SUB] = $self->[$LEXER_SUB];
521             }
522             }
523             # Remark: not documented
524             sub genHeader {
525 4     4 0 7 my $self = shift;
526 4         18 my $template = $self->template;
527 4         4 print STDERR "genHeader()\n" if TRACE_GEN;
528             # build the template env
529 4         40 $template->env(
530             'SKIP' => $self->[$SKIP],
531             'IS_HOLD' => $self->[$HOLD],
532             'HOLD_TEXT' => $HOLD_TEXT,
533             'EOI' => $EOI, # array index
534             'TRACE' => $TRACE,
535             'IS_TRACE' => $self->[$TRACE],
536             'PENDING_TOKEN' => $PENDING_TOKEN, # array index
537             );
538            
539 4 100       285 if ($self->[$FROM_STRING]) {
540 3         9 $self->[$LEXER_CODE]->[0] = $self->template->eval('HEADER_STRING_PART');
541             } else {
542 1         3 $self->[$LEXER_CODE]->[0] = $self->template->eval('HEADER_STREAM_PART');
543             }
544             }
545             # Purpose: create the lexical analyzer
546             # Arguments: list of tokens
547             # Returns: a Lex object
548             # Remark: not documented
549             sub genBody {
550 4     4 0 7 my $self = shift;
551 4         6 print STDERR "genBody()\n" if TRACE_GEN;
552            
553 4         7 my $token;
554 4         9 my $body = '';
555 4         10 my $debug = 0;
556 4         4 print STDERR @_ + 0, " tokens\n" if TRACE_GEN;
557 4         34 while (@_) { # list of Token instances
558 6         31 $body .= shift->genCode();
559             }
560 4         13 $self->[$LEXER_CODE]->[1] = $body;
561             }
562             # Remark: not documented
563             sub genFooter {
564 4     4 0 9 my $self = shift;
565 4         5 print STDERR "genFooter()\n" if TRACE_GEN;
566 4         15 $self->[$LEXER_CODE]->[2] = $self->template->eval('FOOTER_PART');
567             }
568            
569             # Purpose: Returns code of the current lexer
570             # Arguments: nothing
571             # Returns: code of the lexical analyzer
572             # Remark: not documented, doesn't return the state machine definition
573             sub getCode {
574 4     4 0 7 my $self = shift;
575 4         6 my @code = @{$self->[$LEXER_CODE]};
  4         13  
576 4 50       14 unless (@code) {
577 0         0 $self->genCode;
578 0         0 @code = @{$self->[$LEXER_CODE]}
  0         0  
579             }
580 4         28 join '', @code;
581             }
582            
583             # Not documented
584             # Purpose: set/get environnement of the lexer closure
585             # Arguments: see definition
586             # Returns: references to some internal object fields
587             # todo: test type and number of arguments
588             sub _closureEnv {
589 8     8   12 my $self = shift;
590 8 100       18 if (@_) {
591 4         18 ($self->[$BUFFER],
592             $self->[$RECORD_LENGTH],
593             $self->[$LINE],
594             $self->[$POS],
595             $self->[$OFFSET],
596             $self->[$STATES],
597             ) = @_;
598             } else {
599 4         20 ($self->[$BUFFER],
600             $self->[$RECORD_LENGTH],
601             $self->[$LINE],
602             $self->[$POS],
603             $self->[$OFFSET],
604             $self->[$STATES],
605             )
606             }
607             }
608             sub _saveClosureEnv {
609 4     4   6 my $self = shift;
610 4         22 @{$self->[$LEXER_CLOSURE_ENV]} = $self->_closureEnv();
  4         17  
611             }
612             sub _switchClosureEnv {
613 0     0   0 my $self = shift;
614 0         0 my @tmp = $self->_closureEnv();
615 0         0 $self->_closureEnv(@{$self->[$LEXER_CLOSURE_ENV]});
  0         0  
616 0         0 @{$self->[$LEXER_CLOSURE_ENV]} = @tmp;
  0         0  
617             }
618            
619             # Purpose: Generate the lexical analyzer
620             # Arguments:
621             # A Returns: the anonymous subroutine implementing the lexical analyzer
622             # Remark: not documented
623             sub genLex {
624 4     4 0 6 my $self = shift;
625             # optimization: unless @{$self->[$LEXER_CODE]};
626             # or delegate this behavior to getCode() ?
627 4         23 $self->genCode;
628            
629 4         6 print STDERR "Lexer generation...\n" if TRACE_GEN;
630            
631             # Closure environnement
632 4         10 my $LEX_BUFFER = ''; # buffer to analyze
633 4         10 my $LEX_LENGTH = 0; # buffer length
634            
635             # my $LEX_BUFFER = ${$self->[$BUFFER]}; # buffer to analyze
636             # my $LEX_LENGTH = ${$self->[$RECORD_LENGTH]}; # buffer length
637            
638 4         7 my $LEX_RECORD = 0; # current record number
639 4         4 my $LEX_POS = 0; # current position in buffer
640 4         8 my $LEX_OFFSET = 0; # offset from the beginning
641 4         7 my $LEX_TOKEN = ''; # token instance
642 4         11 my %LEX_STATE = (); # states
643            
644 4         24 $self->_saveClosureEnv();
645 4         16 $self->_closureEnv(\(
646             $LEX_BUFFER,
647             $LEX_LENGTH,
648             $LEX_RECORD,
649             $LEX_POS,
650             $LEX_OFFSET,
651             %LEX_STATE,
652             ));
653            
654 4         10 my $LEX_FHR = \$self->[$STREAM];
655 4         22 my $stateMachine = $self->genStateMachine();
656 4         21 my $analyzer = $self->getCode();
657 4         2276 eval qq!$stateMachine; \$self->[$LEXER_SUB] = sub $analyzer!;
658            
659 4         9 my $debug = 0;
660 4 50 33     34 if ($@ or $debug) { # can be useful ;-)
661 0         0 my $line = 0;
662 0         0 $stateMachine =~ s/^/sprintf("%3d ", $line++)/meg; # line numbers
  0         0  
663 0         0 $analyzer =~ s/^/sprintf("%3d ", $line++)/meg;
  0         0  
664 0         0 print STDERR "$stateMachine$analyzer\n";
665 0         0 print STDERR "$@\n";
666 0 0       0 die "\n" if $@;
667             }
668 4         15 $self->[$LEXER_SUB];
669             }
670            
671             # Purpose: returns the lexical analyzer routine
672             # Arguments: nothing
673             # Returns: the anonymous sub implementing the lexical analyzer
674             sub getSub {
675 0     0 0 0 my $self = shift;
676 0 0       0 if (ref($self->[$LEXER_SUB]) eq 'CODE') {
677 0         0 $self->[$LEXER_SUB];
678             } else {
679 0         0 $self->genLex();
680             }
681             }
682             #
683             # The State Machine
684             #
685             #package Parse::State;
686             sub inclusive {
687 10     10 0 15 my $self = shift;
688 10 50       23 if (ref $self) { # instance method
689 10 50       20 if (@_) {
690 0         0 $self->[$INCLUSIVE_COND] = {@_};
691             } else {
692 10         35 $self->[$INCLUSIVE_COND];
693             }
694             } else { # class method
695 0         0 $self->prototype->inclusive(map { $_ => 1 } @_);
  0         0  
696             }
697             }
698             sub exclusive {
699 10     10 0 17 my $self = shift;
700 10 50       26 if (ref $self) { # instance method
701 10 50       28 if (@_) {
702 0         0 $self->[$EXCLUSIVE_COND] = {@_};
703             } else {
704 10         43 $self->[$EXCLUSIVE_COND];
705             }
706             } else { # class method
707 0         0 $self->prototype->exclusive(map { $_ => 1 } @_);
  0         0  
708             }
709             }
710 10     10   114 use constant GEN_CONDITION => 0;
  10         36  
  10         18879  
711             sub genCondition {
712 6     6 0 12 my $self = shift;
713 6         7 my $specif = shift;
714 6 50       21 return '' if $specif =~ /^ALL:/; # special condition
715            
716 6         11 my %exclusion = %{$self->exclusive};
  6         25  
717 6         9 my %inclusion = %{$self->inclusive};
  6         35  
718 6 50 33     54 return '' unless $specif or keys %exclusion;
719            
720 0         0 my $condition;
721             my @condition;
722 0         0 my $cond_group;
723 0         0 my $cond_item;
724 0         0 my @cond_group;
725 0 0       0 if ($specif =~ /^(.+):/g) { # Ex. A:B:C: or A,C:
726 0         0 my ($prefix) = ($1);
727 0         0 foreach $cond_group (split /:/, $prefix) {
728 0         0 foreach $cond_item (@cond_group = split /,/, $cond_group) {
729 0 0 0     0 unless ($cond_item eq 'INITIAL' or
      0        
730             defined $exclusion{$cond_item} or
731             defined $inclusion{$cond_item}) {
732 0         0 require Carp;
733 0         0 Carp::croak "'$cond_item' condition not defined";
734             }
735 0         0 delete $exclusion{$cond_item};
736 0         0 delete $inclusion{$cond_item};
737             }
738 0         0 push @condition, "(" . join(" or ", map { "\$$_" } @cond_group) . ")";
  0         0  
739             }
740 0 0       0 if (@condition == 1) {
741 0         0 $condition = shift @condition;
742             } else {
743 0         0 $condition = "(" . join(" and ", @condition) . ")";
744             }
745             }
746 0         0 my @tmp = ();
747 0 0       0 if (@tmp = map { "\$$_" } keys(%exclusion)) {
  0         0  
748 0 0       0 if ($condition) {
749 0         0 $condition = "not (" . join(" or ", @tmp) . ") and $condition";
750             } else {
751 0         0 $condition = "not (" . join(" or ", @tmp) . ")";
752             }
753             }
754 0         0 print STDERR "genCondition(): $specif -> $condition\n" if GEN_CONDITION;
755 0 0       0 $condition ne '' ? "$condition and" : '';
756             }
757             sub genStateMachine {
758 4     4 0 7 my $self = shift;
759 4         6 my $somevar;
760            
761 4         9 my $stateDeclaration = 'my $INITIAL = 1;' .
762             "\n" .
763             q!$LEX_STATE{'INITIAL'} = \\$INITIAL;! . "\n";
764 4         6 my $stateName = '';
765 4         7 foreach $stateName (keys (%{$self->exclusive}), keys(%{$self->inclusive})) {
  4         9  
  4         10  
766 0         0 $stateDeclaration .=
767             q!my $! . "$stateName" . q! = 0; ! .
768             q!$LEX_STATE{'! . "$stateName" . q!'} = \\$! . "$stateName" . q!;! . "\n";
769             }
770 4         31 $self->setStateMachine($stateDeclaration);
771             }
772             # not documented
773             sub setStateMachine {
774 4     4 0 8 my $self = shift;
775 4         13 $self->[$STATE_MACHINE_CODE] = shift;
776             }
777             # not documented
778             sub getStateMachine {
779 0     0 0 0 my $self = shift;
780 0         0 $self->[$STATE_MACHINE_CODE];
781             }
782             # not documented
783             sub getState {
784 0     0 0 0 my $self = shift;
785 0         0 my $state = shift;
786 0         0 ${$self->[$STATES]->{$state}};
  0         0  
787             }
788             # not documented
789             sub setState {
790 0     0 0 0 my $self = shift;
791 0         0 my $state = shift;
792 0         0 ${$self->[$STATES]->{$state}} = shift;
  0         0  
793             }
794             sub state { # get/set state
795 8     8 0 11 my $self = shift;
796 8         13 my $state = shift;
797 8 50       22 if (@_) {
798 0         0 ${$self->[$STATES]->{$state}} = shift;
  0         0  
799             } else {
800 8         9 ${$self->[$STATES]->{$state}};
  8         23  
801             }
802             }
803             sub start {
804 0     0 0 0 my $self = shift;
805 0         0 my $state = shift;
806 0 0       0 if ($state eq 'INITIAL') {
807 0         0 $self->_restart()
808             } else {
809 0 0       0 if (exists $self->[$EXCLUSIVE_COND]->{$state}) {
810 0         0 $self->_restart;
811             }
812 0         0 ${$self->[$STATES]->{$state}} = 1;
  0         0  
813             }
814             }
815             sub _restart {
816 0     0   0 my $self = shift;
817 0         0 my $state = shift;
818 0         0 my $hashref = $self->[$STATES];
819 0         0 foreach $state (keys %$hashref) {
820 0         0 ${$hashref->{$state}} = 0;
  0         0  
821             }
822 0         0 ${$hashref->{'INITIAL'}} = 1;
  0         0  
823             }
824             sub end {
825 0     0 0 0 my $self = shift;
826 0         0 my $state = shift;
827 0         0 ${$self->[$STATES]->{$state}} = 0;
  0         0  
828             }
829             #sub pushState {}
830             #sub popState {}
831             #sub topState {}
832            
833             package Parse::Tokenizer;
834             @Parse::Tokenizer::ISA = qw/Parse::ALex/;
835            
836 2     2   3 sub next { &{$_[0]->[$LEXER_SUB]} }
  2         52  
837             #
838             # next() wrappers
839             #
840             # Purpose: Analyze all data in one call
841             # Arguments: string or stream to analyze
842             # Returns: self
843             # Todo: generate a specific lexer sub
844             sub parse {
845 0     0   0 my $self = shift;
846 0 0       0 unless (defined $_[0]) {
847 0         0 require Carp;
848 0         0 Carp::carp "no data to analyze";
849             }
850 0         0 $self->from($_[0]);
851 0         0 my $next = $self->[$LEXER_SUB];
852 0         0 &{$next}($self) until $self->[$EOI];
  0         0  
853             # or:
854             # local *next = $self->[$SUB];
855             # &next($self) until $self->[$EOI];
856 0         0 $self;
857             }
858             # Purpose: Analyze data in one call
859             # Arguments: string or stream to analyze
860             # Returns: list of token name and token text
861             # Todo: generate a specific lexer sub
862             sub analyze {
863 0     0   0 my $self = shift;
864 0 0       0 unless (defined $_[0]) {
865 0         0 require Carp;
866 0         0 Carp::carp "no data to analyze";
867             }
868 0         0 $self->from($_[0]);
869 0         0 my $next = $self->[$LEXER_SUB];
870 0         0 my $token = &{$next}($self);
  0         0  
871 0         0 my @token = ($token->name, $token->text);
872 0         0 until ($self->[$EOI]) {
873 0         0 $token = &{$next}($self);
  0         0  
874 0         0 push (@token, $token->name, $token->text);
875             }
876 0         0 @token;
877             }
878             # Remark: not documented
879             # Purpose: put the next token in a scalar reference
880             # Arguments: a scalar reference
881             # Returns: 1 if token isn't equal to the EOI token
882             sub nextis {
883 21     21   159 my $self = shift;
884 21 50       50 unless (@_ == 1) {
885 0         0 require Carp;
886 0         0 Carp::croak "bad argument number";
887             }
888 21 50       47 if (ref $_[0]) {
889 21         24 my $token = &{$self->[$LEXER_SUB]}($self);
  21         570  
890 21         33 ${$_[0]} = $token;
  21         40  
891 21 100       93 $token == $Parse::Token::EOI ? return 0 : return 1;
892             } else {
893 0           require Carp;
894 0           Carp::croak "bad argument $_[0]";
895             }
896             }
897             # Purpose: execute an action on each token
898             # Arguments: an anonymous sub to call on each token
899             # Returns: undef
900             sub every {
901 0     0     my $self = shift;
902 0           my $do_on = shift;
903 0           my $ref = ref($do_on);
904 0 0 0       if (not $ref or $ref ne 'CODE') {
905 0           require Carp;
906 0           Carp::croak "argument of the 'every' method must be an anonymous routine";
907             }
908 0           my $token = &{$self->[$LEXER_SUB]}($self);
  0            
909 0           while (not $self->[$EOI]) {
910 0           &{$do_on}($token);
  0            
911 0           $token = &{$self->[$LEXER_SUB]}($self);
  0            
912             }
913 0           $self;
914             }
915             __PACKAGE__
916            
917             __END__