Chemical calculator: Difference between revisions

Haskell version.
(Added AutoHotkey)
(Haskell version.)
Line 2,000:
C27H46O -> 386.664
Uue -> 315.000</pre>
 
=={{header|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.
<lang haskell>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
</lang>
{{out}}
<pre>
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
</pre>
 
=={{header|Java}}==
Anonymous user