File Coverage

bin/units
Criterion Covered Total %
statement 259 363 71.3
branch 97 174 55.7
condition 7 27 25.9
subroutine 38 46 82.6
pod n/a
total 401 610 65.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package PerlPowerTools::units;
3              
4 1     1   240661 use strict;
  1         3  
  1         45  
5 1     1   6 use warnings;
  1         1  
  1         74  
6 1     1   656 use open qw(:std :utf8);
  1         1584  
  1         13  
7              
8             =begin metadata
9              
10             Name: units
11             Description: conversion program
12             Author: Mark-Jason Dominus, mjd-perl-units@plover.com
13             License: gpl
14              
15             =end metadata
16              
17             =cut
18              
19 1     1   184 use Config;
  1         2  
  1         194  
20              
21             # Usage:
22             # units [-f unittab]
23             our $VERSION = '1.02';
24              
25             BEGIN {
26 1     1   653 require Data::Dumper;
27 32845     32845   147610 sub dumper { Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Deparse(1)->Dump }
28              
29 1         10113 foreach my $letter ( qw( d p o l t ) ) {
30 1     1   10 no strict 'refs';
  1         1  
  1         278  
31 5         10 my $env_var = uc( "UNITS_DEBUG_$letter" );
32 5 100       21 my( $debugging ) = grep { defined and length } ( $ENV{$env_var}, $ENV{UNITS_DEBUG}, 0 );
  15         43  
33 5     485818   206 *{"debug_$letter"} = ! $debugging ? sub {} : sub {
34 0         0 my $indent;
35 0         0 my $m = join '', @_;
36 0 0       0 $indent = $1 if $m =~ s/\A(\s*)//;
37 0         0 print "$indent$letter>>> $m\n";
38             }
39 5 50       18 }
40             }
41              
42             our %unittab; # Definitions loaded here
43              
44             # Metric prefixes. These must be powers of ten or change the
45             # token_value subroutine
46             our %PREF;
47             our $PREF;
48             our $PARSE_ERROR;
49              
50             BEGIN {
51 1     1   16 %PREF = (
52             yotta => -24,
53             zetta => -21,
54             atto => -18,
55             femto => -15,
56             pico => -12,
57             nano => - 9,
58             micro => - 6,
59             milli => - 3,
60             centi => - 2,
61             deci => - 1,
62             deca => 1,
63             deka => 1,
64             hecto => 2,
65             hect => 2,
66             kilo => 3,
67             myria => 4,
68             mega => 6,
69             giga => 9,
70             tera => 12,
71             zepto => 15,
72             yocto => 18,
73             );
74 1         8 $PREF = join '|', sort {$PREF{$a} <=> $PREF{$b}} (keys %PREF);
  68         869  
75             }
76              
77             # run if called directly, indirectly, directly par-packed, undirectly par-packed
78             __PACKAGE__->run(@ARGV) if !caller() || caller(0) =~ /^(PerlPowerTools::Packed|PAR)$/ || caller(1) eq 'PAR'; # modulino
79              
80             sub run {
81 0     0   0 my( $class, @args ) = @_; local @ARGV;
  0         0  
82              
83 0         0 my $args = $class->process_args( @args );
84              
85 0         0 $class->read_unittab( $args->{unittabs}[0] );
86              
87 0 0       0 if (@{ $args->{args} }) {
  0         0  
88 0         0 my ($have, $want) = @{ $args->{args} };
  0         0  
89 0         0 my $have_hr = $class->unit_have($have);
90 0         0 my $want_hr = $class->unit_want($want);
91 0         0 my %r = $class->unit_convert($have_hr, $want_hr);
92 0         0 print_result(%r);
93             } else {
94 0         0 while (1) {
95 0         0 print "You have: ";
96 0         0 my $have = <>;
97 0 0 0     0 exit 0 unless defined($have) && $have =~ /\S/;
98 0         0 my $have_hr = $class->unit_have($have);
99 0 0       0 next if is_Zero($have_hr->{hu});
100              
101 0         0 print "You want: ";
102 0         0 my $want = <>;
103 0 0 0     0 exit 0 unless defined($want) && $want =~ /\S/;
104 0         0 my $want_hr = $class->unit_want($want);
105 0 0       0 next if is_Zero($want_hr->{wu});
106              
107 0         0 my %r = $class->unit_convert($have_hr, $want_hr);
108 0         0 print_result(%r);
109             }
110             }
111              
112 0         0 exit 0;
113             }
114              
115             sub test {
116 20     20   56471 my ($class, $have, $want) = @_;
117              
118 20         93 $class->read_unittab();
119 20         122 my $have_hr = $class->unit_have($have);
120 20         81 my $want_hr = $class->unit_want($want);
121 20         91 my %r = $class->unit_convert($have_hr, $want_hr);
122 20         202 return %r;
123             }
124              
125             sub default_unittabs {
126 0     0   0 grep { -e } qw(/usr/lib/unittab);
  0         0  
127             }
128              
129             sub env_unittabs {
130 1     1   10 no warnings 'uninitialized';
  1         2  
  1         5859  
131 0     0   0 split /$Config{path_sep}/, $ENV{UNITTAB};
132             }
133              
134             sub process_args {
135 0     0   0 my( $class, @args ) = @_;
136              
137 0         0 my @unittabs;
138 0   0     0 while (@args and $args[0] =~ /^-/) {
139 0         0 my $flag = shift @args;
140 0 0       0 if ($flag =~ s/\A\-f//) {
    0          
    0          
141 0         0 my $file = $flag;
142 0 0       0 $file = shift @args if (length($file) == 0);
143 0 0       0 $class->usage() unless defined $file;
144 0         0 push @unittabs, $file;
145             } elsif ($flag eq '--') {
146 0         0 last;
147             } elsif ($flag =~ /^--version$/) {
148 0         0 print "perl units version $VERSION.\n";
149 0         0 exit 0;
150             } else {
151 0         0 warn "Unknown flag: $flag.\n";
152 0         0 $class->usage();
153             }
154             }
155              
156 0 0 0     0 $class->usage() if @args == 1 || @args > 2;
157              
158 0 0       0 @unittabs = $class->env_unittabs unless @unittabs;
159 0 0       0 @unittabs = $class->default_unittabs unless @unittabs;
160              
161 0         0 { unittabs => \@unittabs, args => \@args }
162             }
163              
164             sub read_unittab {
165 20     20   61 my( $class, $file ) = @_;
166              
167 20         48 my $fh;
168 20 50       76 if (defined $file) {
169 0 0       0 unless (-d $file) {
170 0 0       0 open $fh, '<:encoding(UTF-8)', $file or do {
171 0         0 die "Could not open <$file>: $!\n";
172             };
173 0         0 $class->read_defs($file, $fh);
174 0         0 return;
175             }
176             }
177 20         67 debug_d('Reading from DATA');
178 1 50   1   992 open $fh, '<:encoding(UTF-8)', __FILE__ or die;
  1         19  
  1         6  
  20         1641  
179 20         4294 while (<$fh>) {
180 18980 100       53170 last if /\A__(?:DATA|END)__/;
181             }
182 20         205 $class->read_defs('DATA', $fh);
183             }
184              
185             sub unit_have {
186 20     20   62 my ($class, $have) = @_;
187              
188 20         55 trim($have);
189              
190 20         37 my $is_negative = 0;
191 20 100       87 if ($have =~ /^[-]/) {
192 2         4 $is_negative = 1;
193 2         7 $have =~ s/^[-]//; # remove minus sign
194             }
195              
196 20         80 my $is_quantified = $have =~ /^[\d.]+/;
197              
198 20 50       77 if ($have =~ s/^\s*\#\s*//) {
199 0 0       0 if ($class->definition_line($have)) {
200 0         0 print "Defined.\n";
201             } else {
202 0         0 print "Error: $PARSE_ERROR.\n";
203             }
204 0         0 return;
205             }
206 20 50       132 return unless $have =~ /\S/;
207              
208 20         71 my $hu = $class->parse_unit($have);
209              
210 20 50       66 if (is_Zero($hu)) {
211 0         0 print $PARSE_ERROR, "\n";
212 0         0 return;
213             }
214              
215 20         170 return { have => $have, hu => $hu, neg => $is_negative, quan => $is_quantified };
216             }
217              
218             sub unit_want {
219 20     20   50 my ($class, $want) = @_;
220              
221 20         60 trim($want);
222 20 50       90 return unless $want =~ /\S/;
223              
224 20         52 my $wu = $class->parse_unit($want);
225              
226 20 50       62 if (is_Zero($wu)) {
227 0         0 print $PARSE_ERROR, "\n";
228             }
229 20         117 return { want => $want, wu => $wu };
230             }
231              
232             sub unit_convert {
233 20     20   52 my ($class, $have_hr, $want_hr ) = @_;
234              
235 20         55 my $have = $have_hr->{have};
236 20         42 my $hu = $have_hr->{hu};
237 20         39 my $is_negative = $have_hr->{neg};
238 20         42 my $is_quantified = $have_hr->{quan};
239              
240 20         58 my $want = $want_hr->{want};
241 20         34 my $wu = $want_hr->{wu};
242              
243 20         68 debug_t('have unit', dumper($hu));
244 20         208 debug_t('want unit', dumper($wu));
245              
246 20         183 my $is_temperature = 0;
247 20 100       95 $is_temperature++ if $hu->{Temperature};
248 20 100       75 $is_temperature++ if $wu->{Temperature};
249              
250 20 100       79 my $quot
251             = $is_temperature == 2
252             ? undef
253             : unit_divide($hu, $wu);
254              
255 20         36 my %retval;
256              
257 20 100       65 if ($is_temperature == 2) {
    50          
258             # we have temperature units
259 13         84 $have =~ s/^[-]?[\d.]*\s*//;
260 13         39 my $v = $hu->{'_'};
261 13 100       49 $v *= -1 if $is_negative;
262 13 100       46 $v = 0 if not $is_quantified;
263             my $k
264             = exists $hu->{hof}
265 13 100       351 ? $hu->{hof}->{to}->($v)
266             : $v;
267             my $t
268             = exists $wu->{hof}
269 13 100       244 ? $wu->{hof}->{from}->($k)
270             : $k;
271 13         107 %retval = ( type => 'temperature', v => $v, have => $have, t => $t, want => $want );
272             }
273             elsif (is_dimensionless($quot)) {
274 7         13 my $q = $quot->{_};
275 7         15 my $p = 1/$q;
276 7         42 %retval = ( type => 'dimless', q => $q, p => $p);
277             }
278             else {
279 0         0 %retval = ( type=> 'error', msg =>
280             "conformability (Not the same dimension)\n" .
281             "\t" . $have . " is " . text_unit($hu) . "\n" .
282             "\t" . $want . " is " . text_unit($wu) . "\n"
283             );
284             }
285              
286 20         143 return %retval;
287             }
288              
289             sub print_result {
290 0     0   0 my (%r) = @_;
291             printf "\t%.6g %s is %.6g %s\n", $r{v}, $r{have}, $r{t}, $r{want}
292 0 0       0 if $r{type} eq 'temperature';
293             printf "\t* %.6g\n\t/ %.6g\n", $r{q}, $r{p}
294 0 0       0 if $r{type} eq 'dimless';
295 0 0       0 print $r{msg} if $r{type} eq 'error';
296             }
297              
298             ################################################################
299              
300             sub usage {
301 0     0   0 my( $class ) = @_;
302 0         0 require Pod::Usage;
303 0         0 Pod::Usage::pod2usage({ -exitval => 1, -verbose => 0 });
304             }
305              
306             sub read_defs {
307 20     20   72 my ($class, $filename, $fh) = @_;
308 20         82 while (<$fh>) {
309 8420 100       26033 next if m|\A/|; # comment line
310 8400         19186 trim($_);
311 8400 100       29134 next unless /\S/;
312              
313 7060         19249 debug_d( "$_" );
314 7060         23335 my $hash = $class->definition_line($_);
315 7060         22430 foreach my $key ( keys %$hash ) {
316 7060         64865 $unittab{$key} = $hash->{$key};
317             }
318             }
319             }
320              
321             sub definition_line {
322 7060     7060   14304 my ($class, $line) = @_;
323 7060         23822 my ($name, $data) = split /\s+/, $line, 2;
324 7060         15442 my $value = $class->parse_unit($data);
325 7060 100       22909 debug_t("$name => $data") if $data =~ /^\{\s/;
326 7060         8719 my $rc = do {
327 7060 100       20999 if ($data =~ /^\{\s/) {
    50          
    100          
328 60         10177 my $hof = eval $data; # hash of functions
329 60         579 +{ $name => { _ => 1, hof => $hof, Temperature => 1 } }
330             }
331 0         0 elsif (is_Zero($value)) { undef }
332 180         840 elsif (is_fundamental($value)) { +{ $name => {_ => 1, $name => 1} } }
333 6820         24615 else { +{ $name => $value } }
334             };
335              
336 7060 50       17994 unless( defined $rc ) {
337 0         0 $line =~ s/\s+/ => /;
338 0         0 warn "Parse error: $PARSE_ERROR in $line. Skipping.\n";
339 0         0 $rc = {};
340             }
341              
342 7060         15156 return $rc;
343             }
344              
345             sub trim { # note that trim() is a L-value sub
346 8440     8440   19835 $_[0] =~ s/\#.*$//;;
347 8440         62045 $_[0] =~ s/\s+$//;
348 8440         20919 $_[0] =~ s/^\s+//;
349             }
350              
351 120     120   737 sub Zero () { +{ _ => 0 } }
352              
353             # here we guard the zero test by first checking to see
354             # if we're dealing with a temperature
355 7040   66 7040   36926 sub is_Zero { !$_[0]{Temperature} && !$_[0]{_} }
356              
357             sub unit_lookup {
358 9602     9602   16494 my ($name) = @_;
359 9602         29446 debug_l( "Looking up unit '$name'" );
360 9602 100       36281 return $unittab{$name} if exists $unittab{$name};
361 504 100       1509 if ($name =~ /s$/) {
362 4         9 my $shortname = $name;
363 4         17 $shortname =~ s/s$//;
364 4 50       20 return $unittab{$shortname} if exists $unittab{$shortname};
365             }
366 500         2928 my ($prefix, $rest) = ($name =~ /^($PREF-?)(.*)/o);
367 500 100       1292 unless ($prefix) {
368 60         101 $PARSE_ERROR = "Unknown unit '$name'";
369 60         134 return Zero;
370             }
371 440         955 my $base_unit = unit_lookup($rest); # Recursive
372 440         2424 con_multiply($base_unit, 10**$PREF{$prefix});
373             }
374              
375             sub unit_multiply {
376 5064     5064   9002 my ($a, $b) = @_;
377 5064         6976 debug_o( "Multiplying @{[%$a]} by @{[%$b]}: " );
  5064         38316  
  5064         29381  
378 5064         23442 my $r = {%$a};
379 5064         18411 $r->{_} *= $b->{_};
380 5064         13197 for my $u (keys %$b) {
381 6644 100       15386 next if $u eq '_';
382 1580         3826 $r->{$u} += $b->{$u};
383             }
384 5064         7564 debug_o( "\tResult: @{[%$r]}" );
  5064         39294  
385 5064         13937 $r;
386             }
387              
388             sub unit_divide {
389 1389     1389   2614 my ($a, $b) = @_;
390 1389         6339 my $r = {%$a};
391 1389 50       4525 die "Division by zero error" if $b->{_} == 0;
392 1389         4194 $r->{_} /= $b->{_};
393 1389         4204 for my $u (keys %$b) {
394 2899 100       6109 next if $u eq '_';
395 1510         3619 $r->{$u} -= $b->{$u};
396             }
397 1389         3548 $r;
398             }
399              
400             sub unit_power {
401 1481     1481   2957 my ($p, $u) = @_;
402 1481         2061 debug_o( "Raising unit @{[%$u]} to power $p." );
  1481         12614  
403 1481         6497 my $r = {%$u};
404 1481         4917 $r->{_} **= $p;
405 1481         4435 for my $d (keys %$r) {
406 2982 100       6578 next if $d eq '_';
407 1501         3246 $r->{$d} *= $p;
408             }
409 1481         2412 debug_o( "\tResult: @{[%$r]}" );
  1481         11699  
410 1481         4146 $r;
411             }
412              
413             sub unit_dimensionless {
414 4204     4204   13079 debug_o( "Turning $_[0] into a dimensionless unit." );
415 4204         13101 return +{_ => $_[0]};
416             }
417              
418             sub con_multiply {
419 484     484   1019 my ($u, $c) = @_;
420 484         724 debug_o( "Multiplying unit @{[%$u]} by constant $c." );
  484         5454  
421 484         2133 my $r = {%$u};
422 484         1367 $r->{_} *= $c;
423 484         748 debug_o( "\tResult: @{[%$r]}" );
  484         3817  
424 484         1492 $r;
425             }
426              
427             sub is_dimensionless {
428 7     7   13 my ($r) = @_;
429 7         21 for my $u (keys %$r) {
430 15 100       33 next if $u eq '_';
431 8 50       24 return if $r->{$u} != 0;
432             }
433 7         40 return 1;
434             }
435              
436             # Generate bogus unit value that signals that a new fundamental unit
437             # is being defined
438             sub new_fundamental_unit {
439 180     180   602 return +{__ => 'new', _ => 1};
440             }
441              
442             # Recognize this bogus value when it appears again.
443             sub is_fundamental {
444 7000     7000   16197 exists $_[0]{__};
445             }
446              
447             sub text_unit {
448 0     0   0 my ($u) = @_;
449 0         0 my (@pos, @neg);
450 0         0 my $c = $u->{_};
451 0         0 for my $k (sort keys %$u) {
452 0 0 0     0 next if $k eq '_' or $k eq 'hof';
453 0 0       0 push @pos, $k if $u->{$k} > 0;
454 0 0       0 push @neg, $k if $u->{$k} < 0;
455             }
456 0 0       0 my $text = ($c == 1 ? '' : $c);
457 0         0 for my $d (@pos) {
458 0         0 my $e = $u->{$d};
459 0         0 $text .= " $d";
460 0 0       0 $text .= "^$e" if $e > 1;
461             }
462              
463 0 0       0 $text .= ' per' if @neg;
464 0         0 for my $d (@neg) {
465 0         0 my $e = - $u->{$d};
466 0         0 $text .= " $d";
467 0 0       0 $text .= "^$e" if $e > 1;
468             }
469              
470 0         0 $text;
471             }
472             ################################################################
473             #
474             # I'm the parser
475             #
476              
477             our @actions;
478             BEGIN {
479 48     48   186 sub sh { ['shift', $_[0]] };
480 13     13   81 sub go { ['goto', $_[0]] };
481              
482 1     1   5 my $eof_state = 98;
483              
484             @actions =
485             (
486             # Initial state
487             {
488             PREFIX => sh(1),
489             NUMBER => sh(2),
490             NAME => sh(3),
491             FUNDAMENTAL => sh(4),
492             FRACTION => sh(5),
493             POWER => sh(17),
494             '(' => sh(6),
495             'unit' => go(7),
496             'topunit' => go($eof_state),
497             'constant' => go(8),
498             },
499              
500             # State 1: constant -> PREFIX .
501             { _ => ['reduce', 1, 'constant']},
502              
503             # State 2: constant -> NUMBER .
504             { _ => ['reduce', 1, 'constant']},
505              
506             # State 3: unit -> NAME .
507             { _ => ['reduce', 1, 'unit', \&unit_lookup ]},
508              
509             # State 4: unit -> FUNDAMENTAL .
510             { _ => ['reduce', 1, 'unit', \&new_fundamental_unit ]},
511              
512             # State 5: constant -> FRACTION .
513             { _ => ['reduce', 1, 'constant']},
514              
515             # State 6: unit -> '(' . unit ')'
516             {PREFIX => sh(1),
517             NUMBER => sh(2),
518             NAME => sh(3),
519             FUNDAMENTAL => sh(4),
520             FRACTION => sh(5),
521             '(' => sh(6),
522             'unit' => go(9),
523             'constant' => go(8),
524             },
525              
526             # State 7: topunit -> unit .
527             # unit -> unit . TIMES unit
528             # unit -> unit . DIVIDE unit
529             # unit -> unit . NUMBER
530             {NUMBER => sh(10),
531             TIMES => sh(11),
532             DIVIDE => sh(12),
533             POWER => sh(17),
534             _ => ['reduce', 1, 'topunit'],
535             },
536              
537             # State 8: unit -> constant . unit
538             # unit -> constant .
539             {PREFIX => sh(1),
540             NUMBER => sh(2), # Shift-reduce conflict resolved in favor of shift
541             NAME => sh(3),
542             FUNDAMENTAL => sh(4),
543             FRACTION => sh(5),
544             '(' => sh(6),
545             _ => ['reduce', 1, 'unit', \&unit_dimensionless],
546             'unit' => go(13),
547             'constant' => go(8),
548             },
549              
550             # State 9: unit -> unit . TIMES unit
551             # unit -> unit . DIVIDE unit
552             # unit -> '(' unit . ')'
553             # unit -> unit . NUMBER
554             {NUMBER => sh(10),
555             TIMES => sh(11),
556             DIVIDE => sh(12),
557             POWER => sh(17),
558             ')' => sh(14),
559             },
560              
561             # State 10: unit -> unit NUMBER .
562             { _ => ['reduce', 2, 'unit',
563             sub {
564 1481 50       4543 unless (int($_[1]) == $_[1]) {
565 0         0 ABORT("Nonintegral power $_[1]");
566 0         0 return Zero;
567             }
568 1481         3604 unit_power(@_);
569             }
570             ],
571             },
572              
573             # State 11: unit -> unit TIMES . unit
574             {PREFIX => sh(1),
575             NUMBER => sh(2),
576             NAME => sh(3),
577             FUNDAMENTAL => sh(4),
578             FRACTION => sh(5),
579             '(' => sh(6),
580             'unit' => go(15),
581             'constant' => go(8),
582             },
583              
584             # State 12: unit -> unit DIVIDE . unit
585             {PREFIX => sh(1),
586             NUMBER => sh(2),
587             NAME => sh(3),
588             FUNDAMENTAL => sh(4),
589             FRACTION => sh(5),
590             '(' => sh(6),
591             'unit' => go(16),
592             'constant' => go(8),
593             },
594              
595             # State 13: unit -> unit . TIMES unit
596             # unit -> unit . DIVIDE unit
597             # unit -> constant unit .
598             # unit -> unit . NUMBER
599             {NUMBER => sh(10), # Shift-reduce conflict resolved in favor of shift
600             TIMES => sh(11), # Shift-reduce conflict resolved in favor of shift
601             DIVIDE => sh(12), # Shift-reduce conflict resolved in favor of shift
602             POWER => sh(17),
603             _ => ['reduce', 2, 'unit', \&con_multiply],
604             },
605              
606             # State 14: unit => '(' unit ')' .
607 0         0 { _ => ['reduce', 3, 'unit', sub {$_[1]}] },
608              
609             # State 15: unit -> unit . TIMES unit
610             # unit -> unit TIMES unit .
611             # unit -> unit . DIVIDE unit
612             # unit -> unit . NUMBER
613             {NUMBER => sh(10), # Shift-reduce conflict resolved in favor of shift
614 5064         12410 _ => ['reduce', 3, 'unit', sub {unit_multiply($_[0], $_[2])}],
615             },
616              
617             # State 16: unit -> unit . TIMES unit
618             # unit -> unit DIVIDE unit .
619             # unit -> unit . DIVIDE unit
620             # unit -> unit . NUMBER
621             {NUMBER => sh(10), # Shift-reduce conflict resolved in favor of shift
622 1382         3591 _ => ['reduce', 3, 'unit', sub{unit_divide($_[2], $_[0])}],
623             },
624              
625             # State 17: unit -> unit POWER . unit
626             {
627             NUMBER => sh(2),
628             'constant' => go(18),
629             },
630              
631             # State 18: unit -> unit POWER
632             {
633             NUMBER => sh(2),
634 1         4 _ => ['reduce', 3, 'unit', sub{ unit_power($_[0], $_[2])}],
  0         0  
635             },
636              
637             );
638              
639 1         7 $actions[98] = {EOF => go($eof_state + 1),};
640 1         1961 $actions[99] = {_ => ['accept']};
641             }
642              
643             sub ABORT {
644 0     0   0 $PARSE_ERROR = shift;
645             }
646              
647             sub parse_unit {
648 7100     7100   13451 my ($class, $s) = @_;
649 7100         14067 my $tokens = lex($s);
650 7100         10300 my $STATE = 0;
651 7100         9450 my (@state_st, @val_st);
652              
653 7100         11102 $PARSE_ERROR = undef;
654              
655 7100         14298 debug_p( '-' x 50 . "\n" );
656             # Now let's run the parser
657 7100         10692 for (;;) {
658 68462 100       129495 return Zero if $PARSE_ERROR;
659              
660 68402         171217 debug_p( "Tokens: " . join( ' ', map { "<$_>" } @$tokens ) );
  98465         240727  
661 68402 100       170125 my $la = @$tokens ? token_type($tokens->[0]) : 'EOF';
662 68402         173007 debug_p( "Now in state $STATE. Lookahead type is $la." );
663 68402         194333 debug_p( "State stack is (@state_st)." );
664 68402         106615 my $actiontab = $actions[$STATE];
665 68402   66     215641 my $action = $actiontab->{$la} || $actiontab->{_};
666 68402 50       121466 unless ($action) {
667 0         0 $PARSE_ERROR = 'Syntax error';
668 0         0 return Zero;
669             }
670              
671 68402         152322 my ($primary, @actargs) = @$action;
672 68402         218218 debug_p( "Next thing: $primary (@actargs)" );
673 68402 100       185296 if ($primary eq 'accept') {
    100          
    100          
    50          
674 7040         27520 return $val_st[0]; # Success!
675             } elsif ($primary eq 'shift') {
676 21517         37438 my $token = shift @$tokens;
677 21517         37834 my $val = token_value($token);
678 21517         54820 debug_p( "shift: token: <$token> val: <$val>" );
679 21517         39699 push @val_st, $val;
680 21517         33632 push @state_st, $STATE;
681 21517         30666 $STATE = $actargs[0];
682 21517         46717 debug_p( "shift: state: <$STATE>" );
683             } elsif ($primary eq 'goto') {
684 7040         15951 $STATE = $actargs[0];
685             } elsif ($primary eq 'reduce') {
686 32805         64539 my ($n_args, $result_type, $semantic) = @actargs;
687 32805         40592 my @arglist;
688 32805         71606 while ($n_args--) {
689 47222         72927 push @arglist, pop @val_st;
690 47222         108365 $STATE = pop @state_st;
691             }
692 32805 100       71533 my $result = $semantic ? &$semantic(@arglist) : $arglist[0];
693 32805         50400 push @val_st, $result;
694 32805         46987 push @state_st, $STATE;
695 32805         72556 debug_p( "reduce: Value stack is " . dumper( \@val_st ) );
696              
697 32805         314893 debug_p( "Post-reduction state is $STATE." );
698              
699             # Now look for 'goto' actions
700 32805         78283 my $goto = $actions[$STATE]{$result_type};
701 32805 50 33     159673 unless ($goto && $goto->[0] eq 'goto') {
702 0         0 die "No post-reduction goto in state $STATE for $result_type.\n";
703             }
704 32805         89716 debug_p( "goto $goto->[1]" );
705 32805         139127 $STATE = $goto->[1];
706             } else {
707 0         0 die "Bad primary $primary";
708             }
709             }
710             }
711              
712              
713             sub lex {
714 7100     7100   11222 my ($s) = @_;
715 7100         9798 my $N = '(?:\d+\.\d+|\d+|\.\d+)(?:[eE][-+]?\d+)?';
716              
717 7100         105119 my @t = split /(
718             (?: \*.\* | !.! ) # Special 'new unit' symbol
719             | [()*-] # Symbol
720             | \s*(?:\/|\bper\b)\s* # Division
721             | (?:$N\|$N) # Fraction
722             | $N # Decimal number
723             | \d+ # Integer
724             | [A-Za-z_][A-Za-z_.]* # identifier
725             | \s+ # White space
726             )/ox, $s;
727 7100 50       16953 @t = grep {defined and $_ ne ''} @t; # Discard empty and all-white tokens
  47154         155694  
728 7100         33109 debug_p( "Input: $s Tokens: @t" );
729 7100         15952 \@t;
730             }
731              
732             sub token_type {
733 35112     35112   59930 my ($token) = @_;
734 35112 50       64257 return $token->[0] if ref $token;
735 35112 50       96531 return $token if $token =~ /[()]/;
736 35112 100       111845 return 'TIMES' if $token =~ /^\s+$/;
737 22520 100       46324 return 'FUNDAMENTAL' if $token =~ m/\A(!.!|\*.\*)\z/;
738 22340 100       81908 return 'DIVIDE' if $token =~ /^\s*(\/|\bper\b)\s*$/;
739 18716 100 66     77149 return 'TIMES' if $token eq '*' || $token eq '-';
740 16416 100       34954 return 'FRACTION' if $token =~ /^\d+\|\d+$/;
741 15615 100       43106 return 'NUMBER' if $token =~ /^[.\d]/;
742 9206 50       17748 return 'POWER' if $token eq '^';
743 9206         15984 return 'NAME';
744             }
745              
746             sub token_value {
747 21517     21517   36760 my ($token) = @_;
748 21517         48089 debug_p( "TOKEN VALUE: <$token>" );
749              
750 21517         26566 my $rc = do {
751 21517 100       91935 if( $token =~ /^([()*\/-]|\s*\bper\b\s*)$/ ) { $token }
  2162 100       4202  
752             elsif( $token =~ /(\d+)\|(\d+)/ ) {
753 841 50       4156 if( $2 == 0 ) {
754 0         0 ABORT("Zero denominator in fraction '$token'");
755 0         0 return 0;
756             }
757 841         3610 $1/$2;
758             }
759 18514         39145 else { $token }
760             };
761              
762 21517         41906 return $rc; # Perl takes care of the others.
763             }
764              
765             =encoding utf8
766              
767             =head1 NAME
768              
769             units - conversion program
770              
771             =head1 SYNOPSIS
772              
773             % units
774             You have: in
775             You want: cm
776             * 2.54
777             / 0.393701
778              
779             % units [-f /path/to/unittab] [want_unit have_unit]
780              
781             =head1 OPTIONS
782              
783             -f Use specified definition file
784             --version Display version information
785              
786             =head1 DESCRIPTION
787              
788             NOTE: This does not handle the Gnu units format (https://www.gnu.org/software/units/).
789              
790             The units program converts quantities expressed in various scales to their
791             equivalents in other scales. The units program can only handle multiplicative
792             or affine scale changes (except for temperature). It works in one of
793             two ways. If given two units as command line arguments, it reports
794             the conversion. Otherwise, it operates interactively by prompting the user
795             for inputs:
796              
797             % units
798             You have: meters
799             You want: feet
800             * 3.2808399
801             / 0.3048
802              
803             You have: cm3
804             You want: gallons
805             * 0.00026417205
806             / 3785.4118
807              
808             You have: meters/s
809             You want: furlongs/fortnight
810             * 6012.8848
811             / 0.00016630952
812              
813             You have: 1|2 inch
814             You want: cm
815             * 1.27
816             / 0.78740157
817              
818             You have: 98.6 F
819             You want: C
820             98.6 F is 37 C
821              
822             You have: -40 C
823             You want: F
824             -40 C is -40 F
825              
826             Powers of units can be specified using the '^' character as shown in the
827             example, or by simple concatenation: 'cm3' is equivalent to 'cm^3'.
828              
829             Multiplication of units can be specified by using spaces, a dash or an asterisk.
830              
831             Division of units is indicated by the slash ('/'). Note that multiplication has
832             a higher precedence than division, so 'm/s/s' is the same as 'm/s^2' or 'm/s s'.
833             Division of numbers must be indicated using the vertical bar ('|'). To convert
834             half a meter, you would write '1|2 meter'. If you write '1/2 meter' then the
835             units program would interpret that as equivalent to '0.5/meter'.
836              
837             If you enter incompatible unit types, the units program will print a message
838             indicating that the units are not conformable and it will display the reduced
839             form for each unit:
840              
841             You have: ergs/hour
842             You want: fathoms kg^2 / day
843             conformability error
844             2.7777778e-11 kg m^2 / sec^3
845             2.1166667e-05 kg^2 m / sec
846              
847             The conversion information is read from a units data file. The default file
848             includes definitions for most familiar units, abbreviations and metric
849             prefixes. Some constants of nature included are:
850              
851             pi ratio of circumference to diameter
852             c speed of light
853             e charge on an electron
854             g acceleration of gravity
855             force same as g
856             mole Avogadro's number
857             water pressure per unit height of water
858             mercury pressure per unit height of mercury
859             au astronomical unit
860              
861             The unit 'pound' is a unit of mass. Compound names are run together so 'pound
862             force' is a unit of force. The unit 'ounce' is also a unit of mass. The fluid
863             ounce is 'floz'. British units that differ from their US counterparts are
864             prefixed with 'br', and currency is prefixed with its country name:
865             'belgiumfranc', 'britainpound'. When searching for a unit, if the specified
866             string does not appear exactly as a unit name, then units will try to remove a
867             trailing 's' or a trailing 'es' and check again for a match.
868              
869             To find out what units are available read the standard units file. If you want
870             to add your own units you can supply your own file. If no standard file
871             exists and you do not supply your own file, this program uses internal
872             data.
873              
874             A unit is specified on a single line by giving its name and an equivalence. Be
875             careful to define new units in terms of old ones so that reductions leads to
876             primitive units. Primitive (a.k.a. fundamental) units are defined
877             with a string of three characters which begin and end with '*' or '!'.
878             Note that the units program will not detect infinite loops that could be
879             caused by careless unit definitions.
880              
881             Comments in the unit definition file begin with a '/' or '#' character at
882             the beginning of a line. Once the parser has successfully parsed a
883             unit name and it's definition, the remainder of the line is ignored.
884             This makes it safe to include in-line comments.
885              
886             Prefixes are defined in the same way as standard units, but with a
887             trailing dash at the end of the prefix name. If a unit is not found even
888             after removing trailing 's' or 'es', then it will be checked against the
889             list of prefixes. Prefixes will be removed until a legal base unit is
890             identified.
891              
892             Here is an example of a short units file that defines some basic units.
893              
894             m !a!
895             sec ***
896             Temperature ***
897             micro- 1e-6
898             minute 60 sec
899             hour 60 min
900             inch 0.0254 m
901             ft 12 inches
902             mile 5280 ft
903              
904             If a "Temperature" dimension is defined in the units table, then
905             you can define various temperature scales as units by specifying
906             the code needed to convert the unit to or from Kelvin.
907             The built-in units table has definitions for Kelvin (K), Celsius (C),
908             Fahrenheit (F) and Rankine (R).
909              
910             The code consists of a perl hash containing the keys 'to' and 'from'.
911             The values are the subroutine definitions necessary to convert a
912             value from Kelvin to the specified unit, or to Kelvin from the the
913             specified unit. See the built-in unit table for examples.
914              
915             A temperature unit entered at "You have" without any constant
916             preceding it will default to zero units. This is in contrast to
917             non-temperature units, where a bare unit name is assumed to mean 1
918             unit. Also, for temperatures only, negative constants are allowed.
919             This enables, for example, a conversation between -40C and F.
920              
921             =head1 AUTHOR
922              
923             Mark-Jason Dominus, C<< >>
924              
925             Temperature support by Gary Puckering, C<< >>
926              
927             Currently maintained in https://github.com/briandfoy/PerlPowerTools
928              
929             =head1 BUGS
930              
931             =head1 COPYRIGHT and LICENSE
932              
933             This program is copyright (c) M-J. Dominus (1996, 1999).
934              
935             This program is free software; you can redistribute it and/or modify it under
936             the terms of the GNU General Public License as published by the Free Software
937             Foundation; either version 2 of the License, or (at your option) any later
938             version, or under Perl's 'Artistic License'.
939              
940             This program is distributed in the hope that it will be useful, but WITHOUT ANY
941             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
942             PARTICULAR PURPOSE. See the GNU General Public License for more details.
943              
944             =cut
945              
946 1     1   9 no warnings qw(void);
  1         1  
  1         106  
947             __PACKAGE__;
948              
949             __END__