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
};
### 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] == [] );
### PEG helper functions:
# Consume a regular expression rooted at the start of .remainder, or emit empty;
# 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
;
### 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)) ;
### 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 ;
### The task expressed as a series of assertions
# 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