File Coverage

lib/Parse/Token/Lite.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Parse::Token::Lite;
2 7     7   518896 use Moose;
  0            
  0            
3             use Data::Dump;
4             use Log::Log4perl qw(:easy);
5             use Parse::Token::Lite::Token;
6             use Parse::Token::Lite::Rule;
7             Log::Log4perl->easy_init($ERROR);
8              
9             our $VERSION = '0.200'; # VERSION
10             # ABSTRACT: Simply parse String into tokens with rules which are similar to Lex.
11              
12              
13              
14             has rulemap => ( is=>'rw', default=>sub{return {};});
15              
16              
17             has data => ( is=>'rw' );
18              
19              
20             has state_stack => ( is=>'rw', default=>sub{[]} );
21              
22             has rulemap => (is=>'rw', required=>1);
23             sub BUILD{
24             my $self = shift;
25             foreach my $key (keys %{$self->rulemap}){
26             $self->rulemap->{$key} = [map{ Parse::Token::Lite::Rule->new($_) }@{$self->rulemap->{$key}}];
27             }
28             }
29              
30              
31             sub from{
32             my $self = shift;
33             my $data = shift;
34            
35             $self->data($data);
36             $self->state_stack([]); # reset state.
37            
38             return 1;
39             }
40              
41              
42             sub parse{
43             my $self = shift;
44             my $data = shift;
45             $self->from($data) if defined $data;
46            
47             my @tokens;
48             while(!$self->eof){
49             my @ret = $self->nextToken;
50             push(@tokens,\@ret) if wantarray;
51             }
52             return @tokens if wantarray;
53             return 1;
54             }
55              
56              
57             sub currentRules{
58             my $self = shift;
59             return $self->rulemap->{$self->state};
60             }
61              
62              
63             sub nextToken{
64             my $self = shift;
65            
66             foreach my $rule ( @{$self->currentRules} ){
67             my $pat = $rule->re;
68             my $matched = $self->data =~ m/^$pat/s;
69             if( $matched ){
70             my $rest = $';
71             $self->data($rest);
72              
73             if( $rule->state ){
74             foreach my $state (@{$rule->state}) {
75             if( $state =~ /([+-])(.+)/ ){
76             if( $1 eq '-' ){
77             $self->end($2);
78             }
79             else{
80             $self->start($2);
81             }
82             }
83             else{
84             die "invalid state_action '$state'";
85             }
86             }
87             }
88            
89             my $token = Parse::Token::Lite::Token->new(rule=>$rule,data=>$&);
90            
91             my @funcret;
92             if( $rule->func ){
93             @funcret = $rule->func->($self,$token);
94             }
95              
96             if( wantarray ){
97             return $token,@funcret;
98             }
99             else{
100             return $token;
101             }
102             }
103             }
104             die "not matched for first of '".substr($self->data,0,5)."..'";
105             }
106              
107              
108              
109             sub eof{
110             my $self = shift;
111             return length($self->data)?0:1;
112             }
113              
114              
115             sub start{
116             my $self = shift;
117             my $state = shift;
118            
119             if( $state ne $self->state ){
120             DEBUG ">>> START '$state'";
121             push(@{$self->state_stack}, $state)
122             }
123             else{
124             DEBUG ">>> KEEP '$state'";
125             }
126             }
127              
128             sub end{
129             my $self = shift;
130             my $state = shift;
131             DEBUG "<<< STOP '$state'";
132             return pop(@{$self->state_stack});
133             }
134              
135              
136             sub state{
137             my $self = shift;
138             return 'MAIN' if( @{$self->state_stack} == 0 );
139             return $self->state_stack->[@{$self->state_stack}-1];
140             }
141              
142             has flags => ('is'=>'rw', default=>sub{ return {}; } );
143              
144              
145             sub setFlag{
146             my $self = shift;
147             my $flag = shift;
148             $self->flags->{$flag} = 1;
149             }
150              
151              
152             sub resetFlag{
153             my $self = shift;
154             my $flag = shift;
155             delete( $self->flags->{$flag} );
156             }
157              
158             sub isSetFlag{
159             my $self = shift;
160             my $flag = shift;
161             return defined( $self->flags->{$flag} );
162             }
163              
164              
165             1;
166              
167             __END__
168              
169             =pod
170              
171             =encoding UTF-8
172              
173             =head1 NAME
174              
175             Parse::Token::Lite - Simply parse String into tokens with rules which are similar to Lex.
176              
177             =head1 VERSION
178              
179             version 0.200
180              
181             =head1 SYNOPSIS
182              
183             use Parse::Token::Lite;
184              
185             my %rules = (
186             MAIN=>[
187             { name=>'NUM', re=> qr/\d[\d,\.]*/ },
188             { name=>'STR', re=> qr/\w+/ },
189             { name=>'SPC', re=> qr/\s+/ },
190             { name=>'ERR', re=> qr/.*/ },
191             ],
192             );
193              
194             my $parser = Parse::Token::Lite->new(rulemap=>\%rules);
195             $parser->from("This costs 1,000won.");
196             while( ! $parser->eof ){
197             my ($token,@extra) = $parser->nextToken;
198             print $token->rule->name."-->".$token->data."<--\n";
199             }
200              
201             Results are
202              
203             STR -->This<--
204             SPC --> <--
205             STR -->costs<--
206             SPC --> <--
207             NUM -->1,000<--
208             STR -->won<--
209             ERR -->.<--
210              
211             =head1 ATTRIBUTES
212              
213             =head2 rulemap
214              
215             rulemap contains hash refrence of rule objects grouped by STATE.
216             rulemap should have 'MAIN' item.
217              
218             my %rule = (
219             MAIN => [
220             Parse::Token::Lite::Rule->new(name=>'any', re=>qr/./),
221             ],
222             );
223             $parser->rulemap(\%rule);
224              
225             In constructor, it can be replaced with hash reference descripting attributes of L<Parse::Token::Lite::Rule> class, intead of Rule Object.
226              
227             my %rule = (
228             MAIN => [
229             {name=>'any', re=>qr/./}, # ditto
230             ],
231             );
232             my $parser = Parse::Token::Lite->new( rulemap=>\%rule );
233              
234             =head2 data
235              
236             'data' is set by from() method.
237             'data' contains a rest of text which is not processed by nextToken().
238             Please remember, 'data' is changing.
239              
240             If a length of 'data' is 0, eof() returns 1.
241              
242             =head2 state_stack
243              
244             At first time, it contains ['MAIN'].
245             It is reset by from().
246              
247             =head1 METHODS
248              
249             =head2 from($data_string)
250              
251             Setting data to parse.
252              
253             This causes resetting state_stack.
254              
255             =head2 parse()
256              
257             =head2 parse($data)
258              
259             On Scalar context : Returns 1
260             On Array context : Returns array of [L<Parse::Token::Lite::Token>,@return_values_of_callback].
261              
262             Parse all tokens on Event driven.
263             Just call nextToken() during that eof() is not 1.
264              
265             Defined $data causes calling from($data).
266              
267             You should set a callback function at 'func' attribute in 'rulemap' to do something with tokens.
268              
269             =head2 currentRules()
270              
271             Returns an array reference of rules of current state.
272              
273             See L<Parse::Token::Lite::Rule>.
274              
275             =head2 nextToken()
276              
277             On Scalar context : Returns L<Parse::Token::Lite::Token> object.
278             On Array context : Returns (L<Parse::Token::Lite::Token>,@return_values_of_callback).
279              
280             my ($token, @ret) = $parser->nextToken;
281             print $token->rule->name . '->' . $token->data . "\n";
282              
283             See L<Parse::Token::Lite::Token> and L<Parse::Token::Lite::Rule>.
284              
285             =head2 eof()
286              
287             Returns 1 when no more text is.
288              
289             =head2 start($state)
290              
291             =head2 end()
292              
293             =head2 end($state)
294              
295             Push/Pop the state on state_stack to implement AUTOMATA.
296              
297             Also, this is called by a 'state' definition of L<Parse::Token::Lite::Rule>.
298              
299             You can set rules as Lexer like.
300              
301             my $rulemap = {
302             MAIN => [
303             { name=>'QUOTE', re=>qr/'/, func=>
304             sub{
305             my ($parser,$token) = @_;
306             $parser->start('STATE_QUOTE'); # push
307             }
308             },
309             { name=>'ANY', re=>qr/.+/ },
310             ],
311             STATE_QUOTE => [
312             { name=>'QUOTE_PAIR', re=>qr/'/, func=>
313             sub{
314             my ($parser,$token) = @_;
315             $parser->end('STATE_QUOTE'); # pop
316             }
317             },
318             { name=>'QUOTED_TEXT', re=>qr/.+/ }
319             ],
320             };
321              
322             You can also do it in simple way.
323              
324             my $rulemap = {
325             MAIN => [
326             { name=>'QUOTE', re=>qr/'/, state=>['+STATE_QUOTE'] }, # push
327             { name=>'ANY', re=>qr/.+/ },
328             ],
329             STATE_QUOTE => [
330             { name=>'QUOTE_PAIR', re=>qr/'/, state=>['-STATE_QUOTE] }, #pop
331             { name=>'QUOTED_TEXT', re=>qr/.+/ }
332             ],
333             };
334              
335             =head2 state()
336              
337             Returns current state by peeking top of 'state_stack'.
338              
339             =head1 SEE ALSO
340              
341             See L<Parse::Token::Lite::Token> and L<Parse::Token::Lite::Rule>.
342              
343             And see 'samples' directory in source.
344              
345             =head1 AUTHOR
346              
347             khs <sng2nara@gmail.com>
348              
349             =head1 COPYRIGHT AND LICENSE
350              
351             This software is copyright (c) 2013 by khs.
352              
353             This is free software; you can redistribute it and/or modify it under
354             the same terms as the Perl 5 programming language system itself.
355              
356             =cut