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 (== ' ')