oplang 0.2.0.0 → 0.3.0.0
raw patch · 17 files changed
+444/−454 lines, 17 filesdep +text-builder-lineardep −text-builderdep ~containersdep ~directorydep ~mtl
Dependencies added: text-builder-linear
Dependencies removed: text-builder
Dependency ranges changed: containers, directory, mtl, optparse-applicative, process, text, transformers
Files
- CHANGELOG.md +11/−3
- README.md +2/−2
- oplang.cabal +32/−17
- src/Control/Monad/Comp.hs +0/−34
- src/Data/Opts.hs +0/−53
- src/Language/OpLang/Checker.hs +0/−56
- src/Language/OpLang/Codegen.hs +67/−65
- src/Language/OpLang/CompT.hs +31/−0
- src/Language/OpLang/IR.hs +42/−0
- src/Language/OpLang/Optimize.hs +59/−0
- src/Language/OpLang/Optimizer.hs +0/−84
- src/Language/OpLang/Parse.hs +86/−0
- src/Language/OpLang/Parser.hs +0/−87
- src/Language/OpLang/Syntax.hs +0/−33
- src/Language/OpLang/Validate.hs +58/−0
- src/Main.hs +17/−20
- src/Opts.hs +39/−0
CHANGELOG.md view
@@ -1,9 +1,17 @@-## v0.2.0.0 [2022-03-19]+## v0.3.0.0 \[2022-08-12\] +* Upgrade to [`text`](https://hackage.haskell.org/package/text) v2 and use [`text-builder-linear`](https://hackage.haskell.org/package/text-builder-linear), improving compilation performance+* Optimizer overhaul+ * Now uses a separate IR+ * Performs more optimizations, runs in a single pass+ * Generates smaller and slightly faster C code++## v0.2.0.0 \[2022-03-19\]+ * Parser error message improvements * Codegen improvements-* **[Breaking]** Tailcalls are no longer optimized+* **\[Breaking\]** Tailcalls are no longer optimized -## v0.1.0.0 [2020-10-10]+## v0.1.0.0 \[2020-10-10\] * Initial release
README.md view
@@ -6,7 +6,7 @@ ## Installing -The compiler is available on [Hackage](https://hackage.haskell.org/package/oplang), and can be installed via [`cabal`](https://www.haskell.org/cabal/).+The compiler is available on [Hackage](https://hackage.haskell.org/package/oplang), and can be installed via [cabal](https://www.haskell.org/cabal/) (which can itself be installed via [ghcup](https://www.haskell.org/ghcup/)). ```sh cabal install oplang@@ -80,7 +80,7 @@ ,: a ;. ``` -For more example programs, see the [Examples](Examples/) folder.+For more example programs, see the [examples](examples/) folder. ## License
oplang.cabal view
@@ -1,8 +1,8 @@ cabal-version: 3.0 name: oplang-version: 0.2.0.0-synopsis: Compiler for OpLang, an esoteric programming language+version: 0.3.0.0+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 bug-reports: https://github.com/aionescu/oplang/issues@@ -11,7 +11,7 @@ author: Alex Ionescu maintainer: alxi.2001@gmail.com copyright: Copyright (C) 2019-2022 Alex Ionescu-category: Compiler+category: Compilers/Interpreters, Language build-type: Simple extra-source-files:@@ -26,38 +26,45 @@ main-is: Main.hs other-modules:- Control.Monad.Comp- Data.Opts- Language.OpLang.Checker Language.OpLang.Codegen- Language.OpLang.Optimizer- Language.OpLang.Parser- Language.OpLang.Syntax+ Language.OpLang.CompT+ Language.OpLang.IR+ Language.OpLang.Optimize+ Language.OpLang.Parse+ Language.OpLang.Validate+ Opts hs-source-dirs: src build-depends: base >=4.14 && <5- , containers ^>= 0.6.2- , directory ^>= 1.3.6+ , containers ^>= 0.6.5+ , directory ^>= 1.3.7 , filepath ^>= 1.4.2 , megaparsec ^>= 9.2- , mtl ^>= 2.2.2- , optparse-applicative ^>= 0.16- , process ^>= 1.6.9- , text ^>= 1.2.4- , text-builder ^>= 0.6.6- , transformers ^>= 0.5.6+ , mtl ^>= 2.3+ , optparse-applicative ^>= 0.17+ , process ^>= 1.6.14+ , text ^>= 2+ , text-builder-linear ^>= 0.1+ , transformers ^>= 0.6 ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ -Wcompat -Wincomplete-uni-patterns -Wprepositive-qualified-module -Wmissing-deriving-strategies -Wunused-packages+ -Widentities+ -Wredundant-constraints+ -Wunticked-promoted-constructors+ -Wpartial-fields+ -Wmissing-exported-signatures+ -Wno-name-shadowing default-extensions: ApplicativeDo@@ -73,6 +80,7 @@ DeriveLift DeriveTraversable DerivingStrategies+ DerivingVia EmptyCase ExistentialQuantification FlexibleContexts@@ -84,10 +92,15 @@ InstanceSigs KindSignatures LambdaCase+ MagicHash MultiParamTypeClasses MultiWayIf NamedFieldPuns+ NegativeLiterals+ NoMonomorphismRestriction+ NoStarIsType OverloadedStrings+ PartialTypeSignatures PatternSynonyms RankNTypes RecordWildCards@@ -99,6 +112,8 @@ TypeFamilies TypeFamilyDependencies TypeOperators+ UnboxedTuples+ UndecidableInstances ViewPatterns default-language: Haskell2010
− src/Control/Monad/Comp.hs
@@ -1,34 +0,0 @@-module Control.Monad.Comp(Comp, runComp) where--import Control.Applicative(Alternative)-import Control.Category((>>>))-import Control.Monad(MonadPlus)-import Control.Monad.IO.Class(MonadIO)-import Control.Monad.Reader(MonadReader, ReaderT, runReaderT)-import Control.Monad.Trans.Maybe(MaybeT (runMaybeT))-import Control.Monad.Writer.Strict(MonadWriter, WriterT, runWriterT)-import Data.Text(Text)-import Data.Tuple(swap)--import Data.Opts(Opts)--newtype Comp a =- Comp { runComp' :: ReaderT Opts (MaybeT (WriterT [Text] IO)) a }- deriving newtype- ( Functor- , Applicative- , Alternative- , Monad- , MonadPlus- , MonadReader Opts- , MonadWriter [Text]- , MonadIO- )--runComp :: Opts -> Comp a -> IO ([Text], Maybe a)-runComp opts =- runComp'- >>> flip runReaderT opts- >>> runMaybeT- >>> runWriterT- >>> fmap swap
− src/Data/Opts.hs
@@ -1,53 +0,0 @@-module Data.Opts(Opts(..), getOpts) where--import Options.Applicative-import System.FilePath(dropExtension)-import System.Info(os)--data Opts =- Opts- { optsOptPasses :: !Word- , optsStackSize :: !Word- , optsTapeSize :: !Word- , optsKeepCFile :: !Bool- , optsCCPath :: !FilePath- , optsOutPath :: !FilePath- , optsPath :: !FilePath- }--optsParser :: ParserInfo Opts-optsParser =- info- (infoOption "oplang v0.2.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 64 <> help "Specify the number of optimization passes to perform.")- <*> option auto (short 'S' <> long "stack-size" <> metavar "STACK" <> value 4096 <> help "Specify the size of the stack.")- <*> option auto (short 'T' <> long "tape-size" <> metavar "TAPE" <> value 65536 <> 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 "cc" <> help "Specify the path of the C compiler to use.")- <*> strOption (short 'o' <> long "out-path" <> metavar "OUT_PATH" <> value "" <> help "Specify the path of the resulting executable.")- <*> strArgument (metavar "PATH" <> help "The source file to compile.")--withExt :: FilePath -> FilePath-withExt path = dropExtension path <> ext os- where- ext "mingw32" = ".exe"- ext _ = ".out"--setOutPath :: Opts -> Opts-setOutPath opts =- case optsOutPath opts of- "" -> opts { optsOutPath = withExt $ optsPath opts }- _ -> opts--getOpts :: IO Opts-getOpts = setOutPath <$> execParser optsParser
− src/Language/OpLang/Checker.hs
@@ -1,56 +0,0 @@-module Language.OpLang.Checker(check) where--import Control.Monad(guard)-import Control.Monad.Writer.Strict(tell)-import Data.Bifunctor(bimap)-import Data.Functor(($>))-import Data.List(intercalate)-import Data.Map.Strict(Map)-import Data.Map.Strict qualified as M-import Data.Set(Set)-import Data.Set qualified as S-import Data.Text(Text)-import Data.Text qualified as T--import Control.Monad.Comp(Comp)-import Language.OpLang.Syntax(Program(..), Op, Id, calledOps)--enumerate :: Show a => [a] -> Text-enumerate l = T.pack $ intercalate ", " $ show <$> l--checkUndefinedCalls :: Program -> Comp ()-checkUndefinedCalls Program{..} = tell errors *> guard (null errors)- where- defined = M.keysSet opDefs-- undefinedInTopLevel = (Nothing, calledOps topLevel S.\\ defined)- 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)--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- where- used = calledOps ops S.\\ seen--removeUnusedOps :: Program -> Comp Program-removeUnusedOps p@Program{..} =- tell warning $> p { opDefs = usedDefs }- where- warning =- [ "Warning: Unused operators: " <> enumerate (M.keys unusedDefs)- | not $ M.null unusedDefs- ]-- unusedDefs = opDefs M.\\ usedDefs- usedDefs = M.restrictKeys opDefs usedOps- usedOps = allUsedOps opDefs S.empty topLevel--check :: Program -> Comp Program-check p = checkUndefinedCalls p *> removeUnusedOps p
src/Language/OpLang/Codegen.hs view
@@ -1,96 +1,98 @@ module Language.OpLang.Codegen(compile) where import Control.Monad(unless)-import Control.Monad.IO.Class(liftIO) import Control.Monad.Reader(ask)+import Control.Monad.Trans(lift) import Data.Char(ord)-import Data.Foldable(fold)+import Data.Foldable(foldMap') import Data.Map.Strict qualified as M+import Data.Maybe(fromMaybe) import Data.Text(Text)-import Data.Text qualified as T+import Data.Text.Builder.Linear(Builder, fromDec, runBuilder) import Data.Text.IO qualified as T-import Numeric(showHex)-import System.Directory(removeFile)-import System.FilePath(dropExtension)+import System.Directory(createDirectoryIfMissing, removeFile)+import System.FilePath(dropExtension, takeDirectory)+import System.Info(os) import System.Process(system)-import Text.Builder(Builder)-import Text.Builder qualified as B -import Control.Monad.Comp(Comp)-import Data.Opts(Opts(..))-import Language.OpLang.Syntax(Id, Op(..), Program(..))+import Language.OpLang.CompT(CompT)+import Language.OpLang.IR+import Opts(Opts(..)) type CCode = Builder -showC :: Show a => a -> CCode-showC = B.string . show- cName :: Id -> CCode-cName n = "o" <> B.string (showHex (ord n) "")+cName n = "o" <> fromDec (ord n) -programPrologue :: Word -> Word -> CCode-programPrologue stackSize tapeSize =- "#include<stdio.h>\n#include<string.h>\n#define S "- <> showC stackSize- <> "\n#define T "- <> showC tapeSize- <> "\nchar s_[S],*s=s_;"+programHeader :: Word -> Word -> CCode+programHeader stackSize tapeSize =+ "#include<stdio.h>\n#define T " <> fromDec tapeSize+ <> "\nchar q[" <> fromDec stackSize <> "],*s=q;" -compileProto :: Id -> CCode-compileProto name = "void " <> cName name <> "();"+forwardDecl :: Id -> CCode+forwardDecl name = "void " <> cName name <> "();" -compileDef :: Id -> [Op] -> CCode-compileDef name body = "void " <> cName name <> "(){char t_[T],*t=t_;memset(t,0,T);" <> compileOps body <> "}"+compileDef :: Id -> [Instr] -> CCode+compileDef name body = "void " <> cName name <> "(){char u[T]={0},*t=u;" <> compileOps body <> "}" -compileMain :: [Op] -> CCode-compileMain body = "int main(){char t_[T],*t=t_;memset(t,0,T);" <> compileOps body <> "return 0;}"+compileMain :: [Instr] -> CCode+compileMain body = "int main(){char u[T]={0},*t=u;" <> compileOps body <> "return 0;}" -compileOps :: [Op] -> CCode-compileOps = foldMap $ compileOp "t"+compileOps :: [Instr] -> CCode+compileOps = foldMap' compileOp -sign :: (Ord a, Num a) => a -> CCode-sign n- | n < 0 = "-"- | otherwise = "+"+tape :: Offset -> CCode+tape 0 = "*t"+tape off = "t[" <> fromDec off <> "]" -repeatText :: Word -> Text -> CCode-repeatText n = B.text . T.concat . replicate (fromIntegral n)+plusEq :: (Ord a, Num a) => a -> CCode+plusEq n+ | n < 0 = "-="+ | otherwise = "+=" -compileOp :: CCode -> Op -> CCode-compileOp tape = \case- Add n -> "*" <> tape <> sign n <> "=" <> showC (abs n) <> ";"- Move n -> tape <> sign n <> "=" <> showC (abs n) <> ";"- Set n -> "*" <> tape <> "=" <> showC n <> ";"- Pop n -> "*" <> tape <> "=*(s-=" <> showC n <> ");"- Push -> "*(s++)=*" <> tape <> ";"- Peek -> "*" <> tape <> "=*(s-1);"- WithOffset off op -> compileOp ("(" <> tape <> "+" <> showC off <> ")") op- Loop ops -> "while(*t){" <> compileOps ops <> "}"- Read -> "scanf(\"%c\"," <> tape <> ");"- Write 1 -> "printf(\"%c\",*" <> tape <> ");"- Write n -> "{char c=*" <> tape <> ";printf(\"" <> repeatText n "%c" <> "\"" <> repeatText n ",c" <> ");}"- Call c -> cName c <> "();"+addCell :: Val -> Offset -> Offset -> CCode+addCell v o o' = tape o <> plusEq v <> tape o' <> times (abs v) <> ";" <> tape o' <> "=0;"+ where+ times 1 = ""+ times n = "*" <> fromDec n -codegen :: Word -> Word -> Program -> Text+compileOp :: Instr -> CCode+compileOp = \case+ Add n o -> tape o <> plusEq n <> fromDec (abs n) <> ";"+ Set n o -> tape o <> "=" <> fromDec n <> ";"+ Pop o -> tape o <> "=*(--s);"+ Push o -> "*(s++)=" <> tape o <> ";"+ Read o -> "scanf(\"%c\",&" <> tape o <> ");"+ 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 <> "}"+ Call c -> cName c <> "();"++codegen :: Word -> Word -> Program Instr -> Text codegen stackSize tapeSize Program{..} =- B.run- $ programPrologue stackSize tapeSize- <> fold (compileProto <$> M.keys opDefs)- <> fold (M.mapWithKey compileDef opDefs)+ runBuilder+ $ programHeader stackSize tapeSize+ <> foldMap' forwardDecl (M.keys opDefs)+ <> M.foldMapWithKey compileDef opDefs <> compileMain topLevel -cFile :: FilePath -> FilePath-cFile file = dropExtension file <> ".c"+exePath :: FilePath -> FilePath+exePath path = dropExtension path <> ext os+ where+ ext "mingw32" = ".exe"+ ext _ = "" -compile :: Program -> Comp ()+compile :: Program Instr -> CompT IO () compile p = do Opts{..} <- ask- let cPath = cFile optsPath- let code = codegen optsStackSize optsTapeSize p+ let cFile = dropExtension optsPath <> ".c"+ let cCode = codegen optsStackSize optsTapeSize p+ let outFile = fromMaybe (exePath optsPath) optsOutPath - liftIO do- T.writeFile cPath code- system $ show optsCCPath <> " -o " <> show optsOutPath <> " " <> show cPath+ lift do+ T.writeFile cFile cCode+ createDirectoryIfMissing True $ takeDirectory outFile - unless optsKeepCFile $- removeFile cPath+ system $ show optsCCPath <> " -o " <> show outFile <> " " <> show cFile+ unless optsKeepCFile $ removeFile cFile
+ src/Language/OpLang/CompT.hs view
@@ -0,0 +1,31 @@+module Language.OpLang.CompT(CompT(..)) 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.Writer.Strict(MonadWriter, WriterT(..))+import Data.Coerce(coerce)+import Data.Text(Text)++import Opts(Opts)++-- The "Compilation" Monad Transformer+newtype CompT m a =+ CompT { runCompT :: Opts -> m (Maybe a, [Text]) }+ deriving+ ( Functor+ , Applicative+ , Alternative+ , Monad+ , MonadPlus+ , MonadReader Opts+ , MonadWriter [Text]+ , MonadIO+ )+ via ReaderT Opts (MaybeT (WriterT [Text] m))++instance MonadTrans CompT where+ lift = coerce . lift @(ReaderT Opts) . lift @MaybeT . lift @(WriterT [Text])
+ src/Language/OpLang/IR.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE StrictData #-}++module Language.OpLang.IR where++import Data.Int(Int8)+import Data.Map.Strict(Map)++type Id = Char+type Val = Int8+type Offset = Int++-- "Surface-level" AST, produced by the parser+data Op+ = Incr+ | Decr+ | MoveL+ | MoveR+ | Read'+ | Write'+ | Pop'+ | Push'+ | Loop' [Op]+ | Call' Id++-- Internal AST, used for optimizations and codegen+data Instr+ = Add Val Offset+ | Set Val Offset+ | Read Offset+ | Write Offset+ | Pop Offset+ | Push Offset+ | Move Offset+ | Loop [Instr]+ | Call Id+ | AddCell Val Offset Offset++data Program op+ = Program+ { opDefs :: Map Id [op]+ , topLevel :: [op]+ }
+ src/Language/OpLang/Optimize.hs view
@@ -0,0 +1,59 @@+module Language.OpLang.Optimize(optimize) where++import Language.OpLang.IR++syncTally :: Bool -> Val -> Offset -> [Instr] -> [Instr]+syncTally False 0 _ acc = acc+syncTally False n offset acc = Add n offset : acc+syncTally True n offset acc = Set n offset : acc++syncOffset :: Offset -> [Instr] -> [Instr]+syncOffset 0 acc = acc+syncOffset n acc = Move n : acc++syncAll :: Bool -> Val -> Offset -> [Instr] -> [Instr]+syncAll known tally offset = syncTally known tally 0 . syncOffset offset++removeSet0 :: [Instr] -> [Instr]+removeSet0 (Set 0 0 : ops) = ops+removeSet0 ops = ops++optimizeOps :: [Op] -> [Instr]+optimizeOps = removeSet0 . go False True 0 0 []+ where+ go :: Bool -> Bool -> Val -> Offset -> [Instr] -> [Op] -> [Instr]+ go False _ _ _ acc [] = reverse acc+ go True known tally offset acc [] = reverse $ syncAll known tally offset acc+ go loop known tally offset acc (op : ops) =+ case op of+ Incr -> go loop known (tally + 1) offset acc ops+ Decr -> go loop known (tally - 1) offset acc ops+ Read' -> go loop False 0 offset (Read offset : acc) ops+ Pop' -> go loop False 0 offset (Pop offset : acc) ops++ MoveL -> go loop False 0 (offset - 1) (syncTally known tally offset acc) ops+ MoveR -> go loop False 0 (offset + 1) (syncTally known tally offset acc) ops+ Write' -> go loop False 0 offset (Write offset : syncTally known tally offset acc) ops+ Push' -> go loop False 0 offset (Push offset : syncTally known tally offset acc) ops+ Call' c -> go loop False 0 offset (Call c : syncTally known tally offset acc) ops++ Loop' _ | (True, 0) <- (known, tally) -> go loop known tally offset acc ops+ Loop' l ->+ case go True False 0 0 [] l of+ [Add 1 0] -> go loop True 0 offset acc ops+ [Add -1 0] -> go loop True 0 offset acc ops++ [Add 1 o] -> go loop known tally offset (Set 0 (offset + o) : acc) ops+ [Add -1 o] -> go loop known tally offset (Set 0 (offset + o) : acc) ops++ [Add n o, Add -1 0] -> go loop False 0 offset (AddCell n (o + offset) offset : syncTally known tally offset acc) ops+ [Add -1 0, Add n o] -> go loop False 0 offset (AddCell n (o + offset) offset : syncTally known tally offset acc) ops++ l' -> go loop False 0 0 (Loop l' : syncAll known tally offset acc) ops++optimize :: Program Op -> Program Instr+optimize Program{..} =+ Program+ { opDefs = optimizeOps <$> opDefs+ , topLevel = optimizeOps topLevel+ }
− src/Language/OpLang/Optimizer.hs
@@ -1,84 +0,0 @@-module Language.OpLang.Optimizer(optimize) where--import Control.Category((>>>))-import Control.Monad.Reader(asks)--import Control.Monad.Comp(Comp)-import Data.Opts(Opts(..))-import Language.OpLang.Syntax(Program(..), Op(..))--canDoWithOffset :: Op -> Bool-canDoWithOffset (Move _) = False-canDoWithOffset (Loop _) = False-canDoWithOffset (Call _) = False-canDoWithOffset _ = True--optimizeOnce :: [Op] -> (Bool, [Op])-optimizeOnce = go False []- where- go :: Bool -> [Op] -> [Op] -> (Bool, [Op])- 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 (Set 0 : ops)- Loop [Add 1] : ops -> go True acc (Set 0 : 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 (Set 0 : 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-- op : ops -> go changed (op : acc) ops- [] -> (changed, reverse acc)--optimizeN :: Word -> [Op] -> [Op]-optimizeN 0 ops = ops-optimizeN n ops =- if changed- then optimizeN (n - 1) ops'- else ops'-- where- (changed, ops') = optimizeOnce ops--removeSet0 :: [Op] -> [Op]-removeSet0 (Set 0 : ops) = ops-removeSet0 ops = ops--optimizeOps :: Word -> [Op] -> [Op]-optimizeOps passes ops = removeSet0 $ optimizeN passes (Set 0 : ops)--optimize :: Program -> Comp Program-optimize Program{..} =- asks $ optsOptPasses >>> \passes ->- Program- { opDefs = optimizeOps passes <$> opDefs- , topLevel = optimizeOps passes topLevel- }
+ src/Language/OpLang/Parse.hs view
@@ -0,0 +1,86 @@+module Language.OpLang.Parse(parse) where++import Control.Monad.Reader(asks)+import Control.Monad.Writer.Strict(tell)+import Data.Functor(($>))+import Data.List(intercalate)+import Data.Map.Strict(Map)+import Data.Map.Strict qualified as M+import Data.Set qualified as S+import Data.Text(Text)+import Data.Text qualified as T+import Data.Void(Void)+import Text.Megaparsec hiding (parse)+import Text.Megaparsec.Char(space1)+import Text.Megaparsec.Char.Lexer qualified as L++import Language.OpLang.CompT(CompT)+import Language.OpLang.IR(Program(..), Op(..), Id)+import Opts(Opts(..))++type Parser = Parsec Void Text++ws :: Parser ()+ws = L.space space1 (L.skipLineComment "#") empty++lexeme :: Parser a -> Parser a+lexeme = L.lexeme ws++symbol :: Text -> Parser Text+symbol = L.symbol ws++reserved :: [Char]+reserved = "+-<>,.;:[]{}"++intrinsic :: Parser Op+intrinsic =+ choice+ [ symbol "+" $> Incr+ , symbol "-" $> Decr+ , symbol "<" $> MoveL+ , symbol ">" $> MoveR+ , symbol "," $> Read'+ , symbol "." $> Write'+ , symbol ";" $> Pop'+ , symbol ":" $> Push'+ ]+ <?> "intrinsic operator"++block :: Text -> Text -> Parser [Op]+block b e = between (symbol b) (symbol e) $ many op++loop :: Parser Op+loop = Loop' <$> block "[" "]" <?> "loop"++custom :: Parser Char+custom = lexeme (satisfy (`notElem` reserved) <?> "custom operator")++op :: Parser Op+op = choice [loop, intrinsic, Call' <$> custom] <?> "operator"++def :: Parser (Id, [Op])+def = (,) <$> lexeme custom <*> block "{" "}" <?> "definition"++defs :: Parser (Map Id [Op])+defs = many (try def) >>= toMap+ where+ toMap ds+ | unique = pure $ M.fromList ds+ | otherwise = fail $ "Duplicate definition of operators: " <> intercalate ", " (show <$> S.toList set)+ where+ ids = fst <$> ds+ set = S.fromList ids+ unique = S.size set == length ids++program :: Parser (Program Op)+program = Program <$> defs <*> (many op <?> "toplevel")++programFull :: Parser (Program Op)+programFull = ws *> program <* eof++parse :: Monad m => Text -> CompT m (Program Op)+parse code = do+ file <- asks optsPath+ case runParser programFull file code of+ Left e -> tell [T.pack $ errorBundlePretty e] *> empty+ Right p -> pure p
− src/Language/OpLang/Parser.hs
@@ -1,87 +0,0 @@-module Language.OpLang.Parser(parse) where--import Control.Monad.Reader(asks)-import Control.Monad.Writer.Strict(tell)-import Data.Functor(($>))-import Data.List(intercalate)-import Data.Map.Strict(Map)-import Data.Map.Strict qualified as M-import Data.Set qualified as S-import Data.Text(Text)-import Data.Text qualified as T-import Data.Void(Void)-import Text.Megaparsec hiding (parse)-import Text.Megaparsec.Char(space1)-import Text.Megaparsec.Char.Lexer qualified as L--import Control.Monad.Comp(Comp)-import Data.Opts(optsPath)-import Language.OpLang.Syntax(Program(..), Op(..), Id)--type Parser = Parsec Void Text--ws :: Parser ()-ws = L.space space1 (L.skipLineComment "#") empty--lexeme :: Parser a -> Parser a-lexeme = L.lexeme ws--symbol :: Text -> Parser Text-symbol = L.symbol ws--reserved :: [Char]-reserved = "+-<>,.;:[]{}"--intrinsic :: Parser Op-intrinsic =- choice- [ symbol "+" $> Add 1- , symbol "-" $> Add (-1)- , symbol "<" $> Move (-1)- , symbol ">" $> Move 1- , symbol "," $> Read- , symbol "." $> Write 1- , symbol ";" $> Pop 1- , symbol ":" $> Push- ]- <?> "intrinsic operator"--block :: Text -> Text -> Parser [Op]-block b e = between (symbol b) (symbol e) $ many op--loop :: Parser Op-loop = Loop <$> block "[" "]" <?> "loop"--custom :: Parser Char-custom = lexeme (satisfy (`notElem` reserved) <?> "custom operator")--op :: Parser Op-op = choice [try loop, intrinsic, Call <$> custom] <?> "operator"--def :: Parser (Id, [Op])-def = (,) <$> lexeme custom <*> block "{" "}" <?> "definition"--defs :: Parser (Map Id [Op])-defs = many (try def) >>= toMap- where- toMap ds- | unique = pure $ M.fromList ds- | otherwise = fail $ "Duplicate definition of operators: " <> intercalate ", " (show <$> S.toList set)- where- ids = fst <$> ds- set = S.fromList ids- unique = S.size set == length ids--program :: Parser Program-program = Program <$> defs <*> (many op <?> "toplevel")--programFull :: Parser Program-programFull = ws *> program <* eof--parse :: Text -> Comp Program-parse code = do- file <- asks optsPath-- case runParser programFull file code of- Left e -> tell [T.pack $ errorBundlePretty e] *> empty- Right p -> pure p
− src/Language/OpLang/Syntax.hs
@@ -1,33 +0,0 @@-module Language.OpLang.Syntax(Id, Op(..), Program(..), calledOps) where--import Data.Int(Int8)-import Data.Map.Strict(Map)-import Data.Set(Set)-import Data.Set qualified as S--type Id = Char--data Op- = Add !Int8- | Set !Int8- | Move !Int- | Pop !Word- | Push- | Peek- | Read- | Write !Word- | WithOffset !Int !Op- | Loop ![Op]- | Call !Id--data Program- = Program- { opDefs :: Map Id [Op]- , topLevel :: [Op]- }--calledOps :: [Op] -> Set Id-calledOps = foldMap \case- Call op -> S.singleton op- Loop ops -> calledOps ops- _ -> S.empty
+ src/Language/OpLang/Validate.hs view
@@ -0,0 +1,58 @@+module Language.OpLang.Validate(validate) where++import Control.Monad(guard, unless)+import Control.Monad.Writer.Strict(tell)+import Data.Bifunctor(bimap)+import Data.Functor(($>))+import Data.List(intercalate)+import Data.Map.Strict(Map)+import Data.Map.Strict qualified as M+import Data.Set(Set)+import Data.Set qualified as S+import Data.Text(Text)+import Data.Text qualified as T++import Language.OpLang.CompT(CompT)+import Language.OpLang.IR(Program(..), Op(..), Id)++calledOps :: [Op] -> Set Id+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++errUndefinedCalls :: Monad m => Program Op -> CompT m ()+errUndefinedCalls Program{..} = tell errors *> guard (null errors)+ where+ defined = M.keysSet opDefs++ undefinedInTopLevel = (Nothing, calledOps topLevel S.\\ defined)+ 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)++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+ where+ used = calledOps ops S.\\ seen++warnUnusedOps :: Monad m => Program Op -> CompT m (Program Op)+warnUnusedOps p@Program{..} =+ unless (M.null unusedDefs) warn $> 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++validate :: Monad m => Program Op -> CompT m (Program Op)+validate p = errUndefinedCalls p *> warnUnusedOps p
src/Main.hs view
@@ -1,34 +1,31 @@ module Main(main) where -import Control.Monad.IO.Class(liftIO)+import Control.Category((>>>))+import Control.Monad((>=>)) import Control.Monad.Reader(asks)+import Control.Monad.Trans(lift)+import Data.Bifoldable(bitraverse_) import Data.Foldable(traverse_) import Data.Text(Text) import Data.Text.IO qualified as T import System.Exit(exitFailure) -import Control.Monad.Comp(Comp, runComp)-import Data.Opts(getOpts, Opts(..))-import Language.OpLang.Checker(check) import Language.OpLang.Codegen(compile)-import Language.OpLang.Optimizer(optimize)-import Language.OpLang.Parser(parse)+import Language.OpLang.CompT(CompT(..))+import Language.OpLang.Optimize(optimize)+import Language.OpLang.Parse(parse)+import Language.OpLang.Validate(validate)+import Opts(Opts(..), getOpts) -getCode :: Comp Text-getCode = liftIO . T.readFile =<< asks optsPath+getCode :: CompT IO Text+getCode = lift . T.readFile =<< asks optsPath -pipeline :: Comp ()+pipeline :: CompT IO () pipeline =- getCode- >>= parse- >>= check- >>= optimize- >>= compile+ getCode >>= (parse >=> validate >=> optimize >>> compile) main :: IO ()-main = do- opts <- getOpts- (warnings, result) <- runComp opts pipeline-- traverse_ T.putStrLn warnings- maybe exitFailure pure result+main =+ getOpts+ >>= runCompT pipeline+ >>= bitraverse_ (maybe exitFailure pure) (traverse_ T.putStrLn)
+ src/Opts.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE StrictData #-}++module Opts(Opts(..), getOpts) where++import Options.Applicative++data Opts =+ Opts+ { optsStackSize :: Word+ , optsTapeSize :: Word+ , optsKeepCFile :: Bool+ , optsCCPath :: FilePath+ , optsOutPath :: Maybe FilePath+ , optsPath :: FilePath+ }++optsParser :: ParserInfo Opts+optsParser =+ info+ (infoOption "oplang v0.3.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 'S' <> long "stack-size" <> value 4096 <> metavar "SIZE" <> help "Size of the stack.")+ <*> option auto (short 'T' <> long "tape-size" <> value 65536 <> metavar "SIZE" <> help "Size of the memory tape.")+ <*> switch (short 'K' <> long "keep-c-file" <> help "Keep the resulting C file.")+ <*> strOption (short 'C' <> long "cc-path" <> value "cc" <> metavar "PATH" <> help "Path of the C compiler to use.")+ <*> optional (strOption (short 'o' <> long "out-path" <> metavar "PATH" <> help "Path of the resulting executable."))+ <*> strArgument (metavar "PATH" <> help "The source file to compile.")++getOpts :: IO Opts+getOpts = execParser optsParser