packages feed

oplang-0.2.0.0: src/Language/OpLang/Codegen.hs

module Language.OpLang.Codegen(compile) where

import Control.Monad(unless)
import Control.Monad.IO.Class(liftIO)
import Control.Monad.Reader(ask)
import Data.Char(ord)
import Data.Foldable(fold)
import Data.Map.Strict qualified as M
import Data.Text(Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Numeric(showHex)
import System.Directory(removeFile)
import System.FilePath(dropExtension)
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(..))

type CCode = Builder

showC :: Show a => a -> CCode
showC = B.string . show

cName :: Id -> CCode
cName n = "o" <> B.string (showHex (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_;"

compileProto :: Id -> CCode
compileProto 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 <> "}"

compileMain :: [Op] -> CCode
compileMain body = "int main(){char t_[T],*t=t_;memset(t,0,T);" <> compileOps body <> "return 0;}"

compileOps :: [Op] -> CCode
compileOps = foldMap $ compileOp "t"

sign :: (Ord a, Num a) => a -> CCode
sign n
  | n < 0 = "-"
  | otherwise = "+"

repeatText :: Word -> Text -> CCode
repeatText n = B.text . T.concat . replicate (fromIntegral n)

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 <> "();"

codegen :: Word -> Word -> Program -> Text
codegen stackSize tapeSize Program{..} =
  B.run
  $ programPrologue stackSize tapeSize
  <> fold (compileProto <$> M.keys opDefs)
  <> fold (M.mapWithKey compileDef opDefs)
  <> compileMain topLevel

cFile :: FilePath -> FilePath
cFile file = dropExtension file <> ".c"

compile :: Program -> Comp ()
compile p = do
  Opts{..} <- ask
  let cPath = cFile optsPath
  let code = codegen optsStackSize optsTapeSize p

  liftIO do
    T.writeFile cPath code
    system $ show optsCCPath <> " -o " <> show optsOutPath <> " " <> show cPath

    unless optsKeepCFile $
      removeFile cPath