packages feed

inline-asm-0.2.0.0: src/Language/Asm/Inline/QQ.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Asm.Inline.QQ(asm) where

import Control.Arrow
import Control.Monad.Except
import Data.Either.Combinators
import Data.List
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax

import Language.Asm.Inline.AsmCode
import Language.Asm.Inline.Util

instance AsmCode AsmQQParsed where
  codeToString = asmBody
  validateCode ty code =
    check "arguments count mismatch" $ argsCount code == countArgs ty
    where
      check _ True = pure ()
      check str False = throwError $ "Type error: " <> str

asm :: QuasiQuoter
asm = QuasiQuoter { quoteExp = asmQE, quotePat = unsupported, quoteType = unsupported, quoteDec = unsupported }
  where
    unsupported = const $ error "Unsupported quasiquotation type"

asmQE :: String -> Q Exp
asmQE p = case parseAsmQQ p of
               Left err -> error err
               Right parsed -> [e| parsed |]

data AsmQQParsed = AsmQQParsed
  { argsCount :: Int
  , asmBody :: String
  } deriving (Show, Lift)

parseAsmQQ :: String -> Either String AsmQQParsed
parseAsmQQ = findSplitter
         >=> (pure . first words)
         >=> substituteArgs
  where
    findSplitter p = case break (== '|') p of
                          (vars, '|' : body) -> pure (vars, body)
                          _ -> throwError "Unable to find variable section separator"

substituteArgs :: ([String], String) -> Either String AsmQQParsed
substituteArgs (args, contents) = AsmQQParsed (length args) <$> go contents
  where
    go ('$' : '{' : rest)
      | (arg, '}' : rest') <- break (== '}') rest = do
        idx <- if retPrefix `isPrefixOf` arg
                  then pure $ read (drop (length retPrefix) arg)
                  else maybeToRight ("Unknown argument: `" <> trim arg <> "`") $ elemIndex (trim arg) args
        reg <- argIdxToReg idx
        (('%' : reg) <>) <$> go rest'
      | otherwise = throwError $ "Unable to parse argument: " <> take 20 rest <> "..."
    go (x : xs) = (x :) <$> go xs
    go [] = pure []

    retPrefix = "ret"

argIdxToReg :: Int -> Either String String
argIdxToReg 0 = pure "rbx"
argIdxToReg 1 = pure "r14"
argIdxToReg 2 = pure "rsi"
argIdxToReg 3 = pure "rdi"
argIdxToReg 4 = pure "r8"
argIdxToReg 5 = pure "r9"
argIdxToReg n = throwError $ "Unsupported register index: " <> show n

trim :: String -> String
trim = pass . pass
  where
    pass = reverse . dropWhile (== ' ')