packages feed

oplang-0.1.0.0: src/Language/OpLang/Codegen/C.hs

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