oplang (empty) → 0.1.0.0
raw patch · 10 files changed
+738/−0 lines, 10 filesdep +basedep +directorydep +filepath
Dependencies added: base, directory, filepath, optparse-applicative, parsec, process, text, text-builder, unordered-containers
Files
- LICENSE.txt +21/−0
- README.md +145/−0
- oplang.cabal +95/−0
- src/Language/OpLang/AST.hs +47/−0
- src/Language/OpLang/Checker.hs +51/−0
- src/Language/OpLang/Codegen/C.hs +103/−0
- src/Language/OpLang/Optimizer.hs +92/−0
- src/Language/OpLang/Opts.hs +55/−0
- src/Language/OpLang/Parser.hs +76/−0
- src/Main.hs +53/−0
+ LICENSE.txt view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2019-2020 Alex Ionescu++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,145 @@+# oplang++[](https://hackage.haskell.org/package/oplang)++This repository contains the compiler for `OpLang`, a stack-based esoteric programming language based on [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck).++## Installing++To build the compiler yourself, you will need the Haskell Platform, which can be found [here](https://www.haskell.org/platform/).++To build, run `cabal new-build` in the root of the repository.++You can then either install the package globally running `cabal install`, or run the local build with `cabal new-run`.++## OpLang Basics++In short, OpLang (**Op**erator **Lang**uage) is an extended version of Brainfuck that supports defining custom operators, which use a stack for passing arguments and return values.++The memory model of OpLang is similar to that of Brainfuck: There is a "memory tape", consisting of an array of cells (each cell taking up 1 byte), and a "cursor" that keeps track of a cell (the "current cell"), initially set to the first cell in the tape.+The language's primitive instructions, called "intrinsics", modify either the cursor, or the current cell.++In OpLang, every time an operator is invoked, a new tape is allocated on the stack (and zeroed out), and when the operator returns, the tape is deallocated (similar to the stack frames of functions or procedures in other languages).++The only way for operators to communicate (e.g. pass arguments or return values) is through the `stack` (which, ironically, sits on the heap), a special "global" tape which never gets reset, and which all operators have access to.++## Syntax++OpLang has 10 "intrinsic" operators (8 of which are the Brainfuck operators, with the same semantics):++* `+`: Increments the value at the current cell+* `-`: Decrements the value at the current cell+* `<`: Move the current cell to the left+* `>`: Move the current cell to the right+* `;`: Pop a value from the stack and move it into the current cell+* `:`: Push the value of the current cell onto the stack+* `,`: Read a character from stdin and store its ASCII value in the current cell+* `.`: Interpret the current cell as an ASCII character and print it to stdout+* `[`: Begin a loop (i.e. if the value at the current cell is zero, jump to the next `]`)+* `]`: End a loop (i.e. if the value at the current cell is non-zero, jump to the previous `[`)++An OpLang program consists of a (potentially empty) series of `operator definitions`, followed by a series of operator calls, called the `toplevel` (similar to a `main()` function in other languages).++An operator definition consists of an operator name, which is any non-whitespace character that is not reserved (i.e. not one of `+-<>;:,;[]{}#`), followed by a series of operator calls delimited between `{` and `}`.++OpLang supports single-line comments, introduced by the `#` character.++Here's an example of a simple OpLang program:++```op+a { ; +++ : }+++++ : a a a ; .+```++The above program defines a custom operator `a` that pops a value from the stack, adds 3 to it, then pushes it back onto the stack.+The program's top level (i.e. `main()` function) initializes the first cell to 4, pushes it to the stack, then calls `a` 3 times, then pops the value from the stack and prints it.++For more sample programs, see the [Samples](Samples/) folder.++The recommended file extension for OpLang programs is `*.op`.++## Compiler Settings++The compiler can be passed additional configuration via command-line arguments (for example, setting custom tape and stack sizes). In order to see the available options, run the compiler with the `-h` or `--help` argument.++## Compiler Internals++The compiler works by translating `OpLang` code into C, then calling the system's C compiler (via `cc`).++### Optimizations++The compiler performs a series of optimizations on OpLang source code:++#### Instruction Merging++Multiple instructions of the same kind (e.g. + and -, or < and >) are merged into a single instruction.++```op++++ --- ++ => Add 2++> <<<< => Move -3+++++ --- => # Nothing+```++#### Efficient cell zeroing++Cell zeroing is usually achieved by using `[-]`.+The compiler recognizes this pattern, and transforms it into a single assignment.+Note that because cells can overflow, `[+]` achieves the same behavior.++```op+[-] => Set 0++[+] => Set 0+```++#### Dead Code Elimination++In some cases (such as consecutive loops), the compiler can statically determine that particular instructions have no effect, so they are removed.++```op+[+>][<-] => [+>] => Loop [Add 1, Move 1] # Second loop is not compiled+```++Also, operators that are defined but never used do not get compiled.+They are still checked for correctness.++```op+n { ... } # Unused+m { ... } => m { ... }+... m ... m+```++#### Strength reduction++In some cases, certain instructions can be replaced with cheaper ones.+For example, adding after a loop can be replaced with a `Set` instruction, as the value of the cell is 0 (since it exited the loop), so the 2 instructions are equivalent.++```op+[-]+++ => Set 0, Add 3 => Set 3+```++Also, setting a value after adding to it (or subtracting from it) will overwrite the result of the additions, so they are removed.++```op++++[-] => Add 3, Set 0 => Set 0+```++Consecutive `Set`s also behave similarly: Only the last `Set` is compiled, previous ones are elided.++#### Offseted instructions++A common pattern that arises in Brainfuck/OpLang code is the following structure: `[>>>+<<<-]` (the number of `>`s, `<`s and `+`s may vary, and `+` may be replaced by another instruction)++Instead of generating the following code for such structures: `Loop [Move 3, Add 1, Move -3, Add -1]`, the compiler generates the following code: `Loop [AtOffset 3 (Add 1), Add -1]`, thus performing less arithmetic operations.++#### Tail Call Optimization*++The compiler has very basic support for TCO: It is able to optimize recursive self-calls in tail position by converting them to a `goto` to the beginning of the function.+For an example, see [this sample program](Samples/SO.op).++## License++This repository is licensed under the terms of the MIT License.+For more details, see [the license file](LICENSE.txt).
+ oplang.cabal view
@@ -0,0 +1,95 @@+cabal-version: 2.0++name: oplang+version: 0.1.0.0+synopsis: Compiler for OpLang, an esoteric programming language+description: Please see the README on GitHub at <https://github.com/aionescu/oplang#readme>+homepage: https://github.com/aionescu/oplang#readme+bug-reports: https://github.com/aionescu/oplang/issues+license: MIT+license-file: LICENSE.txt+author: Alex Ionescu+maintainer: alxi.2001@gmail.com+copyright: Copyright (C) 2019-2020 Alex Ionescu+category: Compiler+build-type: Simple+extra-source-files: README.md++source-repository head+ type: git+ location: https://github.com/aionescu/oplang++executable oplang+ main-is: Main.hs++ other-modules:+ Language.OpLang.AST+ Language.OpLang.Checker+ Language.OpLang.Codegen.C+ Language.OpLang.Optimizer+ Language.OpLang.Opts+ Language.OpLang.Parser+ Paths_oplang++ autogen-modules:+ Paths_oplang++ hs-source-dirs: src++ build-depends:+ base ^>=4.13+ , directory ^>= 1.3.6+ , filepath ^>= 1.4.2+ , optparse-applicative ^>= 0.16+ , process ^>= 1.6.9+ , parsec ^>= 3.1.14+ , text ^>= 1.2.4+ , text-builder ^>= 0.6.6+ , unordered-containers ^>= 0.2.13++ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N+ -Wall+ -Wincomplete-uni-patterns++ default-extensions:+ ApplicativeDo+ BangPatterns+ BlockArguments+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveLift+ DeriveTraversable+ DerivingStrategies+ EmptyCase+ ExistentialQuantification+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ InstanceSigs+ KindSignatures+ LambdaCase+ MultiParamTypeClasses+ MultiWayIf+ NamedFieldPuns+ OverloadedStrings+ PatternSynonyms+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TupleSections+ TypeApplications+ TypeFamilies+ TypeFamilyDependencies+ TypeOperators++ default-language: Haskell2010
+ src/Language/OpLang/AST.hs view
@@ -0,0 +1,47 @@+module Language.OpLang.AST where++import Data.Int(Int8)+import Data.List(nub)++import Data.HashMap.Strict(HashMap)++data Op+ = Add Int8+ | Set Int8+ | Move Int+ | Pop Word+ | Push+ | Peek+ | Read+ | Write Word+ | WithOffset Int Op+ | Loop [Op]+ | OpCall Name+ | TailCall++type Name = Maybe Char+type Body = [Op]+type Def = (Name, Body)++type DefList = [Def]+type Dict = HashMap Name Body++incr, decr, movl, movr, pop, set0, write :: Op++incr = Add 1+decr = Add (-1)+movl = Move (-1)+movr = Move 1+pop = Pop 1+set0 = Set 0+write = Write 1++calledOps :: Def -> [Name]+calledOps (name, ops) = nub $ go ops+ where+ go = \case+ OpCall c : rest -> c : go rest+ TailCall : rest -> name : go rest+ Loop l : rest -> go l ++ go rest+ _ : rest -> go rest+ [] -> []
+ src/Language/OpLang/Checker.hs view
@@ -0,0 +1,51 @@+module Language.OpLang.Checker(check) where++import Control.Monad(join)+import Data.List((\\), nub)+import Data.Maybe(fromJust)++import Data.Text(Text)+import qualified Data.Text as T++import Data.HashMap.Strict(HashMap)+import qualified Data.HashMap.Strict as HashMap++import Language.OpLang.AST(Dict, Name, Def, DefList, calledOps)++illegalCalls :: Dict -> Def -> [Name]+illegalCalls d (name, body) = nub $ filter (not . (`HashMap.member` d)) $ calledOps (name, body)++illegalBodies :: Dict -> HashMap Name [Name]+illegalBodies d = HashMap.filter (not . null) $ HashMap.mapWithKey (curry $ illegalCalls d) d++errorMsgs :: HashMap Name [Name] -> HashMap Name [Text]+errorMsgs d = HashMap.mapWithKey errorMsg d+ where+ errorMsg name ops = errorMsg1 name <$> ops+ errorMsg1 name op = "Error: Call to undefined operator '" <> T.singleton (fromJust op) <> "' in " <> fromName name <> "."++ fromName Nothing = "top level"+ fromName (Just n) = "body of '" <> T.singleton n <> T.singleton '\''++checkDups :: DefList -> Either Text Dict+checkDups l =+ let+ l' = fst <$> l+ uniq = nub l'+ toMsg a = "Error: Duplicate definition of operator '" <> T.singleton a <> "'."+ concatDups = T.intercalate "\n" . map (toMsg . fromJust)+ in+ case l' \\ uniq of+ [] -> Right $ HashMap.fromList l+ dups -> Left $ concatDups $ nub dups++checkUndefs :: Dict -> Either Text Dict+checkUndefs d =+ let msgs = join $ HashMap.elems $ errorMsgs $ illegalBodies d+ in+ case msgs of+ [] -> Right d+ l -> Left (T.intercalate "\n" l)++check :: DefList -> Either Text Dict+check dl = checkDups dl >>= checkUndefs
+ src/Language/OpLang/Codegen/C.hs view
@@ -0,0 +1,103 @@+module Language.OpLang.Codegen.C(compile) where++import Data.Char(ord)+import Numeric(showHex)++import Control.Monad(unless)++import qualified Data.HashMap.Strict as HashMap++import Data.Text(Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T++import Text.Builder(Builder)+import qualified Text.Builder as B++import System.Directory(removeFile)+import System.FilePath(dropExtension)+import System.Process(system)++import Language.OpLang.AST(Op(..), Name, Body, Dict)+import Language.OpLang.Opts(Opts(..))++type CCode = Builder++showT :: Show a => a -> CCode+showT = B.string . show++cName :: Name -> CCode+cName Nothing = "main"+cName (Just name) = "o" <> B.string (showHex (ord name) "")++programPrologue :: Word -> Word -> CCode+programPrologue stackSize tapeSize =+ "#include<stdio.h>\n#include<string.h>\n#define S "+ <> showT stackSize+ <> "\n#define T "+ <> showT tapeSize+ <> "\nchar s_[S],*s=s_;"++compileProto :: Name -> Body -> CCode+compileProto name _ = "void " <> cName name <> "();"++compileDef :: Name -> Body -> CCode+compileDef name body = "void " <> cName name <> "(){char t_[T],*t;l:t=t_;memset(t,0,T);" <> compileOps name body <> "}"++compileMain :: Body -> CCode+compileMain body = "int main(){char t_[T],*t=t_;memset(t,0,T);" <> compileOps Nothing body <> "return 0;}"++compileOps :: Name -> [Op] -> CCode+compileOps name ops = mconcat $ compileOp name "t" <$> ops++sign :: (Ord a, Num a) => a -> CCode+sign n+ | n < 0 = "-"+ | otherwise = "+"++repeatText :: Word -> Text -> CCode+repeatText n text = B.text $ T.concat $ replicate (fromIntegral n) text++compileOp :: Name -> CCode -> Op -> CCode+compileOp name tape = \case+ Add n -> "*" <> tape <> sign n <> "=" <> showT (abs n) <> ";"+ Move n -> tape <> sign n <> "=" <> showT (abs n) <> ";"+ Set n -> "*" <> tape <> "=" <> showT n <> ";"+ Pop n -> "*" <> tape <> "=*(s-=" <> showT n <> ");"+ Push -> "*(s++)=*" <> tape <> ";"+ Peek -> "*" <> tape <> "=*(s-1);"+ WithOffset off op -> compileOp name ("(" <> tape <> "+" <> showT off <> ")") op+ Loop ops -> "while(*t){" <> compileOps name ops <> "}"+ Read -> "scanf(\"%c\"," <> tape <> ");"+ Write 1 -> "printf(\"%c\",*" <> tape <> ");"+ Write n -> "{char c=*" <> tape <> ";printf(\"" <> repeatText n "%c" <> "\"" <> repeatText n ",c" <> ");}"+ OpCall c -> cName c <> "();"+ TailCall -> "goto l;"++codegen :: Word -> Word -> Dict -> Text+codegen stackSize tapeSize d =+ let d' = HashMap.delete Nothing d+ in+ B.run+ $ programPrologue stackSize tapeSize+ <> mconcat (HashMap.elems . HashMap.mapWithKey compileProto $ d')+ <> mconcat (HashMap.elems . HashMap.mapWithKey compileDef $ d')+ <> compileMain (d HashMap.! Nothing)++cFile :: String -> String+cFile file = dropExtension file <> ".c"++quote :: String -> String+quote s = '"' : (s <> "\"")++compile :: Opts -> Dict -> IO ()+compile Opts{..} d = do+ let cPath = cFile optsPath+ let code = codegen optsStackSize optsTapeSize d++ T.writeFile cPath code++ system $ quote optsCCPath <> " -o " <> quote optsOutPath <> " " <> quote cPath++ unless optsKeepCFile $+ removeFile cPath
+ src/Language/OpLang/Optimizer.hs view
@@ -0,0 +1,92 @@+module Language.OpLang.Optimizer(optimize) where++import Data.List((\\), union)++import qualified Data.HashMap.Strict as HashMap++import Language.OpLang.AST++canDoWithOffset :: Op -> Bool+canDoWithOffset (Move _) = False+canDoWithOffset (Loop _) = False+canDoWithOffset (OpCall _) = False+canDoWithOffset TailCall = False+canDoWithOffset _ = True++optimizeOnce :: Def -> (Bool, Body)+optimizeOnce (name, body) = go False [] body+ where+ go :: Bool -> Body -> Body -> (Bool, Body)+ go changed acc ops' = case ops' of+ Add 0 : ops -> go True acc ops+ Move 0 : ops -> go True acc ops+ Pop 0 : ops -> go True acc ops++ Loop [Add (-1)] : ops -> go True acc (set0 : ops)+ Loop [Add 1] : ops -> go True acc (set0 : ops)++ Set s : Add a : ops -> go True acc (Set (s + a) : ops)+ Add a : Add b : ops -> go True acc (Add (a + b) : ops)+ Move a : Move b : ops -> go True acc (Move (a + b) : ops)+ Pop a : Pop b : ops -> go True acc (Pop (a + b) : ops)+ Write a : Write b : ops -> go True acc (Write (a + b) : ops)++ Add _ : ops@(Read : _) -> go True acc ops+ Add _ : ops@(Pop _ : _) -> go True acc ops+ Add _ : ops@(Set _ : _) -> go True acc ops++ Set _ : ops@(Read : _) -> go True acc ops+ Set _ : ops@(Pop _ : _) -> go True acc ops+ Set _ : ops@(Set _ : _) -> go True acc ops++ Pop n : Push : ops -> go True acc (Pop (n - 1) : Peek : ops)+ Push : Pop n : ops -> go True acc (Pop (n - 1) : ops)++ Move m : op : Move n : ops + | canDoWithOffset op && m == -n -> case op of+ WithOffset o op' -> go True acc (WithOffset (m + o) op' : ops)+ _ -> go True acc (WithOffset m op : ops)++ Set 0 : Loop _ : ops -> go True acc (set0 : ops)+ l@(Loop _) : Loop _ : ops -> go True acc (l : ops)+ Loop [l@(Loop _)] : ops -> go True acc (l : ops)+ Loop l : ops ->+ let (changed', l') = go False [] l+ in go changed' (Loop l' : acc) ops++ [OpCall c] | c == name -> go True acc [TailCall]+ op : ops -> go changed (op : acc) ops+ [] -> (changed, reverse acc)+ +optimizeN :: Word -> Def -> Body+optimizeN 0 (_, ops) = ops+optimizeN n (name, ops) =+ if changed+ then optimizeN (n - 1) (name, ops')+ else ops'++ where+ (changed, ops') = optimizeOnce (name, ops)++removeSet0 :: Body -> Body+removeSet0 (Set 0 : ops) = ops+removeSet0 ops = ops++optimizeOps :: Word -> Def -> Body+optimizeOps passes (name, ops) = removeSet0 $ optimizeN passes (name, set0 : ops)++callGraph :: Word -> Dict -> Dict -> [Name] -> Name -> Dict+callGraph pass d acc toGo crr =+ let+ body = optimizeOps pass (crr, d HashMap.! crr)+ called = calledOps (crr, body) \\ [crr]+ newAcc = (HashMap.insert crr body acc)+ in+ case filter (not . (`HashMap.member` acc)) $ (toGo `union` called) of+ [] -> newAcc+ (next : nexts) ->+ callGraph pass d newAcc nexts next++optimize :: Word -> Dict -> Dict+optimize passes d =+ callGraph passes d HashMap.empty [] Nothing
+ src/Language/OpLang/Opts.hs view
@@ -0,0 +1,55 @@+module Language.OpLang.Opts(Opts(..), getOpts) where++import Options.Applicative++data Opts =+ Opts+ { optsOptPasses :: Word+ , optsStackSize :: Word+ , optsTapeSize :: Word+ , optsKeepCFile :: Bool+ , optsCCPath :: String+ , optsOutPath :: String+ , optsPath :: String+ }++defaultOptPasses :: Word+defaultOptPasses = 64++defaultStackSize :: Word+defaultStackSize = 4096++defaultTapeSize :: Word+defaultTapeSize = 65536++defaultCCPath :: String+defaultCCPath = "cc"++defaultOutPath :: String+defaultOutPath = ""++optsParser :: ParserInfo Opts+optsParser =+ info+ (infoOption "oplang v0.1.0.0" (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")++ where+ programOptions :: Parser Opts+ programOptions =+ Opts+ <$> option auto (short 'O' <> long "opt-passes" <> metavar "PASSES" <> value defaultOptPasses <> help "Specify the number of optimization passes to perform.")+ <*> option auto (short 'S' <> long "stack-size" <> metavar "STACK" <> value defaultStackSize <> help "Specify the size of the stack.")+ <*> option auto (short 'T' <> long "tape-size" <> metavar "TAPE" <> value defaultTapeSize <> help "Specify the size of the memory tape.")+ <*> switch (short 'K' <> long "keep-c-file" <> help "Specifiy whether to keep the resulting C file.")+ <*> strOption (short 'C' <> long "cc-path" <> metavar "CC_PATH" <> value defaultCCPath <> help "Specify the path of the C compiler to use.")+ <*> strOption (short 'o' <> long "out-path" <> metavar "OUT_PATH" <> value defaultOutPath <> help "Specify the path of the resulting executable.")+ <*> strArgument (metavar "PATH" <> help "The source file to compile.")++getOpts :: IO Opts+getOpts = execParser optsParser
+ src/Language/OpLang/Parser.hs view
@@ -0,0 +1,76 @@+module Language.OpLang.Parser(parse) where++import Data.Text(Text)+import qualified Data.Text as T++import Text.Parsec hiding (parse)++import Language.OpLang.AST++type Parser = Parsec Text ()++skip :: Parser a -> Parser ()+skip p = do+ p+ pure ()++comment :: Parser ()+comment =+ char '#'+ *> skip (manyTill anyChar (skip endOfLine <|> eof))++justWs :: Parser ()+justWs = skipMany (skip space <|> comment)++ws :: Parser a -> Parser a+ws p = p <* justWs++intrinsics :: String+intrinsics = "+-<>,.;:"++reserved :: String+reserved = intrinsics ++ "[]{}# \t\r\n"++intrinsic :: Parser Op+intrinsic = choice [incr', decr', movl', movr', read', write', pop', push']+ where+ incr' = incr <$ char '+'+ decr' = decr <$ char '-'+ movl' = movl <$ char '<'+ movr' = movr <$ char '>'+ read' = Read <$ char ','+ write' = write <$ char '.'+ pop' = pop <$ char ';'+ push' = Push <$ char ':'++loop :: Parser Op+loop = Loop <$> between (ws $ char '[') (char ']') (many $ ws op)++custom :: Parser Char+custom = noneOf reserved++op :: Parser Op+op = choice [intrinsic, loop, (OpCall . Just) <$> custom]++opDef :: Parser Def+opDef = do+ name <- ws custom+ ws $ char '{'+ body <- many $ ws op+ char '}'+ pure $ (Just name, body)++program :: Parser DefList+program = do+ justWs+ defs <- many $ try $ ws opDef+ topBody <- many $ ws op+ let topLevel = (Nothing, topBody)+ eof+ pure (topLevel : defs)++parse :: Text -> Either Text DefList+parse input =+ case runParser program () "" input of+ Left err -> Left $ T.pack $ show err+ Right p -> Right p
+ src/Main.hs view
@@ -0,0 +1,53 @@+module Main(main) where++import Data.Functor((<&>))+import System.Directory(doesFileExist)+import Data.Text(Text, pack)+import qualified Data.Text.IO as T+import System.FilePath(dropExtension)+import System.Info(os)++import Language.OpLang.Parser+import Language.OpLang.Checker+import Language.OpLang.Optimizer+import Language.OpLang.Opts+import Language.OpLang.AST++import qualified Language.OpLang.Codegen.C as C++getOutPath :: String -> String+getOutPath file = dropExtension file ++ ext+ where+ ext = case os of+ "mingw32" -> ".exe"+ _ -> ".out"++changeOutPath :: Opts -> Opts+changeOutPath opts =+ case optsOutPath opts of+ "" -> opts { optsOutPath = getOutPath $ optsPath opts }+ _ -> opts++tryReadFile :: String -> IO (Either Text Text)+tryReadFile path = do+ exists <- doesFileExist path++ if exists+ then Right <$> T.readFile path+ else pure $ Left $ "Error: File '" <> pack path <> "' not found."++pipeline :: Word -> Either Text Text -> Either Text Dict+pipeline passes code =+ code+ >>= parse+ >>= check+ <&> optimize passes++runCompiler :: Opts -> IO ()+runCompiler opts@Opts{..} =+ tryReadFile optsPath+ <&> pipeline optsOptPasses+ >>= either T.putStrLn (C.compile opts)++main :: IO ()+main = runCompiler . changeOutPath =<< getOpts