File Coverage

blib/lib/Language/Mumps.pm
Criterion Covered Total %
statement 35 1010 3.4
branch 0 368 0.0
condition 0 163 0.0
subroutine 11 166 6.6
pod 0 46 0.0
total 46 1753 2.6


line stmt bran cond sub pod time code
1             # PerlMUMPS by Ariel Brosh
2             # Usage is free, including commercial use, enterprise and legacy use
3             # However, any modifications should be notified to the maintainer
4             # Email: smueller@cpan.org
5              
6             # Note:
7             # This compiler parses and generates in the same phase, therefore is not
8             # very maintainable
9              
10             package Language::Mumps;
11             $VERSION = '1.08';
12 1     1   7632 use Fcntl;
  1         3  
  1         294  
13 1     1   5 use strict;
  1         1  
  1         50  
14 1         9404 use vars qw($FETCH $STORE $DB $SER $IMPORT @TYING $xpos $ypos
15             %symbols $selected_io $flag @handlers @xreg @yreg
16             $curses_inside $varstack %RES $RESKEYS %COMMANDS $scope_do
17             %FUNCTIONS %FUNS @tmpvars $tmphash $infun $scopes @stack
18             @program %bookmarks $lnum $forgiveful $forscope %dbs
19 1     1   4 $VERSION);
  1         2  
20              
21             # Map short form to long form commands
22              
23             %COMMANDS = qw(B BREAK C CLOSE D DO E ELSE F FOR G GOTO HALT HALT
24             H HANG I IF J JOB K KILL L LOCK O OPEN Q QUIT
25             R READ S SET U USE V VIEW W WRITE X XECUTE
26             ZE HALT ZP ZP ZFUNCTION ZFUNCTION
27             ZRETURN ZRETURN ZD ZD);
28              
29             # Map short form to long form functions
30              
31             %FUNCTIONS = qw(I IO T TEST P PIECE H HOROLOG J JOB
32             X X Y Y ZDATE ZD ZA ZN);
33              
34             # Function schema
35             # array of: funcname => array of | lval => 1/0, prot => prototype
36             # If lval is 1, function can be use as lvalue.
37             # prototype has one char per function parameter.
38             # I = input O = output L = list T = tuple
39              
40             %FUNS = (
41             'ASCII' => [{'lval' => 0, 'prot' => 'II'},
42             {'lval' => 0, 'prot' => 'I'}],
43             'CHAR' => [{'lval' => 0, 'prot' => 'L'}],
44             'DATA' => [{'lval' => 0, 'prot' => 'O'}],
45             'EXTRACT' => [{'lval' => 0, 'prot' => 'I'},
46             {'lval' => 0, 'prot' => 'II'},
47             {'lval' => 0, 'prot' => 'III'}],
48             'FIND' => [{'lval' => 0, 'prot' => 'II'},
49             {'lval' => 0, 'prot' => 'III'}],
50             'JOB' => [{'lval' => 0, 'prot' => ''}],
51             'JUSTIFY' => [{'lval' => 0, 'prot' => 'II'},
52             {'lval' => 0, 'prot' => 'III'}],
53             'HOROLOG' => [{'lval' => 0, 'prot' => ''}],
54             'IO' => [{'lval' => 1, 'prot' => ''}],
55             'LEN' => [{'lval' => 0, 'prot' => 'II'},
56             {'lval' => 0, 'prot' => 'I'}],
57             'NEXT' => [{'lval' => 0, 'prot' => 'O'}],
58             'ORDER' => [{'lval' => 0, 'prot' => 'O'}],
59             'PIECE' => [{'lval' => 1, 'prot' => 'OII'},
60             {'lval' => 0, 'prot' => 'III'},
61             {'lval' => 0, 'prot' => 'IIII'}],
62             'RANDOM' => [{'lval' => 0, 'prot' => 'I'}],
63             'SELECT' => [{'lval' => 0, 'prot' => 'T'}],
64             'TEST' => [{'lval' => 1, 'prot' => ''}],
65             'X' => [{'lval' => 0, 'prot' => ''}],
66             'Y' => [{'lval' => 0, 'prot' => ''}],
67             'ZAB' => [{'lval' => 0, 'prot' => 'I'}],
68             'ZB' => [{'lval' => 0, 'prot' => 'I'}],
69             'ZCD' => [{'lval' => 0, 'prot' => ''},
70             {'lval' => 0, 'prot' => 'I'}],
71             'ZCL' => [{'lval' => 0, 'prot' => ''},
72             {'lval' => 0, 'prot' => 'I'}],
73             'ZD' => [{'lval' => 0, 'prot' => ''}],
74             'ZD1' => [{'lval' => 0, 'prot' => ''}],
75             'ZD2' => [{'lval' => 0, 'prot' => 'I'}],
76             'ZD3' => [{'lval' => 0, 'prot' => 'III'}],
77             'ZD4' => [{'lval' => 0, 'prot' => 'III'}],
78             'ZD5' => [{'lval' => 0, 'prot' => 'III'}],
79             'ZD6' => [{'lval' => 0, 'prot' => 'I'},
80             {'lval' => 0, 'prot' => ''}],
81             'ZD7' => [{'lval' => 0, 'prot' => 'I'},
82             {'lval' => 0, 'prot' => ''}],
83             'ZD8' => [{'lval' => 0, 'prot' => 'I'},
84             {'lval' => 0, 'prot' => ''}],
85             'ZD9' => [{'lval' => 0, 'prot' => 'I'},
86             {'lval' => 0, 'prot' => ''}],
87             'ZDBI' => [{'lval' => 0, 'prot' => 'IIIIO'}],
88             'ZF' => [{'lval' => 0, 'prot' => 'I'}],
89             'ZH' => [{'lval' => 0, 'prot' => 'I'}],
90             'ZL' => [{'lval' => 0, 'prot' => 'II'},
91             {'lval' => 0, 'prot' => 'I'}],
92             'ZN' => [{'lval' => 0, 'prot' => 'I'}],
93             'ZR' => [{'lval' => 0, 'prot' => 'I'}],
94             'ZS' => [{'lval' => 0, 'prot' => 'I'}],
95             'ZSQR' => [{'lval' => 0, 'prot' => 'I'}],
96             'ZT' => [{'lval' => 0, 'prot' => 'I'}],
97             'ZVARIABLE' => [{'lval' => 0, 'prot' => 'I'}],
98             );
99              
100             ####
101             ## M line to Perl line
102              
103             sub m2pl {
104 0     0 0 0 my $line = shift;
105              
106             # Convert 8 spaces to a tab if -f used
107             # M requires lines to begin with tabs
108              
109 0 0       0 $line =~ s/^(\w+) {8}/$1\t/ if ($forgiveful);
110              
111             # Embedded perl code
112              
113 0 0       0 if ($line =~ s/^\%//) {
114 0         0 return "$line\n";
115             }
116              
117             # Comment
118              
119 0 0       0 if ($line =~ s/^\#//) {
120 0         0 return "";
121             }
122              
123             # Does not begin with a tab - plain text
124              
125 0 0       0 unless ($line =~ /\t/) {
126 0         0 return "Language::Mumps::write('$line');\n";
127             }
128              
129             # Reset variable factory
130              
131 0         0 &resetvars;
132              
133 0         0 my ($label, $llin) = split(/\s*\t\s*/, $line, 2);
134 0         0 $line = $llin;
135              
136             # Labels must begin with a letter
137              
138 0 0 0     0 die "Illegal label $label" unless (!$label || $label =~ /^[a-z]\w*/i);
139              
140             # Bookmarks are for source listing. Available only if M program was
141             # compiled and executed inside the same Perl script
142              
143 0         0 $bookmarks{$label} = $lnum;
144 0 0       0 $label = "__lbl_Mumps_$label\: " if ($label);
145              
146             # Do the actual work
147 0         0 $label . &ml2pl($line);
148             }
149              
150             sub ml2pl {
151 0     0 0 0 my $line = shift;
152 0         0 my ($res, $tmp, $code);
153              
154             # M commands may be several in a line
155              
156 0         0 while ($line) {
157 0         0 my ($token, $cond, $pre, $post);
158              
159             # "Eat" one token, cancelling spaces.
160              
161 0 0       0 if ($line =~ s/^\s*(\S*?)\s+//) {
162 0         0 $token = $1;
163             } else {
164 0         0 $token = $line;
165 0         0 $line = '';
166             }
167              
168             # Close block
169              
170 0 0       0 if ($token eq '}') {
171 0 0       0 die "Unexpected right bracket" unless ($scopes--);
172 0         0 $code .= "}\n";
173 0         0 next;
174             }
175              
176             # Command:Condition - Run the command conditionally
177              
178 0 0       0 if ($token =~ /^([a-z]\w*):(.*)$/i) {
179 0         0 $token = $1;
180 0         0 $cond = $2;
181             }
182              
183 0 0       0 if ($cond) {
184 0         0 ($pre, $tmp) = &makecond($cond);
185 0         0 $pre .= "if ($tmp) {\n";
186 0         0 $post = "\n}";
187             }
188              
189 0         0 $token = uc($token);
190              
191             # my ($k, $v);
192 0         0 foreach (keys %COMMANDS) {
193             # If $token is either short or long form of command, call function
194              
195 0 0 0     0 if ($_ eq $token || $COMMANDS{$_} eq $token) {
196             # $line is passed *by reference*
197              
198 0         0 $res = &{$COMMANDS{$_}}($line);
  0         0  
199              
200             # Kill spaces
201              
202 0         0 $line =~ s/^\s*//;
203 0         0 goto success;
204             }
205             }
206 0         0 die "Unrecognized command $token";
207 0         0 success:
208             $code .= "$pre$res$post\n";
209             }
210 0         0 $code;
211             }
212              
213             ####
214             ## Convert a block of M code to a block of Perl code
215              
216             sub compile {
217 0     0 0 0 my $text = shift;
218 0         0 my @lines = split(/\r?\n/, $text);
219 0         0 %bookmarks =();
220 0         0 @program = @lines;
221             # Stack based scope for $scopes - push the scope counter to the stack
222             # until the end of the function
223              
224 0         0 local($scopes);
225 0         0 $lnum = 0;
226             # Iterate over code
227              
228 0         0 my @code = map {++$lnum; "# $lnum) $_\n" . &m2pl($_);} @lines;
  0         0  
  0         0  
229             # Ensure we close all blocks
230              
231 0 0       0 die "Unclosed brackets" if ($scopes);
232              
233             # Add essential code
234             # mumps.cfg will be read only by the compiler, not by programs
235 0         0 join("", "use Language::Mumps qw(Runtime $IMPORT);\nno strict;\n", @code,
236             "### end\n", &m2pl("\tQUIT"));
237             }
238              
239             ####
240             ## Compile an M program and evaluate immediately
241              
242             sub evaluate {
243 0     0 0 0 my $prog = shift;
244 0         0 my $code = &compile($prog);
245 0         0 local (@stack);
246 0         0 $@ = undef;
247 0         0 eval $code;
248 0 0       0 die $@ if ($@);
249             }
250              
251             ####
252             ## Read an M program from a file, compile and run
253              
254             sub interprete {
255 0     0 0 0 my $fn = shift;
256 0         0 open(I, $fn);
257 0         0 my $prog = join("", );
258 0         0 close(I);
259 0         0 evaluate($prog);
260             }
261              
262             ####
263             ## Translate an M file to a Perl file
264              
265             sub translate {
266 0     0 0 0 my ($i, $o) = @_;
267 0         0 open(I, $i);
268 0         0 my $prog = join("", );
269 0         0 close(I);
270 0         0 my $code = &compile($prog);
271 0         0 open(O, ">$o");
272 0         0 print O <
273             #############################################################################
274             # This Perl script was created by the MUMPS to Perl compiler by Ariel Brosh #
275             #############################################################################
276              
277             $code
278              
279             1;
280             EOM
281 0         0 close(O);
282             }
283              
284             ####
285             ## Return a line of the program
286             ## Not thread safe - supports only one M program per Perl script
287              
288             sub list {
289 0     0 0 0 my ($line, $off);
290 0 0 0     0 my $lnum = ($line > 0) ? ($line - 1) : $bookmarks{$line} || die "Unknown label";
291 0         0 $program[$lnum - 1 + $off];
292             }
293              
294             ######################################################################
295             ## COMMANDS ##
296             ######################################################################
297             ## Each function receives a line of code *by reference*, removes ##
298             ## input tokens as they are "eaten" and returns Perl code to add to ##
299             ## the output. ##
300             ######################################################################
301              
302              
303             ####
304             ## BREAK - Stop the program
305              
306             sub BREAK {
307 0     0 0 0 return "exit;";
308             }
309              
310             ####
311             ## CLOSE
312             ## Add code to create a list of parameters
313             ## Add code to iterate through them and close file objects
314              
315             sub CLOSE ($) {
316 0     0   0 my ($code, $var) = &makelist($_[0]);
317 0         0 return $code . <
318             foreach ($var) {
319             die "Can't CLOSE unit 5" if (\$_ == 5);
320             close($Language::Mumps::handlers[\$_]);
321             }
322             EOM
323             }
324              
325             ####
326             ## DO
327             ## DO label - jump to the label. Create a label for returning.
328             ## Add code to push this label to the stack.
329             ## DO "program" or DO @var - Interprete another program
330             ## Add code to invoke the interprete method.
331             ## (Will make program listing useless)
332             ## DO $$ - Call a perl function. Test flag is set to the
333             ## non zeroeness of the return.
334              
335             sub DO ($) {
336 0 0   0 0 0 if ($_[0] =~ s/^\s*([a-z]\w*)\b//i) {
337 0         0 my $dest = $1;
338 0         0 ++$scope_do;
339 0         0 my $lbl = &nextvar("d$scope_do");
340 0         0 return <
341             push(\@Language::Mumps::stack, '$lbl');
342             goto __lbl_Mumps_$dest;
343             $lbl:
344             EOM
345             }
346 0 0       0 if ($_[0] =~ /^[\@"]/) {
347 0         0 $_[0] =~ s/^\@//;
348 0         0 my ($code, $var) = &makeexp($_[0]);
349 0         0 return $code . "Language::Mumps::interprete($var);";
350             }
351 0 0       0 if ($_[0] =~ /^\$\$/) {
352 0         0 my ($code, $var) = &makeexp($_[0]);
353 0         0 return $code . "\$Language::Mumps::flag = $var ? 1 : undef;";
354             }
355 0         0 $_[0] =~ s/\s.*$//;
356 0         0 die "Illegal argument for DO $_[0]";
357             }
358              
359             ####
360             ## ELSE - Things to do if the test flags is false.
361             ## Usually but not necessarily after IF.
362             ## Add code to check test flag and -
363             ## If called with { - Increase the scope counter, leave Perl code
364             ## in a block
365             ## If called with a list of commands - call the interpreter recursively
366             ## to interprete the rest of the line, put it inside the conditional
367             ## block.
368              
369             sub ELSE ($) {
370 0     0 0 0 my $code = "unless (\$Language::Mumps::flag) {";
371 0 0       0 if ($_[0] =~ s/^\{\s*//) {
372 0         0 $scopes++;
373 0         0 return $code;
374             }
375 0         0 my $block = &ml2pl($_[0]);
376 0         0 "$code\n$block}";
377             }
378              
379             ####
380             ## FOR var=token,token,token
381             ## Make a Foreach over the list.
382             ## Token can be: start:step:last
383              
384             sub FOR ($) {
385 0 0   0 0 0 unless ($_[0]) {
386 0         0 die "Iterator expected in FOR";
387             }
388              
389             ## Construct the iteration variable
390 0         0 my ($itercode, $lvar) = &makevar($_[0]);
391              
392             ## Get Perl code to represent lvalue
393 0         0 my $var = $lvar->lval;
394              
395             ## Allocate an iteration var
396 0         0 my $itervar = &nextvar();
397              
398             ## Allocate a var to hold the list
399 0         0 my $eachlist = &nextvar('@');
400             # Allocate vars to hold from, to, step
401 0         0 my $f = &nextvar('$');
402 0         0 my $t = &nextvar('$');
403 0         0 my $s = &nextvar('$');
404              
405             # Code to attach the Perl iteration var to a symbol table entry of the
406             # selected LValue. (Needed to support complex access)
407              
408 0         0 $itercode .= "*$itervar = \\$var;\n";
409              
410             # From now on, $var is the soft reference to $var
411              
412 0         0 $var = "\$$itervar";
413 0 0       0 die "= expected in FOR" unless ($_[0] =~ s/^\=//);
414              
415             # Code inside the loop will be stored in a subroutine
416             # This way we can forward-rely on it
417             # All procedures will have a unique identifier
418              
419 0         0 my $procname = "__tmpfor" . ++$forscope;
420 0         0 my ($flag, $listflag);
421 0         0 my $first = 1;
422              
423             # "Eat" the remainder of the parameter
424 0         0 while (1) {
425             # Set $flag to true if we are in the end of the input
426 0 0 0     0 $flag = 1 unless ($_[0] && $_[0] !~ /^\s/);
427             # Unless it is the first token, or input has ended, we must skip a comma
428 0 0 0     0 die "Comma expected in FOR" unless ($first || $_[0] =~ s/^,// || $flag);
      0        
429             # No more first token
430 0         0 $first = undef;
431             # "Eat" value
432 0         0 my ($code, $val) = &makeexp($_[0]);
433 0 0 0     0 if ($flag || $_[0] =~ s/^\://) {
434             # If we are in the end of input, or we have a compund token,
435             # we have to flush the simple tokens.
436 0 0       0 $itercode .= "foreach \$var ($eachlist) " .
437             "{&$procname;}\n\$eachlist = ();\n" if ($listflag);
438 0 0       0 last if ($flag);
439             # If we got here, it is a compound token
440 0         0 $listflag = undef;
441             # Add the code to evaluate the loop start, and to assign it to the
442             # loop start variable
443 0         0 $itercode .= $code;
444 0         0 $itercode .= "$f = $val;\n";
445              
446             # Get the step value. Note: we have already skipped the colon.
447 0         0 ($code, $val) = &makeexp($_[0]);
448 0         0 $itercode .= $code;
449 0         0 $itercode .= "$s = $val;\n";
450              
451             # If we have got more input, it must be delimited with a colon
452              
453 0 0 0     0 if ($_[0] && $_[0] !~ /^[,\s]/) {
454 0 0       0 die "Upper bound expected in FOR" unless ($_[0] =~ s/^://);
455             # "Eat" the to value.
456 0         0 my ($code, $val) = &makeexp($_[0]);
457 0         0 $itercode .= $code;
458 0         0 $itercode .= "$t = $val;\n";
459             } else {
460             # Infinite loop requested. (M dictates this syntax)
461             # If To is two Steps below From, we probably will never
462             # Reach To.
463 0         0 $itercode .= "$t = $f - $s * 2;\n";
464             }
465             # Obsolete sick code
466             # my $sign = (qw(< == >))[($f <=> $t) + 1];
467             # my $step = (qw(+ + -))[($f <=> $t) + 1];
468             # my $cond = ($t ? "$var $sign $t" : 1);
469             # my $incr = (abs($s) == 1) ? ($var . ($step x 2))
470             # : "$var $step= " . abs($s);
471              
472             # Generate for(;;) code.
473             # Make To run away one step. This way the original To value is still
474             # inside the loop.
475             # We check if the iterator is still different from To, and if it is
476             # in the same direction as From was for.
477 0         0 my $for = "($var = $f, $t += $s; " .
478             "$var != $t && ($var <=> $t) == ($f <=> $t); " .
479             "$var += $s)";
480 0         0 $itercode .= "for $for {\&$procname;}\n";
481             } else {
482             # Simple token - add to list
483 0         0 $itercode .= $code . "push($eachlist, $val);\n";
484 0         0 $listflag = 1;
485             }
486             }
487             # Dismiss soft reference
488              
489 0         0 $itercode .= "*$itervar = \\\$sysundef;\n";
490 0         0 $_[0] =~ s/^\s*//;
491 0 0       0 die "Code expected in FOR" unless ($_[0]);
492              
493             # Define the subroutine we "owe"
494             # Either open a block, or call the interpreter recursively to
495             # translate the rest of the line into the subroutine
496              
497 0         0 $itercode .= "sub $procname {\n";
498 0 0       0 if ($_[0] =~ s/^\{\s*//) {
499 0         0 $scopes++;
500 0         0 return $itercode;
501             }
502 0         0 my $code = &ml2pl($_[0]);
503 0         0 $_[0] = '';
504             # Dixi et salvavi, Anima meam!
505 0         0 return "$itercode$code\n}";
506             }
507              
508             ####
509             ## GOTO label
510             ## Translate to perl gotos, do not check label existence
511              
512             sub GOTO ($) {
513 0 0   0 0 0 if ($_[0] =~ s/^([a-z]\w*)\b//i) {
514 0         0 return "goto __lbl_Mumps_$1;";
515             }
516 0         0 $_[0] =~ s/\s.*$//;
517 0         0 die "Illegal label in GOTO: $_[0]";
518             }
519              
520             ####
521             ## HALT - exit the program
522              
523             sub HALT {
524 0     0 0 0 return "exit;";
525             }
526              
527             ####
528             ## HANG - Exit if no parameter, Sleep if parameter attached
529             sub HANG ($) {
530 0 0   0 0 0 return "exit;" unless ($_[0]);
531 0         0 my ($code, $var) = &makeexp($_[0]);
532 0         0 return $code . "sleep($var);";
533             }
534              
535             ####
536             ## IF
537             ## Load the test flag, then make a block conditional to the test flag
538             ## Block creation like in FOR or ELSE
539              
540             sub IF ($) {
541 0 0   0 0 0 die "Condition expected in IF" unless ($_[0]);
542 0         0 my ($code, $val) = &makeexp($_[0]);
543 0         0 my $condcode = $code . "\$Language::Mumps::flag = $val ? 1 : undef;\nif (\$Language::Mumps::flag) {\n";
544 0         0 $_[0] =~ s/^\s*//;
545 0 0       0 die "Code expected in IF" unless ($_[0]);
546 0 0       0 if ($_[0] =~ s/^\{//) {
547 0         0 $scopes++;
548 0         0 return $condcode;
549             }
550 0         0 $code = &ml2pl($_[0]);
551 0         0 $_[0] = '';
552 0         0 return "$condcode$code\n}";
553             }
554              
555             ####
556             ## JOB - unsopported
557              
558             sub JOB {
559 0     0 0 0 die "Not implemented: JOB";
560             }
561              
562             ####
563             ## KILL - kill the whole symbol table
564             ## KILL var - kill one symbol or array
565             ## KILL (var) - kill everything besides one symbol
566              
567             sub KILL ($) {
568             ## No parameter - kill everything
569 0 0   0 0 0 unless ($_[0]) {
570 0         0 return "%Language::Mumps::symbols = ()";
571             }
572 0         0 my $rev;
573             my $thecode;
574             # my $cond = "if";
575             # Allocate a var name to hold a copy of the symbol table
576 0         0 my $tmptbl = &nextvar();
577              
578             # Check if we have paranthesis
579 0 0       0 if ($_[0] =~ s/^\(//) {
580 0         0 $rev = 1;
581             }
582             # Prepare a hash to store the copied symbol table
583              
584 0         0 $thecode = "{ my \%$tmptbl;\n";
585 0         0 my $n;
586 0   0     0 while ($_[0] && $_[0] !~ /^\s/) {
587 0         0 $n++;
588 0 0 0     0 last if ($n == 2 && $rev && $_[0] =~ s/^\)>//);
      0        
589 0 0       0 die "Variable expected in KILL" unless ($_[0] =~ /^\^?\w/);
590 0         0 my ($code, $var) = &makevar($_[0]);
591 0 0 0     0 die "Can unkill only regular arrays" if ($rev && ref($var) !~ /var/i);
592 0         0 my $addr = $var->addr;
593             # Either extract the variable purge code, or call runtime function
594             # to deep copy the chosen var into the new symbol table
595             # entry
596 0 0       0 $thecode .= $code . (!$rev
597             ? $var->purge . "\n"
598             : "&Language::Mumps::moveimage(\\\%Language::Mumps::symbol, \\\%$tmptbl, " .
599             "$addr);\n"
600             );
601             }
602             # If unkilling, deep copy the symbol table back
603 0 0       0 if ($rev) {
604 0         0 $thecode .= <
605             \%Language::Mumps::symbol = ();
606             foreach (keys \%$tmptbl) {
607             \$Language::Mumps::symbol{\$_} = \$$tmptbl\{\$_};
608             }
609             EOM
610             }
611 0         0 chomp $thecode;
612 0         0 $thecode;
613             }
614              
615             ####
616             ## LOCK ^array - lock an array database. Implemented only for disk mapped
617             ## arrays
618             ## LOCK - With no parameters, remove any previous locks.
619              
620             sub LOCK ($) {
621 0 0   0 0 0 unless ($_[0]) {
622 0         0 return <
623             foreach (\@Language::Mumps::locks) {
624             flock(\$_, 8);
625             }
626             \@Language::Mumps::locks = ();
627             EOM
628             }
629             # Get the var
630 0         0 my ($code, $var) = &makevar($_[0]);
631 0 0 0     0 die "Only one array can be LOCKed" if ($_[0] && $_[0] !~ /^\s/);
632             # Get the dereferencing to the database
633 0         0 my $ext = $var->getdb;
634 0         0 my $tdb = &nextvar('$');
635 0         0 my $fd = &nextvar('$');
636 0         0 return <
637             $tdb = $ext;
638             $fd = $tdb->fd;
639             die "LOCK: flock: $!" unless flock($fd, 6);
640             push(\@Language::Mumps::locks, $fd);
641             EOM
642             }
643              
644             ####
645             ## OPEN file-number:open-string
646             ## open-string = filename/method
647             ## method = NEW|OLD|APPEND
648              
649             sub OPEN ($) {
650             # Allocate a variable to hold the stream number
651 0     0   0 my $opennum = &nextvar('$');
652             # Allocate a variable to hold the parse tokens of the open string
653 0         0 my $tokens = &nextvar('@');
654             # Allocate two vars to hold the actual tokens
655 0         0 my $ofn = &nextvar('$');
656 0         0 my $omet = &nextvar('$');
657             # "Eat" the expression for the file number
658 0         0 my ($code, $var) = &makeexp($_[0]);
659 0 0       0 die ": expected in OPEN" unless ($_[0] =~ s/^\://);
660 0         0 $code .= "$opennum = $var;\n";
661             # "Eat" the open string
662 0         0 my ($code2, $var2) = &makeexp($_[0]);
663             # Generate code
664 0         0 $code . $code2 . <
665             die "Can't reOPEN unit 5" if ($opennum == 5);
666             ($ofn, $omet) = $tokens = split(/\\//, $var2);
667             die "Illegal OPEN string" unless (scalar($tokens) == 2 &&
668             grep /^$omet\$/i, qw(NEW OLD APPEND));
669             \$Language::Mumps::handlers[$opennum] = "F" . $opennum;
670             open(\$Language::Mumps::handlers[$opennum],
671             {NEW => '>', APPEND => '>>', OLD=> '<'}->{uc($omet)} . $ofn);
672             \$Language::Mumps::handlers[$opennum] = \*{\$Language::Mumps::handlers[$opennum]};
673             EOM
674             }
675              
676             ####
677             ## QUIT
678             ## End a subroutine or the whole program
679              
680             sub QUIT {
681 0     0 0 0 return <
682             if (\@Language::Mumps::stack) {
683             goto &{pop \@Language::Mumps::stack};
684             }
685             exit;
686             EOM
687             }
688              
689             ####
690             ## READ var,var.... Read variables
691             ## READ *var - Read one keypress, return ASCII code (with Curses)
692             ## Test flag will be false if we read nothing
693             ## READ ?seconds,var - Read with timeout
694             ## READ "prompt",var
695              
696             sub READ ($) {
697 0     0   0 my ($result, $timeout, $done);
698 0   0     0 while ($_[0] && $_[0] !~ /^\s/) {
699             # Iterate over arguments
700 0 0 0     0 die "Comma expected in READ" unless (!$done++ || $_[0] =~ s/^,//);
701             # If we have a varname
702 0 0       0 if ($_[0] =~ /^\*?[a-z^]/i) {
    0          
703 0         0 my $icode = "&Language::Mumps::read";
704             # Skip asterik if any, and decide we read one char
705 0 0       0 if ($_[0] =~ s/^\*//) {
706 0         0 $icode = "ord(&Language::Mumps::readkey)";
707             }
708             # In both cases, reading uses a runtime function
709 0         0 my ($code, $lvar) = &makevar($_[0]);
710              
711             # Extract lvalue dereferencing code
712 0         0 my $var = $lvar->lval;
713             # If we have a timeout, run the code inside an eval() which will be
714             # interrupted by SIGALARM
715              
716 0 0       0 $result .= "\$SIG{ALRM} = sub {die 1;}; \$\@ = undef; alarm $timeout;\n"
717             . "eval {\n" if ($timeout);
718 0         0 $result .= "$var = $icode;\n";
719 0 0       0 $result .= "};\n\$SIG{ALRM} = undef; alarm 0;\n\$Language::Mumps::flag = (\$\@ ? undef : 1);\n" if ($timeout);
720 0         0 $timeout = undef;
721             } elsif ($_[0] =~ s/^\?//) {
722 0         0 my $snip;
723 0         0 ($snip, $timeout) = &makeexp($_[0]);
724 0         0 $result .= $snip;
725             } else {
726             # Constants - inteprete as prompts to be written
727 0         0 my ($code, $var) = &makeexp($_[0]);
728 0         0 $result .= $code . "&Language::Mumps::write($var);\n";
729             }
730             }
731 0         0 chomp $result;
732 0         0 $result;
733             }
734              
735             ####
736             ## SET var=value,var=value
737              
738             sub SET ($) {
739 0     0 0 0 my ($result, $done);
740 0   0     0 while ($_[0] && $_[0] !~ /^\s/) {
741 0 0 0     0 die ", expected in SET" unless ($_[0] =~ s/^,// || !$done++);
742             # "Eat" var
743 0         0 my ($code, $lvar) = &makevar($_[0]);
744             # Extract code to dereference lvalue
745 0         0 my $var = $lvar->lval;
746             # Enforce equal sign and skip it
747 0 0       0 die "= expected in SET" unless ($_[0] =~ s/^\=//);
748             # "Eat" value
749 0         0 my ($code2, $val) = &makeexp($_[0]);
750 0         0 my $lval = &nextvar("");
751             # Generate code to:
752             # Make a temporary variable with soft reference, make assignment,
753             # dismiss soft referrence
754 0         0 $result .= $code . "*$lval = \\$var;\n" .
755             $code2 . "\$$lval = $val;\n*$lval = \\\$sysundef;\n";
756             }
757 0         0 $result;
758             }
759              
760             ####
761             ## USE file-number
762             ## Generate code to save the xpos and ypos values
763             sub USE ($) {
764 0     0 0 0 my ($code, $val) = &makeexp($_[0]);
765 0         0 return $code . <
766             \$Language::Mumps::xreg[\$Language::Mumps::selected_io] = \$Language::Mumps::xpos;
767             \$Language::Mumps::yreg[\$Language::Mumps::selected_io] = \$Language::Mumps::ypos;
768             \$Language::Mumps::selected_io = $val;
769             \$Language::Mumps::xpos = \$Language::Mumps::xreg[\$Language::Mumps::selected_io];";
770             \$Language::Mumps::ypos = \$Language::Mumps::yreg[\$Language::Mumps::selected_io];";
771             EOM
772             }
773              
774             ####
775             ## VIEW - Not implemented
776              
777             sub VIEW {
778 0     0 0 0 die "Not implemented: VIEW";
779             }
780              
781             ####
782             ## WRITE val,val.....
783              
784             sub WRITE {
785 0     0   0 my ($code, $val) = &makelist($_[0]);
786 0         0 return $code . <
787             foreach ($val) {
788             &Language::Mumps::write(\$_);
789             }
790             EOM
791             }
792              
793             ####
794             ## XECUTE value,value,value
795             ## Evaluate the M code expressed in the parameters
796             sub XECUTE {
797 0     0 0 0 my ($code, $val) = &makelist($_[0]);
798 0         0 return $code . <
799             foreach ($val) {
800             eval &ml2pl($_);
801             die "XECUTE: \$\@" if \$\@;
802             }
803             EOM
804             }
805              
806             ####
807             ## ZP -- Evaluate Perl code until end of the line
808             ## Test flag represents the non zeroeness of the result
809              
810             sub ZP ($) {
811 0     0 0 0 my $line = $_[0];
812 0         0 $_[0] = '';
813 0         0 return "\$Language::Mumps::flag = ($line) ? 1 : undef;";
814             }
815              
816             ####
817             ## ZD - Evaluate perl code until the end of the line
818              
819             sub ZD ($) {
820 0     0 0 0 my $line = $_[0];
821 0         0 $_[0] = '';
822 0         0 return $line;
823             }
824              
825             ####
826             ## ZFUNCTION - Incompatible with MumpsVM!
827             ## ZFUNCTION function(var1,var2,var3...)
828             ## ZFUNCTION function
829             ## Functions are called as var calls to perl functions - with DO $$
830              
831             sub ZFUNCTION ($) {
832 0     0 0 0 my @tokens = ($_[0] =~ s/^\s*([a-z]\w*)(?:\(?:(?:([a-z]\w*)(\,[a-z]\w*)*)?\))?\s*$//i);
833 0 0       0 die "Incorrect function header in ZFUNCTION" unless (@tokens);
834 0 0       0 die "Cannot nest functions in ZFUNCTION" if ($infun++ > 1);
835 0         0 my $fun = shift @tokens;
836 0         0 $tmphash = &nextvar("");
837 0         0 @tmpvars = @tokens;
838 0         0 my $code .= "sub $fun {\nmy \%$tmphash;\n";
839             # Save out of scope variables
840 0         0 foreach (@tokens) {
841 0         0 my $obj = new Language::Mumps::var;
842 0         0 $obj->name($_);
843 0         0 my $var = $obj->lval;
844 0         0 $code .= "\$$tmphash\{'$_'} = $var;\n$var = shift;\n";
845             }
846 0         0 $code;
847             }
848              
849             ####
850             ## ZRETURN - End a function *once*
851              
852             sub ZRETURN ($) {
853 0 0   0 0 0 die "Not in a function in ZRETURN" unless ($infun--);
854 0         0 my ($code, $var) = &makeexp($_[0]);
855             # Pull out of scope vars from the stack
856              
857 0         0 foreach (@tmpvars) {
858 0         0 my $obj = new Language::Mumps::var;
859 0         0 $obj->name($_);
860 0         0 my $var = $obj->lval;
861 0         0 $code .= "$var =\$$tmphash\{'$_'}\n";
862             }
863 0         0 $code . "return $var;\n}";
864             }
865              
866             ################################################################
867             ## Utility functions - parsing ##
868             ################################################################
869             ## Three parameters by reference - ##
870             ## 0 - Line of code - parsed tokens are removed ##
871             ## 1 - depth of parsing - arrays have indexes, functions have ##
872             ## parameters, etc. Used for scoping. ##
873             ## 2 - Number of right paranthesis expected ##
874             ################################################################
875              
876             ####
877             ## makevar - "Eat" a reference to a variable
878             ## This can be a variable identifier, a function identifier,
879             ## or a reference to a disk stored array
880              
881             sub makevar ($) {
882 0     0 0 0 my ($a, $b) = (0, 0);
883 0         0 makevar2($_[0], $a, $b);
884             }
885              
886             sub makevar2 ($$) {
887 0     0 0 0 my ($code, $obj, $val, $var, $isfun, $extra);
888             ## Advance scope
889 0         0 ++$_[1];
890             ## Variables beginning with '$' are functions
891              
892 0 0       0 if ($_[0] =~ s/^\$//) {
    0          
    0          
893             # Function - skip the $
894 0         0 $obj = new Language::Mumps::Func;
895 0         0 $isfun = 1;
896             # Tolerate double $ - Perl function calls
897 0         0 $extra = '$';
898             } elsif ($_[0] =~ s/^\^//) {
899             # Arrays beginning with ^ are actually stored on disk
900 0         0 $obj = new Language::Mumps::Database;
901             } elsif ($_[0] =~ s/^\&//) {
902             # Variables preceded by & are simply perl vars with the
903             # corresponding name
904 0         0 $obj = new Language::Mumps::Freevar;
905             } else {
906             # Regular variables. % is a valid leading char and not skipped
907 0         0 $extra = '%';
908 0         0 $_[0] =~ s/^\@//;
909 0         0 $obj = new Language::Mumps::Var;
910             }
911 0 0       0 die "Illegal array name" unless ($_[0] =~ /^[a-z$extra]/i);
912             # Remove alphanumeric token
913 0         0 $_[0] =~ s/^([a-z$extra]\w*)//i;
914 0         0 my $alias = $1;
915             # Resolve function aliases
916 0 0 0     0 $alias = $FUNCTIONS{uc($alias)} || $alias if ($isfun);
917 0         0 my $this;
918             # If we have opening paranthesis - awaiting array indices or function
919             # parameters
920 0 0       0 if ($_[0] =~ s/^\(//) {
    0          
921 0 0       0 unless ($isfun) {
922             # Array indices arriving
923             # Call makelist2 - scope to be increased - paranthesis counter
924             # increased
925             # Add the code to produce the list.
926 0         0 ($code, $var) = &makelist2($_[0], $_[1], $_[2] + 1);
927 0 0       0 die "No closing brackets" unless ($_[0] =~ /^\)/);
928 0         0 goto regular;
929             }
930             # This must be a function
931 0 0       0 if ($alias =~ s/^(\$)//) {
932             # If it is a Perl function call, convert the Function
933             # object to a Primitive object "partisanically"
934             # Construct the parameter list
935 0         0 ($code, $var) = &makelist2($_[0], $_[1], $_[2] + 1);
936 0         0 bless $obj, 'Language::Mumps::Primitive';
937 0         0 goto regular;
938             }
939             # This is an M function, therefore case insensitive
940 0         0 $alias =~ tr/a-z/A-Z/;
941             # Lookup the function
942 0         0 my $opt = $FUNS{$alias};
943 0 0       0 die "Illegal function $alias" unless (@$opt);
944 0         0 my $line;
945             # Check all the calling conventions of the function, to find
946             # if any of the prototypes match
947 0         0 foreach (@$opt) {
948             # Extract the prototype, copy the code line
949 0         0 $line = $_[0];
950 0         0 $@ = undef;
951 0         0 $obj->prot($_->{'prot'});
952             # Call makelist2 with the extra parameter defining the prototype
953             # $line is passed by reference
954 0         0 eval {
955 0         0 ($code, $var) = &makelist2($line, $_[1], $_[2] + 1,
956             $obj->prot);
957             # makelist2 might raise an exception. This die can as well
958 0 0       0 die "No closing brackets" unless ($line =~ /^\)/);
959             };
960             # If there were no exceptions, this prototype match
961 0 0       0 goto success unless ($@);
962             }
963             # No prototype matched
964 0         0 die "Unmatched function prototype for $alias: $@";
965 0         0 success:
966             # Commit the changes to the code line
967             $_[0] = $line;
968 0         0 regular:
969             # If we were handling a regular variable, we are here
970             # Set the parameter list
971             $obj->list($var);
972 0 0       0 die "No closing brackets" unless ($_[0] =~ s/^\)//);
973             } elsif ($isfun) {
974             # If there were no paranthesis
975 0         0 $alias =~ tr/a-z/A-Z/;
976 0         0 my $opt = $FUNS{$alias};
977 0 0       0 die "Illegal function $alias" unless (@$opt);
978 0         0 my $line;
979             # Check if any of the candidate functions except empty prototypes
980 0         0 foreach (@$opt) {
981 0 0       0 goto day unless ($_->{'prot'});
982             }
983 0         0 die "Function $alias requires parameters";
984             day:
985 0         0 }
986 0         0 $obj->name($alias);
987             # Return the code and the variable object reference
988             # Call ->lval to get a Perl code to dereference it
989 0         0 ($code, $obj);
990             }
991              
992             # Parse an expression
993             # White spaces are forbidden inside expressions
994             # As M is defined - parsing is DUMB - left to right.
995              
996             sub makeexp ($) {
997 0     0 0 0 my ($a, $b) = (0, 0);
998 0         0 makeexp2($_[0], $a, $b);
999             }
1000              
1001             sub makeexp2 ($$) {
1002 0     0 0 0 my ($step);
1003 0         0 my $scope = ++$_[1];
1004 0         0 my ($result, $sum);
1005             # Allocate a Perl variable to hold the result
1006 0         0 my $var = &nextvar('$');
1007 0         0 my $negation;
1008             # Iterate over the code line
1009             # Known delimiters are colons, commas and spaces
1010 0   0     0 while ($_[0] && $_[0] !~ /^(\,|\s|\:)/) {
1011 0         0 my ($val, $code);
1012             # "Eat" one character from the code line
1013 0         0 $_[0] =~ s/^(.)//;
1014 0         0 my $ch = $1;
1015              
1016             # If we found right paranthesis
1017 0 0       0 if ($ch eq ')') {
1018             # Unget the closing paranthesis - somebody needs it
1019 0         0 $_[0] = $ch . $_[0];
1020             # Ensure we had a pending scope
1021 0 0       0 last if ($_[2]);
1022 0         0 die "Unexpected right bracket";
1023             }
1024              
1025             # Double quotes start strings
1026 0 0 0     0 if ($ch eq '"') {
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
1027 0         0 my $flag;
1028             # Iterate over the rest of the string
1029 0         0 while (1) {
1030             # "Eat one character
1031 0         0 $_[0] =~ s/^(.)//;
1032 0         0 my $ch = $1;
1033             # If this is a double quote sign, and not escaped, we've done it
1034 0 0 0     0 last if ($ch eq '"' && !$flag);
1035             # If it is a backslash and not escapes - we are escaping
1036 0 0 0     0 if ($ch eq '\\' && !$flag) {
1037 0         0 $flag = 1;
1038 0         0 next;
1039             }
1040             # If we are escaping - add a backslash. Otherwise add the character,
1041             # taking care of dollar signs and other things that might confuse Perl
1042 0 0       0 $ch = ($flag ? "\\$ch" : quotemeta($ch));
1043             # We are not escaping anymore
1044 0         0 $flag = undef;
1045             # Add to the token
1046 0         0 $val .= $ch;
1047             # We require closing double quotes
1048 0 0       0 die "Unterminated string" unless ($_[0]);
1049             }
1050             # The Perl code to emit is the string in double quotes
1051 0         0 $val = qq!"$val"!;
1052              
1053              
1054             } elsif ($ch eq '!') {
1055             # Line feed
1056 0         0 $val = qq!"\\n"!;
1057             } elsif ($ch eq '#' && !$result) {
1058              
1059             # Emit a clear screen instruction, understood by the write() function
1060 0         0 $val = qq!['cls']!;
1061             } elsif ($ch eq '?' && $result) {
1062              
1063             # ? in M can be either a binary operator or a prefix unary operator
1064             # Depending on context
1065              
1066             # Parse an M style regexp
1067 0 0       0 die "Regexp expected" unless ($_[0] =~ s/^(\S+)//);
1068             # Convert to Perl regexp
1069 0         0 $val = &makeregexp($1);
1070             # Compare the whole string to the regexp - 1 or undef
1071 0         0 $result .= "$var = ($var =~ /^$val\$/);\n";
1072 0         0 $sum = undef;
1073 0         0 next;
1074             } elsif ($ch eq '?') {
1075              
1076             # Tab instruction
1077 0         0 my $var;
1078 0         0 ($code, $var) = makeexp($_[0]);
1079 0         0 $val = qq!['tab', $var]!;
1080             } elsif ($ch =~ /[0-9\.]/) {
1081              
1082             # A number
1083 0         0 my ($exp, $dot);
1084 0         0 $val = $ch;
1085             # Iterate over rest of string, while finding numeric chars
1086 0         0 while ($_[0] =~ s/^(\d+|\.|E)//i) {
1087 0         0 my $ch = $1;
1088 0 0       0 if ($ch eq '.') {
1089             # Dot only once
1090 0         0 $dot++;
1091 0 0 0     0 die "Illegal number" if ($dot > 1 || $exp);
1092             }
1093 0 0       0 if (uc($ch) eq 'E') {
1094             # Exp only once
1095 0         0 $exp++;
1096 0 0       0 die "Illegal number" if ($exp > 1);
1097             }
1098             # Add chars
1099 0         0 $val .= $ch;
1100             }
1101             # Must end in a digit
1102 0 0       0 die "Illegal number" unless ($val =~ /\d$/);
1103             } elsif ($ch =~ /[a-z\$\^\@\%\&]/i) {
1104              
1105             # Seems like a variable
1106             # Unget the char
1107 0         0 $_[0] = $ch . $_[0];
1108             # Get the var using makevar
1109 0         0 ($code, $val) = &makevar2($_[0], $_[1], $_[2]);
1110             # Get the code to dereference the value of the var
1111 0         0 $val = $val->rval;
1112              
1113             } elsif ($ch =~ /['-]/ && ($sum || !$result)) {
1114             # Unary negation operator
1115             # Save the negation for later use
1116 0         0 $ch =~ s/'/!/;
1117 0         0 $negation = $ch;
1118 0         0 next;
1119             }
1120             # End of char switch
1121              
1122 0 0       0 if ($ch eq '(') {
1123 0         0 ($code, $val) = &makeexp2($_[0], $_[1], $_[2] + 1);
1124 0 0       0 die "No closing brackets" unless ($_[0] =~ /^\)/);
1125             }
1126              
1127             # We just passed an operand, not a binary operator
1128 0 0       0 if (defined($val)) {
1129             # Generate assignment
1130 0         0 $result .= $code;
1131 0         0 $result .= "$var = $negation$val;\n";
1132             # Include prepared computation, if any (See below)
1133 0 0       0 $result .= "$sum\n" if ($sum);
1134             # Clear computation and negation registers
1135 0         0 $sum = undef;
1136 0         0 $negation = undef;
1137 0         0 next;
1138             }
1139              
1140             # If we had a binary operator but found no right operand
1141 0 0       0 die "Right operand expected" if ($sum);
1142              
1143             # We are expecting an operator now
1144              
1145             # Allocate a new variable
1146 0         0 my $oldvar = $var;
1147 0         0 $var = &nextvar('$');
1148 0         0 my $qch = quotemeta($ch);
1149             # Handle basic operators
1150 0 0       0 if ("+-*/!&_#" =~ /$qch/) {
1151 0         0 $ch =~ s/\!/||/; # ! means OR in M
1152 0         0 $ch =~ s/\&/&&/;
1153 0         0 $ch =~ s/_/./; # _ is string concatenation
1154 0         0 $ch =~ s/#/%/; # '#' is modulu
1155 0         0 $sum = "$var $ch= $oldvar;"; # Prepare implied increment
1156             }
1157 0 0       0 if ($ch eq "'") {
1158             # This is a negation
1159 0 0       0 if ($_[0] =~ /^\=\<\>/) {
1160 0         0 $_[0] =~ s/^(.)//;
1161 0         0 $ch = "* -1 $1"; # $qch does not change
1162             }
1163             }
1164 0 0       0 if ("=<>" =~ /$qch/) {
1165 0         0 $ch =~ s/\=/==/;
1166 0         0 $sum = "$var = ($oldvar <=> $var) || ($var cmp $oldvar);\n" .
1167             "$var = ($var $ch 0);";
1168             }
1169 0 0       0 if ($ch =~ /\[\]/) {
1170             # $oldvar contains $var
1171 0         0 my ($s1, $s2) = ($var, $oldvar);
1172 0 0       0 ($s2, $s1) = ($var, $oldvar) if ($ch eq '[');
1173 0         0 $sum = "$s2 = quotemeta($s2);\n$var = (($s1 =~ /$s2/) ? 1 : undef);";
1174             }
1175 0 0       0 die "Parse error on $ch" unless ($sum)
1176             }
1177 0 0       0 die "Right operand expected" if ($sum);
1178 0 0 0     0 die "Right bracket expacted $_[2] $_[0]" if ($_[2] && $_[0] =~ /^\s/);
1179 0         0 ("$result", $var);
1180             }
1181              
1182             ####
1183             ## Parse a list, with optional prototype
1184              
1185             sub makelist ($) {
1186 0     0 0 0 my ($a, $b) = (0, 0);
1187 0         0 makelist2($_[0], $a, $b, $_[1]);
1188             }
1189              
1190             sub makelist2 ($$) {
1191 0     0 0 0 my ($step);
1192 0         0 my $scope = ++$_[1];
1193 0         0 my ($result, $sum);
1194             # Allocate a variable to store the list
1195              
1196 0         0 my $var = &nextvar('@');
1197              
1198             # Allocate a label, used for tuple parsing
1199              
1200 0         0 my $lbl = "__lbl_$var";
1201              
1202 0         0 my $i;
1203 0         0 my $first = 1;
1204              
1205             # Generate code to create empty list
1206              
1207 0         0 $result = "$var = ();\n";
1208              
1209             # Optional prototype parameter
1210              
1211 0         0 my $proto = $_[3];
1212 0   0     0 while ($_[0] && $_[0] !~ /^\s/) {
1213             # Iterate on code line
1214              
1215             # Force comme unless first
1216              
1217 0 0 0     0 die "Comma expected" unless ($first || $_[0] =~ s/^,//);
1218              
1219             # If we had a prorotype, used it up, but there still is input - it's
1220             # a mismatch
1221              
1222 0 0 0     0 die "Parameter mismatch" if ($_[3] && !$proto);
1223 0         0 my $typ;
1224              
1225             # Fetch one prototype char
1226              
1227 0 0       0 $typ = $1 if ($proto =~ s/^(.)//);
1228 0 0       0 $proto = 'L' if ($typ eq 'L'); # Nothing to validate in a plain
1229             # list, but must keep $proto
1230             # unepmty
1231 0 0       0 $proto = 'T' if ($typ eq 'T'); # Tuples are length unlimited
1232 0         0 $typ =~ s/[IL]//; # Nothing to validate in a plain input field
1233              
1234             # Define handlers to prototypes
1235              
1236 0     0   0 my %procs = (
1237             ## Unprototyped field - call makeexp2 to fetch data
1238             "", sub($$) {&makeexp2($_[0], $_[1], $_[2])},
1239             ## Output field - get variable signature as a second parameter
1240             "O", sub ($$) {
1241 0     0   0 my ($code, $var) = &makevar2($_[0], $_[1], $_[2]);
1242 0         0 ($code, $var->sig);},
1243             ## Tuples - add a finish condition every candidate
1244 0     0   0 "T", sub ($$) {my ($code, $var2) = &maketuple2($_[0],
1245             $_[1], $_[2], 2, ":");
1246 0         0 my ($cond, $res) = @$var2;
1247 0         0 ("$code $var = ($res);\ngoto $lbl if ($cond);", "undef");
1248             },
1249             ## Source anchor - Line number, Label, or Label + Line number
1250             "S", sub ($$) {
1251 0 0   0   0 die "Source anchor expected" unless
1252             ($_[0] =~ s/^(\d+|(?:[a-z]\w*)?\+\d+|[a-z]\w*)//i);
1253 0         0 my ($lbl, $off) = split(/\+/, $1);
1254 0         0 $off *= 1;
1255 0         0 my $var = &nextvar('$');
1256 0         0 ("$var = &Language::Mumps::list('$lbl', $off);\n", $var);}
1257 0         0 );
1258              
1259             # Call the corresponding function
1260              
1261 0         0 my ($code, $val) = &{$procs{$typ}}($_[0], $_[1], $_[2]);
  0         0  
1262              
1263             # Generate code to add to list
1264 0         0 $result .= $code . "push($var, $val);\n";
1265 0         0 ++$i;
1266 0         0 $first = undef;
1267 0 0       0 if ($_[0] =~ /^\)/) {
1268 0 0       0 last if ($_[2]);
1269 0         0 die "Unexpected right bracket";
1270             }
1271             }
1272             # Add finish label for tuples
1273              
1274 0 0       0 $result .= "$lbl: " if ($proto eq 'T');
1275 0 0       0 die "Expected right operand" if ($sum);
1276 0         0 ($result, $var, $i);
1277             }
1278              
1279             ####
1280             ## Make a tuple - a series of values and conditions to choose each
1281             ## Arguements: Code line, scopes, paranthesis, number of tokens,
1282             ## delimiter
1283              
1284             sub maketuple ($) {
1285 0     0 0 0 my ($a, $b) = (0, 0);
1286 0         0 maketuple2($_[0], $a, $b, $_[1], $_[2]);
1287             }
1288              
1289             sub maketuple2 ($$) {
1290 0     0 0 0 my ($done, $result);
1291 0         0 ++$_[1];
1292 0         0 my @ary;
1293 0         0 my $first = 1;
1294 0         0 my $delim = quotemeta($_[4]);
1295 0         0 foreach (1 .. $_[3]) {
1296             # Count times, expect delmiters
1297 0 0 0     0 die "$_[4] expected" unless ($first || $_[0] =~ s/^$delim//);
1298 0         0 $first = undef;
1299             # Get expression
1300 0         0 my ($code, $var) = &makeexp2($_[0], $_[1], $_[2]);
1301 0         0 my $save = &nextvar('$');
1302 0         0 $result .= $code . "$save = $var;\n";
1303 0         0 push(@ary, $var);
1304             }
1305             # Return a compile time list of referrences to tuple members
1306 0         0 ($result, \@ary);
1307             }
1308              
1309             #####
1310             ## Make regexp
1311              
1312             # Map M meta chars to perl regexps
1313              
1314             %RES = qw(A [a-zA-Z]
1315             C [\x0-\x1F0xFF]
1316             E [\x0-\x7F]
1317             H [\xE0-\xFA]
1318             L [a-z]
1319             N \d
1320             U [A-Z]);
1321             # Prepare an ascii string of all non alphanumeric characters
1322             # in between a white space and lower case 'a'
1323             # Which is M's definition for P
1324              
1325             my $s = pack("C*", (ord(' ') + 1 .. ord('a') - 1));
1326             $s =~ tr/a-z0-9A-Z//;
1327             $RES{'P'} = '[' . quotemeta($s) . ']';
1328             $RESKEYS = join("", keys %RES);
1329              
1330             sub makeregexp {
1331 0     0 0 0 my $result;
1332 0         0 my $src = shift;
1333 0         0 while ($src) {
1334             # Iterate over string
1335 0 0       0 if ($src =~ s/^([$RESKEYS])//) {
    0          
1336             # Is it a meta char?
1337 0         0 $result .= $RES{$1};
1338             } elsif ($src =~ s/^".*?"//) {
1339             # Did we just find a literal?
1340 0         0 $result .= quotemeta($1);
1341             } else {
1342             # Unrecognized
1343 0         0 die "Invalid REGEXP char: " . substr($src, 0, 1);
1344             }
1345              
1346             # These are only after recognized tokens
1347              
1348             # Dot - 1 to many
1349 0 0       0 if ($src =~ s/\.//) {
1350 0         0 $result .= '+';
1351             }
1352             # Number - times
1353 0 0       0 if ($src =~ s/^(\d+)//) {
1354 0         0 $result .= "{$1}";
1355             }
1356             }
1357 0         0 $result;
1358             }
1359              
1360             ####
1361             ## Manufacture a temporary var (register)
1362              
1363             sub nextvar {
1364 0     0 0 0 my $pre = shift;
1365 0         0 $varstack++;
1366 0         0 my $sc = "_" x $scopes;
1367 0         0 "$pre$sc\__tmp$varstack";
1368             }
1369              
1370             ####
1371             ## Reuse varnames after each statement, in order not to overpopulate
1372             ## symbol table
1373              
1374             sub resetvars {
1375 0     0 0 0 $varstack = 0;
1376             }
1377              
1378             #####################################################################
1379             ## Runtime utilities ##
1380             #####################################################################
1381              
1382             ####
1383             ## Load Curses module *once* upon request
1384              
1385             sub curse {
1386 0     0 0 0 require Curses;
1387 0 0       0 return undef unless (*Curses::new{CODE});
1388 0 0       0 Curses::initscr() unless ($curses_inside++);
1389 0         0 1;
1390             }
1391              
1392             ####
1393             ## Clear screen or send form feed
1394              
1395             sub cls {
1396 0 0   0 0 0 if ($Language::Mumps::selected_io == 5) {
1397 0         0 &curse;
1398 0         0 Curses::clear();
1399             } else {
1400 0         0 &write("\l");
1401             }
1402 0         0 ($xpos, $ypos) = (0, 0);
1403             }
1404              
1405             ####
1406             ## Read a char from the keyboard
1407              
1408             sub readkey {
1409 0     0 0 0 &curse;
1410 0         0 Curses::getch();
1411             }
1412              
1413             ####
1414             ## Buffered input
1415              
1416             sub read {
1417             # Choose file number - 5 is STDIO
1418 0 0   0 0 0 my $file = ($selected_io == 5) ? \*STDIN : $handlers[$selected_io];
1419 0         0 my $s = scalar(<$file>);
1420 0         0 chomp $s;
1421 0         0 $xpos = 0;
1422 0         0 $ypos++;
1423 0         0 $s;
1424             }
1425              
1426             ####
1427             ## Output
1428              
1429             sub write {
1430             # Choose file number - 5 is STDIO
1431 0 0   0 0 0 my $file = ($selected_io == 5) ? \*STDOUT : $handlers[$selected_io];
1432 0         0 my $item = shift;
1433             # Do nothing for an empty string
1434 0 0       0 return unless (defined($item));
1435 0 0       0 if (UNIVERSAL::isa($item, 'ARRAY')) {
1436 0 0       0 if ($item->[0] eq 'cls') {
1437 0         0 &cls;
1438 0         0 next;
1439             }
1440 0 0       0 if ($item->[0] eq 'tab') {
1441 0         0 &tab($item->[1]);
1442 0         0 next;
1443             }
1444             }
1445             # Split to lines
1446 0 0       0 my @frags = ($item eq "\n" ? ('', '') : split(/\n/, $item));
1447 0         0 my $i;
1448             # Iterate over lines
1449 0         0 foreach (@frags) {
1450             # Print line
1451 0         0 print $file $_;
1452             # Increase xpos
1453 0         0 $xpos = ($xpos + length($_));
1454             # Advance line counter
1455 0 0       0 if (++$i < @frags) {
1456 0         0 print $file "\n";
1457 0         0 $xpos = 0;
1458 0         0 $ypos++;
1459             }
1460             }
1461             }
1462              
1463             ####
1464             ## Tab the basic style
1465              
1466             sub tab {
1467 0     0 0 0 my $to = shift;
1468             # Are we past the tab point?
1469 0 0       0 &write("\n") if ($xpos > $to);
1470 0         0 my $dist = $to - $xpos;
1471 0         0 &write(' ' x $dist);
1472             }
1473              
1474             ##################################################################
1475             ## Class loader ##
1476             ##################################################################
1477             ## Users of the class should import both the serializer and the ##
1478             ## flat file database engine in order to use disk stored arrays ##
1479             ## Compiled programs should import Runtime to initialize ##
1480             ##################################################################
1481              
1482             sub import {
1483 1     1   9 my $class = shift;
1484 1         2 my $state;
1485              
1486 1         3 foreach $state (@_) {
1487 0 0 0     0 if ($state eq "Runtime") {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1488             # Runtime initialize
1489 0         0 tie %symbols, 'Language::Mumps::Tree';
1490 0         0 tie %dbs, 'Language::Mumps::Forest';
1491 0         0 $selected_io = 5;
1492             } elsif ($state =~ /^[SNG]?DBM?_File$/) {
1493             # Prepare values to tie a DBM engine
1494 0         0 $@ = undef;
1495 0         0 eval "require $state; import $state;";
1496 0 0       0 die $@ if ($@);
1497 0 0       0 @TYING = (O_RDWR|O_CREAT, 0644,
1498             ($state eq 'DB_File') ? ($DB_File::DB_HASH) : ());
1499 0         0 $DB = $state;
1500              
1501             # Choose a serializer
1502             } elsif ($state eq 'Data::Dumper') {
1503 0         0 $@ = undef;
1504 0         0 eval "require $state; import $state;";
1505 0 0       0 die $@ if ($@);
1506 1     1   15 $FETCH = sub {no strict; eval $_[0];};
  1     0   2  
  1         142  
  0         0  
  0         0  
1507 0         0 $STORE = \&Data::Dumper::Dumper;
1508 0         0 $SER = $state;
1509             } elsif ($state eq 'Data::Dump') {
1510 0         0 $@ = undef;
1511 0         0 eval "require $state; import $state;";
1512 0 0       0 die $@ if ($@);
1513 0         0 $STORE = \&Data::Dump::dump;
1514 1     1   7 $FETCH = sub {no strict; eval $_[0];};
  1     0   2  
  1         1861  
  0         0  
  0         0  
1515 0         0 $SER = $state;
1516             } elsif ($state eq 'FreezeThaw' || $state eq 'Storable') {
1517 0         0 $@ = undef;
1518 0         0 eval "require $state; import $state;";
1519 0 0       0 die $@ if ($@);
1520 0         0 $FETCH = \&{"$SER\::thaw"};
  0         0  
1521 0         0 $STORE = \&{"$SER\::freeze"};
  0         0  
1522 0         0 $SER = $state;
1523             } elsif ($state eq 'XML::Dumper') {
1524 0         0 $@ = undef;
1525 0         0 eval "require XML::Parser; import XML::Parser;";
1526 0         0 eval "require XML::Dumper; import XML::Dumper;";
1527 0 0       0 die $@ if ($@);
1528 0         0 $Language::Mumps::Pool::XML = new XML::Dumper;
1529             $FETCH = sub {
1530 0     0   0 my $xml = shift;
1531 0 0       0 return undef unless ($xml);
1532 0         0 my $parser = new XML::Parser(Style => 'Tree');
1533 0         0 my $tree = $parser->parse($xml);
1534 0         0 $Language::Mumps::Pool::XML->xml2pl($tree); };
  0         0  
1535 0     0   0 $STORE = sub { $Language::Mumps::Pool::XML->pl2xml(shift); };
  0         0  
1536 0         0 $SER = $state;
1537             } elsif ($state eq 'Data::DumpXML') {
1538 0         0 $@ = undef;
1539 0         0 eval "require Data::DumpXML; import Data::DumpXML;";
1540 0         0 eval "require Data::DumpXML::Parser; import Data::DumpXML::Parser;";
1541 0         0 $Language::Mumps::Pool::XML = Data::DumpXML::Parser->new();
1542 0 0       0 die $@ if ($@);
1543 0         0 $STORE = \&Data::DumpXML::dump_xml;
1544 0     0   0 $FETCH = sub { $Language::Mumps::Pool::XML->parse(@_); };
  0         0  
1545 0         0 $SER = $state;
1546             # Read configuration file
1547             } elsif ($state eq 'Config') {
1548 0 0       0 require "/etc/pmumps.cf" if (-f "/etc/pmumps.cf");
1549 0 0       0 require "~/.pmumps" if (-f "~/.pmumps");
1550             # Variables received from configuration file, call import again
1551 0         0 import Language::Mumps ($DB, $SER);
1552             } else {
1553             # Error
1554 0         0 die "Unrecognized option $state";
1555             }
1556             }
1557             # Save DBM and serializer choice
1558 1         3 $IMPORT = join(" ", grep /./, grep {defined}($DB, $SER));
  2         1888  
1559             }
1560              
1561             ####
1562             ## Return a tied hash to a named disk stored array
1563              
1564             sub dbs {
1565 0     0 0   my $db = shift;
1566             # Qualified database name
1567 0           my $dbt = "Language::Mumps::DB::_$db";
1568             # Qualified tree name
1569 0           my $dbf = "Language::Mumps::DB::Back::_$db";;
1570             # Create database directory
1571 0 0         unless (-d "global") {
1572 0           mkdir "global", 0755 || die "Can't create global/: $!";
1573             }
1574             # Ensure DBM engine was selected
1575 0 0         die "You must configure database storage" unless ($DB);
1576             # Tie the database flat hash
1577 0 0         tie(%$dbf, $DB, "global/$db.db", @TYING) || die "DB: $!";
1578             # Tie the tree hash
1579 0           my $t = tie %$dbt, 'Language::Mumps::Tree', \%$dbf, $FETCH,
1580             $STORE;
1581             # Returned the tied hash
1582 0           \%$dbt;
1583             }
1584              
1585             ####
1586             ## Deep copy a tree/subtree to another tree/subtree
1587              
1588             sub moveimage {
1589 0     0 0   my ($src, $dst, $key) = @_;
1590 0           $dst->{$key} = $src->{$key};
1591 0           my $t = tied(%$src);
1592 0           my @children = $t->query($key);
1593 0           foreach (@children) {
1594 0           &moveimage($src, $dst, "$key\0$_");
1595             }
1596             }
1597              
1598             ######################################################################
1599              
1600             package Language::Mumps::Tree;
1601              
1602             #######################################################
1603             ## Tied hash holding a tree. ##
1604             #######################################################
1605             ## Possible storing and fetching in a flat hash tied ##
1606             ## to a database. ##
1607             #######################################################
1608             ## The list of access keys is joined with char #0 to ##
1609             ## form the relevant key in the flat hash. ##
1610             ## Each node has its children list attached. ##
1611             #######################################################
1612              
1613              
1614             ####
1615             ## Destroy the tree
1616              
1617             sub CLEAR {
1618 0     0     my $self = shift;
1619 0           my $hash = $self->{'hash'};
1620 0           %$hash = ();
1621             }
1622              
1623             ####
1624             ## Store a value in the tree
1625              
1626             sub STORE {
1627 0     0     my ($self, $key, $val) = @_;
1628 0           my $hash = $self->{'hash'};
1629 0           my $store = $self->{'store'};
1630 0           my $fetch = $self->{'fetch'};
1631             # Split the access keys
1632 0           my @tokens = split(/\0/, $key);
1633 0           my @addr;
1634             my $addr; # Pointer points to root
1635             # Verify the path exists
1636 0           do {
1637             # Fetch one token
1638 0           my $this = shift @tokens;
1639 0           my $flag; # Flag is non zero only if something new
1640             # needed to be created
1641             # Release the structure stored in the current pointer
1642             # If none there, $flags increases, and an empty hash returned
1643 0   0       my $base = &$fetch($hash->{$addr}) || ++$flag && {};
1644             # Ensure the existence of the metadata hash
1645 0   0       $base->{'metadata'} ||= ++$flag && {};
      0        
1646             # Ensure the next node is marked used
1647 0   0       $base->{'metadata'}->{$this} ||= ++$flag;
1648 0 0         $hash->{$addr} = &$store($base) if ($flag);
1649             # Advance the pointer
1650 0           push(@addr, $this);
1651 0           $addr = join("\0", @addr);
1652             } while (@tokens);
1653             # Iterate until all path ensured
1654              
1655 0           my $flag;
1656             # Fetch the data
1657 0   0       my $base = &$fetch($hash->{$addr}) || ++$flag && {};
1658 0 0 0       ($base->{'data'} eq $val) || ++$flag && ($base->{'data'} = $val);
1659             # Do not update storage unless value changed, to save time with
1660             # DBM implemented storage
1661 0 0         $hash->{$addr} = &$store($base) if ($flag);
1662             }
1663              
1664             ####
1665             ## Fetch a value from the tree
1666              
1667             sub FETCH {
1668 0     0     my ($self, $key) = @_;
1669 0           my $hash = $self->{'hash'};
1670 0           my $fetch = $self->{'fetch'};
1671             ## Fetch the structure
1672 0 0         return undef unless ($hash->{$key});
1673 0   0       my $base = &$fetch($hash->{$key}) || {};
1674             ## Extract the data element
1675 0           $base->{'data'};
1676             }
1677              
1678             ####
1679             ## Does a node exist?
1680              
1681             sub EXISTS {
1682 0     0     my ($self, $key) = @_;
1683 0           my $hash = $self->{'hash'};
1684 0           my $fetch = $self->{'fetch'};
1685 0 0         return undef unless ($hash->{$key});
1686 0   0       my $base = &$fetch($hash->{$key}) || {};
1687 0           (exists $base->{'data'});
1688             }
1689              
1690             ####
1691             ## Return the children list for a node
1692              
1693             sub query {
1694 0     0     my ($self, $key) = @_;
1695 0           my $hash = $self->{'hash'};
1696 0           my $store = $self->{'store'};
1697 0           my $fetch = $self->{'fetch'};
1698 0   0       my $base = &$fetch($hash->{$key}) || {};
1699 0           keys %{$base->{'metadata'}};
  0            
1700             }
1701              
1702             ####
1703             ## Delete a node
1704              
1705             sub DELETE {
1706 0     0     my ($self, $key) = @_;
1707 0           my $hash = $self->{'hash'};
1708 0           my $store = $self->{'store'};
1709 0           my $fetch = $self->{'fetch'};
1710 0   0       my $base = &$fetch($hash->{$key}) || {};
1711 0           foreach (keys %{$base->{'metadata'}}) {
  0            
1712 0           $self->DELETE("$key\0$_");
1713             }
1714 0           delete $hash->{$key};
1715 0 0         unless ($key =~ s/\0([^\0]*)$//) {
1716 0           $key =~ s/^(.*)$//;
1717             }
1718 0           delete $hash->{$key}->{'metadata'}->{$1};
1719             }
1720              
1721             ####
1722             ## Return a flat hash with all the structures deserialized
1723             ## This is needed to implement keys and values functions
1724              
1725             sub extrapolate {
1726 0     0     my ($self, $key) = @_;
1727 0           my @sons = $self->query($key);
1728 0           my %recur = map {$self->extrapolate($_);} @sons;
  0            
1729 0 0         $recur{$key} = $self->FETCH($key) if ($self->EXISTS($key));
1730 0           %recur;
1731             }
1732              
1733             ####
1734             ## Return the first pair of the tree
1735              
1736             sub FIRSTKEY {
1737 0     0     my $self = shift;
1738 0           $self->{'keys'} = {$self->extrapolate("")};
1739 0           $self->NEXTKEY;
1740             }
1741              
1742             ####
1743             ## Return the next one
1744              
1745             sub NEXTKEY {
1746 0     0     my ($self, $lastkey) = @_;
1747 0           each %{$self->{'keys'}};
  0            
1748             }
1749              
1750             ####
1751             ## Tie a hash to the class
1752             ## Default serializing and deserializing functions are equality
1753             ## functions, that do not change the values, for memory arrays
1754             ## A hash is tied with the storage hash, fetch function and
1755             ## stroage function.
1756              
1757             sub TIEHASH {
1758 0     0     my ($class, $hash, $fetch, $store) = @_;
1759 0   0 0     $fetch ||= sub {$_[0];};
  0            
1760 0   0 0     $store ||= sub {$_[0];};
  0            
1761 0   0       $hash ||= {};
1762 0           my $self = {'hash' => $hash, 'store' => $store, 'fetch' => $fetch};
1763 0           bless $self, $class;
1764             }
1765             ##################################################################
1766              
1767             package Language::Mumps::Entity;
1768              
1769             ##################################################
1770             ## Base class for variable and function classes ##
1771             ##################################################
1772              
1773             ####
1774             ## Trivial constructor
1775              
1776             sub new {
1777 0     0     bless {}, shift;
1778             }
1779              
1780             ####
1781             ## Return whether names in the class of an object are case sensitive
1782              
1783             sub case {
1784 0     0     my $class = ref(shift);
1785 0           ${$class . "::CASE"};
  0            
1786             }
1787              
1788             ####
1789             ## Set or get the entity name
1790              
1791             sub name {
1792 0     0     my $self = shift;
1793 0 0         $self->{'name'} = shift if (@_);
1794 0 0         $self->case ? $self->{'name'} : uc($self->{'name'});
1795             }
1796              
1797             ####
1798             ## Set or get the list of parameters or indices for an entity
1799              
1800             sub list {
1801 0     0     my $self = shift;
1802 0 0         $self->{'list'} = shift if (@_);
1803 0 0         $self->{'list'} || '()';
1804             }
1805              
1806             ####
1807             ## Check if the entity has parameters or indices
1808              
1809             sub isatom {
1810 0     0     my $self = shift;
1811 0 0         $self->{'list'} ? undef : 1;
1812             }
1813              
1814             ####
1815             ## Return the rvalue representing an entity
1816             ## Does not equal to rval in derived classes
1817              
1818             sub rval {
1819 0     0     my $self = shift;
1820 0           $self->lval;
1821             }
1822              
1823             ####
1824             ## Return the lvalue for a variable
1825             ## By default, points to a element in a hash, holding a variable
1826              
1827             sub lval {
1828 0     0     my $self = shift;
1829 0           '${' . $self->hash . '}{' . $self->addr . '}';
1830             }
1831              
1832             ####
1833             ## Code to erase an entity
1834              
1835             sub purge {
1836 0     0     die "Abstract";
1837             }
1838              
1839             ####
1840             ## Return the hash associated with the entity
1841              
1842             sub hash {
1843 0     0     die "Abstract";
1844             }
1845              
1846             ####
1847             ## Return the key in the hash the entity is stored in
1848              
1849             sub addr {
1850 0     0     die "Abstract";
1851             }
1852              
1853             ####
1854             ## Return a tuple of the hash name and hash key
1855             ## Used mainly for providing functions runtime definitions
1856             ## of variables
1857              
1858             sub sig {
1859 0     0     my $self = shift;
1860 0           "(bless [" . $self->hash . ", " . $self->addr . "], 'varsig')";
1861             }
1862              
1863             #####################################################################
1864              
1865             package Language::Mumps::Var;
1866 1     1   6 use vars qw(@ISA);
  1         2  
  1         187  
1867             @ISA = qw(Language::Mumps::Entity);
1868              
1869             #####################################################
1870             ## An object to represent an M var in compile time ##
1871             #####################################################
1872              
1873             ####
1874             ## Erase a variable by erasing it from the symbol table
1875              
1876             sub purge {
1877 0     0     my $self = shift;
1878 0           my $list = $self->list;
1879 0           my $name = $self->name;
1880 0           "delete \$Language::Mumps::symbols{'$name', $list};";
1881             }
1882              
1883             ####
1884             ## Hash holding regular arrays
1885              
1886             sub hash {
1887 0     0     "Language::Mumps::symbols";
1888             }
1889              
1890             ####
1891             ## Address is either symbol name, or symbol name joined with the value of
1892             ## the intermediate variable containing the indices
1893              
1894             sub addr {
1895 0     0     my $self = shift;
1896 0           my $list = $self->list;
1897 0           my $name = $self->name;
1898 0 0         $self->isatom ? "'$name'" : qq!join("\\0", '$name', $list)!;
1899             }
1900              
1901             #####################################################################
1902              
1903             package Language::Mumps::Primitive;
1904 1     1   5 use vars qw(@ISA $CASE);
  1         6  
  1         113  
1905             @ISA = qw(Language::Mumps::Entity);
1906             $CASE = 1;
1907              
1908             ####################################################
1909             ## Object to represent a perl function ##
1910             ####################################################
1911              
1912             ####
1913             ## Lvalue impossible
1914              
1915             sub lval {
1916 0     0     die "Can't use functions as Lvalue";
1917             }
1918              
1919             ####
1920             ## Rvalue is calling the function
1921              
1922             sub rval {
1923 0     0     my $self = shift;
1924 0           my $name = $self->name;
1925 0           my $list = $self->list;
1926 0           "$name($list);";
1927             }
1928              
1929             #######################################################################
1930              
1931             package Language::Mumps::Database;
1932 1     1   10 use vars qw(@ISA);
  1         3  
  1         209  
1933             @ISA = qw(Language::Mumps::Entity);
1934              
1935             ###########################################################
1936             ## Object to represent a variable in a disk stored array ##
1937             ###########################################################
1938              
1939              
1940             ####
1941             ## Deleteion will be realized using DELETE in the tied hash
1942              
1943             sub purge {
1944 0     0     my $self = shift;
1945 0           my $list = $self->list;
1946 0           my $name = $self->name;
1947 0           "delete \$Language::Mumps::dbs{'$name'}->{$list}";
1948             }
1949              
1950             ####
1951             ## Local method to return code to dereference the DBM object (not tied
1952             ## hash) tied to the array
1953             ## Used in the LOCK function
1954              
1955             sub getdb {
1956 0     0     my $self = shift;
1957 0           my $name = $self->name;
1958 0           "tied(\%{tied(\$Language::Mumps::dbs{'$name'})->{'hash'}})";
1959             }
1960              
1961             ####
1962             ## Return the tree attached to the var
1963              
1964             sub hash {
1965 0     0     my $self = shift;
1966 0           my $name = $self->name;
1967 0           "\$Language::Mumps::dbs{'$name'}";
1968             }
1969              
1970             ####
1971             ## Return the access key to the Tree hash
1972              
1973             sub addr {
1974 0     0     my $self = shift;
1975 0           my $list = $self->list;
1976 0           qq!join("\\0", $list)!;
1977             }
1978              
1979             ####################################################################
1980              
1981             package Language::Mumps::Freevar;
1982 1     1   6 use vars qw(@ISA $CASE);
  1         2  
  1         180  
1983             @ISA = qw(Language::Mumps::Entity);
1984             $CASE = 1;
1985              
1986             ####################################################
1987             ## Object representing a raw perl var ##
1988             ####################################################
1989              
1990              
1991             ####
1992             ## Lvalue is a scalar if no keys, otherwise hash with a key
1993              
1994             sub lval {
1995 0     0     my $self = shift;
1996 0           my $name = $self->name;
1997 0 0         $self->isatom ? "\$$name" : $self->SUPER::lval;
1998             }
1999              
2000             ####
2001             ## Hash name is raw
2002              
2003             sub hash {
2004 0     0     my $self = shift;
2005 0           $self->name;
2006             }
2007              
2008             ####
2009             ## Joined keys, supported in perl notation as well
2010              
2011             sub addr {
2012 0     0     my $self = shift;
2013 0           my $list = $self->list;
2014 0           qq!join("\\0", $list)!;
2015             }
2016              
2017             ####################################################################
2018              
2019             package Language::Mumps::Func;
2020 1     1   14 use vars qw(@ISA @zwi_tokens);
  1         1  
  1         3388  
2021             @ISA = qw(Language::Mumps::Entity);
2022              
2023             ############################################
2024             ## Object to represent an M function call ##
2025             ############################################
2026              
2027             ####
2028             ## Set or get the prototype
2029              
2030             sub prot {
2031 0     0     my $self = shift;
2032 0 0         $self->{'prot'} = shift if (@_);
2033 0           $self->{'prot'};
2034             }
2035              
2036             ####
2037             ## Return Lvalue if applicable
2038              
2039             sub lval {
2040 0     0     my $self = shift;
2041 0           my $name = $self->name;
2042 0           my $prot = $self->prot;
2043 0           my $opt = $Language::Mumps::FUNS{$name};
2044 0           my $rec;
2045             # Search for the metadata entry fitting the choosed prototype
2046              
2047 0           foreach $rec (@$opt) {
2048 0 0         last if ($rec->{'prot'} eq $prot);
2049             }
2050 0 0         die "Lvalue unavailable for function $name" unless ($rec->{'lval'});
2051             # Call the local function to return the Lvalue for this function
2052 0           &{"l_$name"}($self);
  0            
2053             }
2054              
2055             ####
2056             ## Rvalue - generate code to call the runtime function
2057              
2058             sub rval {
2059 0     0     my $self = shift;
2060 0           my $name = $self->name;
2061 0           my $list = $self->list;
2062 0           "&Language::Mumps::Func::$name($list)";
2063             }
2064              
2065             ####
2066             ## $ASCII(string, position = 1) - return ASCII of one char (1 based)
2067              
2068             sub ASCII {
2069 0     0     my ($str, $pos) = @_;
2070 0   0       $pos -= ($pos && 1);
2071 0           my $ch = substr($str, $pos, 1);
2072 0 0         $ch ? -1 : ord($ch);
2073             }
2074              
2075             ####
2076             ## $CHAR(list) Convert ASCII codes to string
2077              
2078             sub CHAR {
2079 0     0     pack("C*", @_);
2080             }
2081              
2082             ####
2083             ## $DATA(array(index,index...))
2084             ## Left digit - does it have children? Right digit - does it exist?
2085              
2086             sub DATA {
2087 0     0     my ($hash, $addr) = @{$_[0]};
  0            
2088 0           my $d0 = defined($hash->{$addr});
2089 0           my $d1 = scalar(tied(%$hash)->query($addr));
2090 0           $d1 * 10 + $d0;
2091             }
2092              
2093             ####
2094             ## $EXTRACT(string, from, to = from) - substring, 1 based locations
2095              
2096             sub EXTRACT {
2097 0     0     my ($str, $from, $to) = @_;
2098 0   0       $to ||= $from;
2099 0           substr($str, $from - 1, $to - $from + 1);
2100             }
2101              
2102             ####
2103             ## $FIND(long string, short string, start = 1) - find substring
2104              
2105             sub FIND {
2106 0     0     my ($str, $sub, $pos) = @_;
2107 0   0       $pos -= ($pos && 1);
2108 0           index($str, $sub, $pos);
2109             }
2110              
2111             ####
2112             ## $HOROLOG - Sailor time function (Works for Y2K, will not work after
2113             ## 2100)
2114              
2115             sub HOROLOG {
2116 0     0     my $years = 1970 - 1841;
2117 0           my $leaps = int($years / 4) - 1;
2118 0           my $distance = 1 + 365 * $years + $leaps;
2119 0           my $now = time;
2120 0           my @here = localtime($now);
2121 0           my @gmt = gmtime($now);
2122 0           my $here = $here[1] + 60 * $here[2];
2123 0           my $gmt = $gmt[1] + 60 * $gmt[2];
2124 0           my $offset = 60 * ($here - $gmt);
2125 0           my $there = $now + $offset;
2126 0           my $n1 = int($there / 3600 / 24) + $distance;
2127 0           my $n2 = $gmt * 60 + $gmt[0];
2128 0           "$n1,$n2";
2129             }
2130              
2131             ####
2132             ## $IO - Currently selected IO channel
2133              
2134             sub IO {
2135 0     0     $Language::Mumps::selected_io;
2136             }
2137              
2138             sub l_IO {
2139 0     0     '$Language::Mumps::selected_io';
2140             }
2141              
2142             ####
2143             ## $JOB - process id
2144              
2145             sub JOB {
2146 0     0     $$;
2147             }
2148              
2149             ####
2150             ## $JUSTIFY(string, length, decimal fraction length) - Right justify.
2151             ## If third parameter is non zero, trailing zeroes are added
2152             ## for numbers.
2153             ##
2154              
2155             sub JUSTIFY {
2156 0     0     my ($str, $ln, $dec) = @_;
2157 0 0         $str = sprintf("%.${dec}d", $str) if ($dec);
2158 0           my $l = $ln - length($str);
2159 0 0         ($l > 0 ? (" " x $ln) : "") . $str;
2160             }
2161              
2162             ####
2163             ## $LEN(string) - Length
2164             ## $LEN(string, substring) - How many times substring exists in string
2165              
2166             sub LEN {
2167 0     0     my ($str, $token) = @_;
2168 0   0       $token = quotemeta($token) || ".";
2169 0           scalar($str =~ s/($token)//g);
2170             }
2171              
2172             ####
2173             ## $NEXT(array(indices...,rightmost index))
2174             ## Returns the rightmost index of the array element
2175             ## whose rightmost index comes right after the parameter.
2176             ## Use array(indices...,-1) to find the first element
2177             ## Returns -1 on failure.
2178             ## M design bug: -1 is a valid key for an array
2179              
2180             sub NEXT {
2181 0     0     my ($hash, $addr) = @{$_[0]};
  0            
2182 0           my @tokens = split(/\0/, $addr);
2183 0           my $right = pop @tokens;
2184 0           my @sons = sort (tied(%$hash)->query(join("\0", @tokens)));
2185 0 0         return -1 unless (@sons);
2186 0 0         return $sons[0] if ($right == -1);
2187 0           foreach (@sons) {
2188 0 0         return $_ if ($_ gt $right);
2189             }
2190 0           return -1;
2191             }
2192              
2193             ####
2194             ## $ORDER - simillar to NEXT, but with numeric and not lexicographic
2195             ## order
2196              
2197             sub ORDER {
2198 0     0     my ($hash, $addr) = @{$_[0]};
  0            
2199 0           my @tokens = split(/\0/, $addr);
2200 0           my $right = pop @tokens;
2201 0           my @sons = sort {$a <=> $b} @{tied(%$hash)->query(join("\0", @tokens))};
  0            
  0            
2202 0           foreach (@sons) {
2203 0 0 0       return $_ if ($_ >= $right || $right == -1);
2204             }
2205 0           return -1;
2206             }
2207              
2208             ####
2209             ## $PIECE(string, delimiter, $from, $to) - Points to to a specific
2210             ## token in a delimited list, or to a range of tokens, including
2211             ## the dleimiters.
2212              
2213             sub PIECE {
2214 0     0     my ($str, $delim, $from, $to) = @_;
2215 0 0         if (ref($str) eq 'varsig') {
2216 0           my ($hash, $addr) = @$str;
2217 0           $str = $hash->{$addr};
2218             }
2219 0           my $qdelim = quotemeta($delim);
2220 0           my @tokens = split(/$qdelim/, $str);
2221 0   0       $to ||= $from;
2222 0           join($delim, @tokens[($from - 1) .. ($to - 1)]);
2223             }
2224              
2225             sub l_PIECE {
2226 0     0     my $list = shift;
2227 0           "\${&Language::Mumps::Func::tiePIECE($list)}";
2228             }
2229              
2230             sub tiePIECE {
2231 0     0     my $scalar;
2232 0           tie $scalar, 'Language::Mumps::Piece', @_;
2233 0           \$scalar;
2234             }
2235              
2236             ####
2237             ## $RANDOM(max) - integer random
2238              
2239             sub RANDOM {
2240 0     0     my $max = shift;
2241 0           int(rand($max));
2242             }
2243              
2244             ####
2245             ## $SELECT(val1:cond1,val2:cond2...)
2246             ## Receives pairs of value:condition. Returns the first value for which
2247             ## the condition is true
2248             ##>> Actual work is done by the tokenizer in makelist2
2249              
2250             sub SELECT {
2251 0     0     $_[0];
2252             }
2253              
2254             ####
2255             ## $TEST - The test flag
2256              
2257             sub TEST {
2258 0     0     $Language::Mumps::flag;
2259             }
2260              
2261             sub l_TEST {
2262 0     0     '$Language::Mumps::flag';
2263             }
2264              
2265             ## No idea what this is doing here
2266              
2267             sub TEXT {
2268 0     0     $_[0];
2269             }
2270              
2271             ####
2272             ## $X - The x position register
2273              
2274             sub X {
2275 0     0     \$Language::Mumps::xreg[\$Language::Mumps::selected_io]
2276             }
2277              
2278             ####
2279             ## $Y - The Y position register
2280              
2281             sub Y {
2282 0     0     \$Language::Mumps::yreg[\$Language::Mumps::selected_io]
2283             }
2284              
2285             ########################################################
2286             ## Z* Functions are not part of the M specification ##
2287             ## and are mostly copied from MumpsVM ##
2288             ########################################################
2289              
2290             ####
2291             ## $ZAB(number) - Absolute value
2292              
2293             sub ZAB {
2294 0     0     abs(shift);
2295             }
2296              
2297             ####
2298             ## $ZB(string) - Trims spaces
2299              
2300             sub ZB {
2301 0     0     $_ = shift;
2302 0           s/^\s*//;
2303 0           s/\s*$//;
2304 0           s/\s+/ /;
2305 0           $_;
2306             }
2307              
2308             ####
2309             ## $ZCD(filename)
2310             ## Data dumper, for backup and database garbage collection
2311             ## Weird API taken from MumpsVM
2312             ## If filename is omitted, 8 leftmost digits of UCT time are taken
2313             ## with the suffix .dmp
2314             ## Dumps all the databases to a text file using the serializer
2315             ## Returns the filename
2316              
2317             sub ZCD {
2318 0   0 0     my $fn = shift || substr(time, 0, 8) . ".dmp";
2319 0           my $forest = {};
2320 0           $! = undef;
2321             # Iterate over the database directory
2322             # Have the hash $forest have references to all the databases
2323              
2324 0           foreach ((glob "global/*.db"), (glob "global/*.db.*")) {
2325 0           s|^global/||;
2326 0           s/\.db(\..*)?$//;
2327             # next if ($forest->{$_});
2328 0           eval {
2329 0           $forest->{$_} = {%{$Language::Mumps::dbs{$_}}};
  0            
2330             };
2331             }
2332 0           open(DUMP, ">$fn");
2333 0           print DUMP &$Language::Mumps::STORE($forest);
2334 0           close(DUMP);
2335             # Remove links to unused databases, to free memory
2336 0           foreach (values %$forest) {
2337 0           my $hash = tied(%$_)->{'hash'};
2338 0           untie %$hash;
2339 0           undef %$hash;
2340 0           untie %$_;
2341 0           undef %$_;
2342             }
2343 0           %Language::Mumps::dbs = ();
2344 0           $fn;
2345             }
2346              
2347             ####
2348             ## $ZCL - Weird API from MumpsVM
2349             ## Restore databases from a dumped file
2350              
2351             sub ZCL {
2352 0   0 0     my $fn = shift || "dump";
2353 0           %Language::Mumps::dbs = ();
2354 0           open(LOAD, $fn);
2355 0           binmode LOAD;
2356 0           my $buffer;
2357 0           while (read(LOAD, $buffer, 8192, length($buffer))) {}
2358 0           close(LOAD);
2359 0           my $forest = &$Language::Mumps::FETCH($buffer);
2360 0           undef $buffer;
2361 0           foreach (keys %$forest) {
2362 0           unlink "global/$_.db";
2363 0           %{$Language::Mumps::dbs{$_}} = %{$forest->{$_}};
  0            
  0            
2364             }
2365             # Remove links to unused databases, to free memory
2366 0           foreach (values %$forest) {
2367 0           my $hash = tied(%$_)->{'hash'};
2368 0           untie %$hash;
2369 0           undef %$hash;
2370 0           untie %$_;
2371 0           undef %$_;
2372             }
2373 0           %Language::Mumps::dbs = ();
2374             }
2375              
2376             ############################
2377             ## Date functions ##
2378             ## API taken from MumpsVM ##
2379             ############################
2380              
2381             ####
2382             ## $ZD - Readable local time
2383              
2384             sub ZD {
2385 0     0     scalar(localtime);
2386             }
2387              
2388             ####
2389             ## $ZD1 - UTC
2390              
2391             sub ZD1 {
2392 0     0     time;
2393             }
2394              
2395             ####
2396             ## $ZD2(utc) - Convert to readable string
2397              
2398             sub ZD2 {
2399 0     0     scalar(localtime(shift));
2400             }
2401              
2402             ####
2403             ## $ZD3(year, month, day) - Return day of the year
2404              
2405             sub ZD3 {
2406 0     0     my ($y, $m, $d) = @_;
2407 0           require Time::Local;
2408 0           my $t = Time::Local::timelocal(0, 0, 0, $d, $m - 1, $y - 1900);
2409 0           my @t = localtime($t);
2410 0           $t[7] + 1;
2411             }
2412              
2413             ####
2414             ## $ZD(year, day of the year) - Returns y + " " + m + " " + d string
2415             ## Hint: use $PIECE
2416              
2417             sub ZD4 {
2418 0     0     my ($y, $dy) = @_;
2419 0           my @mon = qw(31 28 31 30 31 30 31 31 30 31 30 31);
2420 0           my $m;
2421 0           while ($dy > $mon[$m]) {$dy -= $mon[$m++];}
  0            
2422 0           join(" ", $y, $m + 1, $dy);
2423             }
2424              
2425             ####
2426             ## $ZD5(year, month, day) - Returns year + "," + year day + "," +
2427             ## (week day - 1)
2428              
2429             sub ZD5 {
2430 0     0     my ($y, $m, $d) = @_;
2431 0           require Time::Local;
2432 0           my $t = Time::Local::timelocal(0, 0, 0, $d, $m - 1, $y - 1900);
2433 0           my @t = localtime($t);
2434 0           join(",", $y, $t[7] + 1, $t[6]);
2435             }
2436              
2437             ####
2438             ## $ZD6(utc = now) - returns Ho:Mi clock time
2439              
2440             sub ZD6 {
2441 0   0 0     my $t = (shift) || time;
2442 0           my @t = localtime($t);
2443 0           sprintf("%2d:%02d", $t[2], $t[1]);
2444             }
2445              
2446             ####
2447             ## $ZD7(utc = now) Returns y-m-d
2448              
2449             sub ZD7 {
2450 0   0 0     my $t = (shift) || time;
2451 0           my @t = localtime($t);
2452 0           join("-", $t[5] + 1900, $t[4] + 1, $t[3]);
2453             }
2454              
2455             ####
2456             ## $ZD8(utc) returns y-m-d,Ho:Mi
2457              
2458             sub ZD8 {
2459 0     0     my $t = shift;
2460 0           &ZD7($t) . "," . &ZD6($t);
2461             }
2462              
2463             ####
2464             ## $ZD9(utc = now) returns y-m-d,week day - 1,Ho:Mi
2465              
2466             sub ZD9 {
2467 0   0 0     my $t = (shift) || time;
2468 0           my @t = localtime($t);
2469 0           join(",", &ZD7($t), $t[6], &ZD6($t));
2470             }
2471              
2472             ########################################
2473             ## $DBI - Perl oriented data access ##
2474             ########################################
2475             ## $ZDBI(dsn, user, pass, select query, array)
2476             ## Performs query. Result API taken from MumpsVM's ZODBC
2477             ## Array in 5th parameter get the record number (1 based)
2478             ## per any key combination representing the ordered fields.
2479             ## This allows you to navigate using the function $NEXT
2480             ## Array %tpl gets the keys joined by a backslash in each index
2481             ## which is equal to the row number.
2482              
2483             sub ZDBI {
2484 0     0     my ($dsn, $u, $p, $query, $ary) = @_;
2485 0           require DBI;
2486 0           import DBI;
2487 0           my $dbh = DBI->connect($dsn, $u, $p);
2488 0   0       my $sth = $dbh->prepare($query) || die $DBI::errstr;
2489 0 0         $sth->execute || die $DBI::errstr;
2490 0           my ($i, $rec, $glb);
2491 0 0         $glb = $Language::Mumps::dbs{$1} if ($ary =~ /^\^(.*)$/);
2492            
2493 0           while ($rec = $sth->fetchrow_array) {
2494 0           $Language::Mumps::symbol{"%tpl", ++$i} = join("\\", @$rec);
2495 0 0         unless ($glb) {
2496 0           $Language::Mumps::symbol{$ary, @$rec} = $i;
2497             } else {
2498 0           $glb->{@$rec} = $i;
2499             }
2500             }
2501 0           $sth->finish;
2502 0           $i;
2503             }
2504              
2505             ####
2506             ## $ZF(filename) - true if file exists
2507              
2508             sub ZF {
2509 0     0     (-f shift);
2510             }
2511              
2512             ####
2513             ## $ZH(string) - HTTP encodes
2514              
2515             sub ZH {
2516 0     0     my $s = shift;
2517 0           $s =~ s/([^ a-zA-Z0-9])/sprintf("%%%02x", $1)/ge;
  0            
2518 0           $s =~ s/ /+/g;
2519 0           $s;
2520             }
2521              
2522             ####
2523             ## $ZL(num) = ln(num)
2524             ## $ZL(string, len) = Left justify
2525              
2526             sub ZL {
2527 0     0     my ($a1, $a2) = @_;
2528 0 0         return ln($a1) unless (defined($a2));
2529 0           substr($a1 . (" " x $a2), 0, $a2);
2530             }
2531              
2532             ####
2533             ## $ZN(string) - Qualify as a database name
2534             ## All letters converted uppercase, all non alphanumeric
2535             ## characters removed
2536              
2537             sub ZN {
2538 0     0     my $s = uc(shift);
2539 0           $s =~ s/\W//g;
2540 0           $s;
2541             }
2542              
2543             ####
2544             ## $ZP(string, len) - Left justify
2545              
2546             sub ZP {
2547 0     0     my ($a1, $a2) = @_;
2548 0           substr($a1 . (" " x $a2), 0, $a2);
2549             }
2550              
2551             ####
2552             ## $ZR(x) - Square root
2553              
2554             sub ZR {
2555 0     0     sqrt(shift);
2556             }
2557              
2558             ####
2559             ## $ZS(Shell command) - Executes a command sending the output
2560              
2561             sub ZS {
2562 0     0     &Language::Mumps::write(`$_[0]`);
2563             }
2564              
2565             ####
2566             ## $ZSQR(num) - Power of two
2567              
2568             sub ZSQR {
2569 0     0     my $x = shift;
2570 0           $x * $x;
2571             }
2572              
2573             ####
2574             ## $ZT(file nadler) - The position of the cursor
2575              
2576             sub ZT {
2577 0 0   0     my $file = ($Language::Mumps::selected_io == 5) ? \*STDIN : $Language::Mumps::handlers[$Language::Mumps::selected_io];
2578 0           tell($file);
2579             }
2580              
2581             ####
2582             ## $ZVARIABLE(name) - Returns a Perl scalar with that name
2583              
2584             sub ZVARIABLE {
2585 0     0     ${scalar(caller) . '::' . $_[0]};
  0            
2586             }
2587              
2588             ####
2589             ## $ZV1(name) - Checks if the name is an apropriate identifier
2590              
2591             sub ZV1 {
2592 0     0     $_[0] =~ /^[a-z]\w*$/;
2593             }
2594              
2595             ####
2596             ## $ZWI(string) loads the token stack with space delimited tokens
2597             ## from a string
2598              
2599             sub ZWI {
2600 0     0     @zwi_tokens = split(/\s+/, shift);
2601             }
2602              
2603             ####
2604             ## $ZWN Pulls a token from the token stack
2605              
2606             sub ZWN {
2607 0     0     shift @zwi_tokens;
2608             }
2609              
2610             ##################################################################
2611              
2612             package Language::Mumps::Piece;
2613              
2614             ##################################################
2615             ## Class to implement the Lvalue $PIECE binding ##
2616             ##################################################
2617              
2618             # Tie the parameters
2619              
2620             sub TIESCALAR {
2621 0     0     my $class = shift;
2622 0           bless [@_], $class;
2623             }
2624              
2625             # Fetch the $PIECE
2626              
2627             sub FETCH {
2628 0     0     my $self = shift;
2629 0           &Language::Mumps::Func::PIECE(@$self);
2630             }
2631              
2632             # Store
2633              
2634             sub STORE {
2635 0     0     my ($self, $val) = @_;
2636 0           my ($var, $delim, $from, $to) = @$self;
2637 0   0       $to ||= $from;
2638 0           my ($hash, $addr) = @$var;
2639 0           my $str = $hash->{$addr};
2640 0           $delim = quotemeta($delim);
2641 0           my @tokens = split(/$delim/, $str);
2642 0           splice(@tokens, $from - 1 , $to - $from - 1, $val);
2643 0           $str = join($delim, @tokens);
2644 0           $hash->{$addr} = $str;
2645             }
2646              
2647             ###############################################################
2648              
2649             package Language::Mumps::Forest;
2650              
2651             #############################################
2652             ## Class to implement a grove (aka forest) ##
2653             #############################################
2654              
2655              
2656             sub TIEHASH {
2657 0     0     bless {'dbs' => {}}, shift;
2658             }
2659              
2660             sub FETCH {
2661 0     0     my ($self, $key) = @_;
2662 0           my $dbs = $self->{'dbs'};
2663 0   0       $dbs->{$key} ||= &Language::Mumps::dbs($key);
2664 0           $dbs->{$key};
2665             }
2666              
2667             sub DELETE {
2668 0     0     my ($self, $key) = @_;
2669 0           my $dbs = $self->{'dbs'};
2670 0           my $hash = $dbs->{$key};
2671 0           untie %$hash;
2672             }
2673              
2674             sub CLEAR {
2675 0     0     my ($self, $key) = @_;
2676 0           my $dbs = $self->{'dbs'};
2677 0           my $hash;
2678 0           foreach $hash (keys %$dbs) {
2679 0           untie %$hash;
2680             }
2681 0           delete $self->{'dbs'};
2682             }
2683             __END__