File Coverage

blib/lib/Lisp/Reader.pm
Criterion Covered Total %
statement 89 104 85.5
branch 80 108 74.0
condition 26 32 81.2
subroutine 7 7 100.0
pod 0 3 0.0
total 202 254 79.5


line stmt bran cond sub pod time code
1             package Lisp::Reader;
2              
3 6     6   560 use strict;
  6         10  
  6         236  
4 6         692 use vars qw($DEBUG $SYMBOLS_AS_STRINGS $NIL_AS_SYMBOL
5 6     6   31 @EXPORT_OK $VERSION);
  6         8  
6              
7             $VERSION = sprintf("%d.%02d", q$Revision: 1.10 $ =~ /(\d+)\.(\d+)/);
8              
9 6     6   5807 use Lisp::Symbol qw(symbol);
  6         12  
  6         14283  
10              
11             require Exporter;
12             *import = \&Exporter::import;
13             @EXPORT_OK = qw(lisp_read);
14              
15              
16             sub my_symbol
17             {
18 560 100 66 560 0 2406 ($_[0] eq "nil" && !$NIL_AS_SYMBOL) ?
    100          
19             undef :
20             ($SYMBOLS_AS_STRINGS ? $_[0] : symbol($_[0]));
21             }
22              
23             sub lisp_read
24             {
25 155     155 0 390 local($_) = shift;
26 155         208 my $one = shift;
27 155   100     514 my $level = shift || 0;
28 155         228 my $indent = " " x $level;
29              
30 155         155 my @stack;
31 155         204 my $form = [];
32              
33 155 50       298 if ($DEBUG) {
34 0         0 print "${indent}Parse";
35 0 0       0 print "-one" if $one;
36 0         0 print ": $_\n";
37             }
38            
39 155         152 while (1) {
40 2007 100 66     16785 if (/\G\s*;+([^\n]*)/gc) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
41 13 50       34 print "${indent}COMMENT $1\n" if $DEBUG;
42             } elsif (/\G\s*([()\[\]])/gc) {
43 912 50       1495 print "${indent}PARA $1\n" if $DEBUG;
44 912 100 100     3005 if ($1 eq "(" or $1 eq "[") {
45 456         471 my $prev = $form;
46 456         575 push(@stack, $prev);
47 456         795 push(@$prev, $form = []);
48 456 100       1132 bless $form, "Lisp::Vector" if $1 eq "[";
49             } else {
50 456 50       805 last unless @stack;
51 456 50 66     1796 if (ref($form) eq "ARRAY" && @$form == 0) {
52             # () and nil is supposed to be the same thing
53 0         0 $stack[-1][-1] = my_symbol("nil");
54             }
55 456         555 $form = pop(@stack);
56 456 100 100     1179 last if $one && !@stack;
57             }
58             } elsif (/\G\s*(
59             [-+]? # optional sign
60             (?:\d+(\.\d*)? # 0 0. 0.0
61             |
62             \.\d+) # .0
63             ([eE][-+]?\d+)? # optional exponent
64             )
65             (?![^\s()\[\];]) # not followed by plain chars
66             /gcx)
67             {
68 275 50       487 print "${indent}NUMBER $1\n" if $DEBUG;
69 275         699 push(@$form, $1+0);
70 275 100 100     693 last if $one && !@stack;
71             } elsif (/\G\s*\?((?:\\[A-Z]-)*(?:\\\^.|\\[0-7]{1,3}|\\.|.))/sgc) {
72 13 50       27 print "${indent}CHAR $1\n" if $DEBUG;
73 13         21 push(@$form, parse_char($1));
74 13 100 66     44 last if $one && !@stack;
75             } elsif (/\G\s*
76             \"( # start quote
77             [^\"\\]* # unescaped
78             (?:\\.[^\"\\]*)* # (escaped char + unescaped)*
79             )\"/gcxs) # end quote
80             {
81 69         116 my $str = $1;
82              
83             # Unescape
84 69         79 $str =~ s/\\\n//g; # escaped newlines disappear
85 69         78 $str =~ s/((?:\\[A-Z]-)+.)/chr(parse_char($1,1))/ge;
  1         3  
86 69         160 $str =~ s/((?:\\[A-Z]-)*\\(?:\^.|[0-7]{1,3}|.))/
87 9         23 chr(parse_char($1,1))/ge;
88 69 50       124 print "${indent}STRING $str\n" if $DEBUG;
89 69         124 push(@$form, $str);
90 69 100 100     254 last if $one && !@stack;
91             } elsif (/\G\s*\'/gc) {
92 12 50       570 print "${indent}QUOTE\n" if $DEBUG;
93 12         19 my $old_pos = pos($_);
94 12         146 my($subform, $pos) = lisp_read(substr($_, $old_pos), 1, $level+1);
95 12         37 pos($_) = $old_pos + $pos;
96 12         38 push(@$form, [my_symbol("quote"), $subform]);
97 12 50 33     43 last if $one && !@stack;
98             } elsif (/\G\s*\./gc) {
99 22 50       53 print "${indent}DOT\n" if $DEBUG;
100             #XXX Should handle (a b . c) correctly and (a . b c) as error
101 22         47 bless $form, "Lisp::Cons";
102             } elsif (/\G\s*\#/gc) {
103 1         14 die qq(invalid-read-syntax: "\#");
104             } elsif (/\G\s*
105             ( [^\s()\[\];\\]* # unescaped plain chars
106             (?:\\.[^\s()\[\];\\]*)* # (escaped char + unescaped)*
107             )/gcsx
108             && length($1))
109             {
110             # symbols can have space and parentesis embedded if they are
111             # escaped.
112 548         794 my $sym = $1;
113 548         741 $sym =~ s/\\(.)/$1/g; # unescape
114 548 50       974 print "${indent}SYMBOL $sym\n" if $DEBUG;
115 548         915 push(@$form, my_symbol($sym));
116 548 100 100     1538 last if $one && !@stack;
117             } elsif (/\G\s*(.)/gc) {
118 0         0 print "${indent}? $1\n";
119 0         0 die qq(invalid-read-syntax: "$1");
120             } else {
121 142         184 last;
122             }
123             }
124              
125 154 50       287 if (@stack) {
126 0         0 warn "Form terminated early"; # or should we die?
127 0         0 $form = $stack[0];
128             }
129              
130 154 100       267 if ($one) {
131 12 50       28 die "More than one form parsed, this should never happen"
132             if @$form > 1;
133 12         19 $form = $form->[0];
134             }
135              
136 154 100       589 wantarray ? ($form, pos($_)) : $form;
137             }
138              
139              
140             sub parse_char
141             {
142 23     23 0 46 my($char, $instring) = @_;
143 23         25 my $ord = 0;
144 23         22 my @mod;
145 23         76 while ($char =~ s/^\\([A-Z])-//) {
146 6         24 push(@mod, $1);
147             }
148              
149 23 100       129 if (length($char) == 1) {
    100          
    100          
    50          
    100          
    100          
    50          
    100          
    50          
    50          
150 7         10 $ord = ord($char); # a plain one
151             } elsif ($char =~ /^\\([0-7]+)$/) {
152 2         4 $ord = oct($1);
153             } elsif ($char =~ /^\\\^(.)$/) {
154 2         5 $ord = ord(uc($1)) - ord("@");
155 2 50       5 $ord += 128 if $ord < 0;
156             } elsif ($char eq "\\t") {
157 0         0 $ord = ord("\t");
158             } elsif ($char eq "\\n") {
159 7         9 $ord = ord("\n");
160             } elsif ($char eq "\\a") {
161 1         3 $ord = ord("\a");
162             } elsif ($char eq "\\f") {
163 0         0 $ord = ord("\f");
164             } elsif ($char eq "\\r") {
165 1         2 $ord = ord("\r");
166             } elsif ($char eq "\\e") {
167 0         0 $ord = ord("\e");
168             } elsif ($char =~ /^\\(.)$/) {
169 3         6 $ord = ord($1);
170             } else {
171 0         0 warn "Don't know how to handle character ($char)";
172             }
173              
174 23         41 for (@mod) {
175 6 100       16 if ($_ eq "C") {
    100          
    50          
    0          
    0          
176 3     1   278 $ord = ord(uc(chr($ord))) - ord("@");
  1         1186  
  1         10  
  1         13  
177 3 50       33738 $ord += 128 if $ord < 0;
178             } elsif ($_ eq "M") {
179 2 100       6 $ord += $instring ? 2**7 : 2**27;
180             } elsif ($_ eq "H") {
181 1         2 $ord += 2**24;
182             } elsif ($_ eq "S") {
183 0         0 $ord += 2**23;
184             } elsif ($_ eq "A") {
185 0         0 $ord += 2**22;
186             } else {
187 0         0 warn "Unknown character modified ($_)";
188             }
189             }
190              
191 23         77 $ord;
192             }
193              
194              
195             1;