File Coverage

blib/lib/eGuideDog/Festival.pm
Criterion Covered Total %
statement 18 391 4.6
branch 0 160 0.0
condition 0 76 0.0
subroutine 6 50 12.0
pod 14 43 32.5
total 38 720 5.2


line stmt bran cond sub pod time code
1             # This package is developped on the base of the package of Speech::Festival, which is written by Richard Caley
2             # I add some wrapper to make it easier to use without knowing SCHEME language
3             # It is a part of eGuideDog project (http://e-guidedog.sourceforge.net)
4             # Author: Cameron Wong (hgn823-eguidedog002 at yahoo.com.cn)
5              
6             package eGuideDog::Festival;
7              
8             our $VERSION = '0.11';
9              
10 1     1   47064 use strict;
  1         3  
  1         38  
11 1     1   6 use warnings;
  1         2  
  1         33  
12 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         12  
  1         73  
13              
14             require Exporter;
15 1     1   936 use FileHandle;
  1         16213  
  1         8  
16 1     1   1801 use Socket;
  1         5625  
  1         723  
17 1     1   1005 use IPC::Open2;
  1         6428  
  1         7241  
18              
19             sub new;
20             sub DESTROY;
21             sub execute_command;
22             sub speak;
23             sub block_speak;
24             sub play;
25             sub output;
26             sub pause;
27             sub resume; # continue speaking
28             sub stop;
29             sub close;
30             sub mode;
31             sub is_playing;
32             sub voice_list;
33             sub get_voice;
34             sub set_voice;
35             sub duration_stretch;
36             sub volume;
37             sub pitch;
38             sub range;
39             sub reset;
40             sub record_file;
41             sub recording;
42             ###
43             sub new_client;
44             sub conn;
45             sub detach;
46             sub disconnect;
47             sub request;
48             sub wait_for_result;
49             sub result_waiting;
50             sub get_result;
51             sub handle_results;
52             sub waitforsomthing;
53             sub myread_n;
54             sub myread_upto;
55             sub parse_scheme;
56              
57             @ISA = qw(Exporter);
58             @EXPORT = qw(
59             );
60              
61             #*speech_error = *main::synth_error;
62             our $end_key='ft_StUfF_key';
63              
64             our $OK='OK';
65             our $ERROR='ER';
66             our $SCHEME='LP';
67             our $WAVE='WV';
68              
69             my $mode = 'article';
70             my @speech_spooler;
71             my @sentences_spooler;
72             my @words_spooler;
73             my $festival_pid = undef;
74              
75             sub new {
76 0     0 1   my ($self, $host, $port) = @_;
77              
78             # $child_pid = fork();
79              
80             # if (! defined $child_pid) {
81             # die('Fail to fork!');
82             # } elsif ($child_pid) { # parent
83             # $speech_pipe->writer();
84             # $speech_pipe->autoflush();
85              
86             # $self = {};
87             # } else { # child
88 0 0 0       if ($host && $port) {
89 0           $self = new_client($host, $port);
90             } else {
91 0           $festival_pid = open2(*FESTIVAL_OUT, *FESTIVAL_IN, 'festival --server');
92 0           CORE::close(FESTIVAL_IN);
93 0           $self = new_client();
94             }
95 0 0         if () {
96             # check whether server is successfully started
97 0 0         return undef if (!conn($self));
98             } else {
99 0 0         return undef if (!conn($self));
100             }
101              
102             # switch to async mode
103 0           execute_command($self, "(audio_mode 'async)");
104              
105             # $speech_pipe->reader();
106             # while (<$speech_pipe>) {
107             # chomp;
108             # execute_command($self, $_);
109             # block_speak($self, $_);
110             # }
111             # &DESTROY($self);
112             # exit(0);
113             # }
114              
115 0           return $self;
116             }
117              
118             sub DESTROY {
119 0     0     my ($self) = @_;
120              
121             # if ($child_pid) {
122             # kill INT => $child_pid;
123             # close($speech_pipe) if ($speech_pipe);
124             # waitpid($child_pid, 0);
125             # } else {
126              
127 0           disconnect($self);
128 0           CORE::close(FESTIVAL_OUT);# if (defined *FESTIVAL_OUT);
129 0           kill(15, $festival_pid);
130 0           waitpid($festival_pid, 0);
131             # }
132             }
133              
134             sub execute_command {
135 0     0 0   my ($self, $command) = @_;
136              
137             # SayText
138 0 0         if ($command =~ /^[(]SayText /) {
    0          
139 0           request($self, $command);
140 0           my ($type, $data) = get_result($self);
141 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
142             && ($data !~ /^#
143 0           warn("Fail to $command");
144             }
145 0           ($type, $data) = get_result($self);
146 0 0         if ($type ne $eGuideDog::Festival::OK) {
147 0           warn("Fail to $command");
148             }
149             }
150              
151             # async mode
152             elsif ($command eq "(audio_mode 'async)") {
153 0           request($self, $command);
154 0           my ($type, $data) = get_result($self);
155 0           chomp($data);
156 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
157             || $data ne 'async') {
158 0           warn("Fail to async");
159             }
160 0           ($type, $data) = get_result($self);
161 0 0         if ($type ne $eGuideDog::Festival::OK) {
162 0           warn("Fail to async!");
163             }
164             }
165             }
166              
167             sub speak {
168 0     0 1   my ($self, $text) = @_;
169 0           $text =~ s/\\/\\\\/g;
170 0           $text =~ s/"/\\"/g;
171              
172 0           request($self, "(SayText \"$text\")");
173 0           my ($type, $data) = get_result($self);
174 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
175             && ($data !~ /^#
176 0           warn("Fail to speak!");
177             }
178 0           ($type, $data) = get_result($self);
179 0 0         if ($type ne $eGuideDog::Festival::OK) {
180 0           warn("Fail to speak!");
181             }
182             # print $speech_pipe "(SayText \"$text\")\n";
183             }
184              
185             sub block_speak {
186 0     0 1   my ($self, $text) = @_;
187 0           $text =~ s/\\/\\\\/g;
188 0           $text =~ s/"/\\"/g;
189              
190             # wait and close audio stream first
191 0           request($self, "(audio_mode 'close)");
192 0           my ($type, $data) = get_result($self);
193 0           chomp($data);
194 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
195             || $data ne 'close') {
196 0           warn("Fail to close!");
197             }
198 0           ($type, $data) = get_result($self);
199 0 0         if ($type ne $eGuideDog::Festival::OK) {
200 0           warn("Fail to close!");
201             }
202              
203             # sync
204 0           request($self, "(audio_mode 'sync)");
205 0           ($type, $data) = get_result($self);
206 0           chomp($data);
207 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
208             || $data ne 'sync') {
209 0           warn("Fail to sync");
210             }
211 0           ($type, $data) = get_result($self);
212 0 0         if ($type ne $eGuideDog::Festival::OK) {
213 0           warn("Fail to sync!");
214             }
215              
216             # speak
217 0           request($self, "(SayText \"$text\")");
218              
219 0           ($type, $data) = get_result($self);
220 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
221             && ($data !~ /^#
222 0           warn("Fail to speak $text!");
223             }
224 0           ($type, $data) = get_result($self);
225 0 0         if ($type ne $eGuideDog::Festival::OK) {
226 0           warn("Fail to speak $text!");
227             }
228              
229             # async
230 0           request($self, "(audio_mode 'async)");
231 0           ($type, $data) = get_result($self);
232 0           chomp($data);
233 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
234             || $data ne 'async') {
235 0           warn("Fail to async");
236             }
237 0           ($type, $data) = get_result($self);
238 0 0         if ($type ne $eGuideDog::Festival::OK) {
239 0           warn("Fail to async!");
240             }
241             }
242              
243             sub play {
244 0     0 1   my ($self, $filename) = @_;
245 0           $filename =~ s/\\/\\\\/g;
246 0           $filename =~ s/"/\\"/g;
247              
248 0           request($self, "(utt.play (utt.synth (eval (list (quote Utterance) (quote Wave) \"$filename\"))))");
249 0           my ($type, $data) = get_result($self);
250 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
251             && ($data !~ /^#
252 0           warn("Fail to play!");
253             }
254 0           ($type, $data) = get_result($self);
255 0 0         if ($type ne $eGuideDog::Festival::OK) {
256 0           warn("Fail to play!");
257             }
258             }
259              
260             sub output {
261 0     0 1   my ($self, $text, $filename) = @_;
262 0           $text =~ s/\\/\\\\/g;
263 0           $text =~ s/"/\\"/g;
264 0           $filename =~ s/\\/\\\\/g;
265 0           $filename =~ s/"/\\"/g;
266              
267 0           request($self, "(utt.save.wave (utt.synth (eval (list (quote Utterance) (quote Text) \"$text\"))) \"$filename\")");
268 0           my ($type, $data) = get_result($self);
269 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
270             && ($data !~ /^#
271 0           warn("Fail to output $text to $filename:($type, $data)");
272             }
273 0           ($type, $data) = get_result($self);
274 0 0         if ($type ne $eGuideDog::Festival::OK) {
275 0           warn("Fail to output $text to $filename:($type, $data)");
276             }
277             }
278              
279 0     0 0   sub pause {}
280              
281             # continue speaking
282 0     0 0   sub resume {}
283              
284             sub stop {
285 0     0 1   my $self = shift;
286              
287 0           request($self, "(audio_mode 'shutup)");
288 0           my ($type, $data) = get_result($self);
289 0           chomp($data);
290 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
291             || $data ne 'shutup') {
292 0           warn("Fail to shutup:($type, $data)");
293             }
294 0           ($type, $data) = get_result($self);
295 0 0         if ($type ne $eGuideDog::Festival::OK) {
296 0           warn("Fail to shutup:($type, $data)");
297             }
298             }
299              
300             sub close {
301 0     0 1   my $self = shift;
302              
303 0           request($self, "(audio_mode 'close)");
304 0           my ($type, $data) = get_result($self);
305 0           chomp($data);
306 0 0 0       if ($type ne $eGuideDog::Festival::SCHEME
307             || $data ne 'close') {
308 0           warn("Fail to close!");
309             }
310 0           ($type, $data) = get_result($self);
311 0 0         if ($type ne $eGuideDog::Festival::OK) {
312 0           warn("Fail to close!");
313             }
314             }
315              
316             # Mode can be changed from a new speech or after a pause.
317             # When it stops, call 'continue' method to go on reading.
318             # Modes are applied on 'async audio mode' but not 'sync audio mode'
319             #
320             # here are the modes:
321             # article - no stop
322             # paragraph - stop at new line.
323             # sentence - stop at '.'
324             # clause - stop at ',' and '.'
325             # word - stop at every word
326             # letter - stop at every letter
327             # spell - spell a word (letter and word)
328             sub mode {
329 0     0 0   my ($self, $mode) = @_;
330              
331 0 0 0       if ($mode ne 'article'
      0        
      0        
      0        
      0        
      0        
332             && $mode ne 'paragraph'
333             && $mode ne 'sentence'
334             && $mode ne 'clause'
335             && $mode ne 'word'
336             && $mode ne 'letter'
337             && $mode ne 'spell') {
338 0           return 0;
339             }
340              
341 0           $eGuideDog::Festival::mode = $mode;
342 0           return 1;
343             }
344              
345             sub is_playing {
346 0 0   0 1   if (open(my $DSP, '>', '/dev/dsp')) {
347 0           CORE::close($DSP);
348 0           return 0;
349             } else {
350 0           return 1;
351             }
352             }
353              
354 0     0 0   sub language_list {}
355              
356 0     0 0   sub get_language {}
357 0     0 0   sub set_language {}
358              
359             sub voice_list {
360 0     0 1   my $self = shift;
361              
362 0           request($self, '(voice.list)');
363 0           my ($type, $data) = get_result($self);
364 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
365 0           my ($list) = parse_scheme($data);
366 0           ($type, $data) = get_result($self);
367 0 0         if ($type ne $eGuideDog::Festival::OK) {
368 0           warn("Fail to get voice list!");
369 0           return undef;
370             }
371 0           return @$list;
372             } else {
373 0           warn("Fail to get voice list!");
374 0           ($type, $data) = get_result($self);
375 0           return undef;
376             }
377             }
378              
379 0     0 0   sub get_voice {}
380              
381             sub set_voice {
382 0     0 1   my ($self, $name) = @_;
383              
384             # check whether voice exists
385 0           my @voices = voice_list($self);
386 0           my $exist = 0;
387 0           foreach (@voices) {
388 0 0         if ($name eq $_) {
389 0           $exist = 1;
390 0           last;
391             }
392             }
393 0 0         return undef if (! $exist);
394              
395 0           request($self, "(voice.select '$name)");
396 0           my ($type, $data) = get_result($self);
397 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
398 0           chomp($data);
399 0 0         if ($data eq $name) {
400 0           ($type, $data) = get_result($self);
401 0 0         if ($type ne $eGuideDog::Festival::OK) {
402 0           warn("Fail to set voice:($type, $data)");
403 0           return undef;
404             } else {
405 0           return $name;
406             }
407             }
408             } else {
409             # warn("Fail to set voice:($type, $data)");
410 0           return undef;
411             }
412             }
413              
414 0     0 0   sub styles {}
415 0     0 0   sub get_style {}
416 0     0 0   sub set_style {}
417              
418             sub duration_stretch {
419 0     0 1   my ($self, $stretch) = @_;
420              
421 0 0         if (defined $stretch) {
422 0 0 0       return 0 if ($stretch <= 0 || $stretch > 10);
423 0           request($self, "(Parameter.set 'Duration_Stretch $stretch)");
424 0           my ($type, $data) = get_result($self);
425 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
426 0           chomp($data);
427 0           my ($type2, $data2) = get_result($self);
428 0 0         if ($type2 ne $eGuideDog::Festival::OK) {
429 0           warn("Fail to set duration stretch!");
430 0           return undef;
431             } else {
432 0           return $data;
433             }
434             } else {
435 0           warn("Fail to set duration stretch!");
436 0           return undef;
437             }
438             } else {
439 0           request($self, "(Parameter.get 'Duration_Stretch)");
440 0           my ($type, $data) = get_result($self);
441 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
442 0           chomp($data);
443 0           my ($type2, $data2) = get_result($self);
444 0 0         if ($type2 ne $eGuideDog::Festival::OK) {
445 0           warn("Fail to get duration stretch!");
446 0           return undef;
447             } else {
448 0           return $data;
449             }
450             } else {
451 0           warn("Fail to get duration stretch!");
452 0           return undef;
453             }
454             }
455             }
456              
457 0     0 0   sub volume {
458             }
459              
460             sub pitch {
461 0     0 1   my ($self, $pitch) = @_;
462              
463 0 0         if (defined $pitch) {
464 0 0 0       return 0 if ($pitch <=0 || $pitch > 1000);
465 0           request($self, "
466             (let ((model_mean (cadr (assoc 'model_f0_mean int_lr_params)))
467             (model_std (cadr (assoc 'model_f0_std int_lr_params)))
468             (new_std (cadr (assoc 'target_f0_std int_lr_params))))
469             (set! int_lr_params
470             (list
471             (list 'target_f0_mean $pitch)
472             (list 'target_f0_std new_std)
473             (list 'model_f0_mean model_mean)
474             (list 'model_f0_std model_std)
475             )
476             )
477             )");
478 0           my ($type, $data) = get_result($self);
479 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
480 0           chomp($data);
481 0           my ($type2, $data2) = get_result($self);
482 0 0         if ($type2 ne $eGuideDog::Festival::OK) {
483 0           warn("Fail to get pitch:($type2, $data2)");
484 0           return undef;
485             }
486 0           return $data;
487             } else {
488 0           warn("Fail to get pitch:($type, $data)");
489 0           return undef;
490             }
491             } else {
492 0           request($self, "(cadr (assoc 'target_f0_mean int_lr_params))");
493 0           my ($type, $data) = get_result($self);
494 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
495 0           chomp($data);
496 0           my ($type2, $data2) = get_result($self);
497 0 0         if ($type2 ne $eGuideDog::Festival::OK) {
498 0           warn("Fail to get pitch:($type2, $data2)");
499 0           return undef;
500             }
501 0           return $data;
502             } else {
503 0           warn("Fail to get pitch:($type, $data)");
504 0           return undef;
505             }
506             }
507             }
508              
509             sub range {
510 0     0 1   my ($self, $range) = @_;
511              
512 0 0         if (defined $range) {
513 0 0 0       return 0 if ($range <=0 || $range > 1000);
514 0           request($self, "
515             (let ((model_mean (cadr (assoc 'model_f0_mean int_lr_params)))
516             (model_std (cadr (assoc 'model_f0_std int_lr_params)))
517             (new_mean (cadr (assoc 'target_f0_mean int_lr_params))))
518             (set! int_lr_params
519             (list
520             (list 'target_f0_mean new_mean)
521             (list 'target_f0_std $range)
522             (list 'model_f0_mean model_mean)
523             (list 'model_f0_std model_std)
524             )
525             )
526             )");
527 0           my ($type, $data) = get_result($self);
528 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
529 0           chomp($data);
530 0           my ($type2, $data2) = get_result($self);
531 0 0         if ($type2 ne $eGuideDog::Festival::OK) {
532 0           warn("Fail to set range:($type2, $data2)");
533 0           return undef;
534             }
535 0           return $data;
536             } else {
537 0           warn("Fail to set range:($type, $data)");
538 0           return undef;
539             }
540             } else {
541 0           request($self, "(cadr (assoc 'target_f0_std int_lr_params))");
542 0           my ($type, $data) = get_result($self);
543 0 0         if ($type eq $eGuideDog::Festival::SCHEME) {
544 0           chomp($data);
545 0           my ($type2, $data2) = get_result($self);
546 0 0         if ($type2 ne $eGuideDog::Festival::OK) {
547 0           warn("Fail to get range:($type2, $data2)");
548 0           return undef;
549             } else {
550 0           return $data;
551             }
552             } else {
553 0           warn("Fail to get range:($type, $data)");
554 0           return undef;
555             }
556             }
557             }
558              
559             sub reset {
560 0     0 0   my $self = shift;
561              
562 0           request($self, "(voice_reset)");
563 0           my ($type, $data) = get_result($self);
564 0 0         if ($type ne $eGuideDog::Festival::SCHEME) {
565 0           warn("Fail to reset!");
566 0           return undef;
567             }
568 0           ($type, $data) = get_result($self);
569 0 0         if ($type ne $eGuideDog::Festival::OK) {
570 0           warn("Fail to reset voice!");
571             }
572             }
573              
574 0     0 0   sub record_file {}
575 0     0 0   sub recording {}
576 0     0 0   sub history_size {}
577 0     0 0   sub speak_again {}
578              
579              
580             ###### Below is orginal code in Speech::Festival #####
581             sub new_client
582             {
583 0     0 0   my ($host, $port) = @_;
584              
585 0   0       $host ||= 'localhost';
586 0   0       $port ||= 1314;
587             # my ($self) = [ $host, $port, $eGuideDog::Festival::nextstream++, {} ];
588 0           my ($self) = [ $host, $port, new FileHandle, {} ];
589              
590 0           return bless $self, __PACKAGE__;
591             }
592              
593             sub conn
594             {
595 0     0 0   my ($self) = @_;
596 0           my ($host, $port, $s, $prop) = @$self;
597              
598 0           my($iaddr, $paddr, $proto);
599              
600 0 0         unless ($iaddr = inet_aton($host))
601             {
602             # $speech_error = "no host: $host - $!";
603 0           return 0;
604 0           die;
605             }
606              
607 0           $paddr = sockaddr_in($port, $iaddr);
608              
609 0           $proto = getprotobyname('tcp');
610              
611 0 0         unless(socket($s, PF_INET, SOCK_STREAM, $proto))
612             {
613             # $speech_error = "socket: $!";
614 0           return 0;
615 0           die;
616             }
617              
618 0 0         unless(connect($s, $paddr))
619             {
620             # $speech_error = "connect: $!";
621 0           return 0;
622 0           die;
623             }
624              
625 0           my ($old) = select($s);
626 0           $|=1;
627 0           select($old);
628              
629 0           $$prop{C}=1;
630              
631 0           return $s;
632             }
633              
634             sub disconnect
635             {
636 0     0 0   my ($self) = @_;
637              
638 0           my ($host, $port, $s, $prop) = @$self;
639              
640 0 0 0       if (defined($$prop{C}) && $$prop{C})
641             {
642 0           eval { local $SIG{PIPE} = 'IGNORE'; CORE::close($s); }
  0            
  0            
643             }
644 0           $$prop{C}=0;
645             }
646              
647             sub detach
648             {
649 0     0 0   my ($self) = @_;
650              
651 0           &DESTROY($self);
652              
653 0           bless $self, "destroyed Festival";
654             }
655              
656             sub request
657             {
658 0     0 1   my ($self, $scheme, $handler, @info) = @_;
659 0           my ($host, $port, $s) = @$self;
660              
661             # print "request: $scheme\n";
662              
663 0           print $s "$scheme\n";
664              
665 0 0         if (defined($handler))
666             {
667 0           return handle_results($s, $handler, @info);
668             }
669             }
670              
671             sub wait_for_result
672              
673             {
674 0     0 0   my ($self, $time) = @_;
675 0           my ($host, $port, $s) = @$self;
676              
677 0           return waitforsomething($s, $time);
678             }
679              
680             sub result_waiting
681              
682             {
683 0     0 0   my ($self) = @_;
684 0           my ($host, $port, $s) = @$self;
685              
686 0           return waitforsomething($s, 0);
687             }
688              
689             sub get_result
690              
691             {
692 0     0 0   my ($self) = @_;
693 0           my ($host, $port, $s);
694              
695 0 0         if (ref($self))
696             {
697 0           ($host, $port, $s) = @$self;
698             }
699             else
700             {
701 0           $s = $self;
702             }
703              
704 0           my ($type) = '';
705              
706 0 0         if (myread_n($s, $type, 3) != 3)
707             {
708             # $speech_error = "Error reading type - $!";
709 0           return undef;
710             }
711              
712 0           chomp $type;
713 0 0 0       return ($type, 'void')
714             if $type eq $OK || $type eq $ERROR;
715              
716 0           my ($data) = '';
717              
718 0 0         if (myread_upto($s, $data, $end_key) < 0)
719             {
720             # $speech_error = "Error reading data - $!";
721 0           return undef;
722             }
723              
724 0           return ($type, $data);
725             }
726              
727             sub handle_results
728             {
729 0     0 0   my ($s, $handler, @info) = @_;
730 0           my ($nres)=0;
731 0           my ($state);
732              
733 0           while (1)
734             {
735 0           my ($type, $data) = get_result $s;
736            
737 0 0         if (!defined($type))
738             {
739 0           return undef;
740             }
741            
742 0           $state = &$handler($type, $data, @info);
743              
744 0 0         if ($type eq $OK)
    0          
745             {
746 0           $state=$nres;
747 0           last;
748             }
749             elsif ($type eq $ERROR)
750             {
751 0           $state=-1;
752 0           last;
753             }
754 0           $nres++;
755             }
756 0           return $state;
757             }
758              
759             # simple look-ahead IO
760              
761             my $buffer='';
762             my $bend=0;
763              
764             sub waitforsomething
765             {
766 0     0 0   my ($s, $time) = @_;
767              
768 0 0         if (length($buffer) > 0)
769             {
770 0           return 1;
771             }
772              
773 0           my ($rin, $rout) = '';
774 0           vec($rin, fileno($s), 1) = 1;
775              
776 0           return select($rout = $rin, undef, undef, $time);
777             }
778              
779             sub myread_n
780             {
781 0     0 0   my ($s, $b, $n) = @_;
782              
783 0           while ($bend < $n)
784             {
785 0           my ($nr) = sysread($s, $buffer, 1000-$bend, $bend);
786 0 0         $bend += $nr
787             if defined($nr);
788             }
789              
790 0           $_[1] = substr($buffer, 0, $n);
791 0           $buffer = substr($buffer, $n);
792 0           $bend -= $n;
793 0           return $n;
794             }
795              
796             sub myread_upto
797             {
798 0     0 0   my ($s, $b, $key) = @_;
799              
800 0           my ($checkfrom, $keyat) = 0;
801              
802 0           while (($keyat=index($buffer, $key, $checkfrom)) <0)
803             {
804 0 0         $checkfrom = $bend-length($key)
805             if $bend > length($key);
806              
807 0           my ($nr) = sysread($s, $buffer, 10000, $bend);
808 0           $bend += $nr;
809             }
810              
811 0           $_[1] = substr($buffer, 0, $keyat);
812 0           $buffer = substr($buffer, $keyat+length($key));
813 0           $bend -= $keyat+length($key);
814 0           return length($_[1]);
815             }
816              
817             # parse scheme
818              
819             my $scheme_token = '^\\s*(("([^\\]"|[^"]|\s)*")|([-a-zA-Z0-9_+]+)|(\')|(\()|(\)))\\s*';
820              
821             sub parse_scheme
822             {
823 0     0 0   my ($text) = @_;
824              
825 0           my ($scheme);
826              
827 0 0         if ($text eq '')
    0          
828             {
829 0           return (undef, "");
830             }
831             elsif ($text =~ /$scheme_token/om)
832             {
833 0           my ($tok, $str, $strcont, $atom, $sq, $open, $close, $tail) =
834             ($1, $2, $3, $4, $5, $6, $7, $');
835              
836             # print "XXX", join("//", ($tok, $str, $strcont, $atom, $sq, $open, $close, $tail)), "\n";
837              
838 0 0         if (defined($str))
    0          
    0          
    0          
    0          
839             {
840 0           return ($str, $tail);
841             }
842             elsif (defined($atom))
843             {
844 0           return ($atom, $tail);
845             }
846             elsif (defined($sq))
847             {
848 0           my ($quoted, $ttail) = parse_scheme($tail);
849              
850 0           return ([ 'quote', $quoted ], $ttail);
851             }
852             elsif (defined($open))
853             {
854 0           my ($list) = [];
855              
856 0           while (1)
857             {
858 0           my ($item, $ttail) = parse_scheme($tail);
859              
860 0           $tail = $ttail;
861              
862             last
863 0 0 0       if !defined($item) || $item eq ')';
864              
865 0           push(@$list, $item);
866             }
867            
868 0           return ($list, $tail);
869             }
870             elsif (defined($close))
871             {
872 0           return ($close, $tail);
873             }
874             }
875              
876 0           return (undef, substr($text,1));
877             }
878              
879             # Autoload methods go after =cut, and are processed by the autosplit program.
880              
881             1;
882             __END__