oplang 0.3.0.1 → 0.4.0.0
raw patch · 16 files changed
+442/−404 lines, 16 filesdep ~basedep ~containersdep ~directory
Dependency ranges changed: base, containers, directory, filepath, megaparsec, process
Files
- CHANGELOG.md +7/−0
- README.md +15/−10
- oplang.cabal +63/−74
- src/Control/Monad/Comp.hs +34/−0
- src/Language/OpLang/Codegen.hs +34/−23
- src/Language/OpLang/CompT.hs +0/−31
- src/Language/OpLang/IR.hs +0/−42
- src/Language/OpLang/Optimize.hs +0/−59
- src/Language/OpLang/Optimizer.hs +67/−0
- src/Language/OpLang/Parse.hs +0/−86
- src/Language/OpLang/Parser.hs +89/−0
- src/Language/OpLang/Syntax.hs +43/−0
- src/Language/OpLang/Validate.hs +0/−58
- src/Language/OpLang/Validation.hs +61/−0
- src/Main.hs +8/−8
- src/Opts.hs +21/−13
CHANGELOG.md view
@@ -1,3 +1,10 @@+<!-- markdownlint-disable first-line-h1 -->++## v0.4.0.0 \[2023-05-06\]++* Add command-line options for printing the AST and IR, ignoring warnings, and skipping C compilation+* Require GHC 9.2 or newer+ ## v0.3.0.1 \[2022-08-12\] * Update [`base`](https://hackage.haskell.org/package/base) version bound to require GHC 9.0 (`base` 4.15) or newer
README.md view
@@ -14,11 +14,11 @@ ## Usage -To compile an OpLang file with default options, use:+To compile and run an OpLang program, use: ```sh oplang code.op-./code+./code.out ``` For a list of available command-line options, use `oplang --help`.@@ -27,16 +27,15 @@ Prerequisites: -* `GHC` >=9.0-* `cabal` >=3.0+* `GHC` >=9.2+* `cabal` >=3.6 (Both can be installed via [ghcup](https://www.haskell.org/ghcup/)) -```sh-cabal build-cabal run . -- <args>-```+To build the project, use `cabal build`. +To run the project locally (without installing), use `cabal run . -- <args>`.+ ## Language Features OpLang is a strict superset of Brainfuck.@@ -49,7 +48,7 @@ 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. -## Syntax+### Syntax OpLang has 10 "intrinsic" operators, 8 of which are the Brainfuck operators, with the same semantics: @@ -80,7 +79,13 @@ ,: a ;. ``` -For more example programs, see the [examples](examples/) folder.+For more example programs, see the [examples](examples) folder.++## 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. ## License
oplang.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: oplang-version: 0.3.0.1+version: 0.4.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@@ -9,111 +9,100 @@ license: GPL-3.0-only license-file: LICENSE.txt author: Alex Ionescu-maintainer: alxi.2001@gmail.com-copyright: Copyright (C) 2019-2022 Alex Ionescu+maintainer: aaionescu@pm.me+copyright: Copyright (C) 2019-2023 Alex Ionescu category: Compilers/Interpreters, Language build-type: Simple -extra-source-files:+extra-doc-files: CHANGELOG.md++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.Codegen- Language.OpLang.CompT- Language.OpLang.IR- Language.OpLang.Optimize- Language.OpLang.Parse- Language.OpLang.Validate- Opts-- hs-source-dirs: src-- build-depends:- base >=4.15 && <5- , containers ^>= 0.6.5- , directory ^>= 1.3.7- , filepath ^>= 1.4.2- , megaparsec ^>= 9.2- , 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-+common ghc-flags+ default-language: GHC2021 default-extensions: ApplicativeDo- BangPatterns BlockArguments- ConstraintKinds DataKinds DefaultSignatures DeriveAnyClass- DeriveFoldable- DeriveFunctor- DeriveGeneric- DeriveLift- DeriveTraversable- DerivingStrategies DerivingVia- EmptyCase- ExistentialQuantification- FlexibleContexts- FlexibleInstances+ DuplicateRecordFields FunctionalDependencies GADTs- GeneralizedNewtypeDeriving- ImportQualifiedPost- InstanceSigs- KindSignatures+ ImplicitParams+ ImpredicativeTypes LambdaCase+ LexicalNegation MagicHash- MultiParamTypeClasses MultiWayIf- NamedFieldPuns NegativeLiterals+ NoFieldSelectors NoMonomorphismRestriction NoStarIsType+ OverloadedLabels+ OverloadedRecordDot OverloadedStrings PartialTypeSignatures PatternSynonyms- RankNTypes+ QuantifiedConstraints RecordWildCards RecursiveDo- ScopedTypeVariables- StandaloneDeriving- TupleSections- TypeApplications- TypeFamilies TypeFamilyDependencies- TypeOperators UnboxedTuples UndecidableInstances+ UnliftedDatatypes+ UnliftedNewtypes ViewPatterns - default-language: Haskell2010+ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N+ -Wall+ -Wcompat+ -Widentities+ -Wmissing-deriving-strategies+ -Wno-name-shadowing+ -Wpartial-fields+ -Wprepositive-qualified-module+ -Wredundant-constraints+ -Wunused-packages++executable oplang+ import: ghc-flags++ hs-source-dirs: src+ main-is: Main.hs++ other-modules:+ Control.Monad.Comp+ Language.OpLang.Codegen+ Language.OpLang.Optimizer+ Language.OpLang.Parser+ Language.OpLang.Syntax+ Language.OpLang.Validation+ 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+ , mtl ^>= 2.3+ , optparse-applicative ^>= 0.17+ , process ^>= 1.6.17+ , text ^>= 2+ , text-builder-linear ^>= 0.1+ , transformers ^>= 0.6
+ src/Control/Monad/Comp.hs view
@@ -0,0 +1,34 @@+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,6 +1,5 @@ module Language.OpLang.Codegen(compile) where -import Control.Monad(unless) import Control.Monad.Reader(ask) import Control.Monad.Trans(lift) import Data.Char(ord)@@ -11,12 +10,13 @@ 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(system)+import System.Process(callProcess) -import Language.OpLang.CompT(CompT)-import Language.OpLang.IR+import Control.Monad.Comp(CompT)+import Language.OpLang.Syntax import Opts(Opts(..)) type CCode = Builder@@ -58,16 +58,16 @@ 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 <> "();"+ 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{..} =@@ -81,18 +81,29 @@ exePath path = dropExtension path <> ext os where ext "mingw32" = ".exe"- ext _ = ""+ ext _ = ".out" +ccPath :: IO FilePath+ccPath = fromMaybe "cc" <$> lookupEnv "CC"+ compile :: Program Instr -> CompT IO () compile p = do Opts{..} <- ask- let cFile = dropExtension optsPath <> ".c"- let cCode = codegen optsStackSize optsTapeSize p- let outFile = fromMaybe (exePath optsPath) optsOutPath+ let cFile = dropExtension path <> ".c"+ let cCode = codegen stackSize tapeSize p - lift do- T.writeFile cFile cCode- createDirectoryIfMissing True $ takeDirectory outFile+ lift+ if noCC then do+ let outFile = fromMaybe cFile outPath+ createDirectoryIfMissing True $ takeDirectory outFile - system $ show optsCCPath <> " -o " <> show outFile <> " " <> show cFile- unless optsKeepCFile $ removeFile cFile+ 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
− src/Language/OpLang/CompT.hs
@@ -1,31 +0,0 @@-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
@@ -1,42 +0,0 @@-{-# 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
@@ -1,59 +0,0 @@-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 view
@@ -0,0 +1,67 @@+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+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 n 0] | abs n == 1 -> go loop True 0 offset acc ops+ [Add n o] | abs n == 1 -> go loop known tally offset (Set 0 (offset + o) : acc) ops++ [Add n o, Add m 0] | abs m == 1 -> go loop False 0 offset (AddCell n (o + offset) offset : syncTally known tally offset acc) ops+ [Add m 0, Add n o] | abs m == 1 -> 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 -> 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+ }
− src/Language/OpLang/Parse.hs
@@ -1,86 +0,0 @@-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 view
@@ -0,0 +1,89 @@+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 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(CompT)+import Language.OpLang.Syntax+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 :: Text+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 $ not . (`T.elem` reserved)) <?> "custom operator"++op :: Parser Op+op =+ choice+ [ loop+ , intrinsic+ , Call' <$> custom+ ] <?> "operator"++def :: Parser (Id, [Op])+def = (,) <$> 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")++parse :: Text -> CompT IO (Program Op)+parse code = do+ Opts{..} <- ask+ 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
+ src/Language/OpLang/Syntax.hs view
@@ -0,0 +1,43 @@+module Language.OpLang.Syntax 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+ deriving stock Show++-- Internal IR, 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+ deriving stock Show++data Program op+ = Program+ { opDefs :: Map Id [op]+ , topLevel :: [op]+ }+ deriving stock Show
− src/Language/OpLang/Validate.hs
@@ -1,58 +0,0 @@-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/Language/OpLang/Validation.hs view
@@ -0,0 +1,61 @@+module Language.OpLang.Validation(validate) where++import Control.Monad(guard, unless)+import Control.Monad.Reader(ask)+import Control.Monad.Writer(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(CompT)+import Language.OpLang.Syntax+import Opts(Opts(..))++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++checkUndefinedCalls :: Monad m => Program Op -> CompT m ()+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 :: Monad m => Program Op -> CompT m (Program Op)+removeUnusedOps p@Program{..} = do+ Opts{..} <- ask+ unless (noWarn || 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 = checkUndefinedCalls p *> removeUnusedOps p
src/Main.hs view
@@ -1,6 +1,5 @@ module Main(main) where -import Control.Category((>>>)) import Control.Monad((>=>)) import Control.Monad.Reader(asks) import Control.Monad.Trans(lift)@@ -8,24 +7,25 @@ import Data.Foldable(traverse_) import Data.Text(Text) import Data.Text.IO qualified as T+import Data.Tuple(swap) import System.Exit(exitFailure) +import Control.Monad.Comp(CompT, runCompT) import Language.OpLang.Codegen(compile)-import Language.OpLang.CompT(CompT(..))-import Language.OpLang.Optimize(optimize)-import Language.OpLang.Parse(parse)-import Language.OpLang.Validate(validate)+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 optsPath+getCode = lift . T.readFile =<< asks (.path) pipeline :: CompT IO () pipeline =- getCode >>= (parse >=> validate >=> optimize >>> compile)+ getCode >>= (parse >=> validate >=> optimize >=> compile) main :: IO () main = getOpts >>= runCompT pipeline- >>= bitraverse_ (maybe exitFailure pure) (traverse_ T.putStrLn)+ >>= bitraverse_ (traverse_ T.putStrLn) (maybe exitFailure pure) . swap
src/Opts.hs view
@@ -2,37 +2,45 @@ module Opts(Opts(..), getOpts) where +import Data.Version(showVersion) import Options.Applicative +import Paths_oplang(version)+ data Opts = Opts- { optsStackSize :: Word- , optsTapeSize :: Word- , optsKeepCFile :: Bool- , optsCCPath :: FilePath- , optsOutPath :: Maybe FilePath- , optsPath :: FilePath+ { stackSize :: Word+ , tapeSize :: Word+ , noWarn :: Bool+ , noCC :: Bool+ , dumpAST :: Bool+ , dumpIR :: Bool+ , outPath :: Maybe FilePath+ , path :: FilePath } optsParser :: ParserInfo Opts optsParser = info- (infoOption "oplang v0.3.0.1" (short 'v' <> long "version" <> help "Shows version information.")+ (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")- where+ ver = showVersion version+ 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."))+ <$> 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.") getOpts :: IO Opts