grammar UnitsDat; # This is a grammar for the units(1) units.dat database # TODO: parse nonlinear unit definitions my Str @units; my Str @prefixes; my Hash of Num %unitsdef; my Str @fund_units; my Str @fund_unitless; rule TOP { [ | | ]? ? } # Multiplication is implied by whitespace in units.dat # but * can be used also, just treat it as whitespace token ws { \h+ | \h* '*' \h* } token comment { '#' \N* } token float { $ := [ '-'? \d+ [ '.' \d+ ]? ] [ e $ := [ '-'? \d+ ] ]? { $ = $ * 10 ** $ } } rule fraction { $ := [\d+] '|' $ := [\d+] { $ = $ / $ } } rule builtin_func { | sin '(' ')' { $ = sin $ } | cos '(' ')' { $ = cos $ } | tan '(' ')' { $ = tan $ } | ln '(' ')' { $ = log $ } | log '(' ')' { $ = log $, 10 } | log2 '(' ')' { $ = log $, 2 } | exp '(' ')' { $ = exp $ } | acos '(' ')' { $ = acos $ } | asin '(' ')' { $ = asin $ } | atan '(' ')' { $ = atan $ } } rule basicnumber { | { $ = $ } | { $ = $ } | { $ = $ } } rule number_paren { '(' { $ = $ } ')' } rule number_pow { [ { $ = $[0] } | { $ = $[0] } ] [ '^' { $ **= $[1] } | '^' { $ **= $[1] } | '^' { $ **= $ } ] } rule number_mult { [ { $ = $[0] } | { $ = $[0] } | { $ = $[0] } ] [ { $ *= $[1] } | { $ *= $[1] } | { $ *= $[1] } | { $ *= $ } | '/' { $ /= $[1] } | '/' { $ /= $[1] } | '/' { $ /= $[1] } | '/' { $ /= $ } ] } rule number_add { [ { $ = $[0] } | { $ = $[0] } | { $ = $[0] } | { $ = $[0] } ] [ '+' { $ += $[1] } | '+' { $ += $[1] } | '+' { $ += $[1] } | '+' { $ += $[1] } | '+' { $ += $ } | '-' { $ -= $[1] } | '-' { $ -= $[1] } | '-' { $ -= $[1] } | '-' { $ -= $[1] } | '-' { $ -= $ } ] } rule number { | { $ = $ } | { $ = $ } | { $ = $ } | { $ = $ } | { $ = $ } } token fundamental_unit { ^^ $ := [\S+] \h+ '!' [ dimensionless { $ := True } ]? { @units.push: $; if($) { @fund_unitless.push: $ } else { @fund_units.push: $; } } } token prefix { ^^ $ := [\S+] '-' \h+ { @prefixes.push: $; %unitsdef{$} = $; } } token unitname { { $ = 1 } [ $ := [ | @prefixes ] { $ *= %unitsdef{$} } ]* $ := [ | @units ] s? } token unit { ^^ $+ ] \h+ { @units.push: $; %unitsdef{$} = $; } } rule basicunitdef { [ { $ = $ } | { $ = $; $ = $; $ = 1; } [ '^' { $ = $ } ]? { ${$} += $ } | '-' { $ = $; $ = $; $ = 1; } [ '^' { $ = $ } ]? { ${$} -= $; $ *= -1; } ] { $ = $ } } rule unitdef_mult { [ { $ = $[0] } | { $ = $[0] } ] [ { $ = multdef($, $[1], 1) } | { $ = multdef($, $[1], 1) } | { $ = multdef($, $, 1) } | '/' { $ = multdef($, $[1], -1) } | '/' { $ = multdef($, $[1], -1) } | '/' { $ = multdef($, $, -1) } ] } rule unitdef_add { [ { $ = $[0] } | { $ = $[0] } | { $ = $[0] } ] [ '+' { $ = adddef($, $[1], 1) } | '+' { $ = adddef($, $[1], 1) } | '+' { $ = adddef($, $[1], 1) } | '+' { $ = adddef($, $, 1) } | '-' { $ = adddef($, $[1], -1) } | '-' { $ = adddef($, $[1], -1) } | '-' { $ = adddef($, $[1], -1) } | '-' { $ = adddef($, $[1], -1) } ] } rule unitdef_paren { '(' { $ = $ } ')' } rule unitdef { | { $ = defreduce($) } | { $ = defreduce($) } | { $ = defreduce($) } | { $ = defreduce($) } } # reduce a unit definition to fundamental units method defreduce(Num %def is copy --> Hash of Num) { until all(%def.k) eq any('factor', @fund_units, @fund_unitless) { for %def.kv -> (my Str $u, my Num $p) { next if $u eq any('factor', @fund_units, @fund_unitless); %def{$u}.:delete; %def *= %unitsdef{$u} ** $p; for %unitsdef{$u}.kv -> (my Str $cu, my Num $cp) { next if $cu eq 'factor'; %def{$cu} += $cp * $p; } } } return %def; } method adddef(Num %def1, Num %def2, Int $sign --> Hash of Num) { my Num %def1c = defreduce(%def1); my Num %def2c = defreduce(%def2); my Int $f1 = %def1c; my Int $f2 = %def2c; %def1c.:delete; %def2c.:delete; if %def1c !eqv %def2c { warn "Can't add incompatible units: { %def1c } : { %def2c }\n"; return undef; } my Num %def = %def1c; %def = $f1 + $sign * $f2; return %def; } method multdef(Num %def1, Num %def2, Int $sign --> Hash of Num) { my Num %def = %def1; %def *= $sign * %def2; for %def2.kv -> (my Str $u, my Num $p) { next if $u eq 'factor'; $def{$u} += $sign * $p; } return %def; }