File Coverage

blib/lib/Lang/Tree/Builder/Parser.pm
Criterion Covered Total %
statement 101 121 83.4
branch 30 46 65.2
condition 7 17 41.1
subroutine 21 22 95.4
pod 2 10 20.0
total 161 216 74.5


line stmt bran cond sub pod time code
1             package Lang::Tree::Builder::Parser;
2              
3 3     3   22836 use strict;
  3         7  
  3         111  
4 3     3   16 use warnings;
  3         7  
  3         88  
5              
6 3     3   2508 use FileHandle;
  3         42978  
  3         22  
7 3     3   4183 use Lang::Tree::Builder::Data;
  3         10  
  3         101  
8 3     3   3982 use Lang::Tree::Builder::Tokenizer;
  3         8  
  3         196  
9              
10             our $VERSION = '0.01';
11              
12             our $debug = 0;
13              
14 3     3   19 use constant START => 0;
  3         7  
  3         269  
15 3     3   17 use constant MAIN => 1;
  3         6  
  3         125  
16 3     3   15 use constant ARGS => 2;
  3         6  
  3         128  
17 3     3   25 use constant ARG => 3;
  3         6  
  3         240  
18 3     3   25 use constant NAME => 4;
  3         4  
  3         240  
19 3     3   16 use constant SUPER => 6;
  3         5  
  3         142  
20 3     3   14 use constant ABSTRACT => 7;
  3         6  
  3         4945  
21              
22             =head1 NAME
23              
24             Lang::Tree::Builder::Parser - Parse Tree Definitions
25              
26             =head1 SYNOPSIS
27              
28             use Lang::Tree::Builder::Parser;
29             my $parser = new Lang::Tree::Builder::Parser();
30             my $data = $parser->parseFile($datafile);
31              
32             =head1 DESCRIPTION
33              
34             A parser for class definitions. The data file input to the C
35             method has the following simple format:
36              
37             # a comment
38             [abstract] [] ( [], ... )
39             ...
40              
41             for example
42              
43             # this class has no base class and is initialized with a scalar
44             IntNode(scalar)
45             # the list class is abstract
46             abstract List()
47             # this class is a type of List and takes initializers Foo::Node and Foo::List
48             List Foo::List(Foo::Node first,
49             Foo::List rest)
50             # this class is an abstract type of List and takes no initializers
51             List EmptyList()
52              
53             apart from arbitrary class names as initializers, the keyword C is
54             recognized.
55              
56             =head2 new
57              
58             my $parser = new Lang::Tree::Builder::Parser;
59              
60             Creates and returns a new instance of a parser.
61              
62             =cut
63              
64             sub new {
65 1     1 1 14 my ( $class, %params ) = @_;
66 1   50     8 $params{prefix} ||= '';
67 1         14 bless {
68             filename => '',
69             lineno => 0,
70             error => '',
71             prefix => $params{prefix},
72             state => START,
73             class => '',
74             parent => '',
75             args => [],
76             abstract => 0,
77             }, $class;
78             }
79              
80             sub init {
81 2     2 0 6 my ($self) = @_;
82 2         5 $self->{state} = START;
83 2         4 $self->{class} = '';
84 2         4 $self->{parent} = '';
85 2         4 $self->{args} = [];
86 2         10 $self->{is_abstract} = 0;
87             }
88              
89             =head2 parseFile
90              
91             my $data = $parser->parseFile($datafile);
92              
93             Parses C<$datafile> and returns an instance of L
94              
95             =cut
96              
97             sub parseFile {
98 1     1 1 703 my ( $self, $datafile ) = @_;
99              
100 1         4 $self->{error} = '';
101 1         13 $self->{data} = new Lang::Tree::Builder::Data();
102 1         10 $self->{tokenizer} = new Lang::Tree::Builder::Tokenizer($datafile);
103              
104 1 50       14 unless ( $self->{tokenizer} ) {
105 0         0 $self->{error} = "$datafile: $@";
106 0         0 return undef;
107             }
108              
109 1         5 $self->init;
110              
111 1         7 while ( defined( my $token = $self->{tokenizer}->next() ) ) {
112 10 50       26 warn "### state $self->{state}, token $token\n" if $debug;
113 10 100       54 if ( $token eq '(' ) {
    100          
    100          
    50          
    100          
114 1         5 $self->handleLBrace;
115             } elsif ( $token eq ')' ) {
116 1         6 $self->handleRBrace;
117             } elsif ( $token eq ',' ) {
118 2         9 $self->handleComma;
119             } elsif ( $token eq 'abstract' ) {
120 0         0 $self->handleAbstract;
121             } elsif ( $token eq 'scalar' ) {
122 1         5 $self->handleScalar;
123             } else {
124 5         15 $self->handleName($token);
125             }
126             }
127              
128 1 50       7 if ( $self->{tokenizer}->{error} ) {
129 0         0 $self->{error} = $self->{tokenizer}->{error};
130 0         0 return undef;
131             }
132              
133 1 50       16 if ( $self->{state} != START ) {
134 0         0 $self->{error} = "unexpected EOF";
135 0         0 return undef;
136             }
137              
138 1         6 $self->{data}->finalize();
139 1         6 return $self->{data};
140             }
141              
142             sub handleLBrace {
143 1     1 0 2 my ($self) = @_;
144              
145 1 50 33     16 if ( $self->{state} == MAIN || $self->{state} == SUPER ) {
146 1         7 $self->{state} = ARGS;
147             } else {
148 0         0 die "syntax error ", $self->{tokenizer}->info(), "\n";
149             }
150             }
151              
152             sub handleRBrace {
153 1     1 0 18 my ($self) = @_;
154              
155 1 50       5 if ( $self->{state} == ARG ) {
156 0         0 push @{ $self->{args}[-1] }, '';
  0         0  
157             }
158              
159 1 50 33     13 if ( $self->{state} == ARGS
      33        
160             || $self->{state} == ARG
161             || $self->{state} == NAME )
162             {
163 1         4 $self->processOneClass;
164 1         6 $self->init;
165             } else {
166 0         0 die "syntax error ", $self->{tokenizer}->info(), "\n";
167             }
168             }
169              
170             sub handleComma {
171 2     2 0 3 my ($self) = @_;
172              
173 2 50       16 if ( $self->{state} == ARG ) {
174 2         4 push @{ $self->{args}[-1] }, '';
  2         7  
175             }
176              
177 2 50 33     12 if ( $self->{state} == ARG || $self->{state} == NAME ) {
178 2         11 $self->{state} = ARGS;
179             } else {
180 0         0 die "syntax error ", $self->{tokenizer}->info(), "\n";
181             }
182             }
183              
184             sub handleAbstract {
185 0     0 0 0 my ($self) = @_;
186              
187 0 0       0 if ( $self->{state} == START ) {
188 0         0 $self->{is_abstract} = 1;
189 0         0 $self->{state} = ABSTRACT;
190             } else {
191 0         0 die "syntax error ", $self->{tokenizer}->info(), "\n";
192             }
193             }
194              
195             sub handleScalar {
196 1     1 0 2 my ($self) = @_;
197              
198 1         3 $self->handleName('scalar');
199             }
200              
201             sub handleName {
202 6     6 0 13 my ( $self, $token ) = @_;
203              
204 6 100 66     53 if ( $self->{state} == START || $self->{state} == ABSTRACT ) {
    100          
    100          
    50          
205 1         3 $self->{class} = $token;
206 1         8 $self->{state} = SUPER;
207             } elsif ( $self->{state} == ARGS ) {
208 3 50       10 if ( $self->{is_abstract} ) {
209 0         0 die "pointless args to abstract constructor ",
210             $self->{tokenizer}->info(), "\n";
211             }
212              
213 3         5 push @{ $self->{args} }, [$token];
  3         13  
214 3         17 $self->{state} = ARG;
215             } elsif ( $self->{state} == ARG ) {
216 1 50       9 if ( $token =~ /:/ ) {
217 0         0 die "named argument cannot contain colons ",
218             $self->{tokenizer}->info(), "\n";
219             }
220              
221 1         3 push @{ $self->{args}[-1] }, $token;
  1         4  
222 1         6 $self->{state} = NAME;
223             } elsif ( $self->{state} == SUPER ) {
224 1         4 $self->{parent} = $self->{class};
225 1         2 $self->{class} = $token;
226 1         7 $self->{state} = MAIN;
227             } else {
228 0         0 die "syntax error ", $self->{tokenizer}->info(), "\n";
229             }
230             }
231              
232             sub processOneClass {
233 1     1 0 2 my ($self) = @_;
234              
235 1 50       7 $self->{parent} = $self->{prefix} . $self->{parent}
236             if $self->{parent};
237              
238 1         5 $self->{class} = $self->{prefix} . $self->{class};
239 3 100       49 my @args = map {
240 1         4 [
241             (
242             $_->[0] eq 'scalar'
243             ? 'scalar'
244             : $self->{prefix} . $_->[0]
245             ),
246             $_->[1]
247             ];
248 1         2 } @{ $self->{args} };
249              
250 1         34 $self->{data}->add(
251             Lang::Tree::Builder::Class->new(
252             parent => $self->{parent},
253             class => $self->{class},
254             args => [@args],
255             abstract => $self->{is_abstract},
256             )
257             );
258             }
259              
260             =head1 SEE ALSO
261              
262             L
263              
264             =head1 AUTHOR
265              
266             Bill Hails, Eme@billhails.netE
267              
268             =head1 COPYRIGHT AND LICENSE
269              
270             Copyright (C) 2008 by Bill Hails
271              
272             This library is free software; you can redistribute it and/or modify
273             it under the same terms as Perl itself, either Perl version 5.8.8 or,
274             at your option, any later version of Perl 5 you may have available.
275              
276             =cut
277              
278             1;