diff --git a/main/Main.hs b/main/Main.hs
--- a/main/Main.hs
+++ b/main/Main.hs
@@ -1,6 +1,4 @@
 
-{-# OPTIONS_HADDOCK hide, prune #-}
-
 {-|
 Module      : Main
 
@@ -9,30 +7,27 @@
 Maintainer  : public@hjwylde.com
 -}
 
-module Main (
-    main
-) where
+{-# OPTIONS_HADDOCK hide, prune #-}
 
-import Control.Applicative
+module Main where
 
 import Options.Applicative
-import Options.Applicative.Builder
 
-import Qux.Commands
-import qualified Qux.Commands.Build as Build
-import qualified Qux.Commands.Check as Check
-import qualified Qux.Commands.Print as Print
-import qualified Qux.Commands.Run   as Run
+import              Qux.Commands
+import qualified    Qux.Commands.Build      as Build
+import qualified    Qux.Commands.Check      as Check
+import qualified    Qux.Commands.Compile    as Compile
+import qualified    Qux.Commands.Print      as Print
 
 
 -- | Main.
 main :: IO ()
-main = customExecParser (prefs $ columns 100) options >>= handle
+main = customExecParser quxPrefs quxInfo >>= handle
 
 handle :: Options -> IO ()
 handle options = case argCommand options of
-    Build   options -> Build.handle options
-    Check   options -> Check.handle options
-    Print   options -> Print.handle options
-    Run     options -> Run.handle   options
+    Build   options -> Build.handle     options
+    Check   options -> Check.handle     options
+    Compile options -> Compile.handle   options
+    Print   options -> Print.handle     options
 
diff --git a/qux.cabal b/qux.cabal
--- a/qux.cabal
+++ b/qux.cabal
@@ -1,12 +1,14 @@
 name:           qux
-version:        0.1.0.0
+version:        0.2.0.0
 
 author:         Henry J. Wylde
 maintainer:     public@hjwylde.com
 homepage:       https://github.com/qux-lang/qux
 
 synopsis:       Command line binary for working with the Qux language
--- description:
+description:    Qux is an experimental language developed from the ground up with the aim of static
+                compile time verification. This package provides a binary for working with it
+                (compiling, type checking and pretty printing).
 
 license:        BSD3
 license-file:   LICENSE
@@ -19,16 +21,24 @@
     main-is: Main.hs
     hs-source-dirs: main/ src/
     other-modules:
+        Paths_qux,
         Qux.Commands,
         Qux.Commands.Build,
         Qux.Commands.Check,
+        Qux.Commands.Compile,
         Qux.Commands.Print,
-        Qux.Commands.Run
+        Qux.Version
 
     default-language: Haskell2010
     build-depends:
         base >= 4.8 && < 5,
-        language-qux == 0.1.*,
+        bytestring == 0.10.*,
+        containers == 0.5.*,
+        directory == 1.*,
+        filepath == 1.*,
+        language-qux == 0.2.*,
+        llvm-general == 3.4.*,
         mtl == 2.*,
-        optparse-applicative == 0.11.*
+        optparse-applicative == 0.11.*,
+        pretty >= 1.1.2 && < 2
 
diff --git a/src/Qux/Commands.hs b/src/Qux/Commands.hs
--- a/src/Qux/Commands.hs
+++ b/src/Qux/Commands.hs
@@ -1,6 +1,4 @@
 
-{-# OPTIONS_HADDOCK hide, prune #-}
-
 {-|
 Module      : Qux.Commands
 
@@ -9,54 +7,73 @@
 Maintainer  : public@hjwylde.com
 -}
 
+{-# OPTIONS_HADDOCK hide, prune #-}
+
 module Qux.Commands where
 
-import Language.Qux.Annotated.PrettyPrinter
+import Data.Version (showVersion)
 
+import qualified Language.Qux.Version as Qux
+
 import Options.Applicative
 import Options.Applicative.Types
 
 import Prelude hiding (print)
 
-import qualified Qux.Commands.Build as Build
-import qualified Qux.Commands.Check as Check
-import qualified Qux.Commands.Print as Print
-import qualified Qux.Commands.Run   as Run
+import qualified Qux.Commands.Build     as Build
+import qualified Qux.Commands.Check     as Check
+import qualified Qux.Commands.Compile   as Compile
+import qualified Qux.Commands.Print     as Print
+import qualified Qux.Version            as Binary
 
+import System.FilePath
 
--- * Qux command
+import Text.PrettyPrint (Mode(..))
 
-options :: ParserInfo Options
-options = info (helper <*> version <*> qux) (fullDesc <> noIntersperse)
+
+-- * Optparse for Qux
+
+quxPrefs :: ParserPrefs
+quxPrefs = prefs $ columns 100
+
+quxInfo :: ParserInfo Options
+quxInfo = info (infoOptions <*> qux) (fullDesc <> noIntersperse)
     where
-        version = infoOption "0.1.0" $ mconcat [
-            long "version",
-            short 'V',
-            help "Show this binary's version",
-            hidden
+        infoOptions = helper <*> version <*> numericVersion <*> quxVersion
+        version = infoOption ("Version " ++ showVersion Binary.version) $ mconcat [
+            long "version", short 'V', hidden,
+            help "Show this binary's version"
             ]
+        numericVersion = infoOption (showVersion Binary.version) $ mconcat [
+            long "numeric-version", hidden,
+            help "Show this binary's version (without the prefix)"
+            ]
+        quxVersion = infoOption ("Qux version " ++ showVersion Qux.version) $ mconcat [
+            long "qux-version", hidden,
+            help "Show the qux version this binary was compiled with"
+            ]
 
+-- * Command
+
 data Options = Options {
     argCommand :: Command
     }
 
--- TODO (hjw): show the help message if no commands given
--- TODO (hjw): improve error message to say "Invalid command x"
 qux :: Parser Options
 qux = Options <$> subparser (mconcat [
-    command "build" $ info (helper <*> build)   (fullDesc <> progDesc "Build FILE using composable options"),
-    command "check" $ info (helper <*> check)   (fullDesc <> progDesc "Check FILE for correctness" <> header "Synonymous to `qux build --type-check'"),
-    command "print" $ info (helper <*> print)   (fullDesc <> progDesc "Pretty print FILE"),
-    command "run"   $ info (helper <*> run)     (fullDesc <> progDesc "Run FILE passing in ARGS as program arguments")
+    command "build"     $ info (helper <*> build)   (fullDesc <> progDesc "Build FILES using composable options"),
+    command "check"     $ info (helper <*> check)   (fullDesc <> progDesc "Check FILES for correctness" <> header "Shortcut for `qux build --type-check'"),
+    command "compile"   $ info (helper <*> compile) (fullDesc <> progDesc "Compile FILES into the LLVM IR" <> header "Shortcut for `qux build --compile'"),
+    command "print"     $ info (helper <*> print)   (fullDesc <> progDesc "Pretty print FILE")
     ])
 
 
 -- * Subcommands
 
-data Command    = Build Build.Options
-                | Check Check.Options
-                | Print Print.Options
-                | Run   Run.Options
+data Command    = Build     Build.Options
+                | Check     Check.Options
+                | Compile   Compile.Options
+                | Print     Print.Options
 
 
 -- ** Build
@@ -64,71 +81,89 @@
 build :: Parser Command
 build = fmap Build $ Build.Options
     <$> switch (mconcat [
+        long "compile", short 'c',
+        help "Enable compilation to LLVM IR"
+        ])
+    <*> strOption (mconcat [
+        long "destination", short 'd', metavar "DIR",
+        value ("." ++ [pathSeparator]), showDefault,
+        help "Specify the output directory to put the compiled LLVM IR"
+        ])
+    <*> formatOption (mconcat [
+        long "format", short 'f', metavar "FORMAT",
+        value Build.Bitcode, showDefault,
+        help "Specify the LLVM output format as either `bitcode' or `assembly'"
+        ])
+    <*> switch (mconcat [
         long "type-check",
         help "Enable type checking"
         ])
-    <*> strArgument (mconcat [
-        metavar "FILE"
+    <*> some (strArgument $ mconcat [
+        metavar "FILES..."
         ])
 
 
 -- ** Check
 
 check :: Parser Command
-check = Check . Check.Options <$> strArgument (mconcat [
-    metavar "FILE"
+check = Check . Check.Options <$> some (strArgument $ mconcat [
+    metavar "FILES..."
     ])
 
 
+-- ** Compile
+
+compile :: Parser Command
+compile = fmap Compile $ Compile.Options
+    <$> strOption (mconcat [
+        long "destination", short 'd', metavar "DIR",
+        value ("." ++ [pathSeparator]), showDefault,
+        help "Specify the output directory to put the compiled LLVM IR"
+        ])
+    <*> formatOption (mconcat [
+        long "format", short 'f', metavar "FORMAT",
+        value Build.Bitcode, showDefault,
+        help "Specify the LLVM output format as either `bitcode' or `assembly'"
+        ])
+    <*> some (strArgument $ mconcat [
+        metavar "FILES..."
+    ])
+
+
 -- ** Print
 
 print :: Parser Command
 print = fmap Print $ Print.Options
     <$> option auto (mconcat [
-        long "line-length",
-        short 'l',
-        metavar "LENGTH",
-        value 100,
-        showDefault,
+        long "line-length", short 'l', metavar "LENGTH",
+        value 100, showDefault,
         help "Specify the maximum line length"
         ])
     <*> modeOption (mconcat [
-        long "mode",
-        short 'm',
-        metavar "MODE",
-        value PageMode,
-        showDefaultWith $ const "normal",
-        help "Specify the rendering mode as either 'normal' or 'one-line'"
+        long "mode", short 'm', metavar "MODE",
+        value PageMode, showDefaultWith $ const "normal",
+        help "Specify the rendering mode as either `normal' or `one-line'"
         ])
     <*> option auto (mconcat [
-        long "ribbons-per-line",
-        short 'r',
-        metavar "RIBBONS",
-        value 1.5,
-        showDefault,
+        long "ribbons-per-line", short 'r', metavar "RIBBONS",
+        value 1.5, showDefault,
         help "Specify the ratio of line length to ribbon length"
         ])
     <*> strArgument (mconcat [
         metavar "FILE"
         ])
     where
-        modeOption :: Mod OptionFields Mode -> Parser Mode
-        modeOption = option $ do
-            opt <- readerAsk
+        modeOption = option $ readerAsk >>= \opt -> case opt of
+            "normal"    -> return PageMode
+            "one-line"  -> return OneLineMode
+            _           -> readerError $ "unrecognised mode `" ++ opt ++ "'"
 
-            case opt of
-                "normal"    -> return PageMode
-                "one-line"  -> return OneLineMode
-                _           -> readerError $ "unrecognised mode `" ++ opt ++ "'"
 
--- ** Run
+-- ** Helpers
 
-run :: Parser Command
-run = fmap Run $ Run.Options
-    <$> strArgument (mconcat [
-        metavar "FILE"
-        ])
-    <*> many (strArgument $ mconcat [
-        metavar "ARGS..."
-        ])
+formatOption :: Mod OptionFields Build.Format -> Parser Build.Format
+formatOption = option $ readerAsk >>= \opt -> case opt of
+    "assembly"  -> return Build.Assembly
+    "bitcode"   -> return Build.Bitcode
+    _           -> readerError $ "unrecognised format `" ++ opt ++ "'"
 
diff --git a/src/Qux/Commands/Build.hs b/src/Qux/Commands/Build.hs
--- a/src/Qux/Commands/Build.hs
+++ b/src/Qux/Commands/Build.hs
@@ -1,6 +1,4 @@
 
-{-# OPTIONS_HADDOCK hide, prune #-}
-
 {-|
 Module      : Qux.Commands.Build
 
@@ -9,39 +7,102 @@
 Maintainer  : public@hjwylde.com
 -}
 
+{-# OPTIONS_HADDOCK hide, prune #-}
+
 module Qux.Commands.Build where
 
 import Control.Monad.Except
+import Control.Monad.Identity
 
-import Language.Qux.Annotated.Parser
-import Language.Qux.Annotated.Syntax
-import Language.Qux.Annotated.TypeChecker
+import qualified    Data.ByteString as BS
+import              Data.List       (intercalate)
 
+import              Language.Qux.Annotated.Parser           hiding (parse)
+import qualified    Language.Qux.Annotated.Parser           as P
+import              Language.Qux.Annotated.Syntax
+import              Language.Qux.Annotated.TypeChecker
+import              Language.Qux.Annotated.TypeResolver
+import qualified    Language.Qux.Llvm.Compiler              as C
+
+import LLVM.General
+import LLVM.General.Context
+
+import System.Directory
 import System.Exit
+import System.FilePath
 import System.IO
+import System.IO.Error
 
 
 data Options = Options {
+    optCompile      :: Bool,
+    optDestination  :: FilePath,
+    optFormat       :: Format,
     optTypeCheck    :: Bool,
-    argFilePath     :: String
+    argFilePaths    :: [FilePath]
     }
 
+defaultOptions :: Options
+defaultOptions = Options {
+    optCompile      = False,
+    optDestination  = "." ++ [pathSeparator],
+    optFormat       = Bitcode,
+    optTypeCheck    = False,
+    argFilePaths    = []
+    }
+
+data Format = Assembly | Bitcode
+    deriving Eq
+
+instance Show Format where
+    show Assembly   = "assembly"
+    show Bitcode    = "bitcode"
+
+
 handle :: Options -> IO ()
 handle options = do
-    let filePath = argFilePath options
+    let filePaths = argFilePaths options
+    fileContents <- mapM readFile filePaths
 
-    contents <- readFile $ argFilePath options
+    ethr <- catchIOError
+        (runExceptT $ zipWithM parse filePaths fileContents >>= mapM_ (build options))
+        (return . Left . ioeGetErrorString)
 
-    case runExcept $ tryParse filePath contents >>= build options of
+    case ethr of
         Left error  -> hPutStrLn stderr error >> exitFailure
         Right _     -> return ()
 
-tryParse :: FilePath -> String -> Except String (Program SourcePos)
-tryParse filePath contents = withExcept show (parse program filePath contents)
+parse :: FilePath -> String -> ExceptT String IO (Program SourcePos)
+parse filePath contents = resolve <$> mapExceptT (return . runIdentity) (withExcept show (P.parse program filePath contents))
 
-build :: Options -> Program SourcePos -> Except String ()
-build options program = when (optTypeCheck options) (typeCheck program)
+build :: Options -> Program SourcePos -> ExceptT String IO ()
+build options program = do
+    when (optTypeCheck options) $ typeCheck program
+    when (optCompile options)   $ compile options program
 
-typeCheck :: Program SourcePos -> Except String ()
-typeCheck program = withExcept show (check program)
+typeCheck :: Program SourcePos -> ExceptT String IO ()
+typeCheck program = when (not $ null errors) $ throwError (intercalate "\n\n" $ map show errors)
+    where
+        errors = check program
+
+compile :: Options -> Program SourcePos -> ExceptT String IO ()
+compile options program
+    | optFormat options == Assembly = liftIO $ do
+        assembly <- withContext $ \context ->
+            runExceptT (withModuleFromAST context mod moduleLLVMAssembly) >>= either fail return
+
+        createDirectoryIfMissing True basePath
+        writeFile (addExtension (basePath ++ baseName) "ll") assembly
+    | optFormat options == Bitcode  = liftIO $ do
+        bitcode <- withContext $ \context ->
+            runExceptT (withModuleFromAST context mod moduleBitcode) >>= either fail return
+
+        createDirectoryIfMissing True basePath
+        BS.writeFile (addExtension (basePath ++ baseName) "bc") bitcode
+    | otherwise                     = error $ "format not implemented `" ++ show (optFormat options) ++ "'"
+    where
+        module_     = let (Program _ module_ _) = program in map simp module_
+        mod         = C.compile $ simp program
+        basePath    = intercalate [pathSeparator] ([optDestination options] ++ init module_) ++ [pathSeparator]
+        baseName    = last module_
 
diff --git a/src/Qux/Commands/Check.hs b/src/Qux/Commands/Check.hs
--- a/src/Qux/Commands/Check.hs
+++ b/src/Qux/Commands/Check.hs
@@ -1,6 +1,4 @@
 
-{-# OPTIONS_HADDOCK hide, prune #-}
-
 {-|
 Module      : Qux.Commands.Check
 
@@ -9,18 +7,31 @@
 Maintainer  : public@hjwylde.com
 -}
 
+{-# OPTIONS_HADDOCK hide, prune #-}
+
 module Qux.Commands.Check where
 
+import Control.Monad.Except
+
+import Language.Qux.Annotated.Parser
+import Language.Qux.Annotated.Syntax
+
 import qualified Qux.Commands.Build as Build
 
 
 data Options = Options {
-    argFilePath :: String
+    argFilePaths :: [FilePath]
     }
 
 handle :: Options -> IO ()
-handle options = Build.handle Build.Options {
-    Build.argFilePath = argFilePath options,
-    Build.optTypeCheck = True
+handle options = Build.handle $ buildOptions options
+
+check :: Options -> Program SourcePos -> ExceptT String IO ()
+check options = Build.build $ buildOptions options
+
+buildOptions :: Options -> Build.Options
+buildOptions options = Build.defaultOptions {
+    Build.optTypeCheck = True,
+    Build.argFilePaths = argFilePaths options
     }
 
diff --git a/src/Qux/Commands/Compile.hs b/src/Qux/Commands/Compile.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Commands/Compile.hs
@@ -0,0 +1,33 @@
+
+{-|
+Module      : Qux.Commands.Compile
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+module Qux.Commands.Compile where
+
+import qualified Qux.Commands.Build as Build
+
+
+data Options = Options {
+    optDestination  :: FilePath,
+    optFormat       :: Build.Format,
+    argFilePaths    :: [FilePath]
+    }
+
+handle :: Options -> IO ()
+handle options = Build.handle $ buildOptions options
+
+buildOptions :: Options -> Build.Options
+buildOptions options = Build.defaultOptions {
+    Build.optCompile        = True,
+    Build.optDestination    = optDestination options,
+    Build.optFormat         = optFormat options,
+    Build.argFilePaths      = argFilePaths options
+    }
+
diff --git a/src/Qux/Commands/Print.hs b/src/Qux/Commands/Print.hs
--- a/src/Qux/Commands/Print.hs
+++ b/src/Qux/Commands/Print.hs
@@ -1,6 +1,4 @@
 
-{-# OPTIONS_HADDOCK hide, prune #-}
-
 {-|
 Module      : Qux.Commands.Print
 
@@ -9,32 +7,39 @@
 Maintainer  : public@hjwylde.com
 -}
 
+{-# OPTIONS_HADDOCK hide, prune #-}
+
 module Qux.Commands.Print where
 
 import Control.Monad.Except
 
-import Language.Qux.Annotated.PrettyPrinter
-
-import Qux.Commands.Build (tryParse)
+import Qux.Commands.Build (parse)
 
 import System.Exit
 import System.IO
+import System.IO.Error
 
+import Text.PrettyPrint                 hiding (style)
+import Text.PrettyPrint.HughesPJClass   hiding (style)
 
+
 data Options = Options {
     optLineLength       :: Int,
     optMode             :: Mode,
     optRibbonsPerLine   :: Float,
-    argFilePath         :: String
+    argFilePath         :: FilePath
     }
 
 handle :: Options -> IO ()
 handle options = do
     let filePath = argFilePath options
-
     contents <- readFile $ argFilePath options
 
-    case runExcept $ tryParse filePath contents of
+    ethr <- catchIOError
+        (runExceptT $ parse filePath contents)
+        (return . Left . ioeGetErrorString)
+
+    case ethr of
         Left error      -> hPutStrLn stderr (show error) >> exitFailure
         Right program   -> putStrLn $ renderStyle (style options) (pPrint program)
 
diff --git a/src/Qux/Commands/Run.hs b/src/Qux/Commands/Run.hs
deleted file mode 100644
--- a/src/Qux/Commands/Run.hs
+++ /dev/null
@@ -1,48 +0,0 @@
-
-{-# OPTIONS_HADDOCK hide, prune #-}
-
-{-|
-Module      : Qux.Commands.Run
-
-Copyright   : (c) Henry J. Wylde, 2015
-License     : BSD3
-Maintainer  : public@hjwylde.com
--}
-
-module Qux.Commands.Run where
-
-import Control.Monad.Except
-
-import Language.Qux.Annotated.Simplify
-import Language.Qux.Annotated.Syntax
-import Language.Qux.Interpreter
-import Language.Qux.PrettyPrinter
-
-import Qux.Commands.Build (tryParse)
-
-import System.Exit
-import System.IO
-
-
-data Options = Options {
-    argFilePath :: FilePath,
-    argsExtra   :: [String]
-    }
-
-handle :: Options -> IO ()
-handle options = do
-    let filePath = argFilePath options
-
-    contents <- readFile $ argFilePath options
-
-    case runExcept $ tryParse filePath contents >>= run options of
-        Left error      -> hPutStrLn stderr error >> exitFailure
-        Right result    -> putStrLn result
-
-run :: Options -> Program a -> Except String String
-run options program = return $ render (pPrint result)
-    where
-        result = exec (sProgram program) main args
-        main = "main"
-        args = map (IntValue . read) (argsExtra options)
-
diff --git a/src/Qux/Version.hs b/src/Qux/Version.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Version.hs
@@ -0,0 +1,17 @@
+
+{-|
+Module      : Qux.Version
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+module Qux.Version (
+    version
+) where
+
+import Paths_qux (version)
+
