packages feed

oplang 0.4.0.0 → 0.4.0.1

raw patch · 19 files changed

+202/−237 lines, 19 filesdep +monad-chronicledep −transformersdep ~megaparsecdep ~text

Dependencies added: monad-chronicle

Dependencies removed: transformers

Dependency ranges changed: megaparsec, text

Files

CHANGELOG.md view
@@ -1,4 +1,10 @@-<!-- markdownlint-disable first-line-h1   -->+<!-- markdownlint-disable first-line-h1 -->++## v0.4.0.1 \[2023-12-28\]++* Improve error messages, warnings, and `--help` text+* Fix warnings not being shown if there were also compilation errors+* Update dependencies to allow compiling with GHC 9.8  ## v0.4.0.0 \[2023-05-06\] 
README.md view
@@ -36,21 +36,21 @@  To run the project locally (without installing), use `cabal run . -- <args>`. -## Language Features+## Language features  OpLang is a strict superset of Brainfuck.  Its main improvement is the addition of user-defined operators, which are analogous to user-defined functions in languages like C or Python. -The "memory tape" in OpLang is specific to each operator invocation (akin to the stack space allocated for functions in C), and there is a separate "stack" which persists across operator invocations.+The "memory tape" in OpLang is specific to each operator invocation (akin to the stack frames of functions in C), and there is a separate "stack" which persists across operator invocations.  Each cell in the tape(s) and stack is 1 byte, and overflow/underflow is allowed. -The default size of the stack is 4KB, and the default size for each memory tape is 64KB. These can be modified via the command-line.+The default sizes of the stack and of memory tapes is 4KB. These defaults can be individually modified via command-line arguments.  ### Syntax -OpLang has 10 "intrinsic" operators, 8 of which are the Brainfuck operators, with the same semantics:+OpLang has 10 built-in operators, 8 of which are the Brainfuck operators, with the same semantics:  * `+`: Increment the current cell * `-`: Decrement the current cell@@ -79,13 +79,13 @@ ,: a ;. ``` -For more example programs, see the [examples](examples) folder.+For more example programs, see the [examples](examples/) directory. -## Compiler Architecture+## Compiler architecture  The compiler works by translating the input OpLang source into C, and then compiling that using the system's C compiler. To use a different C compiler, set the `CC` environment variable. -The compiler uses an intermediate representation (which can be shown with the `--dump-ir` option) on which it performs a number of optimizations, generating C code that is much smaller and faster than a direct translation would be.+The compiler uses an intermediate representation (which can be shown with the `--dump-ir` option) on which it performs a number of optimizations, generating C code that is much smaller and faster than a direct translation would yield.  ## License 
+ examples/errors/both.op view
@@ -0,0 +1,7 @@+# Should fail to compile, producing the following warnings and errors:+# Warning: Unused operator 'f'.+# Warning: Unused operator 'g'.+# Error: In definition of 'f': Call to undefined operator 'a'.+# Error: In definition of 'g': Call to undefined operator 'b'.+f { a }+g { b }
+ examples/errors/errors.op view
@@ -0,0 +1,5 @@+# Should fail to compile with the following errors:+# Error: In top level: Call to undefined operator 'b'.+# Error: In definition of 'f': Call to undefined operator 'a'.+f { a }+f b
+ examples/errors/warnings.op view
@@ -0,0 +1,5 @@+# Should produce the following warnings (unless compiled with --no-warn):+# Warning: Unused operator 'f'.+# Warning: Unused operator 'g'.+f { }+g { }
+ examples/factorial.op view
@@ -0,0 +1,15 @@+# Reads a single-digit number n, then prints factorial(n) '*' chars.+# To easily check the value, you can pipe the output into `wc -c`, like so:+# `echo 5 | ./factorial.out | wc -c`+# ^ Should print 120++0 { +++ +++ [> ++++ ++++ < -] > : }+s { ; > ; [- < - >] < : }+r { 0 , : s }++m { ; > ; < [->[->+>+<<]>[-<+>]<<]>[-]>>[-<<<+>>>]<<< : }+f { ; > + < [: > : m ; < -] > : }++* { +++ +++ + [> +++ +++ < -] > : }++*; > rf ; [ < . > - ]
+ examples/hello-world.bf view
@@ -0,0 +1,4 @@+# Prints 'hello world'.+# Relies on cells overflowing in order to work.+++[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-.
+ examples/lost-kingdom.bf view

file too large to diff

+ examples/stack.op view
@@ -0,0 +1,5 @@+# Prints 'cba'.++f { ++++ ++++ [> ++++ ++++ ++++ < -] > + : + : + : }+m { f ;. ;. ;. }+m
+ examples/triangle.bf view
@@ -0,0 +1,8 @@+# Downloaded from http://www.hevanet.com/cristofd/brainfuck/sierpinski.b+# Prints the Sierpinski triangle++++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[+    -<<<[+        ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<<+    ]>.>+[>>]>++]
oplang.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0  name: oplang-version: 0.4.0.0+version: 0.4.0.1 synopsis: Stack-based esoteric programming language description: Please see the README on GitHub at <https://github.com/aionescu/oplang#readme> homepage: https://github.com/aionescu/oplang#readme@@ -14,57 +14,33 @@ category: Compilers/Interpreters, Language build-type: Simple +tested-with: GHC == { 9.2.8, 9.4.8, 9.6.3, 9.8.1 }+ extra-doc-files:   CHANGELOG.md--extra-source-files:   README.md+  examples/**/*.bf+  examples/**/*.op  source-repository head   type: git   location: https://github.com/aionescu/oplang -common ghc-flags+executable oplang   default-language: GHC2021   default-extensions:-    ApplicativeDo     BlockArguments-    DataKinds-    DefaultSignatures-    DeriveAnyClass-    DerivingVia-    DuplicateRecordFields-    FunctionalDependencies-    GADTs-    ImplicitParams-    ImpredicativeTypes+    DerivingStrategies     LambdaCase-    LexicalNegation-    MagicHash-    MultiWayIf-    NegativeLiterals     NoFieldSelectors-    NoMonomorphismRestriction-    NoStarIsType-    OverloadedLabels     OverloadedRecordDot     OverloadedStrings-    PartialTypeSignatures-    PatternSynonyms-    QuantifiedConstraints     RecordWildCards-    RecursiveDo-    TypeFamilyDependencies-    UnboxedTuples-    UndecidableInstances-    UnliftedDatatypes-    UnliftedNewtypes-    ViewPatterns +  other-extensions:+    StrictData+   ghc-options:-    -threaded-    -rtsopts-    -with-rtsopts=-N     -Wall     -Wcompat     -Widentities@@ -75,14 +51,11 @@     -Wredundant-constraints     -Wunused-packages -executable oplang-  import: ghc-flags-   hs-source-dirs: src   main-is: Main.hs+  autogen-modules: Paths_oplang    other-modules:-    Control.Monad.Comp     Language.OpLang.Codegen     Language.OpLang.Optimizer     Language.OpLang.Parser@@ -91,18 +64,15 @@     Opts     Paths_oplang -  autogen-modules:-    Paths_oplang-   build-depends:     base >=4.16 && <5     , containers ^>= 0.6.7     , directory ^>= 1.3.8     , filepath ^>= 1.4.100-    , megaparsec ^>= 9.3+    , megaparsec ^>= 9.6.1+    , monad-chronicle ^>= 1.0.1     , mtl ^>= 2.3     , optparse-applicative ^>= 0.17     , process ^>= 1.6.17-    , text ^>= 2+    , text >=2 && <2.2     , text-builder-linear ^>= 0.1-    , transformers ^>= 0.6
− src/Control/Monad/Comp.hs
@@ -1,34 +0,0 @@-module Control.Monad.Comp(CompT(..), runCompT) where--import Control.Applicative(Alternative)-import Control.Monad(MonadPlus)-import Control.Monad.IO.Class(MonadIO)-import Control.Monad.Reader(MonadReader, ReaderT(..))-import Control.Monad.Trans(MonadTrans(..))-import Control.Monad.Trans.Maybe(MaybeT(..))-import Control.Monad.Trans.Writer.CPS(WriterT, runWriterT)-import Control.Monad.Writer.CPS(MonadWriter)-import Data.Text(Text)--import Opts(Opts)---- The "Compilation" Monad Transformer-newtype CompT m a =-  CompT (ReaderT Opts (MaybeT (WriterT [Text] m)) a)-  deriving newtype-    ( Functor-    , Applicative-    , Alternative-    , Monad-    , MonadPlus-    , MonadReader Opts-    , MonadWriter [Text]-    , MonadIO-    )--instance MonadTrans CompT where-  lift :: Monad m => m a -> CompT m a-  lift = CompT . lift . lift . lift--runCompT :: CompT m a -> Opts -> m (Maybe a, [Text])-runCompT (CompT m) = runWriterT . runMaybeT . runReaderT m
src/Language/OpLang/Codegen.hs view
@@ -1,23 +1,12 @@-module Language.OpLang.Codegen(compile) where+module Language.OpLang.Codegen(codegen) where -import Control.Monad.Reader(ask)-import Control.Monad.Trans(lift) import Data.Char(ord) import Data.Foldable(foldMap') import Data.Map.Strict qualified as M-import Data.Maybe(fromMaybe) import Data.Text(Text) import Data.Text.Builder.Linear(Builder, fromDec, runBuilder)-import Data.Text.IO qualified as T-import System.Directory(createDirectoryIfMissing, removeFile)-import System.Environment(lookupEnv)-import System.FilePath(dropExtension, takeDirectory)-import System.Info(os)-import System.Process(callProcess) -import Control.Monad.Comp(CompT) import Language.OpLang.Syntax-import Opts(Opts(..))  type CCode = Builder @@ -32,14 +21,14 @@ forwardDecl :: Id -> CCode forwardDecl name = "void " <> cName name <> "();" -compileDef :: Id -> [Instr] -> CCode-compileDef name body = "void " <> cName name <> "(){char u[T]={0},*t=u;" <> compileOps body <> "}"+codegenDef :: Id -> [Instr] -> CCode+codegenDef name body = "void " <> cName name <> "(){char u[T]={0},*t=u;" <> codegenOps body <> "}" -compileMain :: [Instr] -> CCode-compileMain body = "int main(){char u[T]={0},*t=u;" <> compileOps body <> "return 0;}"+codegenMain :: [Instr] -> CCode+codegenMain body = "int main(){char u[T]={0},*t=u;" <> codegenOps body <> "return 0;}" -compileOps :: [Instr] -> CCode-compileOps = foldMap' compileOp+codegenOps :: [Instr] -> CCode+codegenOps = foldMap' codegenOp  tape :: Offset -> CCode tape 0 = "*t"@@ -56,8 +45,8 @@     times 1 = ""     times n = "*" <> fromDec n -compileOp :: Instr -> CCode-compileOp = \case+codegenOp :: Instr -> CCode+codegenOp = \case   Add n o -> tape o <> plusEq n <> fromDec (abs n) <> ";"   Set n o -> tape o <> "=" <> fromDec n <> ";"   Pop o -> tape o <> "=*(--s);"@@ -66,7 +55,7 @@   Write o -> "printf(\"%c\"," <> tape o <> ");"   Move n -> "t" <> plusEq n <> fromDec (abs n) <> ";"   AddCell n o o' -> addCell n o o'-  Loop ops -> "while(*t){" <> compileOps ops <> "}"+  Loop ops -> "while(*t){" <> codegenOps ops <> "}"   Call c -> cName c <> "();"  codegen :: Word -> Word -> Program Instr -> Text@@ -74,36 +63,5 @@   runBuilder   $ programHeader stackSize tapeSize   <> foldMap' forwardDecl (M.keys opDefs)-  <> M.foldMapWithKey compileDef opDefs-  <> compileMain topLevel--exePath :: FilePath -> FilePath-exePath path = dropExtension path <> ext os-  where-    ext "mingw32" = ".exe"-    ext _ = ".out"--ccPath :: IO FilePath-ccPath = fromMaybe "cc" <$> lookupEnv "CC"--compile :: Program Instr -> CompT IO ()-compile p = do-  Opts{..} <- ask-  let cFile = dropExtension path <> ".c"-  let cCode = codegen stackSize tapeSize p--  lift-    if noCC then do-      let outFile = fromMaybe cFile outPath-      createDirectoryIfMissing True $ takeDirectory outFile--      T.writeFile outFile cCode-    else do-      T.writeFile cFile cCode--      let outFile = fromMaybe (exePath path) outPath-      createDirectoryIfMissing True $ takeDirectory outFile--      cc <- ccPath-      callProcess cc ["-o", outFile, cFile]-      removeFile cFile+  <> M.foldMapWithKey codegenDef opDefs+  <> codegenMain topLevel
src/Language/OpLang/Optimizer.hs view
@@ -1,13 +1,6 @@ module Language.OpLang.Optimizer(optimize) where -import Control.Monad(when)-import Control.Monad.Reader(ask)-import Control.Monad.Trans(lift)-import Data.Functor(($>))--import Control.Monad.Comp(CompT) import Language.OpLang.Syntax-import Opts(Opts(..))  syncTally :: Bool -> Val -> Offset -> [Instr] -> [Instr] syncTally False 0 _ acc = acc@@ -55,13 +48,9 @@              l' -> go loop False 0 0 (Loop l' : syncAll known tally offset acc) ops -optimize :: Program Op -> CompT IO (Program Instr)-optimize Program{..} = do-  Opts{..} <- ask-  when dumpIR (lift $ putStrLn $ "IR:\n" <> show p <> "\n") $> p-  where-    p =-      Program-      { opDefs = optimizeOps <$> opDefs-      , topLevel = optimizeOps topLevel-      }+optimize :: Program Op -> Program Instr+optimize Program{..} =+  Program+  { opDefs = optimizeOps <$> opDefs+  , topLevel = optimizeOps topLevel+  }
src/Language/OpLang/Parser.hs view
@@ -1,9 +1,6 @@ module Language.OpLang.Parser(parse) where -import Control.Monad(when)-import Control.Monad.Reader(ask)-import Control.Monad.Trans(lift)-import Control.Monad.Writer(tell)+import Control.Monad.Chronicle(MonadChronicle(..)) import Data.Functor(($>)) import Data.List(intercalate) import Data.Map.Strict(Map)@@ -16,9 +13,7 @@ import Text.Megaparsec.Char(space1) import Text.Megaparsec.Char.Lexer qualified as L -import Control.Monad.Comp(CompT) import Language.OpLang.Syntax-import Opts(Opts(..))  type Parser = Parsec Void Text @@ -34,8 +29,8 @@ reserved :: Text reserved = "+-<>,.;:[]{}" -intrinsic :: Parser Op-intrinsic =+builtIn :: Parser Op+builtIn =   choice   [ symbol "+" $> Incr   , symbol "-" $> Decr@@ -45,7 +40,7 @@   , symbol "." $> Write'   , symbol ";" $> Pop'   , symbol ":" $> Push'-  ] <?> "intrinsic operator"+  ] <?> "built-in operator"  block :: Text -> Text -> Parser [Op] block b e = between (symbol b) (symbol e) $ many op@@ -60,7 +55,7 @@ op =   choice   [ loop-  , intrinsic+  , builtIn   , Call' <$> custom   ] <?> "operator" @@ -81,9 +76,8 @@ program :: Parser (Program Op) program = Program <$> defs <*> (many op <?> "toplevel") -parse :: Text -> CompT IO (Program Op)-parse code = do-  Opts{..} <- ask+parse :: MonadChronicle [Text] m => FilePath -> Text -> m (Program Op)+parse path code =   case runParser (ws *> program <* eof) path code of-    Left e -> tell ["Parse error at " <> T.pack (errorBundlePretty e)] *> empty-    Right p -> when dumpAST (lift $ putStrLn $ "AST:\n" <> show p <> "\n") $> p+    Left e -> confess ["Parse error at " <> T.pack (errorBundlePretty e)]+    Right p -> pure p
src/Language/OpLang/Syntax.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE StrictData #-} module Language.OpLang.Syntax where  import Data.Int(Int8)
src/Language/OpLang/Validation.hs view
@@ -1,33 +1,29 @@ module Language.OpLang.Validation(validate) where -import Control.Monad(guard, unless)-import Control.Monad.Reader(ask)-import Control.Monad.Writer(tell)+import Control.Monad(unless)+import Control.Monad.Chronicle(MonadChronicle(..)) import Data.Bifunctor(bimap)-import Data.Functor(($>))-import Data.List(intercalate)+import Data.Foldable(foldMap')+import Data.Functor((<&>)) import Data.Map.Strict(Map) import Data.Map.Strict qualified as M+import Data.Maybe(fromMaybe) import Data.Set(Set) import Data.Set qualified as S import Data.Text(Text) import Data.Text qualified as T -import Control.Monad.Comp(CompT) import Language.OpLang.Syntax-import Opts(Opts(..))  calledOps :: [Op] -> Set Id-calledOps = foldMap \case+calledOps = foldMap' \case   Call' op -> S.singleton op   Loop' ops -> calledOps ops   _ -> S.empty -enumerate :: Show a => [a] -> Text-enumerate l = T.pack $ intercalate ", " $ show <$> l--checkUndefinedCalls :: Monad m => Program Op -> CompT m ()-checkUndefinedCalls Program{..} = tell errors *> guard (null errors)+undefinedCalls :: Program Op -> [Text]+undefinedCalls Program{..} =+  toMsgs =<< filter (not . S.null . snd) (undefinedInTopLevel : undefinedInDefs)   where     defined = M.keysSet opDefs @@ -35,27 +31,31 @@     undefinedInDefs = bimap Just ((S.\\ defined) . calledOps) <$> M.toList opDefs      fmt = maybe "top level" $ ("definition of " <>) . T.pack . show-    toMsg (name, ops) =-      "Error (in " <> fmt name <> "): Calls to undefined operators: " <> enumerate (S.toList ops)--    errors = toMsg <$> filter (not . S.null . snd) (undefinedInTopLevel : undefinedInDefs)+    toMsgs (name, ops) = S.toList ops <&> \op ->+      "Error: In " <> fmt name <> ": Call to undefined operator " <> T.pack (show op) <> "."  allUsedOps :: Map Id [Op] -> Set Id -> [Op] -> Set Id allUsedOps defs seen ops   | S.null used = seen-  | otherwise = foldMap (allUsedOps defs (seen <> used) . (defs M.!)) used+  | otherwise = foldMap' ((allUsedOps defs (seen <> used)) . fromMaybe [] . (defs M.!?)) used   where     used = calledOps ops S.\\ seen -removeUnusedOps :: Monad m => Program Op -> CompT m (Program Op)-removeUnusedOps p@Program{..} = do-  Opts{..} <- ask-  unless (noWarn || M.null unusedDefs) warn $> p { opDefs = usedDefs }+removeUnusedOps :: Program Op -> ([Text], Program Op)+removeUnusedOps p@Program{..} = (warnings, p{opDefs = usedDefs})   where-    warn = tell ["Warning: Unused operators: " <> enumerate (M.keys unusedDefs)]-    unusedDefs = opDefs M.\\ usedDefs-    usedDefs = M.restrictKeys opDefs usedOps     usedOps = allUsedOps opDefs S.empty topLevel+    unusedDefs = M.withoutKeys opDefs usedOps+    usedDefs = M.restrictKeys opDefs usedOps -validate :: Monad m => Program Op -> CompT m (Program Op)-validate p = checkUndefinedCalls p *> removeUnusedOps p+    warnings = M.keys unusedDefs <&> \op ->+      "Warning: Unused operator " <> T.pack (show op) <> "."++validate :: MonadChronicle [Text] m => Bool -> Program Op -> m (Program Op)+validate noWarn p = do+  let (warnings, p') = removeUnusedOps p+  unless noWarn $ dictate warnings++  case undefinedCalls p of+    [] -> pure p'+    errs -> confess errs
src/Main.hs view
@@ -1,31 +1,71 @@ module Main(main) where -import Control.Monad((>=>))-import Control.Monad.Reader(asks)+import Control.Monad(when)+import Control.Monad.Chronicle(ChronicleT(..)) import Control.Monad.Trans(lift) import Data.Bifoldable(bitraverse_) import Data.Foldable(traverse_)+import Data.Maybe(fromMaybe) import Data.Text(Text) import Data.Text.IO qualified as T-import Data.Tuple(swap)-import System.Exit(exitFailure)+import System.Directory(createDirectoryIfMissing, removeFile)+import System.Environment(lookupEnv)+import System.Exit(exitFailure, exitSuccess)+import System.FilePath(dropExtension, takeDirectory)+import System.Info(os)+import System.Process(callProcess) -import Control.Monad.Comp(CompT, runCompT)-import Language.OpLang.Codegen(compile)+import Language.OpLang.Codegen(codegen) import Language.OpLang.Optimizer(optimize) import Language.OpLang.Parser(parse) import Language.OpLang.Validation(validate) import Opts(Opts(..), getOpts) -getCode :: CompT IO Text-getCode = lift . T.readFile =<< asks (.path)+exePath :: FilePath -> FilePath+exePath path = dropExtension path <> ext os+  where+    ext "mingw32" = ".exe"+    ext _ = ".out" -pipeline :: CompT IO ()-pipeline =-  getCode >>= (parse >=> validate >=> optimize >=> compile)+compileCCode :: Opts -> Text -> IO ()+compileCCode opts cCode = do+  let cFile = dropExtension opts.path <> ".c" +  if opts.noCC then do+    let outFile = fromMaybe cFile opts.outPath+    createDirectoryIfMissing True $ takeDirectory outFile+    T.writeFile outFile cCode+  else do+    T.writeFile cFile cCode++    let outFile = fromMaybe (exePath opts.path) opts.outPath+    createDirectoryIfMissing True $ takeDirectory outFile++    cc <- fromMaybe "cc" <$> lookupEnv "CC"+    callProcess cc ["-o", outFile, cFile]+    removeFile cFile++runCompiler :: Opts -> Text -> ChronicleT [Text] IO ()+runCompiler opts code = do+  ast <- parse opts.path code+  when opts.dumpAST do+    lift $ putStrLn $ "AST:\n" <> show ast <> "\n"++  ast' <- validate opts.noWarn ast++  let ir = optimize ast'+  when opts.dumpIR do+    lift $ putStrLn $ "IR:\n" <> show ir <> "\n"++  let cCode = codegen opts.stackSize opts.tapeSize ir+  lift $ compileCCode opts cCode+ main :: IO ()-main =-  getOpts-  >>= runCompT pipeline-  >>= bitraverse_ (traverse_ T.putStrLn) (maybe exitFailure pure) . swap+main = do+  opts <- getOpts+  code <- T.readFile opts.path++  runChronicleT (runCompiler opts code)+    >>= bitraverse_ (traverse_ T.putStrLn) (\_ -> exitSuccess)++  exitFailure
src/Opts.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE StrictData #-}- module Opts(Opts(..), getOpts) where  import Data.Version(showVersion)@@ -19,29 +18,22 @@   , path :: FilePath   } -optsParser :: ParserInfo Opts-optsParser =-  info-    (infoOption ("oplang v" <> ver) (short 'v' <> long "version" <> help "Shows version information.")-      <*> helper-      <*> programOptions)-    (fullDesc-      <> progDesc "Compiles an OpLang source file to a native executable."-      <> header "oplang - The OpLang Compiler")+getOpts :: IO Opts+getOpts = execParser optsParser   where-    ver = showVersion version+    ver = "oplang v" <> showVersion version+    verOpt = infoOption ver (short 'v' <> long "version" <> help "Show version information") -    programOptions :: Parser Opts-    programOptions =+    opts :: Parser Opts+    opts =       Opts-      <$> option auto (long "stack-size" <> value 4096 <> metavar "SIZE" <> help "Size of the stack.")-      <*> option auto (long "tape-size" <> value 65536 <> metavar "SIZE" <> help "Size of the memory tape.")-      <*> switch (long "no-warn" <> help "Don't report warnings.")-      <*> switch (long "no-cc" <> help "Output a C file without compiling it.")-      <*> switch (long "dump-ast" <> help "Print the AST after parsing.")-      <*> switch (long "dump-ir" <> help "Print the IR after optimization.")-      <*> optional (strOption (short 'o' <> long "out-path" <> metavar "PATH" <> help "Path of the resulting executable (or C file if --no-cc is passed)."))-      <*> strArgument (metavar "PATH" <> help "The source file to compile.")+      <$> option auto (long "stack-size" <> value 4096 <> metavar "SIZE" <> help "Size of the stack, in bytes (default 4096)")+      <*> option auto (long "tape-size" <> value 4096 <> metavar "SIZE" <> help "Size of the memory tape, in bytes (default 4096)")+      <*> switch (long "no-warn" <> help "Don't report warnings")+      <*> switch (long "no-cc" <> help "Output a C file without compiling it")+      <*> switch (long "dump-ast" <> help "Print the AST after parsing")+      <*> switch (long "dump-ir" <> help "Print the IR after optimization")+      <*> optional (strOption $ short 'o' <> long "out-path" <> metavar "PATH" <> help "Path of the resulting executable (or C file if --no-cc is passed)")+      <*> strArgument (metavar "PATH" <> help "The source file to compile") -getOpts :: IO Opts-getOpts = execParser optsParser+    optsParser = info (helper <*> verOpt <*> opts) (fullDesc <> header ver)