File Coverage

blib/lib/Math/Logic/Ternary/Calculator/Parser.pm
Criterion Covered Total %
statement 98 179 54.7
branch 14 66 21.2
condition 1 6 16.6
subroutine 27 36 75.0
pod 0 7 0.0
total 140 294 47.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2012-2017 Martin Becker, Blaubeuren. All rights reserved.
2             # This package is free software; you can redistribute it and/or modify it
3             # under the same terms as Perl itself.
4              
5             package Math::Logic::Ternary::Calculator::Parser;
6              
7 3     3   46070 use 5.008;
  3         9  
8 3     3   13 use strict;
  3         7  
  3         50  
9 3     3   12 use warnings;
  3         6  
  3         67  
10 3     3   13 use Carp qw(croak);
  3         6  
  3         111  
11 3     3   781 use Math::Logic::Ternary::Calculator::Command;
  3         8  
  3         93  
12 3     3   19 use Math::Logic::Ternary::Calculator::Version;
  3         5  
  3         120  
13              
14             our $VERSION = '0.004';
15             our $INTERACTIVE_PROMPT = q[.: ];
16              
17 3     3   14 use constant _INPUT_FILENAME => 0;
  3         5  
  3         152  
18 3     3   20 use constant _INPUT_HANDLE => 1;
  3         5  
  3         142  
19 3     3   15 use constant _INPUT_OPENED => 2;
  3         5  
  3         115  
20 3     3   14 use constant _PROMPT => 3;
  3         5  
  3         119  
21 3     3   14 use constant _TERMINAL => 4;
  3         5  
  3         105  
22 3     3   15 use constant _INITIAL_CMDS => 5;
  3         5  
  3         107  
23              
24 3     3   12 use constant CMD => Math::Logic::Ternary::Calculator::Command::;
  3         6  
  3         5044  
25              
26             sub _get_varname {
27 0     0   0 my ($session, $raw_name) = @_;
28 0 0       0 if ($raw_name =~ /^=?([^\W\d]\w*)\z/) {
29 0         0 return "=$1";
30             }
31 0         0 my $msg = qq{"$raw_name" is not a variable name};
32 0         0 CMD->wrong_usage($msg)->execute($session);
33 0         0 return undef;
34             }
35              
36             sub _strip_error {
37 0     0   0 my ($error) = @_;
38 0         0 $error =~ s/^(.*) at .* line \d+\.\n/$1/;
39 0         0 return $error;
40             }
41              
42             sub _get_value {
43 0     0   0 my ($session, $raw_value) = @_;
44 0         0 my $prefix = substr $raw_value, 0, 1;
45 0 0       0 if ($prefix =~ /^[\%\@\$]/) {
    0          
    0          
    0          
    0          
46 0         0 my $value = eval { $session->operand_from_string($raw_value) };
  0         0  
47 0 0       0 return $value if defined $value;
48             }
49             elsif ($prefix =~ /^[\-\+\d]/) {
50 0         0 my $value = eval { $session->operand_from_integer($raw_value) };
  0         0  
51 0 0       0 return $value if defined $value;
52             }
53             elsif ('=' eq $prefix) {
54 0         0 my $value = eval { $session->recall_value($raw_value) };
  0         0  
55 0 0       0 return $value if defined $value;
56             }
57             elsif ($raw_value =~ /^#(-?\d+)\z/) {
58 0         0 my $value = eval { $session->fetch_value($1) };
  0         0  
59 0 0       0 return $value if defined $value;
60             }
61             elsif ($raw_value =~ /^\^+\z/) {
62 0         0 my $value = eval { $session->fetch_value(-length $raw_value) };
  0         0  
63 0 0       0 return $value if defined $value;
64             }
65             else {
66 0         0 CMD->wrong_usage(qq{"$raw_value": not an operand})->execute($session);
67 0         0 return undef;
68             }
69 0         0 my $error = _strip_error($@);
70 0         0 CMD->bad_value(qq{"$raw_value": $error})->execute($session);
71 0         0 return undef;
72             }
73              
74             # will be used as quasi-initial command
75             sub _readline_status {
76 2     2   3 my ($session, $parser) = @_;
77 2 50       5 if (defined $parser->_terminal) {
78 0         0 print "readline support is enabled\n";
79             }
80 2         12 return 1;
81             }
82              
83             # check variable name and value, do the storage operation
84             CMD->def_tool_command('/def', 2, 0, \&_store_this, <<'EOT');
85             /def variable value
86             store a value under a name
87             EOT
88             sub _store_this {
89 0     0   0 my ($session, $raw_name, $raw_value) = @_;
90 0         0 my $name = _get_varname($session, $raw_name);
91 0         0 my $value = _get_value($session, $raw_value);
92 0 0 0     0 if (defined $name and defined $value) {
93 0         0 $session->storage_store($name, $value);
94             }
95 0         0 return 1;
96             }
97              
98             sub _execute_operator {
99 0     0   0 my ($session, $name, @raw_operands) = @_;
100 0         0 my $errors = 0;
101             my @operands = map {
102 0         0 my $value = _get_value($session, $_);
  0         0  
103 0 0       0 defined($value)? $value: ++$errors
104             } @raw_operands;
105 0 0       0 if (!$errors) {
106 0         0 $session->execute_operator($name, @operands);
107             }
108 0         0 return 1;
109             }
110              
111             sub _append_values {
112 0     0   0 my ($session, @raw_operands) = @_;
113 0         0 my $errors = 0;
114             my @operands = map {
115 0         0 my $value = _get_value($session, $_);
  0         0  
116 0 0       0 defined($value)? $value: ++$errors
117             } @raw_operands;
118 0 0       0 if (!$errors) {
119 0         0 $session->storage_append(@operands);
120             }
121 0         0 return 1;
122             }
123              
124             sub _operator {
125 0     0   0 my ($this, $name, @operands) = @_;
126 0 0       0 if ($name !~ /^[^\W\d]\w*\z/) {
127 0         0 return CMD->wrong_usage(qq{"$name" is not a valid operator name});
128             }
129 0         0 return CMD->custom_command(\&_execute_operator, $name, @operands);
130             }
131              
132             sub _values {
133 0     0   0 my ($this, @operands) = @_;
134 0         0 return CMD->custom_command(\&_append_values, @operands);
135             }
136              
137             sub _read_line {
138 2     2   3 my ($this) = @_;
139 2         4 my $terminal = $this->_terminal;
140 2 50       7 if (defined $terminal) {
141 0         0 return $terminal->readline($this->prompt);
142             }
143 2         5 $this->do_prompt;
144 2         5 my $handle = $this->_input_handle;
145 2         14 return scalar <$handle>;
146             }
147              
148             sub _read_items {
149 2     2   4 my ($this) = @_;
150 2         3 my @items = ();
151 2         5 while (!@items) {
152 2         5 my $line = $this->_read_line;
153 2 50       6 if (!defined $line) {
154 2         6 $this->do_unprompt;
155 2         4 return ();
156             }
157 0         0 @items = split q[ ], $line;
158             }
159 0         0 return @items;
160             }
161              
162 0     0 0 0 sub input_filename { $_[0]->[_INPUT_FILENAME] }
163 2     2   3 sub _input_handle { $_[0]->[_INPUT_HANDLE ] }
164 2     2   7 sub _input_opened { $_[0]->[_INPUT_OPENED ] }
165 4     4 0 7 sub prompt { $_[0]->[_PROMPT ] }
166 4     4   8 sub _terminal { $_[0]->[_TERMINAL ] }
167 10     10   13 sub _pending_initial { shift @{$_[0]->[_INITIAL_CMDS ]} }
  10         25  
168              
169             sub do_prompt {
170 2     2 0 4 my ($this) = @_;
171 2         3 my $prompt = $this->prompt;
172 2 50       6 print $prompt if q[] ne $prompt;
173 2         2 return;
174             }
175              
176             sub do_unprompt {
177 2     2 0 3 my ($this) = @_;
178 2         4 my $prompt = $this->prompt;
179 2 50       5 print "\n" if q[] ne $prompt;
180 2         3 return;
181             }
182              
183             sub _drop_input {
184 2     2   3 undef $_[0]->[_INPUT_HANDLE];
185 2         11 undef $_[0]->[_INPUT_OPENED];
186             }
187              
188             sub open {
189 2     2 0 4 my ($class, $in_filename, $prompt, $enable_readline) = @_;
190 2         4 my $this = bless [], $class;
191 2         3 my $in_handle = undef;
192 2         4 my $terminal = undef;
193 2         3 my $from_file = '-' ne $in_filename;
194 2 50       6 if ($from_file) {
195 0 0       0 open $in_handle, '<', $in_filename
196             or croak "$in_filename: cannot open: $!";
197 0         0 $enable_readline = 0;
198             }
199             else {
200 2         4 $in_handle = \*STDIN;
201             }
202 2 50       14 if (!defined $prompt) {
203 2 50       9 $prompt = -t $in_handle? $INTERACTIVE_PROMPT: q[];
204             }
205 2 50       4 if (!defined $enable_readline) {
206 2         5 $enable_readline = -t $in_handle;
207             }
208 2 50       5 if ($enable_readline) {
209 0         0 my $app_name = Math::Logic::Ternary::Calculator::Version->long_name;
210 0         0 my $out_handle = \*STDOUT;
211 0         0 $terminal = eval {
212 0         0 require Term::ReadLine;
213 0         0 Term::ReadLine->new($app_name, $in_handle, $out_handle)
214             };
215             }
216 2         6 $this->[_INPUT_FILENAME] = $in_filename;
217 2         4 $this->[_INPUT_OPENED ] = $from_file;
218 2         4 $this->[_INPUT_HANDLE ] = $in_handle;
219 2         2 $this->[_PROMPT ] = $prompt;
220 2         4 $this->[_TERMINAL ] = $terminal;
221 2         21 $this->[_INITIAL_CMDS ] = [
222             CMD->get_initial_commands,
223             CMD->custom_command(\&_readline_status, $this),
224             ];
225 2         6 return $this;
226             }
227              
228             sub close {
229 2     2 0 5 my ($this) = @_;
230 2 50 33     4 if ($this->_input_opened && !close $this->_input_handle) {
231 0         0 my $filename = $this->input_filename;
232 0         0 croak "$filename: cannot close: $!";
233             }
234 2         5 $this->_drop_input;
235 2         8 return $this;
236             }
237              
238             sub read_command {
239 10     10 0 17 my ($this) = @_;
240 10 100       15 if (my $initial_cmd = $this->_pending_initial) {
241 8         21 return $initial_cmd;
242             }
243 2         5 my @items = $this->_read_items;
244 2 50       8 return undef if !@items;
245 0           my $prefix = substr $items[0], 0, 1;
246 0 0         if ($prefix =~ m{^[/?]}) {
247 0           my $name = shift @items;
248 0           return CMD->tool_command($name, @items);
249             }
250 0 0         if ($prefix =~ m{^[^\W\d]}) {
251 0           return $this->_operator(@items);
252             }
253 0           return $this->_values(@items);
254             }
255              
256             1;
257             __END__