File Coverage

blib/lib/Data/SExpression.pm
Criterion Covered Total %
statement 96 96 100.0
branch 32 32 100.0
condition 15 16 93.7
subroutine 20 20 100.0
pod 8 9 88.8
total 171 173 98.8


line stmt bran cond sub pod time code
1 8     8   250888 use warnings;
  8         21  
  8         293  
2 8     8   46 use strict;
  8         13  
  8         1743  
3              
4             package Data::SExpression;
5              
6             our $VERSION = '0.41';
7              
8             =head1 NAME
9              
10             Data::SExpression -- Parse Lisp S-Expressions into perl data
11             structures.
12              
13             =head1 SYNOPSIS
14              
15             use Data::SExpression;
16              
17             my $ds = Data::SExpression->new;
18              
19             $ds->read("(foo bar baz)"); # [\*::foo, \*::bar, \*::baz]
20              
21             my @sexps;
22             my $sexp;
23             while(1) {
24             eval {
25             ($sexp, $text) = $ds->read($text);
26             };
27             last if $@;
28             push @sexps, $sexp;
29             }
30              
31             $ds = Data::SExpression->new({fold_alists => 1});
32              
33             $ds->read("((top . 4) (left . 5))"); # {\*::top => 4, \*::left => 5}
34              
35             =cut
36              
37 8     8   46 use base qw(Class::Accessor::Fast Exporter);
  8         23  
  8         7816  
38             __PACKAGE__->follow_best_practice;
39             __PACKAGE__->mk_ro_accessors(qw(parser symbol_case use_symbol_class fold_dashes fold_lists fold_alists));
40              
41             our @EXPORT_OK = qw(cons consp scalarp);
42              
43 8     8   55604 use Symbol;
  8         6452  
  8         801  
44 8     8   10957 use Data::SExpression::Cons;
  8         85  
  8         67  
45 8     8   5608 use Data::SExpression::Parser;
  8         24  
  8         283  
46 8     8   4504 use Data::SExpression::Symbol;
  8         21  
  8         70  
47 8     8   258 use Carp qw(croak);
  8         14  
  8         8849  
48              
49              
50             sub cons ($$);
51             sub consp ($);
52             sub scalarp ($);
53              
54              
55             =head1 METHODS
56              
57             =head2 new [\%args]
58              
59             Returns a new Data::SExpression object. Possibly args are:
60              
61             =over 4
62              
63             =item fold_lists
64              
65             If true, fold lisp lists (e.g. "(1 2 3)") into Perl listrefs, e.g. [1, 2, 3]
66              
67             Defaults to true.
68              
69             =item fold_alists
70              
71             If true, fold lisp alists into perl hashrefs. e.g.
72              
73             C<"((fg . red) (bg . black) (weight . bold))">
74              
75             would become
76              
77             {
78             \*fg => \*red,
79             \*bg => \*black,
80             \*weight => \*bold
81             }
82              
83             Alists will only be folded if they are a list of conses, all of which
84             have scalars as both their C and C (See
85             L)
86              
87             This option implies L
88              
89             Defaults to false.
90              
91             =item symbol_case
92              
93             Can be C<"up">, C<"down">, or C, to fold symbol case to
94             uppercase, lowercase, or to leave as-is.
95              
96             Defaults to leaving symbols alone.
97              
98             =item use_symbol_class
99              
100             If true, symbols become instances of L
101             instead of globrefs.
102              
103             Defaults to false
104              
105             =item fold_dashes
106              
107             If true, dash characters in symbols (C<->) will be folded to the more
108             perlish underscore, C<_>. This is especially convenient when symbols
109             are being converted to globrefs.
110              
111             Defaults to false.
112              
113             =back
114              
115             =cut
116              
117             sub new {
118 11     11 1 2651 my $class = shift;
119 11   100     66 my $args = shift || {};
120              
121 11         93 my $parser = Data::SExpression::Parser->new;
122              
123 11 100       42 $args->{fold_lists} = 1 if $args->{fold_alists};
124              
125 11         84 my $self = {
126             fold_lists => 1,
127             fold_alists => 0,
128             symbol_case => 0,
129             use_symbol_class => 0,
130             fold_dashes => 0,
131             %$args,
132             parser => $parser,
133             };
134            
135 11         30 bless($self, $class);
136              
137 11         49 $parser->set_handler($self);
138              
139 11         34 return $self;
140             }
141              
142             =head2 read STRING
143              
144             Parse an SExpression from the start of STRING, or die if the parse
145             fails.
146              
147             In scalar context, returns the expression parsed as a perl data
148             structure; In list context, also return the part of STRING left
149             unparsed. This means you can read all the expressions in a string
150             with:
151              
152             my @sexps;
153             my $sexp;
154             while(1) {
155             eval {
156             ($sexp, $text) = $ds->read($text);
157             };
158             last if $@;
159             push @sexps, $sexp;
160             }
161              
162              
163             This method converts Lisp SExpressions into perl data structures by
164             the following rules:
165              
166             =over 4
167              
168             =item Numbers and Strings become perl scalars
169              
170             Lisp differentiates between the types; perl doesn't.
171              
172             =item Symbols become globrefs in main::
173              
174             This means they become something like \*main::foo, or \*::foo for
175             short. To convert from a string to a symbol, you can use
176             L, with C<"main"> as the package.
177              
178             But see L if you'd prefer to get back objects.
179              
180             =item Conses become Data::SExpression::Cons objects
181              
182             See L for how to deal with these. See also
183             the C and C arguments to L.
184              
185             If C is false, the Lisp empty list C<()> becomes the perl
186             C. With C, it turns into C<[]> as you would expect.
187              
188             =item Quotation is parsed as in scheme
189              
190             This means that "'foo" is parsed like "(quote foo)", "`foo" like
191             "(quasiquote foo)", and ",foo" like "(unquote foo)".
192              
193             =back
194              
195             =cut
196              
197             sub read {
198 54     54 1 32938 my $self = shift;
199 54         81 my $string = shift;
200              
201 54         164 $self->get_parser->set_input($string);
202            
203 54         142 my $value = $self->get_parser->parse;
204              
205 53 100       167 $value = $self->_fold_lists($value) if $self->get_fold_lists;
206 53 100       260 $value = $self->_fold_alists($value) if $self->get_fold_alists;
207              
208 53         327 my $unparsed = $self->get_parser->unparsed_input;
209              
210 53 100       340 return wantarray ? ($value, $unparsed) : $value;
211             }
212              
213             sub _fold_lists {
214 123     123   1213 my $self = shift;
215 123         584 my $thing = shift;
216              
217 123 100       229 if(!defined($thing)) {
218 5         11 $thing = [];
219 123 100       193 } if(consp $thing) {
220             # Recursively fold the car
221 41         125 $thing->set_car($self->_fold_lists($thing->car));
222              
223             # Unroll the cdr-folding, since recursing over really long
224             # lists will net us warnings
225 41 100 100     295 if(consp $thing->cdr || !defined($thing->cdr)) {
226 33         95 my $cdr = $thing->cdr;
227 33         126 my $array;
228 33         59 while(consp $cdr) {
229 42         111 $cdr = $cdr->cdr;
230             }
231 33 100       72 if(defined($cdr)) {
232             # We hit the end of the chain, and found something other
233             # than nil. This is an improper list.
234 1         3 return $thing;
235             }
236            
237 32         74 $array = [$thing->car];
238 32         172 $cdr = $thing->cdr;
239 32         144 while(defined $cdr) {
240 41         145 push @$array, $self->_fold_lists($cdr->car);
241 41         103 $cdr = $cdr->cdr;
242             }
243 32         165 return $array;
244             }
245             }
246              
247 90         283 return $thing;
248             }
249              
250 13   100 13 0 50 sub for_all(&@) {$_[0]() or return 0 foreach (@_[1..$#_]); 1;}
  1         5  
251              
252             sub _fold_alists {
253 37     37   83 my $self = shift;
254 37         38 my $thing = shift;
255              
256             #Assume $thing has already been list-folded
257              
258 37 100       94 if(ref($thing) eq "ARRAY") {
    100          
259 13 100 100 15   62 if( for_all {consp $_ && scalarp $_->car && scalarp $_->cdr} @{$thing} ) {
  15 100       27  
  13         44  
260 1         2 return {map {$_->car => $_ -> cdr} @{$thing}};
  3         19  
  1         3  
261             } else {
262 12         15 return [map {$self->_fold_alists($_)} @{$thing}];
  28         61  
  12         23  
263             }
264             } elsif(consp $thing) {
265 2         8 return cons($self->_fold_alists($thing->car),
266             $self->_fold_alists($thing->cdr));
267             } else {
268 22         95 return $thing;
269             }
270             }
271              
272             =head1 LISP-LIKE CONVENIENCE FUNCTIONS
273              
274             These are all generic methods to make operating on cons's easier in
275             perl. You can ask for any of these in the export list, e.g.
276              
277             use Data::SExpression qw(cons consp);
278              
279             =head2 cons CAR CDR
280              
281             Convenience method for Data::SExpression::Cons->new(CAR, CDR)
282              
283             =cut
284              
285             sub cons ($$) {
286 116     116 1 127 my ($car, $cdr) = @_;
287 116         370 return Data::SExpression::Cons->new($car, $cdr);
288             }
289              
290             =head2 consp THING
291              
292             Returns true iff C is a reference to a
293             C
294              
295             =cut
296              
297             sub consp ($) {
298 278     278 1 526 my $thing = shift;
299 278   100     1482 return ref($thing) && UNIVERSAL::isa($thing, 'Data::SExpression::Cons');
300             }
301              
302             =head2 scalarp THING
303              
304             Returns true iff C is a scalar -- i.e. a string, symbol, or
305             number
306              
307             =cut
308              
309             sub scalarp ($) {
310 7     7 1 32 my $thing = shift;
311 7   66     71 return !ref($thing) ||
312             ref($thing) eq "GLOB" ||
313             ref($thing) eq 'Data::SExpression::Symbol';;
314             }
315              
316             =head1 Data::SExpression::Parser callbacks
317              
318             These are for internal use only, and are used to generate the data
319             structures returned by L.
320              
321             =head2 new_cons CAR CDR
322              
323             Returns a new cons with the given CAR and CDR
324              
325             =cut
326              
327             sub new_cons {
328 114     114 1 153 my ($self, $car, $cdr) = @_;
329 114         199 return cons($car, $cdr);
330             }
331              
332             =head2 new_symbol NAME
333              
334             Returns a new symbol with the given name
335              
336             =cut
337              
338             sub new_symbol {
339 68     68 1 105 my ($self, $name) = @_;
340 68 100       158 if($self->get_symbol_case eq 'up') {
    100          
341 2         12 $name = uc $name;
342             } elsif($self->get_symbol_case eq 'down') {
343 2         21 $name = lc $name;
344             }
345              
346 68 100       774 if($self->get_fold_dashes) {
347 1         6 $name =~ tr/-/_/;
348             }
349              
350 68 100       376 if($self->get_use_symbol_class) {
351 1         21 return Data::SExpression::Symbol->new($name);
352             } else {
353 67         381 return Symbol::qualify_to_ref($name, 'main');
354             }
355             }
356              
357             =head2 new_string CONTENT
358              
359             Returns a new string with the given raw content
360              
361             =cut
362              
363             sub new_string {
364 12     12 1 16 my ($self, $content) = @_;
365              
366 12         19 $content =~ s/\\"/"/g;
367              
368 12         31 return $content;
369             }
370              
371             =head1 BUGS
372              
373             None known, but there are probably a few. Please reports bugs via
374             rt.cpan.org by sending mail to:
375              
376             L
377              
378              
379             =head1 AUTHOR
380              
381             Nelson Elhage
382              
383             =cut
384              
385             1;
386