Skip to content

Instantly share code, notes, and snippets.

@xkr47
Last active November 11, 2019 12:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xkr47/ed2ca66636c487bf6ef44385b88c0e7e to your computer and use it in GitHub Desktop.
Save xkr47/ed2ca66636c487bf6ef44385b88c0e7e to your computer and use it in GitHub Desktop.
Simple calculator in https://raku.org/ language, with support for time durations & base 2..36

Example session:

2+3

Result: 5₁₀

5d

Result: 5,00:00:00 = 5.000000 days
         120:00:00 = 120.000000 hours
           7200:00 = 7200.000000 minutes
            432000.000000000 seconds

a = 13:22:19

Result: 0,13:22:19 = 0.557164 days
          13:22:19 = 13.371944 hours
            802:19 = 802.316667 minutes
             48139.000000000 seconds

a * 2 + a / 5

Result: 1,05:25:05.8 = 1.225762 days
          29:25:05.8 = 29.418278 hours
           1765:05.8 = 1765.096667 minutes
            105905.800000000 seconds

a * 2.237723

Result: 1,05:55:21.747497 = 1.246779 days
          29:55:21.747497 = 29.922708 hours
           1795:21.747497 = 1795.362458 minutes
            107721.747497000 seconds

5d / 2h

Result: 60₁₀

0b101010

Result: 101010₂
        42₁₀

xkr47_36

Result: 11010111001000011101000111₂
        56395591₁₀
        XKR47₃₆

0o100

Result: 1000000₂
        100₈
        64₁₀
        1S₃₆

(5s * 2 + 9m) / 2 + 2h

Result: 0,02:04:35 = 0.086516 days
           2:04:35 = 2.076389 hours
            124:35 = 124.583333 minutes
              7475.000000000 seconds

5µs * 10000

Result: 0,00:00:00.05 = 0.000001 days
           0:00:00.05 = 0.000014 hours
              0:00.05 = 0.000833 minutes
                 0.050000000 seconds
#!/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.";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment