|
#!/home/xkr47/rakudo/bin/perl6 |
|
|
|
use v6; |
|
|
|
my %vars; |
|
my %bases; |
|
|
|
enum ValueType < TNumber TDuration >; |
|
|
|
class Value { |
|
has Real $.val; |
|
has ValueType $.type; |
|
method gist { |
|
return "[" ~ self.type ~ " " ~ self.val ~ "]"; |
|
} |
|
} |
|
|
|
grammar Parser { |
|
rule TOP { |
|
<.ws> <expr> { make $<expr>.made } |
|
} |
|
rule expr { |
|
<assign> { |
|
make $<assign>.made |
|
} |
|
} |
|
rule assign { |
|
<symbol> '=' <assign> { |
|
make(%vars{$<symbol>} = $<assign>.made) |
|
} |
|
|| <addsub> { |
|
make $<addsub>.made |
|
} |
|
} |
|
rule addsub { |
|
<muldiv>+ % <add-op> { |
|
make self.do_calc($/, $<muldiv>, $<add-op>) |
|
} |
|
} |
|
token add-op { <[ + - ]> } |
|
|
|
rule muldiv { |
|
<infix>+ % <mul-op> { |
|
make self.do_calc($/, $<infix>, $<mul-op>) |
|
} |
|
} |
|
token mul-op { <[ * / ]> } |
|
|
|
rule infix { |
|
'-' <atom> { |
|
my $a = $<atom>.made; |
|
make Value.new(val => -$a.val, type => $a.type); |
|
} |
|
| '+' <atom> { |
|
make $<atom>.made |
|
} |
|
| <atom> { |
|
make $<atom>.made |
|
} |
|
} |
|
rule atom { |
|
<numb> { |
|
make $<numb>.made |
|
} |
|
| <time> { |
|
%bases{10}=1; # need to have something in case you do e.g. "duration / duration" |
|
make $<time>.made |
|
} |
|
| <symbol> { |
|
my $var = $<symbol>; |
|
my $v = %vars{$var}; |
|
if !defined($v) { |
|
self.panic($/, "No such var $var"); |
|
} |
|
make $v; |
|
} |
|
| '(' <expr> ')' { |
|
make $<expr>.made |
|
} |
|
} |
|
token numi { \d+ } |
|
token num { \d+[\.\d*]? | \.\d+ } |
|
token numb { |
|
<num> { %bases{10}=1; make Value.new(val => $<num>.Real, type => TNumber) } |
|
| 0<[xX]> $<val> = ( <[0..9 a..f A..F]>+ [ \. <[0..9 a..f A..F]>* ]? ) { %bases{16}=1; make Value.new(val => $<val>.Str.parse-base(16), type => TNumber) } |
|
| 0<[dD]> $<val> = ( <[0..9 ab]>+ [ \. <[0..9 ab]>* ]? ) { %bases{12}=1; make Value.new(val => $<val>.Str.parse-base(12), type => TNumber) } |
|
| 0<[oO]> $<val> = ( <[0..7]>+ [ \. <[0..7]>* ]? ) { %bases{8}=1; make Value.new(val => $<val>.Str.parse-base(8), type => TNumber) } |
|
| 0<[sS]> $<val> = ( <[0..5]>+ [ \. <[0..5]>* ]? ) { %bases{6}=1; make Value.new(val => $<val>.Str.parse-base(6), type => TNumber) } |
|
| 0<[qQ]> $<val> = ( <[0..3]>+ [ \. <[0..3]>* ]? ) { %bases{4}=1; make Value.new(val => $<val>.Str.parse-base(4), type => TNumber) } |
|
| 0<[tT]> $<val> = ( <[0..2]>+ [ \. <[0..2]>* ]? ) { %bases{3}=1; make Value.new(val => $<val>.Str.parse-base(3), type => TNumber) } |
|
| 0<[bB]> $<val> = ( <[01]>+ [ \. <[01]>* ]? ) { %bases{2}=1; make Value.new(val => $<val>.Str.parse-base(2), type => TNumber) } |
|
| $<val> = ( <[0..9 a..z A..Z]>+ [ \. <[0..9 a..z A..Z]>* ]? ) _ $<base> = <numi> { %bases{$<base>.Str.Int}=1; make Value.new(val => $<val>.Str.parse-base($<base>.Str.Int), type => TNumber) } |
|
} |
|
token int { \d+ } |
|
token time { |
|
<numb> \h* $<unit> = ( w | d | h | m | s | <[muµn]>s ) { |
|
my $mul = do given $<unit> { |
|
when 'w' { 60 * 60 * 24 * 7 } |
|
when 'd' { 60 * 60 * 24 } |
|
when 'h' { 60 * 60 } |
|
when 'm' { 60 } |
|
when 's' { 1 } |
|
when 'ms' { 0.001 } |
|
when 'us' { 0.000001 } |
|
when 'µs' { 0.000001 } |
|
when 'ns' { 0.000000001 } |
|
} |
|
make Value.new(val => $<numb>.Str.Real * $mul, type => TDuration) |
|
} |
|
| [ $<d> = <num> ',' ]? $<h> = <num> ':' $<m> = <num> [ 'm' | ':' $<s> = <num> ]? { |
|
my $d = defined($<d>) ?? $<d>.Str.Real !! 0; |
|
my $h = $<h>.Str.Real; |
|
my $m = $<m>.Str.Real; |
|
my $s = defined($<s>) ?? $<s>.Str.Real !! 0; |
|
make Value.new(val => (($d * 24 + $h) * 60 + $m) * 60 + $s, type => TDuration); |
|
} |
|
| $<m> = <num> ':' $<s> = <num> 's'? { |
|
my $m = $<m>.Str.Real; |
|
my $s = $<s>.Str.Real; |
|
make Value.new(val => $m * 60 + $s, type => TDuration); |
|
} |
|
} |
|
token symbol { <[a..z A..Z _]> <[a..z A..Z _ 0..9]>* } |
|
|
|
method ensureSameType($/, Value $l, Value $r, Match $op) { |
|
if ($l.type != $r.type) { |
|
self.panic($/, $l.gist ~ " and " ~ $r.gist ~ " are of different types for $op"); |
|
} |
|
} |
|
|
|
method ensureType($/, Value $v, ValueType $type, Match $op) { |
|
if ($v.type != $type) { |
|
self.panic($/, $v.gist ~ " must be a " ~ $type ~ " for $op"); |
|
} |
|
} |
|
|
|
# do_calc() forked from https://examples.p6c.dev/categories/interpreters/calc.html |
|
method do_calc($/, $operands, $operators) { |
|
my $res = $operands[0].made; |
|
my $n = $operands.elems; |
|
loop (my $i = 1; $i < $n; $i++) { |
|
my $op = $operators[$i - 1]; |
|
my $num = $operands[$i].made; |
|
|
|
given $op { |
|
when '+' { |
|
self.ensureSameType($/, $res, $num, $op); |
|
$res = Value.new(val => $res.val + $num.val, type => $res.type); |
|
} |
|
when '-' { |
|
self.ensureSameType($/, $res, $num, $op); |
|
$res = Value.new(val => $res.val - $num.val, type => $res.type); |
|
} |
|
when '*' { |
|
if ($res.type == TNumber) { |
|
$res = Value.new(val => $res.val * $num.val, type => $num.type); |
|
} else { |
|
self.ensureType($/, $num, TNumber, $op); |
|
$res = Value.new(val => $res.val * $num.val, type => $res.type); |
|
} |
|
} |
|
when '/' { |
|
if ($res.type == TNumber) { |
|
self.ensureType($/, $num, TNumber, $op); |
|
} |
|
my $type = $res.type == $num.type ?? TNumber !! $res.type; |
|
$res = Value.new(val => $res.val / $num.val, type => $type); |
|
} |
|
default { |
|
self.panic($/, "bad op $op"); |
|
} |
|
} |
|
} |
|
make $res; |
|
} |
|
|
|
# from https://examples.p6c.dev/categories/interpreters/calc.html |
|
method panic($/, $msg) { |
|
my $c = $/.CURSOR; |
|
my $pos := $c.pos; |
|
die "$msg found at pos $pos"; |
|
} |
|
} |
|
|
|
for lines() { |
|
my $a; |
|
try $a = Parser.parse: $_; |
|
if $! { |
|
say "Parse failed: ", $!; |
|
} elsif $/ { |
|
my $res = $a.made; |
|
given $res.type { |
|
when TNumber { |
|
my $pref = "Result: "; |
|
for %bases.keys.map({$_.Int}).sort -> $base { |
|
say $pref,$res.val.base($base), $base.Str.ords.map({ chr($_ - 48 + 8320) }).join(""); |
|
$pref = " "; |
|
} |
|
} |
|
when TDuration { |
|
my $d = $res.val; |
|
my $s2 = $d; |
|
my $s = $d % 60; |
|
$d = $d.Int div 60; |
|
my $m2 = $d; |
|
my $m = $d % 60; |
|
$d = $d div 60; |
|
my $h2 = $d; |
|
my $h = $d % 24; |
|
$d = $d div 24; |
|
my $sf = $s.round(10**-9); |
|
$sf = "0$sf" if ($sf ~~ /^\d[\.|$]/); |
|
print |
|
sprintf("Result: %d,%02d:%02d:%s = %f days\n", $d, $h, $m, $sf, $res.val / 86400), |
|
sprintf(" %4d:%02d:%s = %f hours\n", $h2, $m, $sf, $res.val / 3600), |
|
sprintf(" %7d:%s = %f minutes\n", $m2, $sf, $res.val / 60), |
|
sprintf(" %20.9f seconds\n", $s2), |
|
} |
|
} |
|
} else { |
|
say "Parse failed."; |
|
} |
|
} |