Chemical calculator/jq

From Rosetta Code

<lang jq> def masses: {

H: 1.008,
He: 4.002602,
Li: 6.94,
Be: 9.0121831,
B:  10.81,
C:  12.011,
N:  14.007,
O:  15.999,
F:  18.998403163,
Ne: 20.1797,
Na: 22.98976928,
Mg: 24.305,
Al: 26.9815385,
Si: 28.085,
P:  30.973761998,
S:  32.06,
Cl: 35.45,
Ar: 39.948,
K:  39.0983,
Ca: 40.078,
Sc: 44.955908,
Ti: 47.867,
V:  50.9415,
Cr: 51.9961,
Mn: 54.938044,
Fe: 55.845,
Co: 58.933194,
Ni: 58.6934,
Cu: 63.546,
Zn: 65.38,
Ga: 69.723,
Ge: 72.630,
As: 74.921595,
Se: 78.971,
Br: 79.904,
Kr: 83.798,
Rb: 85.4678,
Sr: 87.62,
Y:  88.90584,
Zr: 91.224,
Nb: 92.90637,
Mo: 95.95,
Ru: 101.07,
Rh: 102.90550,
Pd: 106.42,
Ag: 107.8682,
Cd: 112.414,
In: 114.818,
Sn: 118.710,
Sb: 121.760,
Te: 127.60,
I:  126.90447,
Xe: 131.293,
Cs: 132.90545196,
Ba: 137.327,
La: 138.90547,
Ce: 140.116,
Pr: 140.90766,
Nd: 144.242,
Pm: 145,
Sm: 150.36,
Eu: 151.964,
Gd: 157.25,
Tb: 158.92535,
Dy: 162.500,
Ho: 164.93033,
Er: 167.259,
Tm: 168.93422,
Yb: 173.054,
Lu: 174.9668,
Hf: 178.49,
Ta: 180.94788,
W:  183.84,
Re: 186.207,
Os: 190.23,
Ir: 192.217,
Pt: 195.084,
Au: 196.966569,
Hg: 200.592,
Tl: 204.38,
Pb: 207.2,
Bi: 208.98040,
Po: 209,
At: 210,
Rn: 222,
Fr: 223,
Ra: 226,
Ac: 227,
Th: 232.0377,
Pa: 231.03588,
U:  238.02891,
Np: 237,
Pu: 244,
Am: 243,
Cm: 247,
Bk: 247,
Cf: 251,
Es: 252,
Fm: 257,
Ubn: 299,
Uue: 315

};

      1. Primary PEG operators:

def star(E): (E | star(E)) // .; def plus(E): E | (plus(E) // . ); def optional(E): E // .; def amp(E): . as $in | E | $in; def neg(E): select( [E] == [] );

      1. PEG helper functions:
  1. Consume a regular expression rooted at the start of .remainder, or emit empty;
  2. on success, update .remainder and set .match but do NOT update .result

def consume($re):

 # on failure, match yields empty
 (.remainder | match("^" + $re)) as $match
 | .remainder |= .[$match.length :]
 | .match = $match.string ;

def parse($re):

 consume($re)
 | .result = .result + [.match] ;

def parseNumber($re):

 consume($re)
 | .result = .result + [.match|tonumber] ;

def literal($s):

 select(.remainder | startswith($s))
 | .result += [$s]
 | .remainder |= .[$s | length :] ;

def nonempty: select( (.remainder | length) > 0 );

def box(E):

  ((.result = null) | E) as $e
  | .remainder = $e.remainder
  | .result += [$e.result]  # the magic sauce
  ;
      1. The PEG grammar:

def Element:

 parse("(?<e>^[A-Z][a-z]*)"); # greedy

def Number: parseNumber("^[0-9]+"); # greedy

def EN: Element | optional(Number);

def Parenthesized:

  consume("[(]")
  | box( (plus(EN) | optional(Parenthesized)) // (Parenthesized | plus(EN)) )
  | consume("[)]")
  | Number;

def Formula:

    (plus(EN) | Parenthesized | Formula)
 // (plus(EN) | optional(Parenthesized))
 // (Parenthesized | optional(Formula)) ;
      1. input: .result

def eval:

 # eval an array beginning with an Element
 def evalENs:
   if length==0 then 0
   elif length==1 then .[0] | eval
   elif .[1]|type == "string" then (.[0]|eval) + (.[1:]|eval)
   elif .[1]|type == "number" then (.[0]|eval) * .[1] + (.[2:]|eval)
   elif .[1]|type == "array" then (.[0]|eval) + (.[1:]|eval)
   else eval
   end ;
 . as $in |
 if type == "string" then masses[.]
 elif type != "array" then "eval was called on \(.)"| error
 elif length == 0 then 0
 elif .[0]|type == "array" then (.[0]|eval) * .[1] + (.[2:]|eval)
 else evalENs
 end ;


      1. The task expressed as a series of assertions
  1. A `debug` statement has been retained to show the parsed result.

def molar_mass(formula):

 {remainder: formula} | Formula |  .result | debug | eval;

def assert(a;b):

 if (a - b)|length > 1e-3 then "\(a) != \(b)" else empty end;

def task: assert( 1.008; molar_mass("H")), # hydrogen assert( 2.016; molar_mass("H2")), # hydrogen gas assert( 18.015; molar_mass("H2O")), # water assert( 34.014; molar_mass("H2O2")), # hydrogen peroxide assert( 34.014; molar_mass("(HO)2")), # hydrogen peroxide assert( 142.036; molar_mass("Na2SO4")), # sodium sulfate assert( 84.162; molar_mass("C6H12")), # cyclohexane assert( 186.295; molar_mass("COOH(C(CH3)2)3CH3")), # butyric or butanoic acid assert( 176.124; molar_mass("C6H4O2(OH)4")), # vitamin C assert( 386.664; molar_mass("C27H46O")), # cholesterol assert( 315  ; molar_mass("Uue")) # ununennium

task </lang>