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 +7/−1
- README.md +7/−7
- examples/errors/both.op +7/−0
- examples/errors/errors.op +5/−0
- examples/errors/warnings.op +5/−0
- examples/factorial.op +15/−0
- examples/hello-world.bf +4/−0
- examples/lost-kingdom.bf too large to diff
- examples/stack.op +5/−0
- examples/triangle.bf +8/−0
- oplang.cabal +14/−44
- src/Control/Monad/Comp.hs +0/−34
- src/Language/OpLang/Codegen.hs +12/−54
- src/Language/OpLang/Optimizer.hs +6/−17
- src/Language/OpLang/Parser.hs +9/−15
- src/Language/OpLang/Syntax.hs +1/−0
- src/Language/OpLang/Validation.hs +27/−27
- src/Main.hs +55/−15
- src/Opts.hs +15/−23
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)