Chemical calculator

From Rosetta Code
Revision as of 07:06, 15 November 2020 by rosettacode>Gerard Schildberger (→‎{{header|REXX}}: added the ability to have a common name for the chemical formula, presented the output better, reduced output clutter.)
Chemical calculator is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Given a molecule's chemical formula, calculate the   molar mass.


Introduction
  • A molecule consists of atoms.   E.g. water, H2O, has two hydrogen atoms and one oxygen atom
  • The mass of   H2O   is   1.008 * 2 + 15.999 = 18.015
  • An atom name consists of one upper-case letter followed by zero, one or two lower-case letters.
    • H       (hydrogen)
    • He     (helium)
    • Uue   (ununennium)
  • The number of atoms is stated behind the atom or atom group
  • An atom group is specified using parenthesis.   E.g. butyric acid, (CH3)2CHCOOH, has two CH3 groups
  • A group may contain other groups, e.g.   COOH(C(CH3)2)3CH3


Background
  • The mass is dimensionless,   it is relative to   1/12   of Carbon-12
  • Carbon-12   has exactly   6 protons,   6 electrons,   and   6 neutrons
  • One mole of   H2O   has the mass 18.015 grams
  • One mole is defined as exactly   6.02214076×1023   particles


The above number is known as the   Avogadro constant,   which is named by the International Bureau of Weights and Measures (IBPM);
the initials are taken from   Bureau International des Poids et Mesures,   the official name of the bureau.

An older name for   Avogadro constant   is   Avogadro number.


Atom masses

A mapping between most recognized element names and atomic mass follows in comma separated value format:

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
K,39.0983
Ar,39.948
Ca,40.078
Sc,44.955908
Ti,47.867
V,50.9415
Cr,51.9961
Mn,54.938044
Fe,55.845
Ni,58.6934
Co,58.933194
Cu,63.546
Zn,65.38
Ga,69.723
Ge,72.63
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.9055
Pd,106.42
Ag,107.8682
Cd,112.414
In,114.818
Sn,118.71
Sb,121.76
I,126.90447
Te,127.6
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.5
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.9804
Po,209
At,210
Rn,222
Fr,223
Ra,226
Ac,227
Pa,231.03588
Th,232.0377
Np,237
U,238.02891
Am,243
Pu,244
Cm,247
Bk,247
Cf,251
Es,252
Fm,257
Ubn,299
Uue,315


Examples

<lang python>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</lang>


Reference



ALGOL W

Algol W has fixed length strings and no regular expressions, this parses the molecule with a simple recursive descent parser.
Some error checking is included. <lang algolw>begin

   % calculates the molar mass of the specified molecule %
   real procedure molar_mass ( string(256) value molecule ) ; begin
       string(1) c;
       integer   chPos, chMax;
       logical   hadError;
       real      mass;
       % reports a syntax error in the molecule starting at position chPos %
       procedure syntaxError( string(80) value message ) ; begin
           integer mPos;
           write( "Syntax error in molecule: " );
           mPos := 0;
           while mPos < 80 and message( mPos // 1 ) not = "." do begin
               writeon( message( mPos // 1 ) );
               mPos := mPos + 1
           end while_not_end_of_message ;
           write( "    " );for i := 0 until chMax     do writeon( molecule( i // 1 ) );
           write( "    " );for i := 0 until chPos - 1 do writeon( " " );
           writeon( "^" );
           % ensure parsing stops %
           hadError := true;
           chPos    := chMax * 2
       end syntaxError ;
       % gets the next character from the molecule %
       procedure nextChar ; begin
           chPos := chPos + 1;
           c     := if chPos > chMax then " " else molecule( chPos // 1 )
       end nextChar ;
       % parses a compound: a sequence of element names and bracketed compounds, each with   %
       % an optional trailing repeat count                                                   %
       real procedure parseCompound ; begin
           real mass, itemMass;
           % parses an element symbol from the molecule and returns its mass                 %
           real procedure parseAtom ; begin
               string(3)       symbol;
               reference(Atom) element;
               symbol := c;
               nextChar;
               if c >= "a" and c <= "z" then begin
                   symbol( 1 // 1 ) := c;
                   nextChar;
                   if c >= "a" and c <= "z" then begin
                       symbol( 2 // 1 ) := c;
                       nextChar
                   end if_have_lc_letter
               end if_have_lc_letter ;
               % find the element in the table %
               element := atoms( decode( symbol( 0 // 1 ) ) - decode( "A" ) );
               while element not = null and aSymbol(element) not = symbol do element := aNext(element);
               if element not = null then % found the element % aMass(element)
               else begin % unknown element %
                   chPos := chPos - 1;
                   syntaxError( "Unrecognised element." );
                   0
               end
           end parseAtom ;
           mass := 0;
           while not hadError and ( ( c >= "A" and c <= "Z" ) or c = "(" ) do begin
               if c >= "A" and c <= "Z" then itemMass := parseAtom
               else if c = "(" then begin % bracketed group %
                   nextChar;
                   itemMass := parseCompound;
                   if chPos > chMax or molecule( chPos // 1 ) not = ")" then syntaxError( "Expected "")""." );
                   nextChar
               end ;
               if c >= "0" and c <= "9" then begin % have a repeat count %
                   integer count;
                   count := 0;
                   while not hadError and c >= "0" and c <= "9" do begin
                       count := ( count * 10 ) + ( decode( c ) - decode( "0" ) );
                       nextChar
                   end while_not_end_of_number ;
                   itemMass := itemMass * count
               end if_have_a_digit ;
               mass := mass + itemMass
           end while_still_parseing ;
           mass
       end parseCompound ;
       hadError := false;
       % find the end of the molecule %
       chMax := 255;
       while chMax > 0 and molecule( chMax // 1 ) = " " do chMax := chMax - 1;
       % parse the molecule %
       chPos := -1;
       nextChar;
       mass := parseCompound;
       if chPos <= chMax then syntaxError( "Unexpected text after the molecule." );
       mass
   end molar_mass ;
   % record to hold element symbols and masses %
   record Atom( string(3) aSymbol; real aMass; reference(Atom) aNext );
   % hash table of atome, hash is the first character of the symbol - "A" %
   reference(Atom) array atoms ( 0 :: 25 ); for i := 0 until 25 do atoms( i ) := null;
   begin % setup element symbols and masses as specified in the task %
       % adds an element and its mass to the atoms table %
       procedure A ( string(3) value symbol; real value mass ) ; begin
           integer index;
           index := decode( symbol( 0 // 1 ) ) - decode( "A" );
           atoms( index ) := Atom( symbol, mass, atoms( index ) )
       end A ;
       procedure Lanthanides ; begin
           A("La",138.90547);A("Ce",140.116 );A("Pr",140.90766);A("Nd",144.242  );A("Pm",145     );
           A("Sm",150.36   );A("Eu",151.964 );A("Gd",157.25   );A("Tb",158.92535);A("Dy",162.5   );
           A("Ho",164.93033);A("Er",167.259 );A("Tm",168.93422);A("Yb",173.054  );A("Lu",174.9668);
       end Lanthanides ;
       procedure Actinides ; begin
           A("Ac",227      );A("Th",232.0377);A("Pa",231.03588);A("U", 238.02891);A("Np",237     );
           A("Pu",244      );A("Am",243     );A("Cm",247      );A("Bk",247      );A("Cf",251     );
           A("Es",252      );A("Fm",257     ); % Md           % %, No           %  % Lr          %
       end Actinides ;
       A("Li", 6.94       );A("Na",22.98976928 );A("K", 39.0983  );A("Rb", 85.4678 );A("Cs",132.90545196);A("Fr", 223 );
       A("Be", 9.0121831  );A("Mg",24.305      );A("Ca",40.078   );A("Sr", 87.62   );A("Ba",137.327     );A("Ra", 226 );
                                                 A("Sc",44.955908);A("Y",  88.90584);   Lanthanides;         Actinides;
                                                 A("Ti",47.867   );A("Zr", 91.224  );A("Hf",178.49      ); % Rf       %
                                                 A("V", 50.9415  );A("Nb", 92.90637);A("Ta",180.94788   ); % Db       %
                                                 A("Cr",51.9961  );A("Mo", 95.95   );A("W", 183.84      ); % Sg       %
                                                 A("Mn",54.938044); % Tc           % A("Re",186.207     ); % Bh       %
                                                 A("Fe",55.845   );A("Ru",101.07   );A("Os",190.23      ); % Hs       %
                                                 A("Co",58.933194);A("Rh",102.9055 );A("Ir",192.217     ); % Mt       %
                                                 A("Ni",58.6934  );A("Pd",106.42   );A("Pt",195.084     ); % Ds       %
                                                 A("Cu",63.546   );A("Ag",107.8682 );A("Au",196.966569  ); % Rg       %
                                                 A("Zn",65.38    );A("Cd",112.414  );A("Hg",200.592     ); % Cn       %
       A("B", 10.81       );A("Al",26.9815385  );A("Ga",69.723   );A("In",114.818  );A("Tl",204.38      ); % Nh       %
       A("C", 12.011      );A("Si",28.085      );A("Ge",72.63    );A("Sn",118.71   );A("Pb",207.2       ); % Fl       %
       A("N", 14.007      );A("P", 30.973761998);A("As",74.921595);A("Sb",121.76   );A("Bi",208.9804    ); % Ms       %
       A("O", 15.999      );A("S", 32.06       );A("Se",78.971   );A("Te",127.6    );A("Po",209         ); % Lv       %
       A("F", 18.998403163);A("Cl",35.45       );A("Br",79.904   );A("I", 126.90447);A("At",210         ); % Ts       %
       A("Ne",20.1797     );A("Ar",39.948      );A("Kr",83.798   );A("Xe",131.293  );A("Rn",222         ); % Og       %
       % ---------------- first period elements ---> % A("H",    1.008);A("He",   4.002602);
       % --- hypothetical eigth period elements ---> % A("Uue",315    );A("Ubn",299       );
   end;
   begin % test cases %
       procedure test( real value expectedMass; string(256) value molecule ) ; begin
           real mass, diff;
           mass := molar_mass( molecule );
           write( r_format := "A", r_d := 3, r_w := 9
                , molecule( 0 // 20 ), ":", mass
                );
           diff := expectedMass - mass;
           if diff > 1'-12 or diff < -1'-12 then writeon( r_format := "A", r_d := 2, r_w := 9
                                                        , " expected:", expectedMass
                                                        )
       end text ;                 
       test( 1.008, "H" ); test( 2.016, "H2" );         test(  18.015, "H2O"   );
       test( 142.03553856000002, "Na2SO4"            ); test(  84.162, "C6H12" );
       test( 186.29499999999996, "COOH(C(CH3)2)3CH3" ); test( 350.45,  "UueCl" );
   end

end.</lang>

Output:
H                   :    1.008
H2                  :    2.016
H2O                 :   18.015
Na2SO4              :  142.035
C6H12               :   84.162
COOH(C(CH3)2)3CH3   :  186.295
UueCl               :  350.450

C

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>

typedef char *string;

typedef struct node_t {

   string symbol;
   double weight;
   struct node_t *next;

} node;

node *make_node(string symbol, double weight) {

   node *nptr = malloc(sizeof(node));
   if (nptr) {
       nptr->symbol = symbol;
       nptr->weight = weight;
       nptr->next = NULL;
       return nptr;
   }
   return NULL;

}

void free_node(node *ptr) {

   if (ptr) {
       free_node(ptr->next);
       ptr->next = NULL;
       free(ptr);
   }

}

node *insert(string symbol, double weight, node *head) {

   node *nptr = make_node(symbol, weight);
   nptr->next = head;
   return nptr;

}

node *dic; void init() {

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

}

double lookup(string symbol) {

   for (node *ptr = dic; ptr; ptr = ptr->next) {
       if (strcmp(symbol, ptr->symbol) == 0) {
           return ptr->weight;
       }
   }
   printf("symbol not found: %s\n", symbol);
   return 0.0;

}

double total(double mass, int count) {

   if (count > 0) {
       return mass * count;
   }
   return mass;

}

double total_s(string sym, int count) {

   double mass = lookup(sym);
   return total(mass, count);

}

double evaluate_c(string expr, size_t *pos, double mass) {

   int count = 0;
   if (expr[*pos] < '0' || '9' < expr[*pos]) {
       printf("expected to find a count, saw the character: %c\n", expr[*pos]);
   }
   for (; expr[*pos]; (*pos)++) {
       char c = expr[*pos];
       if ('0' <= c && c <= '9') {
           count = count * 10 + c - '0';
       } else {
           break;
       }
   }
   return total(mass, count);

}

double evaluate_p(string expr, size_t limit, size_t *pos) {

   char sym[4];
   int sym_pos = 0;
   int count = 0;
   double sum = 0.0;
   for (; *pos < limit && expr[*pos]; (*pos)++) {
       char c = expr[*pos];
       if ('A' <= c && c <= 'Z') {
           if (sym_pos > 0) {
               sum += total_s(sym, count);
               sym_pos = 0;
               count = 0;
           }
           sym[sym_pos++] = c;
           sym[sym_pos] = 0;
       } else if ('a' <= c && c <= 'z') {
           sym[sym_pos++] = c;
           sym[sym_pos] = 0;
       } else if ('0' <= c && c <= '9') {
           count = count * 10 + c - '0';
       } else if (c == '(') {
           if (sym_pos > 0) {
               sum += total_s(sym, count);
               sym_pos = 0;
               count = 0;
           }
           (*pos)++; // skip past the paren
           double mass = evaluate_p(expr, limit, pos);
           sum += evaluate_c(expr, pos, mass);
           (*pos)--; // neutralize the position increment
       } else if (c == ')') {
           if (sym_pos > 0) {
               sum += total_s(sym, count);
               sym_pos = 0;
               count = 0;
           }
           (*pos)++;
           return sum;
       } else {
           printf("Unexpected character encountered: %c\n", c);
       }
   }
   if (sym_pos > 0) {
       sum += total_s(sym, count);
   }
   return sum;

}

double evaluate(string expr) {

   size_t limit = strlen(expr);
   size_t pos = 0;
   return evaluate_p(expr, limit, &pos);

}

void test(string expr) {

   double mass = evaluate(expr);
   printf("%17s -> %7.3f\n", expr, mass);

}

int main() {

   init();
   test("H");
   test("H2");
   test("H2O");
   test("H2O2");
   test("(HO)2");
   test("Na2SO4");
   test("C6H12");
   test("COOH(C(CH3)2)3CH3");
   test("C6H4O2(OH)4");
   test("C27H46O");
   test("Uue");
   free_node(dic);
   dic = NULL;
   return 0;

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

C#

Translation of: D

<lang csharp>using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

namespace ChemicalCalculator {

   class Program {
       static Dictionary<string, double> atomicMass = new Dictionary<string, double>() {
           {"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},
           {"Uue", 315},
           {"Ubn", 299},
       };
       static double Evaluate(string s) {
           s += "[";
           double sum = 0.0;
           string symbol = "";
           string number = "";
           for (int i = 0; i < s.Length; ++i) {
               var c = s[i];
               if ('@' <= c && c <= '[') {
                   // @, A-Z
                   int n = 1;
                   if (number != "") {
                       n = int.Parse(number);
                   }
                   if (symbol != "") {
                       sum += atomicMass[symbol] * n;
                   }
                   if (c == '[') {
                       break;
                   }
                   symbol = c.ToString();
                   number = "";
               } else if ('a' <= c && c <= 'z') {
                   symbol += c;
               } else if ('0' <= c && c <= '9') {
                   number += c;
               } else {
                   throw new Exception(string.Format("Unexpected symbol {0} in molecule", c));
               }
           }
           return sum;
       }
       // Taken from return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
       static string ReplaceFirst(string text, string search, string replace) {
           int pos = text.IndexOf(search);
           if (pos < 0) {
               return text;
           }
           return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
       }
       static string ReplaceParens(string s) {
           char letter = 's';
           while (true) {
               var start = s.IndexOf('(');
               if (start == -1) {
                   break;
               }
               for (int i = start + 1; i < s.Length; ++i) {
                   if (s[i] == ')') {
                       var expr = s.Substring(start + 1, i - start - 1);
                       var symbol = string.Format("@{0}", letter);
                       s = ReplaceFirst(s, s.Substring(start, i + 1 - start), symbol);
                       atomicMass[symbol] = Evaluate(expr);
                       letter++;
                       break;
                   }
                   if (s[i] == '(') {
                       start = i;
                       continue;
                   }
               }
           }
           return s;
       }
       static void Main() {
           var molecules = new string[]{
               "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
               "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
           };
           foreach (var molecule in molecules) {
               var mass = Evaluate(ReplaceParens(molecule));
               Console.WriteLine("{0,17} -> {1,7:0.000}", molecule, mass);
           }
       }
   }

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

C++

Translation of: C#

<lang cpp>#include <iomanip>

  1. include <iostream>
  2. include <map>
  3. include <string>
  4. include <vector>

std::map<std::string, double> atomicMass = {

   {"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},
   {"Uue", 315},
   {"Ubn", 299},

};

double evaluate(std::string s) {

   s += '[';
   double sum = 0.0;
   std::string symbol;
   std::string number;
   for (auto c : s) {
       if ('@' <= c && c <= '[') {
           // @, A-Z
           int n = 1;
           if (number != "") {
               n = stoi(number);
           }
           if (symbol != "") {
               sum += atomicMass[symbol] * n;
           }
           if (c == '[') {
               break;
           }
           symbol = c;
           number = "";
       } else if ('a' <= c && c <= 'z') {
           symbol += c;
       } else if ('0' <= c && c <= '9') {
           number += c;
       } else {
           std::string msg = "Unexpected symbol ";
           msg += c;
           msg += " in molecule";
           throw std::runtime_error(msg);
       }
   }
   return sum;

}

std::string replaceFirst(const std::string &text, const std::string &search, const std::string &replace) {

   auto pos = text.find(search);
   if (pos == std::string::npos) {
       return text;
   }
   auto beg = text.substr(0, pos);
   auto end = text.substr(pos + search.length());
   return beg + replace + end;

}

std::string replaceParens(std::string s) {

   char letter = 'a';
   while (true) {
       auto start = s.find("(");
       if (start == std::string::npos) {
           break;
       }
       for (size_t i = start + 1; i < s.length(); i++) {
           if (s[i] == ')') {
               auto expr = s.substr(start + 1, i - start - 1);
               std::string symbol = "@";
               symbol += letter;
               auto search = s.substr(start, i + 1 - start);
               s = replaceFirst(s, search, symbol);
               atomicMass[symbol] = evaluate(expr);
               letter++;
               break;
           }
           if (s[i] == '(') {
               start = i;
               continue;
           }
       }
   }
   return s;

}

int main() {

   std::vector<std::string> molecules = {
       "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
       "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
   };
   for (auto molecule : molecules) {
       auto mass = evaluate(replaceParens(molecule));
       std::cout << std::setw(17) << molecule << " -> " << std::setw(7) << std::fixed << std::setprecision(3) << mass << '\n';
   }
   return 0;

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

CoffeeScript

No Regular Expression

<lang coffeescript>ATOMIC_MASS = {H:1.008,C:12.011,O:15.999,Na:22.98976928,S:32.06,Uue:315}

molar_mass = (s) -> result = i = 0 member = (a,c) -> a <= s[i] <= c next = -> i += 1 s[i-1] while i < s.length if s[i] == '(' then result += '+' + next() else if s[i] == ')' then result += next() else if member '0','9' result += '*' result += next() while member '0','9' else if member 'A','Z' name = next() name += next() while member 'a','z' result += '+' + ATOMIC_MASS[name] parseFloat eval(result).toFixed 3

assert 1.008, molar_mass 'H' assert 2.016, molar_mass 'H2' assert 18.015, molar_mass 'H2O' assert 34.014, molar_mass 'H2O2' assert 34.014, molar_mass '(HO)2' assert 142.036, molar_mass 'Na2SO4' assert 84.162, molar_mass 'C6H12' assert 186.295, molar_mass 'COOH(C(CH3)2)3CH3' assert 176.124, molar_mass 'C6H4O2(OH)4' # Vitamin C assert 386.664, molar_mass 'C27H46O' # Cholesterol assert 315, molar_mass 'Uue'</lang>

Regular Expression

Translation of: Julia

<lang coffeescript>ATOMIC_MASS = {H:1.008,C:12.011,O:15.999,Na:22.98976928,S:32.06,Uue:315}

mul = (match, p1, offset, string) -> '*' + p1 add = (match, p1, offset, string) -> if p1 == '(' then return '+' + p1 "+#{ATOMIC_MASS[p1]}"

molar_mass = (s) -> s = s.replace /(\d+)/g, mul s = s.replace /([A-Z][a-z]{0,2}|\()/g, add parseFloat(eval(s).toFixed(3))

assert 1.008, molar_mass('H') assert 2.016, molar_mass('H2') assert 18.015, molar_mass('H2O') assert 34.014, molar_mass('H2O2') assert 34.014, molar_mass('(HO)2') assert 142.036, molar_mass('Na2SO4') assert 84.162, molar_mass('C6H12') assert 186.295, molar_mass('COOH(C(CH3)2)3CH3') assert 176.124, molar_mass('C6H4O2(OH)4') # Vitamin C assert 386.664, molar_mass('C27H46O') # Cholesterol assert 315, molar_mass('Uue')</lang>

D

Translation of: Go

<lang d>import std.array; import std.conv; import std.format; import std.stdio; import std.string;

double[string] atomicMass; static this() {

   atomicMass = [
       "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,
       "Uue": 315,
       "Ubn": 299,
   ];

}

double evaluate(string s) {

   s ~= "["; // add end of string marker
   double sum = 0.0;
   string symbol, number;
   for (int i = 0; i < s.length; ++i) {
       auto c = s[i];
       if (c >= '@' && c <= '[') {
           // @, A-Z, [
           int n = 1;
           if (number != "") {
               n = to!int(number);
           }
           if (symbol != "") {
               sum += atomicMass[symbol] * n;
           }
           if (c == '[') {
               break;
           }
           symbol = c.to!string;
           number = "";
       } else if (c >= 'a' && c <= 'z') {
           symbol ~= c;
       } else if (c >= '0' && c <= '9') {
           number ~= c;
       } else {
           throw new Exception("Unexpected symbol " ~ c ~ " in molecule");
       }
   }
   return sum;

}

string replaceParens(string s) {

   char letter = 'a';
   while (true) {
       auto start = s.indexOf('(');
       if (start == -1) {
           break;
       }
       restart:
       for (auto i = start + 1; i < s.length; ++i) {
           if (s[i] == ')') {
               auto expr = s[start + 1 .. i];
               auto symbol = format("@%c", letter);
               s = s.replaceFirst(s[start .. i + 1], symbol);
               atomicMass[symbol] = evaluate(expr);
               letter++;
               break;
           }
           if (s[i] == '(') {
               start = i;
               continue restart;
           }
       }
   }
   return s;

}

void main() {

   auto molecules = [
       "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
       "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
   ];
   foreach (molecule; molecules) {
       auto mass = evaluate(replaceParens(molecule));
       writefln("%17s -> %7.3f", molecule, mass);
   }
   writeln(atomicMass);

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

Delphi

Translation of: Go

<lang Delphi> program ChemicalCalculator;

{$APPTYPE CONSOLE}

{$R *.res}

uses

 System.SysUtils,
 System.Generics.Collections;

{$I AtomicMass.inc }

type

 TAtomicMass = class(TDictionary<string, Double>)
 public
   constructor Create(Keys: array of string; Values: array of Double); overload;
 end;

{ TAtomicMass }

constructor TAtomicMass.Create(Keys: array of string; Values: array of Double); var

 i: Integer;

begin

 inherited Create;
 Assert(length(Keys) = Length(Values), 'Keys and values must have the same size');
 if Length(Keys) = 0 then
   exit;
 for i := 0 to High(Keys) do
   Add(Keys[i], Values[i]);

end;

var

 AtomicMassData: TAtomicMass;

function Evaluate(s: string): Double; var

 sum: Double;
 symbol: string;
 number: string;
 c: char;
 i, n: Integer;

begin

 s := s + '[';
 symbol := ;
 number := ;
 for i := 1 to s.Length do
 begin
   c := s[i];
   if ('@' <= c) and (c <= '[') then
   begin
     n := 1;
     if not number.IsEmpty then
       n := StrToInt(number);
     if not symbol.IsEmpty then
       sum := sum + AtomicMassData[symbol] * n;
     if c = '[' then
       Break;
     symbol := c;
     number := ;
     Continue;
   end;
   if ('a' <= c) and (c <= 'z') then
   begin
     symbol := symbol + c;
     Continue;
   end;
   if ('0' <= c) and (c <= '9') then
   begin
     number := number + c;
     Continue;
   end;
   raise Exception.Create('Unexpected symbol ' + c + ' in molecule');
 end;
 Result := sum;

end;

function ReplaceFirst(text, search, replace: string): string; var

 pos: Integer;

begin

 pos := text.IndexOf(search);
 if (pos < 0) then
   Exit(text);
 Result := text.Substring(0, pos) + replace + text.Substring(pos + search.Length);

end;

function ReplaceParens(s: string): string; var

 letter: Char;
 start: Integer;
 i: Integer;
 expr, symbol: string;

begin

 letter := 's';
 while True do
 begin
   start := s.IndexOf('(');
   if (start = -1) then
     Break;
   for i := start + 1 to s.Length - 1 do
   begin
     if s[i + 1] = ')' then
     begin
       expr := s.Substring(start + 1, i - start - 1);
       symbol := '@' + letter;
       s := ReplaceFirst(s, s.Substring(start, i + 1 - start), symbol);
       if not (AtomicMassData.ContainsKey(symbol)) then
         AtomicMassData.Add(symbol, Evaluate(expr))
       else
         AtomicMassData[symbol] := Evaluate(expr);
       inc(letter);
       Break;
     end;
     if (s[i + 1] = '(') then
       start := i;
   end;
 end;
 Result := s;

end;


var

 molecules: array of string;
 i: Integer;
 mass: Double;

begin

 molecules := ['H', 'H2', 'H2O', 'H2O2', '(HO)2', 'Na2SO4', 'C6H12',
   'COOH(C(CH3)2)3CH3', 'C6H4O2(OH)4', 'C27H46O', 'Uue'];
 AtomicMassData := TAtomicMass.Create(ATOMIC_MASS_SYMBOL, ATOMIC_MASS_VALUE);
 for i := 0 to 10 do
 begin
   mass := Evaluate(ReplaceParens(molecules[i]));
   Writeln(format('%17s -> %7s', [molecules[i], FormatFloat('####.000',mass)]));
 end;
 AtomicMassData.Free;
 readln;

end. </lang> Include file with Atomic Mass Constants (AtomicMass.inc). <lang Delphi> const

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

</lang>


Factor

Works with: Factor version 0.98

<lang factor>USING: assocs compiler.units definitions grouping infix.parser infix.private kernel math.functions math.parser multiline peg.ebnf qw sequences splitting strings words words.constant ; IN: rosetta-code.chemical-calculator

<<  ! Do the stuff inside << ... >> at parse time.

HEREDOC: END 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 END

! Make constants from the pairs in the above string. " \n" split harvest 2 <groups> [

   first2 [
       [ "rosetta-code.chemical-calculator" create-word ] dip
       string>number define-constant
   ] 2curry with-compilation-unit

] each

>>

! Evaluate a string like "+C+O+O+H+(+C+(+C+H*3)*2)*3+C+H*3" ! Note that the infix vocabulary can work with the constants ! defined above.

eval-infix ( seq -- n )
   build-infix-ast infix-codegen prepare-operand call( -- x ) ;

! A grammar to put a + before every element/left paren and a * ! before every number and then evaluate the expression. EBNF: molar-mass [=[

 number = [0-9]+ => "" like "*" prepend 
 elt = [A-Z] [a-z]? [a-z]? => sift "" like "+" prepend 
 lparen = "(" => "" like "+" prepend 
 any = . => 1string 
 mass = (elt|number|lparen|any)+ => concat eval-infix       

]=]

! assert= doesn't work due to floating point weirdness. ERROR: failed-assertion expected +/- got ;

approx-assert= ( x y epsilon -- )
   3dup ~ [ 3drop ] [ swap failed-assertion ] if ;
chemical-calculator-demo ( -- )
   {
       { 1.008 "H" }
       { 2.016 "H2" }
       { 18.015 "H2O" }
       { 142.03553856 "Na2SO4" }
       { 84.16200000000001 "C6H12" }
       { 186.295 "COOH(C(CH3)2)3CH3" }
   } [ molar-mass 1e-5 approx-assert= ] assoc-each ;

MAIN: chemical-calculator-demo</lang>

No assertion errors.

Fōrmulæ

In this page you can see the solution of this task.

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text (more info). Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for transportation effects more than visualization and edition.

The option to show Fōrmulæ programs and their results is showing images. Unfortunately images cannot be uploaded in Rosetta Code.

Go

This doesn't use regular expressions, RPN or eval (which Go doesn't have). It's just a simple molar mass evaluator written from scratch. <lang go>package main

import (

   "fmt"
   "strconv"
   "strings"

)

var atomicMass = map[string]float64{

   "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,
   "Uue": 315,
   "Ubn": 299,

}

func replaceParens(s string) string {

   var letter byte = 'a'
   for {
       start := strings.IndexByte(s, '(')
       if start == -1 {
           break
       }
   restart:
       for i := start + 1; i < len(s); i++ {
           if s[i] == ')' {
               expr := s[start+1 : i]
               symbol := fmt.Sprintf("@%c", letter)
               s = strings.Replace(s, s[start:i+1], symbol, 1)
               atomicMass[symbol] = evaluate(expr)
               letter++
               break
           }
           if s[i] == '(' {
               start = i
               goto restart
           }
       }
   }
   return s

}

func evaluate(s string) float64 {

   s += string('[') // add end of string marker
   var symbol, number string
   sum := 0.0
   for i := 0; i < len(s); i++ {
       c := s[i]
       switch {
       case c >= '@' && c <= '[': // @, A-Z, [
           n := 1
           if number != "" {
               n, _ = strconv.Atoi(number)
           }
           if symbol != "" {
               sum += atomicMass[symbol] * float64(n)
           }
           if c == '[' {
               break
           }
           symbol = string(c)
           number = ""
       case c >= 'a' && c <= 'z':
           symbol += string(c)
       case c >= '0' && c <= '9':
           number += string(c)
       default:
           panic(fmt.Sprintf("Unexpected symbol %c in molecule", c))
       }
   }
   return sum

}

func main() {

   molecules := []string{
       "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3",
       "C6H4O2(OH)4", "C27H46O", "Uue",
   }
   for _, molecule := range molecules {
       mass := evaluate(replaceParens(molecule))
       fmt.Printf("%17s -> %7.3f\n", molecule, mass)
   }

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

Java

Translation of: Kotlin

<lang java>import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern;

public class ChemicalCalculator {

   private static final Map<String, Double> atomicMass = new HashMap<>();
   static {
       atomicMass.put("H", 1.008);
       atomicMass.put("He", 4.002602);
       atomicMass.put("Li", 6.94);
       atomicMass.put("Be", 9.0121831);
       atomicMass.put("B", 10.81);
       atomicMass.put("C", 12.011);
       atomicMass.put("N", 14.007);
       atomicMass.put("O", 15.999);
       atomicMass.put("F", 18.998403163);
       atomicMass.put("Ne", 20.1797);
       atomicMass.put("Na", 22.98976928);
       atomicMass.put("Mg", 24.305);
       atomicMass.put("Al", 26.9815385);
       atomicMass.put("Si", 28.085);
       atomicMass.put("P", 30.973761998);
       atomicMass.put("S", 32.06);
       atomicMass.put("Cl", 35.45);
       atomicMass.put("Ar", 39.948);
       atomicMass.put("K", 39.0983);
       atomicMass.put("Ca", 40.078);
       atomicMass.put("Sc", 44.955908);
       atomicMass.put("Ti", 47.867);
       atomicMass.put("V", 50.9415);
       atomicMass.put("Cr", 51.9961);
       atomicMass.put("Mn", 54.938044);
       atomicMass.put("Fe", 55.845);
       atomicMass.put("Co", 58.933194);
       atomicMass.put("Ni", 58.6934);
       atomicMass.put("Cu", 63.546);
       atomicMass.put("Zn", 65.38);
       atomicMass.put("Ga", 69.723);
       atomicMass.put("Ge", 72.630);
       atomicMass.put("As", 74.921595);
       atomicMass.put("Se", 78.971);
       atomicMass.put("Br", 79.904);
       atomicMass.put("Kr", 83.798);
       atomicMass.put("Rb", 85.4678);
       atomicMass.put("Sr", 87.62);
       atomicMass.put("Y", 88.90584);
       atomicMass.put("Zr", 91.224);
       atomicMass.put("Nb", 92.90637);
       atomicMass.put("Mo", 95.95);
       atomicMass.put("Ru", 101.07);
       atomicMass.put("Rh", 102.90550);
       atomicMass.put("Pd", 106.42);
       atomicMass.put("Ag", 107.8682);
       atomicMass.put("Cd", 112.414);
       atomicMass.put("In", 114.818);
       atomicMass.put("Sn", 118.710);
       atomicMass.put("Sb", 121.760);
       atomicMass.put("Te", 127.60);
       atomicMass.put("I", 126.90447);
       atomicMass.put("Xe", 131.293);
       atomicMass.put("Cs", 132.90545196);
       atomicMass.put("Ba", 137.327);
       atomicMass.put("La", 138.90547);
       atomicMass.put("Ce", 140.116);
       atomicMass.put("Pr", 140.90766);
       atomicMass.put("Nd", 144.242);
       atomicMass.put("Pm", 145.0);
       atomicMass.put("Sm", 150.36);
       atomicMass.put("Eu", 151.964);
       atomicMass.put("Gd", 157.25);
       atomicMass.put("Tb", 158.92535);
       atomicMass.put("Dy", 162.500);
       atomicMass.put("Ho", 164.93033);
       atomicMass.put("Er", 167.259);
       atomicMass.put("Tm", 168.93422);
       atomicMass.put("Yb", 173.054);
       atomicMass.put("Lu", 174.9668);
       atomicMass.put("Hf", 178.49);
       atomicMass.put("Ta", 180.94788);
       atomicMass.put("W", 183.84);
       atomicMass.put("Re", 186.207);
       atomicMass.put("Os", 190.23);
       atomicMass.put("Ir", 192.217);
       atomicMass.put("Pt", 195.084);
       atomicMass.put("Au", 196.966569);
       atomicMass.put("Hg", 200.592);
       atomicMass.put("Tl", 204.38);
       atomicMass.put("Pb", 207.2);
       atomicMass.put("Bi", 208.98040);
       atomicMass.put("Po", 209.0);
       atomicMass.put("At", 210.0);
       atomicMass.put("Rn", 222.0);
       atomicMass.put("Fr", 223.0);
       atomicMass.put("Ra", 226.0);
       atomicMass.put("Ac", 227.0);
       atomicMass.put("Th", 232.0377);
       atomicMass.put("Pa", 231.03588);
       atomicMass.put("U", 238.02891);
       atomicMass.put("Np", 237.0);
       atomicMass.put("Pu", 244.0);
       atomicMass.put("Am", 243.0);
       atomicMass.put("Cm", 247.0);
       atomicMass.put("Bk", 247.0);
       atomicMass.put("Cf", 251.0);
       atomicMass.put("Es", 252.0);
       atomicMass.put("Fm", 257.0);
       atomicMass.put("Uue", 315.0);
       atomicMass.put("Ubn", 299.0);
   }
   private static double evaluate(String s) {
       String sym = s + "[";
       double sum = 0.0;
       StringBuilder symbol = new StringBuilder();
       String number = "";
       for (int i = 0; i < sym.length(); ++i) {
           char c = sym.charAt(i);
           if ('@' <= c && c <= '[') {
               // @, A-Z, [
               int n = 1;
               if (!number.isEmpty()) {
                   n = Integer.parseInt(number);
               }
               if (symbol.length() > 0) {
                   sum += atomicMass.getOrDefault(symbol.toString(), 0.0) * n;
               }
               if (c == '[') {
                   break;
               }
               symbol = new StringBuilder(String.valueOf(c));
               number = "";
           } else if ('a' <= c && c <= 'z') {
               symbol.append(c);
           } else if ('0' <= c && c <= '9') {
               number += c;
           } else {
               throw new RuntimeException("Unexpected symbol " + c + " in molecule");
           }
       }
       return sum;
   }
   private static String replaceParens(String s) {
       char letter = 'a';
       String si = s;
       while (true) {
           int start = si.indexOf('(');
           if (start == -1) {
               break;
           }
           for (int i = start + 1; i < si.length(); ++i) {
               if (si.charAt(i) == ')') {
                   String expr = si.substring(start + 1, i);
                   String symbol = "@" + letter;
                   String pattern = Pattern.quote(si.substring(start, i + 1));
                   si = si.replaceFirst(pattern, symbol);
                   atomicMass.put(symbol, evaluate(expr));
                   letter++;
                   break;
               }
               if (si.charAt(i) == '(') {
                   start = i;
               }
           }
       }
       return si;
   }
   public static void main(String[] args) {
       List<String> molecules = List.of(
           "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
           "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
       );
       for (String molecule : molecules) {
           double mass = evaluate(replaceParens(molecule));
           System.out.printf("%17s -> %7.3f\n", molecule, mass);
       }
   }

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

Julia

Note that Julia's 64-bit floating point gets a slightly different result for one of the assertions, hence a small change in the last example. The function uses Julia's own language parser to evaluate the compound as an arithmetic expression. <lang julia>const H = 1.008 const He = 4.002602 const Li = 6.94 const Be = 9.0121831 const B = 10.81 const C = 12.011 const N = 14.007 const O = 15.999 const F = 18.998403163 const Ne = 20.1797 const Na = 22.98976928 const Mg = 24.305 const Al = 26.9815385 const Si = 28.085 const P = 30.973761998 const S = 32.06 const Cl = 35.45 const Ar = 39.948 const K = 39.0983 const Ca = 40.078 const Sc = 44.955908 const Ti = 47.867 const V = 50.9415 const Cr = 51.9961 const Mn = 54.938044 const Fe = 55.845 const Co = 58.933194 const Ni = 58.6934 const Cu = 63.546 const Zn = 65.38 const Ga = 69.723 const Ge = 72.630 const As = 74.921595 const Se = 78.971 const Br = 79.904 const Kr = 83.798 const Rb = 85.4678 const Sr = 87.62 const Y = 88.90584 const Zr = 91.224 const Nb = 92.90637 const Mo = 95.95 const Ru = 101.07 const Rh = 102.90550 const Pd = 106.42 const Ag = 107.8682 const Cd = 112.414 const In = 114.818 const Sn = 118.710 const Sb = 121.760 const Te = 127.60 const I = 126.90447 const Xe = 131.293 const Cs = 132.90545196 const Ba = 137.327 const La = 138.90547 const Ce = 140.116 const Pr = 140.90766 const Nd = 144.242 const Pm = 145 const Sm = 150.36 const Eu = 151.964 const Gd = 157.25 const Tb = 158.92535 const Dy = 162.500 const Ho = 164.93033 const Er = 167.259 const Tm = 168.93422 const Yb = 173.054 const Lu = 174.9668 const Hf = 178.49 const Ta = 180.94788 const W = 183.84 const Re = 186.207 const Os = 190.23 const Ir = 192.217 const Pt = 195.084 const Au = 196.966569 const Hg = 200.592 const Tl = 204.38 const Pb = 207.2 const Bi = 208.98040 const Po = 209 const At = 210 const Rn = 222 const Fr = 223 const Ra = 226 const Ac = 227 const Th = 232.0377 const Pa = 231.03588 const U = 238.02891 const Np = 237 const Pu = 244 const Am = 243 const Cm = 247 const Bk = 247 const Cf = 251 const Es = 252 const Fm = 257


function molar_mass(s)

   s = replace(s, r"\d+" => (x) -> "*" * x)
   s = replace(s, r"[A-Z][a-z]{0,2}|\(" => (x) ->"+" * x)
   eval(Meta.parse(s))

end

@assert 1.008 == molar_mass("H") @assert 2.016 == molar_mass("H2") @assert 18.015 == molar_mass("H2O") @assert 142.03553856000002 == molar_mass("Na2SO4") @assert 84.162 == molar_mass("C6H12") @assert 186.29500000000002 == molar_mass("COOH(C(CH3)2)3CH3") </lang> No assertion errors.

Kotlin

Translation of: D

<lang scala>var atomicMass = mutableMapOf(

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

)

fun evaluate(s: String): Double {

   val sym = "$s["
   var sum = 0.0
   var symbol = ""
   var number = ""
   for (i in 0 until sym.length) {
       val c = sym[i]
       if (c in '@'..'[') {
           // @, A-Z, [
           var n = 1
           if (number != "") {
               n = Integer.parseInt(number)
           }
           if (symbol != "") {
               sum += atomicMass.getOrElse(symbol) { 0.0 } * n
           }
           if (c == '[') {
               break
           }
           symbol = c.toString()
           number = ""
       } else if (c in 'a'..'z') {
           symbol += c
       } else if (c in '0'..'9') {
           number += c
       } else {
           throw RuntimeException("Unexpected symbol $c in molecule")
       }
   }
   return sum

}

fun replaceParens(s: String): String {

   var letter = 'a'
   var si = s
   while (true) {
       var start = si.indexOf('(')
       if (start == -1) {
           break
       }
       for (i in start + 1 until si.length) {
           if (si[i] == ')') {
               val expr = si.substring(start + 1 until i)
               val symbol = "@$letter"
               si = si.replaceFirst(si.substring(start until i + 1), symbol)
               atomicMass[symbol] = evaluate(expr)
               letter++
               break
           }
           if (si[i] == '(') {
               start = i
               continue
           }
       }
   }
   return si

}

fun main() {

   val molecules = listOf(
       "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
       "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
   )
   for (molecule in molecules) {
       val mass = evaluate(replaceParens(molecule))
       val moleStr = "%17s".format(molecule)
       val massStr = "%7.3f".format(mass)
       println("$moleStr -> $massStr")
   }

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

Nim

  • Nim lacks runtime eval, that's the reason for so much code. (And me being a sloppy programmer)
  • Also, seqs can't contain mixed types.

<lang python>#? replace(sub = "\t", by = " ")

import tables, strutils, sequtils, math

let ATOMIC_MASS = {"H":1.008, "C":12.011, "O":15.999, "Na":22.98976928, "S":32.06, "Uue":315.0}.toTable

proc pass1(s:string): seq[string] = # "H2O" => @["H","*","2","+","O"] result.add "0" var i = 0 proc member(a:char,c:char): bool = i < s.len and a <= s[i] and s[i] <= c proc next(): char = i += 1 s[i-1] while i < s.len: if s[i] == '(': result = result.concat @["+","("] discard next() elif s[i] == ')': result.add $next() elif member('0','9'): var number = "" result.add "*" while member('0','9'): number &= $next() result.add number elif member('A','Z'): if i>0 and s[i-1] != '(': result.add "+" var name = "" name.add next() while member('a','z'): name.add next() result.add name

proc pass2(s:string): seq[string] = # "H2O" => @["H", "2", "*", "O", "+"] let ops = "+*" var tokens = pass1 s var stack: seq[string] var op: string

for token in tokens: case token of "(": stack.add token of ")": while stack.len > 0: op = stack.pop() if op == "(": break result.add op else: if token in ops: while stack.len > 0: op = stack[^1] if not (op in ops): break if ops.find(token) >= ops.find(op): break discard stack.pop() result.add op stack.add token else: result.add token

while stack.len > 0: result.add stack.pop()

proc pass3(s:string): Table[string,int] = # "H2O" => { H:2, O:1 } let rpn: seq[string] = pass2 s var stack: seq[Table[string,int]] = @[] for item in rpn: if item == "+": let h1:Table[string,int] = stack.pop() let h2:Table[string,int] = stack.pop() var res: Table[string,int] = initTable[string,int]() for key in h1.keys: if key != "factor": res[key] = h1[key] for key in h2.keys: if key != "factor": if h1.haskey key: res[key] = h1[key] + h2[key] else: res[key] = h2[key] stack.add res elif item == "*": let top: Table[string,int] = stack.pop() # let hash: Table[string,int] = stack.pop() let factor: int = top["factor"] var res: Table[string,int] = initTable[string,int]() for key in hash.keys: let str : string = key let value: int = hash[key] res[key] = value * factor stack.add res elif ATOMIC_MASS.haskey(item): let res : Table[string,int] = {item: 1}.toTable stack.add res else: # number let factor : int = parseInt item let res : Table[string,int] = {"factor": factor}.toTable stack.add res return stack.pop()

proc pass4(s: string) : float = # "H2O" => 18.015 let atoms: Table[string,int] = pass3 s for key in atoms.keys: let count : int = atoms[key] result += float(count) * ATOMIC_MASS[key] round result,3

let molar_mass = pass4

assert 18.015 == molar_mass "H2O" assert 34.014 == molar_mass "H2O2" assert 34.014 == molar_mass "(HO)2" assert 142.036 == molar_mass "Na2SO4" assert 84.162 == molar_mass "C6H12" assert 186.295 == molar_mass "COOH(C(CH3)2)3CH3" assert 176.124 == molar_mass "C6H4O2(OH)4" # Vitamin C assert 386.664 == molar_mass "C27H46O" # Cholesterol assert 315 == molar_mass "Uue"</lang>

Perl

Grammar

<lang perl>use strict; use warnings; use List::Util; use Parse::RecDescent;

my $g = Parse::RecDescent->new(<<'EOG');

 {
    my %atomic_weight = <H 1.008 C 12.011 O 15.999 Na 22.99 S 32.06>
 }
 weight   : compound         { $item[1] }
 compound : group(s)         { List::Util::sum( @{$item[1]} ) }
 group    : element /\d+/    { $item[1] * $item[2] }
          | element          { $item[1] }
 element  : /[A-Z][a-z]*/    { $atomic_weight{ $item[1] } }
          | "(" compound ")" { $item[2] }

EOG

for (<H H2 H2O Na2SO4 C6H12 COOH(C(CH3)2)3CH3>) {

   printf "%7.3f %s\n", $g->weight($_), $_

}</lang>

Regular Expression

<lang perl>use strict; use warnings; my %atomic_weight = < H 1.008 C 12.011 O 15.999 Na 22.99 S 32.06 >;

sub molar_mass {

   my($mf) = @_;
   my(%count,$mass);
   my $mf_orig = $mf;
   my $mf_std;
   # expand repeated groups
   $mf =~ s/(.*)\((.*?)\)(\d*)/$1 . $2 x ($3 ? $3 : 1) /e while $mf =~ m/\(/;
   # totals for each atom type
   $mf =~ s/([A-Z][a-z]{0,2})(\d*)/ $count{$1} += $2 ? $2 : 1/eg;
   # calculate molar mass and display, along with standardized MF and original MF
   $mass += $count{$_}*$atomic_weight{"$_"} for sort keys %count;
   $mf_std .= 'C' . $count{C} if $count{C};
   $mf_std .= 'H' . $count{H} if $count{H};
   $mf_std .= $_  . $count{$_} for grep { $_ ne 'C' and $_ ne 'H' } sort keys %count;
   $mf     .= $count{$_} * $atomic_weight{$_} for sort keys %count;
   printf "%7.3f %-9s %s\n", $mass, $mf_std, $mf_orig;

}

molar_mass($_) for <H H2 H2O Na2SO4 C6H12 COOH(C(CH3)2)3CH3></lang>

Output:
  1.008 H1        H
  2.016 H2        H2
 18.015 H2O1      H2O
142.036 Na2O4S1   Na2SO4
 84.162 C6H12     C6H12
186.295 C11H22O2  COOH(C(CH3)2)3CH3

Phix

A simple hand-written single-pass formula parser and evaluator in one.
Note the use of string comparison for error checking rather than floats direct, always much safer in general.
Also note that initially it all worked absolutely fine with the default precision (ie "%g" instead of "%.12g"), and that the higher precision expected value for Na2SO4 also works just fine at both printing precisions. <lang Phix>constant elements = new_dict() -- (eg "H" -> 1.008)

function multiplier(string formula, integer fdx) -- check for a trailing number, or return 1

   integer n = 1
   if fdx<=length(formula) then
       integer ch = formula[fdx]
       if ch>='1' and ch<='9' then
           n = ch-'0'
           fdx += 1
           while fdx<=length(formula) do
               ch = formula[fdx]
               if ch<'0' or ch>'9' then exit end if
               n = n*10 + ch-'0'
               fdx += 1
           end while
       end if
   end if
   return {n,fdx}

end function

procedure molar_mass(string formula, name, atom expected)

   sequence stack = {0} -- (for parenthesis handling)
   integer sdx = 1, fdx = 1, n
   while fdx<=length(formula) do
       integer ch = formula[fdx]
       if ch>='A' and ch<='Z' then
           -- All elements start with capital, rest lower
           integer fend = fdx
           while fend<length(formula) do
               ch = formula[fend+1]
               if ch<'a' or ch>'z' then exit end if
               fend += 1
           end while
           string element = formula[fdx..fend]
           atom mass = getd(element,elements)
           if mass=0 then ?9/0 end if -- missing?
           {n,fdx} = multiplier(formula,fend+1)
           stack[sdx] += n*mass
       elsif ch='(' then
           sdx += 1
           stack &= 0
           fdx += 1
       elsif ch=')' then
           {n,fdx} = multiplier(formula,fdx+1)
           sdx -= 1
           stack[sdx] += stack[$]*n
           stack = stack[1..sdx]
       else
           ?9/0    -- unknown?
       end if
   end while
   if sdx!=1 then ?9/0 end if -- unbalanced brackets?
   if name!="" then formula &= " ("&name&")" end if

-- string res = sprintf("%g",stack[1]), -- (still fine) -- e = sprintf("%g",expected) -- """

   string res = sprintf("%.12g",stack[1]),
          e = sprintf("%.12g",expected)
   if res!=e then res &= " *** ERROR: expected "&e end if
   printf(1,"%26s = %s\n",{formula,res})

end procedure

-- (following clipped for brevity, works fine with whole table from task description pasted in) constant etext = split(""" H,1.008 C,12.011 O,15.999 Na,22.98976928 S,32.06 Cl,35.45 Uue,315""","\n") for i=1 to length(etext) do

   Template:String element,atom mass = scanf(etext[i],"%s,%f")
   setd(element,mass,elements)

end for molar_mass("H","Hydrogen",1.008) molar_mass("H2","Hydrogen gas",2.016) molar_mass("H2O","Water",18.015) molar_mass("H2O2","Hydrogen peroxide",34.014) molar_mass("(HO)2","Hydrogen peroxide",34.014) --molar_mass("Na2SO4","Sodium sulfate",142.036) -- (fine for "%g") molar_mass("Na2SO4","Sodium sulfate",142.03553856) -- """ molar_mass("C6H12","Cyclohexane",84.162) molar_mass("COOH(C(CH3)2)3CH3","",186.295) molar_mass("C6H4O2(OH)4","Vitamin C",176.124) molar_mass("C27H46O","Cholesterol",386.664) molar_mass("Uue","Ununennium",315) molar_mass("UueCl","",350.45)</lang>

Output:
              H (Hydrogen) = 1.008
         H2 (Hydrogen gas) = 2.016
               H2O (Water) = 18.015
  H2O2 (Hydrogen peroxide) = 34.014
 (HO)2 (Hydrogen peroxide) = 34.014
   Na2SO4 (Sodium sulfate) = 142.03553856
       C6H12 (Cyclohexane) = 84.162
         COOH(C(CH3)2)3CH3 = 186.295
   C6H4O2(OH)4 (Vitamin C) = 176.124
     C27H46O (Cholesterol) = 386.664
          Uue (Ununennium) = 315
                     UueCl = 350.45

Python

Translation of: Julia

<lang python>import re

ATOMIC_MASS = {"H":1.008, "C":12.011, "O":15.999, "Na":22.98976928, "S":32.06, "Uue":315}

mul = lambda x : '*' + x.group(0) def add(x) :

   name = x.group(0)
   return '+' + name if name == '(' else '+' + str(ATOMIC_MASS[name])

def molar_mass(s):

   nazwa = s
   s = re.sub(r"\d+", mul, s)
   s = re.sub(r"[A-Z][a-z]{0,2}|\(", add, s)
   return print("Atomic mass {:17s} {} {:7.3f}".format(nazwa,'\t',round(eval(s),3)))

</lang>

Output:
Atomic mass H                 	   1.008
Atomic mass H2                	   2.016
Atomic mass H2O               	  18.015
Atomic mass H2O2              	  34.014
Atomic mass (HO)2             	  34.014
Atomic mass Na2SO4            	 142.036
Atomic mass C6H12             	  84.162
Atomic mass COOH(C(CH3)2)3CH3 	 186.295
Atomic mass C6H4O2(OH)4       	 176.124
Atomic mass C27H46O           	 386.664
Atomic mass Uue               	 315.000

Racket

<lang racket>#lang racket

(define table '([H 1.008]

               [C 12.011]
               [O 15.999]
               [Na 22.98976928]
               [S 32.06]
               [Uue 315.0]))

(define (lookup s) (first (dict-ref table s)))

(define (calc s)

 (define toks
   (with-input-from-string (regexp-replaces s '([#px"(\\d+)" " \\1"]
                                                [#px"([A-Z])" " \\1"]))
     (thunk (sequence->list (in-port)))))
 (let loop ([toks toks])
   (match toks
     ['() 0]
     [(list (? list? sub) (? number? n) toks ...) (+ (* (loop sub) n) (loop toks))]
     [(list (? list? sub) toks ...) (+ (loop sub) (loop toks))]
     [(list sym (? number? n) toks ...) (+ (* (lookup sym) n) (loop toks))]
     [(list sym toks ...) (+ (lookup sym) (loop toks))])))

(define tests '("H"

               "H2"
               "H2O"
               "H2O2"
               "(HO)2"
               "Na2SO4"
               "C6H12"
               "COOH(C(CH3)2)3CH3"
               "C6H4O2(OH)4"
               "C27H46O"
               "Uue"))

(for ([test (in-list tests)])

 (printf "~a: ~a\n"
         (~a test #:align 'right #:min-width 20)
         (~r (calc test) #:precision 3)))</lang>
Output:
                   H: 1.008
                  H2: 2.016
                 H2O: 18.015
                H2O2: 34.014
               (HO)2: 34.014
              Na2SO4: 142.036
               C6H12: 84.162
   COOH(C(CH3)2)3CH3: 186.295
         C6H4O2(OH)4: 176.124
             C27H46O: 386.664
                 Uue: 315

Raku

(formerly Perl 6) <lang perl6>my %ATOMIC_MASS =

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

grammar Chemical_formula {

   my @ATOMIC_SYMBOLS = %ATOMIC_MASS.keys.sort;
   rule  TOP      { ^ (<lparen>|<rparen>|<element>)+ $ }
   token quantity { \d+ }
   token lparen   { '(' }
   token rparen   { ')'                    <quantity>? }
   token element  { $<e>=[@ATOMIC_SYMBOLS] <quantity>? }

} class Chemical_formula_actions {

   has @stack = 0;
   method TOP     ($/) { $/.make: @stack }
   method lparen  ($/) { push @stack, 0  }
   method rparen  ($/) { my $m = @stack.pop;
                         @stack[*-1] += ($<quantity> // 1) * $m }
   method element ($/) { @stack[*-1] += ($<quantity> // 1) * %ATOMIC_MASS{~$<e>} }

} sub molar_mass ( Str $formula --> Real ) {

   Chemical_formula.parse( $formula, :actions(Chemical_formula_actions.new) )
       orelse die "Chemical formula not recognized: '$formula'";
   return $/.made.[0];

} say .&molar_mass.fmt('%7.3f '), $_ for <H H2 H2O Na2SO4 C6H12 COOH(C(CH3)2)3CH3>;</lang>

Output:
  1.008 H
  2.016 H2
 18.015 H2O
142.036 Na2SO4
 84.162 C6H12
186.295 COOH(C(CH3)2)3CH3

REXX

This REXX version has some basic error checking to catch malformed chemical formulas.

Some extra coding was added to format the output better and to also include a common name for the chemical formula.

Also a more precise atomic mass for the (all) known elements is used;   for instance for   F   (fluorine).

Some of the (newer) elements added for the REXX example are:

   mendelevium (Md),  nobelium (No),     lawrencium (Lr),  rutherfordium (Rf),     dubnium (Db),
   seaborgium  (Sg),  bohrium (Bh),      hassium (Hs),     meitnerium (Mt),   darmstadtium (Ds),
   roentgenium (Rg),  copernicium (Cn),  nihoniym (Nh),    flerovium (Fl),       moscovium (Mc),
   livermorium (Lv),  tennessine (Ts),   oganesson (Og)

<lang rexx>/*REXX program calculates the molar mass from a specified chemical formula. */ numeric digits 30 /*ensure enough decimal digits for mass*/ /*─────────── [↓] table of known elements (+2 more) with their atomic mass ────────────*/ @.=  ; @.Co= 58.933195 ; @.H = 1.00794  ; @.Np=237  ; @.Se= 78.96

                  @.Cr= 51.9961   ;  @.In=114.818     ;  @.N = 14.0067  ;  @.Sg=266

@.Ac=227  ; @.Cs=132.9054519; @.Ir=192.217  ; @.Og=294  ; @.Si= 28.0855 @.Ag=107.8682  ; @.Cu= 63.546  ; @.I =126.904  ; @.Os=190.23  ; @.Sm=150.36 @.Al= 26.9815386; @.C = 12.0107  ; @.Kr= 83.798  ; @.O = 15.9994  ; @.Sn=118.710 @.Am=243  ; @.Db=262  ; @.K = 39.0983  ; @.Pa=231.03588 ; @.Sr= 87.62 @.Ar= 39.948  ; @.Ds=271  ; @.La=138.90547  ; @.Pb=207.2  ; @.S = 32.065 @.As= 74.92160  ; @.Dy=162.500  ; @.Li= 6.941  ; @.Pd=106.42  ; @.Ta=180.94788 @.At=210  ; @.Er=167.259  ; @.Lr=262  ; @.Pm=145  ; @.Tb=158.92535 @.Au=196.966569 ; @.Es=252  ; @.Lu=174.967  ; @.Po=210  ; @.Tc= 98 @.Ba=137.327  ; @.Eu=151.964  ; @.Lv=292  ; @.Pr=140.90765 ; @.Te=127.60 @.Be= 9.012182 ; @.Fe= 55.845  ; @.Mc=288  ; @.Pt=195.084  ; @.Th=232.03806 @.Bh=264  ; @.Fl=289  ; @.Md=258  ; @.Pu=244  ; @.Ti= 47.867 @.Bi=208.98040  ; @.Fm=257  ; @.Mg= 24.3050  ; @.P = 30.973762; @.Tl=204.3833 @.Bk=247  ; @.Fr=223  ; @.Mn= 54.938045  ; @.Ra=226  ; @.Tm=168.93421 @.Br= 79.904  ; @.F = 18.9984032; @.Mo= 95.94  ; @.Rb= 85.4678  ; @.Ts=293 @.B = 10.811  ; @.Ga= 69.723  ; @.Mt=268  ; @.Re=186.207  ; @.U =238.02891 @.Ca= 40.078  ; @.Gd=157.25  ; @.Na= 22.98976928; @.Rf=261  ; @.V = 50.9415 @.Cd=112.411  ; @.Ge= 72.64  ; @.Nb= 92.906  ; @.Rg=272  ; @.W =183.84 @.Ce=140.116  ; @.He= 4.002602 ; @.Nd=144.242  ; @.Rh=102.905  ; @.Xe=131.293 @.Cf=251  ; @.Hf=178.49  ; @.Ne= 20.1797  ; @.Rn=220  ; @.Yb=173.04 @.Cl= 35.453  ; @.Hg=200.59  ; @.Nh=284  ; @.Ru=101.07  ; @.Y = 88.90585 @.Cm=247  ; @.Ho=164.930  ; @.Ni= 58.6934  ; @.Sb=121.760  ; @.Zn= 65.409 @.Cn=285  ; @.Hs=277  ; @.No=259  ; @.Sc= 44.955912; @.Zr= 91.224

                                                         @.Ubn=299      ;  @.Uue=315

parse arg $; _ = '─' say center(' chemical formula {common name} ', 45) center("molar mass", 16) say center( , 45, _) center( , 16, _) if $= | $="," then $= 'H{hydrogen} H2{molecular_hydrogen} H2O2{hydrogen_peroxide}',

                        '(HO)2{hydrogen_peroxide}   H2O{water}   Na2SO4{sodium_sulfate}',
                        'C6H12{cyclohexane}         COOH(C(CH3)2)3CH3{butyric_acid}'    ,
                        'C6H4O2(OH)4{vitamin_C}  C27H46O{cholesterol}   Uue{ununennium}',
                        'Mg3Si4O10(OH)2{talc}'
 do j=1  for words($);   x= word($, j)          /*obtain the formula of the molecule.  */
 parse var  x    x  '{'  -0  name               /*   "    "     "    and also a name.  */
 mm= chemCalc(x)                                /*   "    "  molar mass.               */
 name= strip(x '   'translate(name, 'ff'x,"_")) /*   "    "  molar mass; fix─up name.  */
 if mm<0  then iterate                          /*if function had an error, skip output*/
 say ' 'justify(name, 45-2)    "  "     mm      /*show chemical name and its molar mass*/
 end   /*j*/

exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ chemCalc: procedure expose @.; parse arg z /*obtain chemical formula of molecule. */

         lev= 0                                 /*indicates level of parentheses depth.*/
         $.= 0                                  /*the sum of the molar mass  (so far). */
              do k=1  to  length(z);              y= substr(z, k, 1)      /*get a thing*/
              if y=='('  then do;  lev= lev + 1;  iterate k;  end
              if y==')'  then do;  y= substr(z, k+1, 1)
                                   if \datatype(y, 'W')  then do; say "illegal number:" y
                                                                  return -1
                                                              end
                                   n= getNum()                            /*get number.*/
                                   $.lev= $.lev * n;  $$= $.lev; $.lev= 0 /*sum level. */
                                   lev= lev - 1;      $.lev= $.lev + $$   /*add to prev*/
                                   k= k + length(n)                       /*adjust  K. */
                                   iterate   /*k*/
                              end                                         /*[↑] get ele*/
              e=y;   e= getEle();                     upper e             /* and upper.*/
              if   e==.  then do;  say 'missing element: '  e;   return -2;    end
              if @.e==.  then do;  say 'invalid element: '  e;   return -3;    end
              y= substr(z, k+length(e), 1)
              k= k + length(e) - 1                                        /*adjust  K. */
              n= getNum()                                                 /*get number.*/
              if n\==.  then k= k + length(n)                             /*adjust  K. */
                        else n= 1                                         /*no number. */
              $.lev= $.lev   +   n * @.e                                  /*add product*/
              end   /*k*/
         return format($.lev, max(4, pos(., $.lev) ) )                    /*align the #*/

/*──────────────────────────────────────────────────────────────────────────────────────*/ getEle: if \datatype(y, 'U') then do; say err "illegal element: " y; return .; end

                        do i=1  until \datatype(q, 'L');  q= substr(z, k+i, 1)
                        if datatype(q, 'L')  then e= e || q               /*lowercase? */
                        end   /*i*/;                         return e

/*──────────────────────────────────────────────────────────────────────────────────────*/ getNum: if \datatype(y, 'W') then return .; n=

                        do i=1  until \datatype(q, 'W');  q= substr(z, k+i, 1)
                        if datatype(q, 'W')  then n= n || q               /*is a digit?*/
                        end   /*i*/;                         return n</lang>
output   when using the default inputs:
    chemical formula        {common name}        molar mass
───────────────────────────────────────────── ────────────────
 H                                {hydrogen}       1.00794
 H2                     {molecular hydrogen}       2.01588
 H2O2                    {hydrogen peroxide}      34.01468
 (HO)2                   {hydrogen peroxide}      34.01468
 H2O                                 {water}      18.01528
 Na2SO4                     {sodium sulfate}     142.04213856
 C6H12                         {cyclohexane}      84.15948
 COOH(C(CH3)2)3CH3            {butyric acid}     186.29118
 C6H4O2(OH)4                     {vitamin C}     176.12412
 C27H46O                       {cholesterol}     386.65354
 Uue                            {ununennium}     315
 Mg3Si4O10(OH)2                       {talc}     379.26568

Ruby

Translation of: D

<lang ruby>$atomicMass = {

   "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,
   "Uue" =>  315,
   "Ubn" =>  299,

}

def evaluate(s)

   s += "[" # add end of string marker
   sum = 0.0
   i = 0
   symbol = ""
   number = ""
   while i < s.length
       c = s[i]
       if '@' <= c and c <= '[' then
           n = 1
           if number != "" then
               n = number.to_i
           end
           if symbol != "" then
               mass = $atomicMass[symbol]
               sum = sum + mass * n
           end
           if c == '[' then
               break
           end
           symbol = c.to_s
           number = ""
       elsif 'a' <= c and c <= 'z' then
           symbol = symbol + c
       elsif '0' <= c and c <= '9' then
           number = number + c
       else
           raise "Unexpected symbol %c in molecule" % [c]
       end
       i = i + 1
   end
   return sum

end

def replaceParens(s)

   letter = 'a'
   while true
       start = s.index '('
       if start == nil then
           break
       end
       i = start + 1
       while i < s.length
           if s[i] == ')' then
               expr = s[start + 1 .. i - 1]
               symbol = "@%c" % [letter]
               r = s[start .. i + 0]
               s = s.sub(r, symbol)
               $atomicMass[symbol] = evaluate(expr)
               letter = letter.next
               break
           end
           if s[i] == '(' then
               start = i
           end
           i = i + 1
       end
   end
   return s

end

def main

   molecules = [
       "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
       "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
   ]
   for molecule in molecules
       mass = evaluate(replaceParens(molecule))
       print "%17s -> %7.3f\n" % [molecule, mass]
   end

end

main()</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

Swift

<lang swift>import Foundation

struct Chem {

 struct Molecule {
   var formula: String
   var parts: [Molecule]
   var quantity = 1
   var molarMass: Double {
     switch parts.count {
     case 0:
       return Chem.atomicWeights[formula]! * Double(quantity)
     case _:
       return parts.lazy.map({ $0.molarMass }).reduce(0, +) * Double(quantity)
     }
   }
   private init(formula: String, parts: [Molecule], quantity: Int) {
     self.formula = formula
     self.parts = parts
     self.quantity = quantity
   }
   init?(fromString str: String) {
     guard let mol = Molecule.parseString(str[...]) else {
       return nil
     }
     self = mol
   }
   private static func parseString(_ str: Substring) -> Molecule? {
     guard !str.isEmpty else {
       return nil
     }
     var parts = [Molecule]()
     var workingMol = ""
     var idx = str.startIndex
     func completeAtom() -> Bool {
       guard Chem.atomicWeights.keys.contains(workingMol) else {
         return false
       }
       parts.append(Molecule(formula: workingMol, parts: [], quantity: 1))
       workingMol = ""
       return true
     }
     while idx != str.endIndex {
       let char = str[idx]
       guard char.isASCII else {
         return nil
       }
       if (char.isUppercase || char == "(" || char.isNumber) && !workingMol.isEmpty {
         guard completeAtom() else {
           return nil
         }
       }
       if char == "(" {
         var parenLevel = 1
         let subMolStart = str.index(after: idx)
         idx = subMolStart
         while parenLevel != 0 {
           guard idx != str.endIndex else {
             return nil
           }
           if str[idx] == "(" {
             parenLevel += 1
           } else if str[idx] == ")" {
             parenLevel -= 1
           }
           if parenLevel != 0 {
             idx = str.index(after: idx)
           }
         }
         guard let subMol = parseString(str[subMolStart..<idx]) else {
           return nil
         }
         parts.append(subMol)
         idx = str.index(after: idx)
         continue
       } else if char == ")" {
         fatalError()
       }
       workingMol.append(char)
       if char.isNumber {
         guard !parts.isEmpty else {
           return nil
         }
         var workNum = workingMol
         idx = str.index(after: idx)
         while idx != str.endIndex && str[idx].isNumber {
           workNum.append(str[idx])
           idx = str.index(after: idx)
         }
         parts[parts.count - 1].quantity = Int(workNum)!
         workingMol = ""
         continue
       }
       idx = str.index(after: idx)
     }
     guard workingMol.isEmpty || completeAtom() else {
       return nil
     }
     return Molecule(formula: String(str), parts: parts, quantity: 1)
   }
 }
 static func calculateMolarMass(of chem: String) -> Double? {
   guard let mol = Molecule(fromString: chem) else {
     return nil
   }
   return mol.molarMass
 }
 fileprivate static let atomicWeights = [
   "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,
   "K": 39.0983,
   "Ar": 39.948,
   "Ca": 40.078,
   "Sc": 44.955908,
   "Ti": 47.867,
   "V": 50.9415,
   "Cr": 51.9961,
   "Mn": 54.938044,
   "Fe": 55.845,
   "Ni": 58.6934,
   "Co": 58.933194,
   "Cu": 63.546,
   "Zn": 65.38,
   "Ga": 69.723,
   "Ge": 72.63,
   "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.9055,
   "Pd": 106.42,
   "Ag": 107.8682,
   "Cd": 112.414,
   "In": 114.818,
   "Sn": 118.71,
   "Sb": 121.76,
   "I": 126.90447,
   "Te": 127.6,
   "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.5,
   "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.9804,
   "Po": 209,
   "At": 210,
   "Rn": 222,
   "Fr": 223,
   "Ra": 226,
   "Ac": 227,
   "Pa": 231.03588,
   "Th": 232.0377,
   "Np": 237,
   "U": 238.02891,
   "Am": 243,
   "Pu": 244,
   "Cm": 247,
   "Bk": 247,
   "Cf": 251,
   "Es": 252,
   "Fm": 257,
   "Ubn": 299,
   "Uue": 315
 ]

}

let testCases = [

 ("H", "1.008"),
 ("H2", "2.016"),
 ("H2O", "18.015"),
 ("H2O2", "34.014"),
 ("(HO)2", "34.014"),
 ("Na2SO4", "142.036"),
 ("C6H12", "84.162"),
 ("COOH(C(CH3)2)3CH3", "186.295"),
 ("C6H4O2(OH)4", "176.124"),
 ("C27H46O", "386.664"),
 ("Uue", "315.000")

]

let fmt = { String(format: "%.3f", $0) }

for (mol, expected) in testCases {

 guard let mass = Chem.calculateMolarMass(of: mol) else {
   fatalError("Bad formula \(mol)")
 }
 assert(fmt(mass) == expected, "Incorrect result")
 print("\(mol) => \(fmt(mass))")

}</lang>

Output:
H => 1.008
H2 => 2.016
H2O => 18.015
H2O2 => 34.014
(HO)2 => 34.014
Na2SO4 => 142.036
C6H12 => 84.162
COOH(C(CH3)2)3CH3 => 186.295
C6H4O2(OH)4 => 176.124
C27H46O => 386.664
Uue => 315.000

VBA

<lang vba>Option Explicit

Enum ParsingStateCode

   NORM
   GROUP_JUST_ENDED

End Enum

Dim masses As Collection

Sub main()

   Dim molecule
   For Each molecule In Array("H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3", _
   "C6H4O2(OH)4", "C27H46O", "Uue")
       Debug.Print molecule; Tab(20); GetMM(molecule)
   Next

End Sub

Function GetMM(ByVal f As String) As Double

   If masses Is Nothing Then init
   f = f & "@"
   Dim pos As Long
   Dim mass(5) As Double
   For pos = 1 To Len(f)
       Dim sym$: sym = Mid(f, pos, 1)
       Select Case sym
       Case "A" To "Z"
           GoSub calc
           Dim atom$: atom = sym
       Case "a" To "z"
           atom = atom & sym
       Case "("
           GoSub calc
           Dim depth As Long: depth = depth + 1
       Case ")"
           GoSub calc
           Dim parsingState As ParsingStateCode
           parsingState = GROUP_JUST_ENDED
       Case 0 To 9
           Dim nStr As String
           nStr = nStr & sym
       Case "@"
           GoSub calc
       End Select
   Next
   GetMM = mass(0)

Exit Function '------------------------------------------------------------------- calc:

   Dim n As Long
   If nStr = "" Then
       n = 1
   Else
       n = CLng(nStr)
   End If
   Select Case parsingState
   Case NORM
       mass(depth) = mass(depth) + masses(atom) * n
       atom = ""
   Case GROUP_JUST_ENDED
       mass(depth) = mass(depth) * n
       depth = depth - 1
       mass(depth) = mass(depth) + mass(depth + 1)
       parsingState = NORM
   End Select
   'n = 0
   nStr = ""

Return End Function

Sub init()

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

End Sub</lang>

Output:
H                   1,008 
H2                  2,016 
H2O                 18,015 
H2O2                34,014 
(HO)2               34,014 
Na2SO4              142,03553856 
C6H12               84,162 
COOH(C(CH3)2)3CH3   186,295 
C6H4O2(OH)4         176,124 
C27H46O             386,664 
Uue                 315

Visual Basic .NET

Translation of: C#

<lang vbnet>Module Module1

   Dim atomicMass As New Dictionary(Of String, Double) From {
       {"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.63},
       {"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.9055},
       {"Pd", 106.42},
       {"Ag", 107.8682},
       {"Cd", 112.414},
       {"In", 114.818},
       {"Sn", 118.71},
       {"Sb", 121.76},
       {"Te", 127.6},
       {"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.5},
       {"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.9804},
       {"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},
       {"Uue", 315},
       {"Ubn", 299}
   }
   Function Evaluate(s As String) As Double
       s += "["
       Dim sum = 0.0
       Dim symbol = ""
       Dim number = ""
       For i = 1 To s.Length
           Dim c = s(i - 1)
           If "@" <= c AndAlso c <= "[" Then
               ' @,A-Z
               Dim n = 1
               If number <> "" Then
                   n = Integer.Parse(number)
               End If
               If symbol <> "" Then
                   sum += atomicMass(symbol) * n
               End If
               If c = "[" Then
                   Exit For
               End If
               symbol = c.ToString
               number = ""
           ElseIf "a" <= c AndAlso c <= "z" Then
               symbol += c
           ElseIf "0" <= c AndAlso c <= "9" Then
               number += c
           Else
               Throw New Exception(String.Format("Unexpected symbol {0} in molecule", c))
           End If
       Next
       Return sum
   End Function
   Function ReplaceFirst(text As String, search As String, replace As String) As String
       Dim pos = text.IndexOf(search)
       If pos < 0 Then
           Return text
       Else
           Return text.Substring(0, pos) + replace + text.Substring(pos + search.Length)
       End If
   End Function
   Function ReplaceParens(s As String) As String
       Dim letter = "s"c
       While True
           Dim start = s.IndexOf("(")
           If start = -1 Then
               Exit While
           End If
           For i = start + 1 To s.Length - 1
               If s(i) = ")" Then
                   Dim expr = s.Substring(start + 1, i - start - 1)
                   Dim symbol = String.Format("@{0}", letter)
                   s = ReplaceFirst(s, s.Substring(start, i + 1 - start), symbol)
                   atomicMass(symbol) = Evaluate(expr)
                   letter = Chr(Asc(letter) + 1)
                   Exit For
               End If
               If s(i) = "(" Then
                   start = i
                   Continue For
               End If
           Next
       End While
       Return s
   End Function
   Sub Main()
       Dim molecules() As String = {
           "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12",
           "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue"
       }
       For Each molecule In molecules
           Dim mass = Evaluate(ReplaceParens(molecule))
           Console.WriteLine("{0,17} -> {1,7:0.000}", molecule, mass)
       Next
   End Sub

End Module</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

Wren

Translation of: Go
Library: Wren-fmt
Library: Wren-str

<lang ecmascript>import "/fmt" for Fmt import "/str" for Char, Str

var atomicMass = {

   "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,
   "Uue": 315,
   "Ubn": 299

}

var evaluate = Fn.new { |s|

   s = s + "[" // add end of string marker
   var symbol = ""
   var number = ""
   var sum = 0
   for (i in 0...s.count) {
       var c = s[i]
       if (Str.ge(c, "@") && Str.le(c, "[")) {  // @, A-Z, [
           var n = 1
           if (number != "") n = Num.fromString(number)
           if (symbol != "") sum = sum + atomicMass[symbol] * n
           if (c == "[") break
           symbol = c
           number = ""
       } else if (Char.isLower(c)) {
           symbol = symbol + c
       } else if (Char.isDigit(c)) {
           number = number + c
       } else {
           Fiber.abort("Unexpected symbol '%(c)' in molecule.")
       }
   }
   return sum

}

var replaceFirst = Fn.new { |s, from, to|

   var ix = s.indexOf(from)
   if (ix == -1) return s
   var tail = (s.count >= ix + from.count) ? s[(ix + from.count)..-1] : ""
   return s[0...ix] + to + tail

}

var replaceParens = Fn.new { |s|

   var letter = "a"
   while (true) {
       var start = s.indexOf("(")
       if (start == -1) return s
       var restart = true
       while (restart) {
           var i = start + 1
           while (i < s.count) {
               if (s[i] == ")") {
                   var expr = s[start+1...i]
                   var symbol = "@%(letter)"
                   s = replaceFirst.call(s, s[start..i], symbol)
                   atomicMass[symbol] = evaluate.call(expr)
                   letter = String.fromByte(letter.bytes[0] + 1)
                   restart = false
                   break
               }
               if (s[i] == "(") {
                   start = i
                   break
               }
               i = i + 1
           }
       }
   }
   return s

}

var molecules = [

   "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3",
   "C6H4O2(OH)4", "C27H46O", "Uue"

] for (molecule in molecules) {

   var mass = evaluate.call(replaceParens.call(molecule))
   System.print("%(Fmt.s(17, molecule)) -> %(Fmt.f(7, mass, 3))")

}</lang>

Output:
                H ->   1.008
               H2 ->   2.016
              H2O ->  18.015
             H2O2 ->  34.014
            (HO)2 ->  34.014
           Na2SO4 -> 142.036
            C6H12 ->  84.162
COOH(C(CH3)2)3CH3 -> 186.295
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315.000

zkl

Really bad error checking <lang zkl>fcn molarMass(str,mass=0.0){

   while(span:=str.span("(",")",False)){  // get inner most () group
     group:=str[span.xplode()];	  // (CH3)
     str   =str.del(span.xplode());      // nuke (CH3)
     w    :=molarMass(group[1,-1],mass); // remove "(" & ")"
     i,s2 := span[0], str[i,*];
     if(m.search(s2))			  // well crap, (CH3)2
        { z:=m.matched[1]; str=str.del(i,z.len()); mass=w*z.toInt() }
     else mass=w;
  }
  ms:=List(mass);	// HO --> (1.008,15.999).sum()
  while(str){ 
     if(not atomRE.search(str)) throw(Exception.ValueError);
     ns,nm,n := atomRE.matched;
     n=(if(n) n.toInt() else 1);	// H2
     ms.append(atomicMass[nm]*n);
     str=str.del(ns.xplode());		// nuke H or H2
  }
  ms.reduce('+);

}</lang> <lang zkl>var [const] atomicMass = Dictionary(

 "Ac",227.000000, "Ag",107.868200, "Al", 26.981538, "Am",243.000000, "Ar", 39.948000, 
 "As", 74.921595, "At",210.000000, "Au",196.966569, "B" , 10.810000, "Ba",137.327000, 
 "Be",  9.012183, "Bi",208.980400, "Bk",247.000000, "Br", 79.904000, "C" , 12.011000, 
 "Ca", 40.078000, "Cd",112.414000, "Ce",140.116000, "Cf",251.000000, "Cl", 35.450000, 
 "Cm",247.000000, "Co", 58.933194, "Cr", 51.996100, "Cs",132.905452, "Cu", 63.546000, 
 "Dy",162.500000, "Er",167.259000, "Es",252.000000, "Eu",151.964000, "F" , 18.998403, 
 "Fe", 55.845000, "Fm",257.000000, "Fr",223.000000, "Ga", 69.723000, "Gd",157.250000, 
 "Ge", 72.630000, "H" ,  1.008000, "He",  4.002602, "Hf",178.490000, "Hg",200.592000, 
 "Ho",164.930330, "I" ,126.904470, "In",114.818000, "Ir",192.217000, "K" , 39.098300, 
 "Kr", 83.798000, "La",138.905470, "Li",  6.940000, "Lu",174.966800, "Mg", 24.305000, 
 "Mn", 54.938044, "Mo", 95.950000, "N" , 14.007000, "Na", 22.989769, "Nb", 92.906370, 
 "Nd",144.242000, "Ne", 20.179700, "Ni", 58.693400, "Np",237.000000, "O" , 15.999000, 
 "Os",190.230000, "P" , 30.973762, "Pa",231.035880, "Pb",207.200000, "Pd",106.420000, 
 "Pm",145.000000, "Po",209.000000, "Pr",140.907660, "Pt",195.084000, "Pu",244.000000, 
 "Ra",226.000000, "Rb", 85.467800, "Re",186.207000, "Rh",102.905500, "Rn",222.000000, 
 "Ru",101.070000, "S" , 32.060000, "Sb",121.760000, "Sc", 44.955908, "Se", 78.971000, 
 "Si", 28.085000, "Sm",150.360000, "Sn",118.710000, "Sr", 87.620000, "Ta",180.947880, 
 "Tb",158.925350, "Te",127.600000, "Th",232.037700, "Ti", 47.867000, "Tl",204.380000, 
 "Tm",168.934220, "U" ,238.028910, "V" , 50.941500, "W" ,183.840000, "Xe",131.293000, 
 "Y" , 88.905840, "Yb",173.054000, "Zn", 65.380000, "Zr", 91.224000, 

), m=RegExp("([1-9]+)"),

  atomRE=fcn{  // sort by name length, build RE: "(Lu|Es|Er..|W|Y)([1-9]*)"

nms:=atomicMass.keys; ( [(nms.apply("len") : (0).max(_)) .. 1, -1].pump(List, // 2..1 'wrap(n){ nms.filter('wrap(nm){ nm.len()==n }) }).flatten() .concat("|","(",")([1-9]*)") )  : RegExp(_);

  }();</lang>

<lang zkl>foreach cstr in (T("H","H2","H2O","Na2SO4","C6H12","COOH(C(CH3)2)3CH3"))

  { println(cstr," --> ",molarMass(cstr)) }</lang>
Output:
H --> 1.008
H2 --> 2.016
H2O --> 18.015
Na2SO4 --> 142.036
C6H12 --> 84.162
COOH(C(CH3)2)3CH3 --> 186.295