File Coverage

blib/lib/Math/SymbolicX/Calculator/Interface/Shell.pm
Criterion Covered Total %
statement 21 106 19.8
branch 0 42 0.0
condition 0 13 0.0
subroutine 7 17 41.1
pod 6 6 100.0
total 34 184 18.4


line stmt bran cond sub pod time code
1             package Math::SymbolicX::Calculator::Interface::Shell;
2              
3 2     2   33766 use 5.006;
  2         6  
  2         138  
4 2     2   16 use strict;
  2         2  
  2         88  
5 2     2   11 use warnings;
  2         3  
  2         105  
6              
7             our $VERSION = '0.02';
8              
9 2     2   1994 use Term::ReadLine;
  2         7809  
  2         78  
10 2     2   2086 use Params::Util qw/_INSTANCE/;
  2         9458  
  2         756  
11 2     2   1899 use Math::SymbolicX::Calculator;
  2         488994  
  2         75  
12 2     2   17 use base 'Math::SymbolicX::Calculator::Interface';
  2         4  
  2         2321  
13              
14             # For convenience, we extend the Math::Symbolic parser.
15             # This will become the shortcut derive() => partial derivative.
16             $Math::Symbolic::Operator::Op_Symbols{derive} = Math::Symbolic::ExportConstants::U_P_DERIVATIVE;
17             $Math::Symbolic::Parser = Math::Symbolic::Parser->new();
18             $Math::Symbolic::Parser->Extend(<<'GRAMMAR');
19             function_name: 'derive'
20             GRAMMAR
21              
22             # Matches identifiers
23             my $Ident = $Math::SymbolicX::Calculator::Identifier_Regex;
24              
25             =head1 NAME
26              
27             Math::SymbolicX::Calculator::Interface::Shell - A Calculator Shell
28              
29             =head1 SYNOPSIS
30              
31             # simplest form of usage:
32             use Math::SymbolicX::Calculator::Interface::Shell;
33             my $shell = Math::SymbolicX::Calculator::Interface::Shell->new();
34             $shell->run();
35              
36             =head1 DESCRIPTION
37              
38             This module implements a shell interface to the Math::SymbolicX::Calculator.
39              
40             =head1 METHODS
41              
42             =cut
43              
44              
45             # defined or
46             sub _dor {
47 0     0     foreach (@_) {
48 0 0         return $_ if defined $_;
49             }
50 0           return(undef);
51             }
52              
53              
54             =head2 new
55              
56             Returns a new shell application object. Call the C method on it
57             to run the application.
58              
59             Optional parameters: (default in parenthesis)
60              
61             calc => a Math::SymbolicX::Calculator object to use
62             input_handle => a file handle to read from (\*STDIN)
63             prompt => the prompt string to use ('~> ')
64             continued_prompt => prompt string to use for continued lines ('>> ')
65             app_name => the name of the application ('Symbolic Calculator Shell')
66              
67             =cut
68              
69             sub new {
70 0     0 1   my $proto = shift;
71 0   0       my $class = ref($proto)||$proto;
72              
73 0           my %args = @_;
74              
75 0   0       my $self = {
      0        
76             calc => $args{calculator}
77             || Math::SymbolicX::Calculator->new(),
78             input_handle => $args{input_handle} || \*STDIN,
79             prompt => _dor($args{prompt}, '~> '),
80             continued_prompt => _dor($args{continued_prompt}, '>> '),
81             app_name => _dor($args{app_name}, 'Symbolic Calculator Shell'),
82             };
83 0           bless $self => $class;
84              
85 0           $self->_setup_readline();
86              
87 0           return $self;
88             }
89              
90             sub _setup_readline {
91 0     0     my $self = shift;
92 0           $self->{readline} = Term::ReadLine->new(
93             $self->{app_name}, $self->{input_handle}, *STDOUT,
94             );
95 0           $self->{readline}->ornaments(0);
96             }
97              
98              
99             =head2 run
100              
101             Runs the main loop of the shell.
102              
103             =cut
104              
105             sub run {
106 0     0 1   my $self = shift;
107              
108             # FIXME refactor
109             # Main Loop
110 0           while (1) {
111             # get a new expression.
112 0           my $expr = $self->get_expression();
113            
114 0 0         return $self->exit_hook() if not defined $expr;
115              
116 0           my $cmd;
117             # What type of command?
118 0 0         if ($expr =~ /=~~?/) {
    0          
119 0           $cmd = $self->_parse_transformation($expr);
120             }
121             elsif ($expr =~ /=/) {
122 0           $cmd = $self->_parse_assignment($expr);
123             }
124             else {
125 0           $cmd = $self->_parse_command($expr);
126             }
127            
128 0 0         if (not defined $cmd) {
    0          
    0          
    0          
129 0           next;
130             }
131             elsif (_INSTANCE($cmd, 'Math::SymbolicX::Calculator::Command')) {
132 0           my @output = $self->calc->execute($cmd);
133 0           $self->_generic_out(@output);
134             }
135             elsif (ref($cmd) eq 'ARRAY') {
136 0 0         if ($cmd->[0] eq 'print') {
137 0           $self->_generic_out(@{$cmd}[1..$#$cmd]);
  0            
138             }
139             }
140             elsif ($cmd eq 'exit') {
141 0           return $self->exit_hook();
142             }
143             else {
144            
145             }
146              
147             }
148              
149 0           return();
150             }
151              
152             =head2 calc
153              
154             Returns the Calculator object of this Shell.
155              
156             =cut
157              
158             sub calc {
159 0     0 1   my $self = shift;
160 0           return $self->{calc};
161             }
162              
163             =head2 exit_hook
164              
165             Call this before stopping the shell. It runs all cleanup actions
166             such as those needed for a possible persistance.
167              
168             This method doesn't actually kill your script, but returns after
169             doing the cleanup.
170              
171             =cut
172              
173             sub exit_hook {
174 0     0 1   my $self = shift;
175 0           return();
176             }
177              
178              
179             =head2 error
180              
181             Used to issue a warning to the user. First argument must be an error
182             message to display.
183              
184             =cut
185              
186             sub error {
187 0     0 1   my $self = shift;
188 0           my $message = shift;
189 0           print "!!! $message\n";
190             }
191              
192             =head2 get_expression
193              
194             Reads a new expression from the input handle. An expression ends
195             in a semicolon followed by a newline.
196              
197             Used internally by the run loop. Probably not that useful outside of
198             that.
199              
200             Returns the expression or the empty list on error.
201              
202             =cut
203              
204             sub get_expression {
205 0     0 1   my $self = shift;
206              
207 0           my $readline = $self->{readline};
208 0           my $expr;
209 0           while (1) {
210 0           my $prompt = '';
211 0 0 0       if (not defined $expr and defined $self->{prompt}) {
212 0           $prompt = $self->{prompt}
213             }
214             else {
215 0           $prompt = $self->{continued_prompt}
216             }
217 0           my $line = $readline->readline($prompt);
218 0 0         return() if not defined $line;
219 0           chomp $line;
220 0           $line .= ' ';
221 0           $expr .= $line;
222 0 0         last if $line =~ /;\s*$/;
223             }
224 0           $expr =~ s/;\s*$//;
225 0           return $expr;
226             }
227              
228              
229             =head2 _parse_command
230              
231             Parses generic commands such as exit and print.
232              
233             This might change. (Name and implementation)
234              
235             First argument: Expression to parse.
236              
237             FIXME: Document what this does or refactor
238              
239             =cut
240              
241             sub _parse_command {
242 0     0     my $self = shift;
243 0           my $expr = shift;
244              
245 0 0         if ($expr =~ /^\s*exit\s*$/i) {
    0          
    0          
    0          
    0          
246 0           return 'exit';
247             }
248             elsif ($expr =~ /^\s*print\s+($Ident)\s*$/) {
249 0           my $id = $1;
250             return [
251 0           'print', $id, "==", _dor($self->calc->stash($id), '/Undefined/')
252             ];
253             }
254             elsif ($expr =~ /^\s*apply_deriv\s+($Ident)(?:\s*|\s+(\d+))$/) {
255 0   0       my $level = $2||undef;
256 0           my $id = $1;
257 0           my $cmd = $self->calc->new_command(
258             type => 'DerivativeApplication', symbol => $id,
259             level => $level,
260             );
261 0           return $cmd;
262             }
263             elsif ($expr =~ /^\s*insert\s+($Ident|\*)\s+in\s+($Ident)\s*$/) {
264 0           my $what = $1;
265 0           my $where = $2;
266 0           my $cmd = $self->calc->new_command(
267             type => 'Insertion', symbol => $where,
268             what => $what
269             );
270 0           return $cmd;
271             }
272             elsif ($expr =~ /^\s*$/) {
273 0           return();
274             }
275             else {
276 0           $self->error("Could not parse command '$expr'.");
277 0           return();
278             }
279              
280 0           die "Sanity check";
281             }
282              
283             =head2 _generic_out
284              
285             Generic output routine: Print Formulas and messages alike
286              
287             FIXME: Subject to change and refactoring.
288              
289             =cut
290              
291             sub _generic_out {
292 0     0     my $self = shift;
293 0           my @out = @_;
294 0 0         if (not @out) {
295 0           print "\n";
296 0           return();
297             }
298              
299             my $str = join ' ',
300             map {
301 0 0         if (not defined) {
  0 0          
302 0           "\n"
303             }
304             # insert special cases here...
305             elsif (_INSTANCE($_, 'Math::Symbolic::Custom::Transformation')) {
306 0           $_->to_string();
307             }
308             else {
309 0           "$_"
310             }
311             } @out;
312              
313 0 0         $str .= "\n" if not $str =~ /\n$/;
314 0           print $str;
315 0           return(1);
316             }
317              
318              
319              
320             1;
321             __END__