Chemical calculator

From Rosetta Code
Revision as of 12:22, 17 November 2023 by PureFox (talk | contribs) (→‎{{header|Wren}}: Minor tidy)
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 some recognized element names and atomic mass is:

  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
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


Reference



ALGOL 68

Translation of: ALGOL W
BEGIN # chemical calculator - calculate the molar mass of compounds                           #
    # MODE to hold element symbols and masses                                                 #
    MODE ATOM = STRUCT( STRING symbol, REAL mass, REF ATOM next );

    # returns the molar mass of the specified molecule #
    PROC molarmass = ( STRING molecule )REAL:
    BEGIN
        CHAR c;
        BOOL had error := FALSE;
        INT  ch max     = UPB molecule;
        INT  ch pos    := LWB molecule - 1;

        # reports a syntax error in the molecule starting at position ch pos                  #
        PROC error = ( STRING message )VOID:
        BEGIN
            print( ( "Syntax error in molecule: ", molecule, message, newline ) );
            print( ( "                          " ) );
            FOR i TO ch pos - 1 DO print( ( " " ) ) OD;
            print( ( "^", newline ) );
            # ensure parsing stops #
            had error := TRUE;
            ch pos    := ch max * 2
        END # error # ;
        # gets the next character from the molecule #
        PROC next char = VOID:
            c := IF ch pos +:= 1; ch pos > ch max THEN " " ELSE molecule[ ch pos ] FI;
        # parses a compound: a sequence of element names and bracketed compounds, each with   #
        # an optional trailing repeat count                                                   #
        PROC parse compound = REAL:
        BEGIN
            # parses an element symbol feom the molecule and returns its mass                 #
            PROC parse atom = REAL:
            BEGIN
                STRING symbol := c;
                next char;
                FOR i TO 2 WHILE c >= "a" AND c <= "z" DO
                    symbol +:= c;
                    next char
                OD;
                # find the element in the table #
                ATOM element := atoms[ ABS symbol[ LWB symbol ] - ABS "A" ];
                WHILE IF element IS REF ATOM(NIL) THEN FALSE ELSE symbol OF element /= symbol FI DO
                    element := next OF element
                OD;
                IF   element ISNT REF ATOM(NIL)
                THEN # found the element # mass OF element
                ELSE # unknown element #
                     ch pos -:= 1;
                     error( "Unrecognised element." );
                     0
                FI
            END # parse atom # ;
            REAL mass := 0;
            WHILE NOT had error AND ( ( c >= "A" AND c <= "Z" ) OR c = "(" ) DO
                REAL item mass := 0;
                IF   c >= "A" AND c <= "Z" THEN item mass := parse atom
                ELIF c = "(" THEN
                    # bracketed group #
                    next char;
                    item mass := parse compound;
                    IF IF ch pos > ch max THEN " " ELSE molecule[ ch pos ] FI /= ")" THEN
                        error( "Expected "")""." )
                    FI;
                    next char
                FI;
                IF c >= "0" AND c <= "9" THEN
                    # have a repeat count #
                    INT count := 0;
                    WHILE NOT had error AND c >= "0" AND c <= "9" DO
                        count *:= 10 +:= ABS c - ABS "0";
                        next char
                    OD;
                    item mass *:= count
                FI;
                mass +:= item mass
            OD;
            mass
        END # parse compound # ;
        next char;
        REAL mass = parse compound;
        IF ch pos <= ch max THEN error( "Unexpected text after the molecule." ) FI;
        mass
    END # molar mass # ;
    # hash table of atome, hash is the first character of the symbol - "A" #
    [ 0 : 25 ]REF ATOM atoms; FOR i FROM LWB atoms TO UPB atoms DO atoms( i ) := NIL OD;
    BEGIN # setup element symbols and masses as specified in the task #
        # adds an element and its mass to the atoms table #
        OP / = ( STRING symbol, REAL mass )VOID:
        BEGIN
            INT index = ABS symbol[ LWB symbol ] - ABS "A";
            atoms[ index ] := HEAP ATOM := ATOM( symbol, mass, atoms[ index ] )
        END # / # ;
        OP / = ( STRING symbol, INT  mass )VOID: symbol / REAL(mass);
        OP / = ( CHAR   symbol, REAL mass )VOID: STRING(symbol) / mass;
        PROC lanthanides = VOID:
        BEGIN
            "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;
            SKIP
        END # lanthanides # ;
        PROC actinides = VOID:
        BEGIN
            "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     ;#Md     ;       No     ;       Lr          ;#
            SKIP
        END # actinides # ;
        "H" /1.008   ;"Li"/ 6.94       ;"Na"/22.98976928 ;"K" /39.0983  ;"Rb"/85.4678  ;"Cs"/132.90545196;"Fr"/223 ;
                      "Be"/ 9.0121831  ;"Mg"/24.305      ;"Ca"/40.078   ;"Sr"/87.62    ;"Ba"/137.327     ;"Ra"/226 ;
                                                          "Sc"/44.955908;"Y" /88.90584 ;lanthanides      ;actinides;
                                                          "Ti"/47.867   ;"Zr"/91.224   ;"Hf"/178.49      ;# Rf     #
                                                          "V" /50.9415  ;"Nb"/92.90637 ;"Ta"/180.94788   ;# Db     #
                                                          "Cr"/51.9961  ;"Mo"/95.95    ;"W" /183.84      ;# Sg     #
                                                          "Mn"/54.938044;#Tc           #"Re"/186.207     ;# Bh     #
                                                          "Fe"/55.845   ;"Ru"/101.07   ;"Os"/190.23      ;# Hs     #
                                                          "Co"/58.933194;"Rh"/102.9055 ;"Ir"/192.217     ;# Mt     #
                                                          "Ni"/58.6934  ;"Pd"/106.42   ;"Pt"/195.084     ;# Ds     #
                                                          "Cu"/63.546   ;"Ag"/107.8682 ;"Au"/196.966569  ;# Rg     #
                                                          "Zn"/65.38    ;"Cd"/112.414  ;"Hg"/200.592     ;# Cn     #
                      "B" /10.81       ;"Al"/26.9815385  ;"Ga"/69.723   ;"In"/114.818  ;"Tl"/204.38      ;# Nh     #
                      "C" /12.011      ;"Si"/28.085      ;"Ge"/72.63    ;"Sn"/118.71   ;"Pb"/207.2       ;# Fl     #
                      "N" /14.007      ;"P" /30.973761998;"As"/74.921595;"Sb"/121.76   ;"Bi"/208.9804    ;# Ms     #
                      "O" /15.999      ;"S" / 32.06      ;"Se"/78.971   ;"Te"/127.6    ;"Po"/209         ;# Lv     #
                      "F" /18.998403163;"Cl"/35.45       ;"Br"/79.904   ;"I" /126.90447;"At"/210         ;# Ts     #
        "He"/4.002602;"Ne"/20.1797     ;"Ar"/39.948      ;"Kr"/83.798   ;"Xe"/131.293  ;"Rn"/222         ;# Og     #
        # --- hypothetical eigth period elements --/ #  "Uue"/315;"Ubn"/299;
        SKIP
    END;
    BEGIN # test cases #
        PROC test = ( REAL expected mass, STRING molecule )VOID:
        BEGIN
            REAL   mass = molar mass( molecule );
            STRING pad  = IF INT length = ( UPB molecule - LWB molecule ) + 1;
                             length > 20
                          THEN ""
                          ELSE ( 20 - length ) * " "
                          FI;
            print( ( newline, pad, molecule, ":", fixed( mass, -9, 3 ) ) );
            REAL diff = expected mass - mass;
            IF diff > 1e-12 OR diff < -1e-12 THEN
                print( ( " expected:", fixed( expected mass, -9, 3 ) ) )
            FI
        END # test # ;                 
        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
Output:
                   H:    1.008
                  H2:    2.016
                 H2O:   18.015
              Na2SO4:  142.036
               C6H12:   84.162
   COOH(C(CH3)2)3CH3:  186.295
               UueCl:  350.450

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.

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 feom the molecule and retutns 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 ;
        real CsMass; CsMass := 132.90545196;
        A("Li", 6.94       );A("Na",22.98976928 );A("K", 39.0983  );A("Rb", 85.4678 );A("Cs",CsMass    );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.
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

AutoHotkey

test := ["H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O"
        , "Uue", "C6H4O2(O)H)4", "X2O"]
for i, str in test
    result .= str "`t`t`t> " Chemical_calculator(str) "`n"
MsgBox, 262144, , % result
return

Chemical_calculator(str){
    if (RegExReplace(str, "\(([^()]|(?R))*\)")~="[()]")
        return "Invalid Group"
    oAtomM := {"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}

    str := RegExReplace(str, "\d+", "*$0")
    while InStr(str, "("){
        pos := RegExMatch(str, "\(([^()]+)\)\*(\d+)", m)
        m1 := RegExReplace(m1, "[A-Z]([a-z]*)", "$0*" m2)
        str := RegExReplace( str, "\Q" m "\E", m1,, 1, pos)
    }
    str := Trim(RegExReplace(str, "[A-Z]", "+$0"), "+")
    sum := 0
    for i, atom in StrSplit(str, "+"){
        prod := 1
        for j, p in StrSplit(atom, "*")
            prod *= (p~="\d+") ? p : 1
        atom := RegExReplace(atom, "\*\d+")
        if !oAtomM[atom]
            return "Invalid atom name"
        sum += oAtomM[atom] * prod
    }
    return str " > " sum
}
Output:
H			> H > 1.008000
H2			> H*2 > 2.016000
H2O			> H*2+O > 18.015000
H2O2			> H*2+O*2 > 34.014000
(HO)2			> H*2+O*2 > 34.014000
Na2SO4			> Na*2+S+O*4 > 142.035539
C6H12			> C*6+H*12 > 84.162000
COOH(C(CH3)2)3CH3	> C+O+O+H+C*3+C*3*2+H*3*2*3+C+H*3 > 186.295000
C6H4O2(OH)4		> C*6+H*4+O*2+O*4+H*4 > 176.124000
C27H46O			> C*27+H*46+O > 386.664000
Uue			> Uue > 315
C6H4O2(O)H)4		> Invalid Group
X2O			> Invalid atom name

C

#include <stdio.h>
#include <stdlib.h>
#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;
}
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
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);
            }
        }
    }
}
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#
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#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;
}
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

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'

Regular Expression

Translation of: Julia
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')

D

Translation of: Go
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);
}
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
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.

Include file with Atomic Mass Constants (AtomicMass.inc).

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);

Factor

Works with: Factor version 0.98
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

No assertion errors.

Fōrmulæ

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. 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 storage and transfer purposes more than visualization and edition.

Programs in Fōrmulæ are created/edited online in its website.

In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.

Solution

Fōrmulæ has a module for chemistry. Notice that it is not a library, it effectively adds chemical elements as first class citizens to the language, and useful functions that operate with them, for example, to get their atomic masses.

There is an expression for a homonuclear compound, a compound made from the union of several atoms of the same element, such as O2

There is also an expression for a heteronuclear compound, a compound made from the union of several atoms of different elements, such as NaCl

Notes

  • The Tag(Expression) expression retrieves the tag of an expression. For example, when it is called on an homonuclear compound expression, it retrieves the string expression representing the string "Chemistry.HomonuclearCompound"
  • The |Expression| retrieves the cardinality of the expression, this is, the number of subexpressions it has. If the expression is a heteronuclear compound it gives the number of elements being composed.
  • If the expression given as parameter is a heteronuclear compound expression, the molar mass is the sum of the molar masses of each component. Note that this function is recursively called.
  • If the expression given as parameter is a homonuclear compound expression, the molar mass is the product of the number of the group (the second component) and the molar mass of the expression (the first component). Note that this function is recursively called.
  • Elsewhere, the result is the call of the GetAtomicMass(Expression) with the expression given as parameter.

Test cases

Using it symbolically

Fōrmulæ is a symbolic language. Although chemical elements expressions are intended to be used to create chemical formulae, other expressions can be used, specially symbols, as in the following examples:

Example 1. Using a symbol to denote and unspecified number of repetitions in a homonuclear compound expression. For this exercise, n is a free symbol (a symbol with no associated value).

Example 2. Using a symbol to denote an unspecified chemical element. For this exercise, X is a free symbol (a symbol with no associated value).

Example 3. Using symbols to denote an unspecified chemical element and an unspecified number of repetitions. For this exercise, X and n are free symbols (symbols with no associated values).

Example 4. Using symbols to denote different unspecified chemical elements. For this exercise, X, Y and Z are free symbols (symbols with no associated values).

Example 5. Other combinations. For this exercise, X, Y, Z, n and m are free symbols (symbols with no associated values).

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.

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)
    }
}
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

Groovy

Translation of: Java
import java.util.regex.Pattern

class ChemicalCalculator {
    private static final Map<String, Double> ATOMIC_MASS = new HashMap<>()

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

    static void main(String[] args) {
        List<String> molecules = [
                "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))
            printf("%17s -> %7.3f\n", molecule, mass)
        }
    }
}
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

Haskell

Create a set of parsers for molecular formulae and their subparts. The parsers maintain a running total of the mass parsed so far. Use a Reader monad to store a map from atom names to their masses. The contents of the map are read from the file chemcalc_masses.in, not shown here.

import Control.Monad (forM_)
import Control.Monad.Reader (Reader, ask, runReader)
import Data.Bifunctor (first)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Void (Void)
import System.Environment (getArgs)
import System.IO (IOMode(ReadMode), withFile)
import System.IO.Strict (hGetContents)
import Text.Megaparsec (ParsecT, (<|>), between, errorBundlePretty, getOffset,
                        many, option, runParserT, some, setOffset)
import Text.Megaparsec.Char (char, lowerChar, upperChar)
import Text.Megaparsec.Char.Lexer (decimal)
import Text.Printf (printf)

type Masses     = Map String Double
type ChemParser = ParsecT Void String (Reader Masses) Double

-- Parse the formula of a molecule, returning the latter's total mass.
molecule :: ChemParser
molecule = sum <$> some (atomGroup <|> atom)

-- Parse an atom group, optionally followed by its count, returning its total
-- mass.
atomGroup :: ChemParser
atomGroup = mul <$> between (char '(') (char ')') molecule <*> option 1 decimal

-- Parse an atom name, optionally followed by a count, returning its total mass.
atom :: ChemParser
atom = mul <$> atomMass <*> option 1 decimal

-- Parse an atom name, returning its mass.  Fail if the name is unknown.
atomMass :: ChemParser
atomMass = do
  off <- getOffset
  masses <- ask
  atomName <- (:) <$> upperChar <*> many lowerChar
  case M.lookup atomName masses of
    Nothing -> setOffset off >> fail "invalid atom name starting here"
    Just mass -> return mass

-- Given a molecular formula and a map from atom names to their masses, return
-- the the total molar mass, or an error message if the formula can't be parsed.
molarMass :: String -> String -> Masses -> Either String Double
molarMass file formula = first errorBundlePretty . runChemParser
  where runChemParser = runReader (runParserT molecule file formula)

-- Read from a file the map from atom names to their masses.
getMasses :: FilePath -> IO Masses
getMasses path = withFile path ReadMode (fmap read . hGetContents)

mul :: Double -> Int -> Double
mul s n = s * fromIntegral n

main :: IO ()
main = do
  masses <- getMasses "chemcalc_masses.in"
  molecs <- getArgs
  forM_ molecs $ \molec -> do
    printf "%-20s" molec
    case molarMass "<stdin>" molec masses of
      Left err   -> printf "\n%s" err
      Right mass -> printf " %.4f\n" mass
Output:
H                    1.0080
H2                   2.0160
H2O                  18.0150
H2O2                 34.0140
(HO)2                34.0140
Na2SO4               142.0355
C6H12                84.1620
COOH(C(CH3)2)3CH3    186.2950
C6H4O2(OH)4          176.1240
C27H46O              386.6640
Uue                  315.0000
(HOz)2              
<stdin>:1:3:
  |
1 | (HOz)2
  |   ^
invalid atom name starting here

J

This could be done a bit more concisely, but it's not clear that that would be an advantage here.

do{{)n
   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
}} rplc ':';'=:';  ',';'[';  LF;''

NB. 0: punctuation, 1: numeric, 2: upper case, 3: lower case
ctyp=: e.&'0123456789' + (2*]~:tolower) + 3*]~:toupper
tokenize=: (0;(0 10#:10*do;._2{{)n
 1.1  2.1  3.1  4.1   NB. start here
 1.2  2.2  3.2  4.2   NB. punctuation is 1 character per word
 1.2  2    3.2  4.2   NB. numeric characters are word forming
 1.2  2.2  3.2  4     NB. upper case always begins a word
 1.2  2.2  3.2  4     NB. lower case always continues a word
}});ctyp a.)&;:

molar_mass=: {{
 W=.,0  NB. weight stack
 M=.,1  NB. multiplier stack
 digit=. (1=ctyp a.)#<"0 a.
 alpha=. (2=ctyp a.)#<"0 a.
 for_t.|.tokenize y do. select. {.;t 
   case. '(' do.    W=. (M #.&(2&{.) W), 2}.W
    M=. 1,2}.M
   case. ')' do.    W=. 0,W
    M=. 1,M 
   case. digit do.
    M=. (do;t),}.M
   case. alpha do.  W=. (({.W)+({.M)*do;t),}.W
    M=. 1,}.M
   case. do. NB. ignore irrelevant whitespace
  end. end. assert. 1=#W
 <.@+&0.5&.(*&1000){.W
}}

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

Java

Translation of: Kotlin
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);
        }
    }
}
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

JavaScript

const MASSES = {
  C:   12.011,
  H:   1.008,
  Na:  22.98976928,
  O:   15.99,
  Sb:  121.76,
  Sn:  118.71,
  S:   32.06,   
  Uue: 315
}; // to be continued

function getMolarMass(formula) {
  formula = formula.replace(/[0-9]+/g, x => '*' + x + ' ');
  formula = formula.replace(/[A-Z][A-Z]/g, x => x[0] + '+' + x[1]);
  formula = formula.replace(/[0-9] [A-Z]/g, x => x[0] + '+' + x[2]);
  formula = formula.replace(/[A-Z]\(/g, x => x[0] + '+' + x[1]);
  formula = formula.replace(/[0-9] \(/g, x => x[0] + '+' + x[2]);
  formula = formula.replace(/[A-Z][A-Z]/g, x => x[0] + '+' + x[1]);
  for (let key in MASSES)
    formula = formula.replace(new RegExp(key, 'g'), MASSES[key]);
  return eval(formula);
}

// testing
function getSubNums(str) { return str.replace(/[0-9]/g, x => '₀₁₂₃₄₅₆₇₈₉'[x]); }

let formulae =
  'H H2 H2O H2O2 (HO)2 Na2SO4 C6H12 COOH(C(CH3)2)3CH3 C6H4O2(OH)4 C27H46O Uue'.split(' ');

for (let i = 0; i < formulae.length; i++)
  console.log(`${getSubNums(formulae[i])}: ${getMolarMass(formulae[i]).toPrecision(3)}`);
Output:
H: 1.01
H₂: 2.02
H₂O: 18.0
H₂O₂: 34.0
(HO)₂: 34.0
Na₂SO₄: 142
C₆H₁₂: 84.2
COOH(C(CH₃)₂)₃CH₃: 186
C₆H₄O₂(OH)₄: 176
C₂₇H₄₆O: 387
Uue: 315

jq

Works with: jq

Works with gojq, the Go implementation of jq

jq is well-suited to "Parsing Expression Grammars" (PEGs) so this entry illustrates how to implement the chemical calculator using a PEG approach.

In the remainder of this entry we focus on the highlights. The complete program is on the subpage at Chemical calculator/jq.

To understand the PEG grammar presented below, here is a table showing the correspondence between the main PEG operators (on the left) and jq constructs (on the right), the first two of which (`|` and `//`) are part of the jq language, and the others of which are defined as jq functions on the subpage.

  Sequence: e1 e2             e1 | e2
  Ordered choice: e1 / e2     e1 // e2
  Zero-or-more: e*            star(E)
  One-or-more: e+             plus(E)
  Optional: e?                optional(E)
  And-predicate: &e           amp(E) 
  Not-predicate: !e           neg(E)

The PEG grammar

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

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

def EN: Element | optional(Number);

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

def Formula:
     (plus(EN) | Parenthesized | Formula)
  // (plus(EN) | optional(Parenthesized))
  // (Parenthesized | optional(Formula)) ;

Evaluation of the parsed expression

This is accomplished using the `eval` function defined on the subpage.

The task expressed in terms of assertions

# A "debug" statement has been retained so that the parsed chemical formula can be seen.
def molar_mass(formula):
  {remainder: formula} | Formula |  .result | debug | eval;

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

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

As mentioned above, a "debug" statement has been retained so that the parsed chemical formula can be seen.

["DEBUG:",["H"]]
["DEBUG:",["H",2]]
["DEBUG:",["H",2,"O"]]
["DEBUG:",["H",2,"O",2]]
["DEBUG:",[["H","O"],2]]
["DEBUG:",["Na",2,"S","O",4]]
["DEBUG:",["C",6,"H",12]]
["DEBUG:",["C","O","O","H",["C",["C","H",3],2],3,"C","H",3]]
["DEBUG:",["C",6,"H",4,"O",2,["O","H"],4]]
["DEBUG:",["C",27,"H",46,"O"]]
["DEBUG:",["Uue"]]

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.

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")

No assertion errors.

Kotlin

Translation of: D
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")
    }
}
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

Lua

Translation of: C#
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,
}

function evaluate(s)
    s = s .. '['
    local sum = 0.0
    local symbol = ""
    local number = ""

    for i=1,s:len() do
        local c = s:sub(i,i)
        if '@' <= c and c <= '[' then
            local n = 1
            if number ~= "" then
                n = tonumber(number)
            end
            if symbol ~= "" then
                sum = sum + atomicMass[symbol] * n
            end
            if c == '[' then
                break
            end
            symbol = tostring(c)
            number = ""
        elseif 'a' <= c and c <= 'z' then
            symbol = symbol .. c
        elseif '0' <= c and c <= '9' then
            number = number .. c
        else
            error("Unexpected symbol `"..c.."` in molecule")
        end
    end

    return sum
end

function replaceFirst(text, search, replacement)
    local pattern = search:gsub('%W', '%%%1')
    return string.gsub(text, pattern, replacement, 1)
end

function replaceParens(s)
    local letter = "s"
    while true do
        local start = string.find(s, "(", 1, true)
        if start == nil then
            break
        end

        for i=start,s:len() do
            local c = s:sub(i,i)
            if c == ')' then
                local expr = s:sub(start + 1, i - 1)
                local symbol = '@' .. letter
                local search = s:sub(start, i)
                s = replaceFirst(s, search, symbol)
                atomicMass[symbol] = evaluate(expr)
                letter = string.char(string.byte(letter) + 1)
                break
            end
            if c == '(' then
                start = i
            end
        end
    end
    return s
end

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

for i,molecule in pairs(molecules) do
    local mass = evaluate(replaceParens(molecule))
    print(string.format("%17s -> %7.3f", molecule, mass))
end
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.
#? 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"

Perl

Grammar

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($_), $_
}

Regular Expression

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>
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.

with javascript_semantics
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
    string element
    atom mass
    {{element,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)
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
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)))
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

(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)))
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)

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>;
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)
/*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
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
$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()
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

Rust

Rust is precompiled for execution, so there is no runtime eval for arbitrary Rust code. The `eval` crate allows Rust to process syntax similar to JSON while executing. This allows the example to run an `eval` on strings which have been first translated into numeric arithmetic.

use regex::Regex;
use eval::{eval, to_value};
use aho_corasick::AhoCorasick;

const ELEMENTS: &[&str; 5] = &["H", "C", "O", "Na", "S",];
const WEIGHTS: &[&str; 5] = &["1.008", "12.011", "15.999", "22.98976928", "32.06",];

fn main() {
    let test_strings = ["H", "H2", "H2O", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3"];
    let test_values = [1.008, 2.016, 18.015, 142.03553856000002, 84.162, 186.29500000000002];
    let ac = AhoCorasick::new(ELEMENTS).unwrap();   
    let regex1 = Regex::new(r"(?<num>\d+)").unwrap();
    let regex2 = Regex::new(r"(?<group>[A-Z][a-z]{0,2}|\()").unwrap();

    for (i, s) in test_strings.iter().enumerate() {
        let s1 = regex1.replace_all(*s, "*$num");
        let s2 = regex2.replace_all(&s1, "+$group");
        let s3 = ac.replace_all(&s2, WEIGHTS).trim_start_matches("+").replace("(+", "(");
        let mass: Result<eval::Value, eval::Error> = eval(&s3);
        assert_eq!(mass, Ok(to_value(test_values[i])));
        println!("The molar mass of {} checks correctly as {}.", s, test_values[i]);
    }
}
Output:
The molar mass of H checks correctly as 1.008.
The molar mass of H2 checks correctly as 2.016.
The molar mass of H2O checks correctly as 18.015.
The molar mass of Na2SO4 checks correctly as 142.03553856000002.
The molar mass of C6H12 checks correctly as 84.162.
The molar mass of COOH(C(CH3)2)3CH3 checks correctly as 186.29500000000002.

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))")
}
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

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
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#
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
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
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))
    Fmt.print("$17s -> $7.3f", molecule, mass)
}
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

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('+);
}
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(_);
   }();
foreach cstr in (T("H","H2","H2O","Na2SO4","C6H12","COOH(C(CH3)2)3CH3"))
   { println(cstr," --> ",molarMass(cstr)) }
Output:

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

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

X-script

<var $weighttab[],
-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
->
<var $level>
<var $stackTab[]>

chemicalCalculator.x
--------------------

<def charcode,<htod <stoh $1>>>
<var $name>
<var $multiplier>
<var $unusedCharacters>

!"<in <sp 1>,string>
-<set $level,0>
-<set $stackTab[0],0>
-"!

(* 1-3 characters in a row plus optional multipier. Examples: "Uee", "NaO", "COO", "Na2"  *)
?"<format l><opt <format l>><opt <format l>><opt <integer>>"?
!"
-<set $name,<p 1>>
-<set $multiplier,1>
-<set $unusedCharacters,>
-<ifis <p 2>,
--<if <charcode <p 2>>'>=96,
---(* Lower case char - add to name. *)
---<append $name,<p 2>>
---<ifis <p 3>,
----<if <charcode <p 3>>'>=96,
-----(* Lower case char again - add to name. *)
-----<append $name,<p 3>>
-----,{else}
-----(* Not for this name, put in unread buffer. *)
-----<append $unusedCharacters,<p 3>>
----->
---->
---,{else}
---(* Not for this name, put in unread buffer. *)
---<append $unusedCharacters,<p 2>>
---<append $unusedCharacters,<p 3>>
--->
-->
-(* Multiplier. *)
-<set $multiplier,1>
-<ifis <p 4>,
--<ifis $unusedCharacters,
---(* Multiplier is not for this name, put in unread buffer. *)
---<append $unusedCharacters,<p 4>>
---,{else}
---(* Use as multiplier. *)
---<set $multiplier,<p 4>>
--->
-->
-
-(* Unread unused characters. *)
-<unread $unusedCharacters>
-
-(* Update weight. *)
-<update $stackTab[$level],+$weightTab[$name]*$multiplier,3>
-"!

(* Beginning of group. *)
?"("?
!"
-<update $level,+1>
-<set $stackTab[$level],0>
-"!

(* End of group. *)
?")<opt <integer>>"?
!"
-<ifis <p 1>,
--<update $stackTab[$level],*<p 1>,3>
-->
-<update $stackTab[<calc $level-1>],+$stackTab[$level],3>
-<update $level,-1>
-"!

?"<eof>"?
!"
-<wcons Weight of <sp 1> = $stackTab[$level].>
-<r>
-"!

!"<r $stackTab[$level]>"!
--------------

<function assert,
-<unless $1=<c chemicalCalculator,$2>,<wcons <c chemicalCalculator,$2>!= $1.>>
->

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

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