module Language.Subleq.Assembly.Parser where
import Language.Subleq.Assembly.Prim
import Control.Applicative ((<$>),(<*),(*>))
import Control.Monad
import Text.Parsec
import Text.Printf
import qualified Data.Map as M
import Data.List
symbol :: Stream b m Char => String -> ParsecT b u m String
symbol s = nonLineBreakSpaces *> string s <* nonLineBreakSpaces
nonLineBreakSpace :: Stream b m Char => ParsecT b u m ()
nonLineBreakSpace = void $ oneOf " \t\v"
nonLineBreakSpaces :: Stream b m Char => ParsecT b u m ()
nonLineBreakSpaces = try . void $ many nonLineBreakSpace
parseIdChar :: Stream b m Char => ParsecT b u m Char
parseIdChar = oneOf "abc"
parseId :: Stream b m Char => ParsecT b u m Id
parseId = do
c <- letter
cs <- many (alphaNum <|> oneOf "_")
return (c:cs)
parseExternalReference :: Stream b m Char => ParsecT b u m Expr
parseExternalReference = do
string "&@"
c <- lower
cs <- many alphaNum
return $ Identifier ('_':c:cs)
parseSubroutineName :: Stream b m Char => ParsecT b u m Id
parseSubroutineName = do
c <- lower
cs <- many alphaNum
return (c:cs)
parseSubroutineArgument :: Stream b m Char => ParsecT b u m Id
parseSubroutineArgument = parseId
parseLoc :: Stream b m Char => ParsecT b u m Location
parseLoc = do
c <- upper <|> lower
cs <- many alphaNum <* char ':'
return (c:cs)
parseIntegerLiteral :: Stream b m Char => ParsecT b u m Integer
parseIntegerLiteral = do
s <- option 1 $ char '-' *> return (1)
n <- read <$> many1 digit
return (s * n)
parseCurrentPos :: Stream b m Char => ParsecT b u m Expr
parseCurrentPos = do
char '?'
return $ Identifier "?"
parseExprParen :: Stream b m Char => ParsecT b u m Expr
parseExprParen = do
char '(' <* spaces
op <- oneOf "+-" <* space <* spaces
e1 <- parseExpr
e2 <- optionMaybe (space *> spaces *> parseExpr) <* spaces
char ')'
return $ op' op e1 e2
where
op' '+' e1 Nothing = EAdd (Number 0) e1
op' '+' e1 (Just e2') = EAdd e1 e2'
op' '-' e1 Nothing = ESub (Number 0) e1
op' '-' e1 (Just e2') = ESub e1 e2'
parseExprCurrentPos :: Stream b m Char => ParsecT b u m Expr
parseExprCurrentPos = do
char '(' <* spaces
e1 <- parseCurrentPos
op <- oneOf "+-" <* spaces
e2 <- parseExpr <* spaces <* char ')'
return $ op' op e1 e2
where
op' '+' = EAdd
op' '-' = ESub
parseExpr :: Stream b m Char => ParsecT b u m Expr
parseExpr = parseExprParen <|> (Number <$> parseIntegerLiteral) <|> parseExternalReference <|> (Identifier <$> parseId)
parseLocExpr :: Stream b m Char => ParsecT b u m LocExpr
parseLocExpr = do
loc <- optionMaybe $ try parseLoc
expr <- parseExpr
return (loc, expr)
parseInstructionType :: Stream b m Char => ParsecT b u m Instruction
parseInstructionType = (string "!subleq" *> return Subleq) <|> return Subleq
parseInstruction :: Stream b m Char => ParsecT b u m Element
parseInstruction = do
insn <- parseInstructionType <* spaces
args <- (parseLocExpr `sepBy` (space >> spaces)) <* symbol ";"
let (arityMin, arityMax) = instructionArity insn
let arity = length args
if arityMin <= arity && arity <= arityMax
then return $ ElemInst insn args
else error $ printf "Instruction %s takes %d to %d arguments, but got: %s" (show insn) arityMin arityMax (show args)
parseSubroutineCall :: Stream b m Char => ParsecT b u m Element
parseSubroutineCall = do
loc <- optionMaybe parseLoc
(n, args) <- between (string "$(@@") (string ")" >> symbol ";") content
return $ SubroutineCall loc ('@':n) args
where
content = do
n <- parseSubroutineName <* space <* spaces
args <- (parseExpr `sepBy` symbol ",") <* spaces
return (n, args)
parseElement :: Stream b m Char => ParsecT b u m Element
parseElement = try parseSubroutineCall <|> parseInstruction
parseHeader :: Stream b m Char => ParsecT b u m (Id, [Id])
parseHeader = do
n <- parseSubroutineName <* many1 nonLineBreakSpace
args <- parseSubroutineArgument `sepBy` try (symbol ",") <* (try (nonLineBreakSpaces >> void parseComment) <|> void (many nonLineBreakSpace >> endOfLine))
return (n, args)
parseComment :: Stream b m Char => ParsecT b u m String
parseComment = string "//" >> manyTill anyChar (try endOfLine)
skipCommentOrSpaces :: Stream b m Char => ParsecT b u m ()
skipCommentOrSpaces = spaces >> (void (try parseComment) <|> spaces)
parseObject :: Stream b m Char => ParsecT b u m Object
parseObject = do
isMacro <- try (string "@@" *> return True) <|> (string "@" *> return False)
(n, args) <- parseHeader
es <- many (parseElement <* many nonLineBreakSpace <* skipCommentOrSpaces)
let obj = (if isMacro then makeMacro else makeSubroutine) n args es
let errors = errorsObject obj
if null errors
then return obj
else error $ unlines errors
where
makeMacro n args es = Macro ('@': n) args es
makeSubroutine n args es = Subroutine n args es
parseMeaninglessLine :: Stream b m Char => ParsecT b u m String
parseMeaninglessLine = (replicate 1 <$> endOfLine) <|> parseComment
parseModule :: Stream b m Char => ParsecT b u m Module
parseModule = do
many parseMeaninglessLine
objs <- many (parseObject <* spaces) <* eof
let freqs = M.fromListWith (+) [(objectId obj, 1) | obj <- objs]
if M.null (M.filter (> (1 :: Integer)) freqs)
then return $ Module $ M.fromList [(objectId obj, obj) | obj <- objs]
else fail $ "Multiple definitions: " ++ intercalate ", " (M.keys $ M.filter (> 1) freqs)