module Language.Subleq.Assembly.Prim where
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as S
import Data.Map (Map)
import qualified Data.Map as M
import Text.Printf
import Data.List
type Id = String
type Location = String
type Substitution = Map Id Expr
data Expr = Identifier Id
| Number Integer
| EAdd Expr Expr
| ESub Expr Expr
deriving (Read, Show, Eq, Ord)
type LocExpr = (Maybe Location, Expr)
data Instruction = Subleq
deriving (Read, Show, Eq, Ord)
instructionArity :: Instruction -> (Int, Int)
instructionArity Subleq = (1, 3)
data Element = ElemInst Instruction [LocExpr]
| SubroutineCall (Maybe Location) Id [Expr]
| ElemLoc Location
deriving (Read, Show, Eq, Ord)
data Object = Subroutine Id [Id] [Element]
| Macro Id [Id] [Element]
deriving (Read, Show, Eq, Ord)
data Module = Module (Map Id Object)
deriving (Read, Show, Eq, Ord)
maybeToSet :: Maybe a -> Set a
maybeToSet = maybe S.empty S.singleton
elemsSet :: (Ord a)=>Map k a -> Set a
elemsSet m = S.fromList (M.elems m)
unionsMap :: (Ord b)=>(a -> Set b) -> Map k a -> Set b
unionsMap f m = fst $ M.mapAccum (\a b-> (S.union a (f b), ())) S.empty m
objectId :: Object -> Id
objectId (Subroutine n _ _) = n
objectId (Macro n _ _) = n
objectArity :: Object -> Int
objectArity (Subroutine _ args _) = length args
objectArity (Macro _ args _) = length args
evaluateNumExpr :: Expr -> Integer
evaluateNumExpr (Identifier x) = error $ "unexpected identifier " ++ x ++ "."
evaluateNumExpr (Number n) = n
evaluateNumExpr (EAdd e1 e2) = evaluateNumExpr e1 + evaluateNumExpr e2
evaluateNumExpr (ESub e1 e2) = evaluateNumExpr e1 evaluateNumExpr e2
evaluateNumExprInLocElem :: LocExpr -> LocExpr
evaluateNumExprInLocElem (l, e) = (l, Number $ evaluateNumExpr e)
evaluateNumExprInElem :: Element -> Element
evaluateNumExprInElem (ElemInst i les) = ElemInst i $ map evaluateNumExprInLocElem les
evaluateNumExprInElem e@(SubroutineCall {}) = e
evaluateNumExprInElem e@(ElemLoc {}) = e
substituteExpr :: Substitution -> Expr -> Expr
substituteExpr sub i@(Identifier x) = M.findWithDefault i x sub
substituteExpr sub i@(EAdd e1 e2) = EAdd (substituteExpr sub e1) (substituteExpr sub e2)
substituteExpr sub i@(ESub e1 e2) = ESub (substituteExpr sub e1) (substituteExpr sub e2)
substituteExpr _ (Number n) = Number n
substituteLocId :: Substitution -> Id -> Id
substituteLocId sub l | l `M.member` sub = case M.lookup l sub of
Just (Identifier l') -> l'
Just x -> printf "%s(%s)" l (show x)
Nothing -> l
substituteLocId _ l = l
substituteLocExpr :: Substitution -> LocExpr -> LocExpr
substituteLocExpr sub (Just l, e') = (Just (substituteLocId sub l), substituteExpr sub e')
substituteLocExpr sub (l, e') = (l, substituteExpr sub e')
substituteElement :: Substitution -> Element -> Element
substituteElement sub (ElemInst i es) = ElemInst i (map (substituteLocExpr sub) es)
substituteElement sub (SubroutineCall l i es) = SubroutineCall (fmap (substituteLocId sub) l) i (map (substituteExpr sub) es)
substituteElement sub (ElemLoc l) = ElemLoc $ substituteLocId sub l
substituteObject :: Substitution -> Object -> Object
substituteObject sub (Subroutine n args elems) = Subroutine n args $ map (substituteElement sub) elems
substituteObject sub (Macro n args elems) = Macro n args $ map (substituteElement sub) elems
locationsElement :: Element -> Set Id
locationsElement (ElemInst _ es) = S.fromList $ mapMaybe fst es
locationsElement (ElemLoc l) = S.singleton l
locationsElement (SubroutineCall l _ _) = maybeToSet l
locationsElements :: [Element] -> Set Id
locationsElements = S.unions . map locationsElement
locationsObject :: Object -> Set Id
locationsObject (Subroutine _ _ elems) = S.unions $ map locationsElement elems
locationsObject (Macro _ _ elems) = S.unions $ map locationsElement elems
freqMap :: (Ord a)=>[a] -> M.Map a Int
freqMap xs = M.fromListWith (+) . zip xs $ repeat 1
locationsOccursionElement :: Element -> Map Id Int
locationsOccursionElement (ElemInst _ es) = freqMap $ mapMaybe fst es
locationsOccursionElement (ElemLoc l) = M.singleton l 1
locationsOccursionElement (SubroutineCall Nothing _ _) = M.empty
locationsOccursionElement (SubroutineCall (Just l) _ _) = M.singleton l 1
locationsOccursionElements :: [Element] -> Map Id Int
locationsOccursionElements = M.unionsWith (+) . map locationsOccursionElement
locationsOccursionObject :: Object -> Map Id Int
locationsOccursionObject (Subroutine _ _ elems) = locationsOccursionElements elems
locationsOccursionObject (Macro _ _ elems) = locationsOccursionElements elems
errorsObject :: Object -> [String]
errorsObject (Subroutine n args elems) = errorsObject' n args elems
errorsObject (Macro n args elems) = errorsObject' n args elems
errorsObject' :: Id -> [Id] -> [Element] -> [String]
errorsObject' n args elems = catMaybes [e1, e2, e3]
where
e1 | not . null $ dupLocs =
Just $
printf "Object %s: locations must be exclusive, but: %s" n (show dupLocs)
| otherwise = Nothing
e2 | not . null $ dupArgs =
Just $
printf "Object %s: arguments must be exclusive, but: %s" n (show dupArgs)
| otherwise = Nothing
e3 | not . null $ dupArgLocs =
Just $
printf "Object %s: locations and arguments must be exclusive, but: %s" n (show dupArgLocs)
| otherwise = Nothing
argFreq = freqMap args
locFreq = locationsOccursionElements elems
dupArgs = M.elems . M.filter (> 1) $ argFreq
dupLocs = M.elems . M.filter (> 1) $ locFreq
dupArgLocs = M.elems . M.filter (> 1) $ M.unionWith (+) argFreq locFreq
boundedVars :: Object -> Set Id
boundedVars o@(Subroutine _ args _) = S.fromList args `S.union` locationsObject o
boundedVars o@(Macro _ args _) = S.fromList args `S.union` locationsObject o
freeVarExpr :: Expr -> Set Id
freeVarExpr (Identifier i) = S.singleton i
freeVarExpr _ = S.empty
freeVarLocExpr :: LocExpr -> Set Id
freeVarLocExpr (_,e) = freeVarExpr e
freeVarElement :: Element -> Set Id
freeVarElement (ElemInst _ es) = S.unions $ map freeVarLocExpr es
freeVarElement (SubroutineCall l x es) = S.unions $ [maybeToSet l, S.singleton x] ++ map freeVarExpr es
freeVarElement (ElemLoc _) = S.empty
freeVarObject :: Object -> Set Id
freeVarObject o@(Subroutine _ args es) = S.unions (map freeVarElement es) S.\\ S.fromList args S.\\ locationsObject o
freeVarObject o@(Macro _ args es) = S.unions (map freeVarElement es) S.\\ S.fromList args S.\\ locationsObject o
freeVarModule :: Module -> Set Id
freeVarModule (Module m) = unionsMap freeVarObject m S.\\ M.keysSet m
applyObject :: LabelPrefix -> Object -> [Expr] -> [Element]
applyObject lp (Macro x as es) = applyObject' lp x as es
applyObject _ (Subroutine x _ _) = error $ printf "%s is a subroutine and not applicable" x
applyObject' :: LabelPrefix -> Id -> [Id] -> [Element] -> [Expr] -> [Element]
applyObject' lp x as es aes | length as == length aes = map (substituteElement sub) $ addLocationPrefix lp targets es
| otherwise = error $ printf "%s takes %d argument(s), but got: %s" x (length as) (show aes)
where
sub = M.fromList $ zip (map (labelPrefixToString lp ++) as) aes
targets = S.fromList as `S.union` locationsElements es
type DistinctStack a = ([a], Set a)
push :: (Ord a)=>a -> DistinctStack a -> Maybe (DistinctStack a)
push x (xs, st) | x `S.member` st = Nothing
| otherwise = Just (x:xs, S.insert x st)
pop :: (Ord a)=>DistinctStack a -> Maybe (a, DistinctStack a)
pop ([], _) = Nothing
pop (x:xs, st) = Just (x, (xs, S.delete x st))
emptyStack :: DistinctStack a
emptyStack = ([], S.empty)
singletonStack :: (Ord a)=>a -> DistinctStack a
singletonStack x = ([x], S.singleton x)
stackToList :: DistinctStack a -> [a]
stackToList = fst
lookupModule :: Id -> Module -> Maybe Object
lookupModule x (Module m) = M.lookup x m
expandMacroAll :: Module -> Module
expandMacroAll m@(Module m') = Module $ M.map (expandMacro m) m'
expandMacro :: Module -> Object -> Object
expandMacro _ o@(Macro {}) = o
expandMacro m (Subroutine x as es) = Subroutine x as (concatMap (\(i, e)->expandMacro' (singletonStack x) m [i] e) $ zip [0..] es)
expandMacro' :: DistinctStack Id -> Module -> LabelPrefix -> Element -> [Element]
expandMacro' stk m lp (SubroutineCall l x as) = es''
where
stk' = fromMaybe
(error $ printf "%s: Cyclic macro expansion: %s" x (show $ stackToList stk))
(push x stk)
o :: Object
o = fromMaybe
(error $ printf "Object %s is not found in the module: %s" x (show $ stackToList stk))
(lookupModule x m)
es' :: [Element]
es' = map ElemLoc (maybeToList l) ++ applyObject lp o as
es'' = concatMap (\(i, e)-> expandMacro' stk' m (i:lp) e) $ zip [0..] es'
expandMacro' _ _ _ e@(ElemInst _ _) = [e]
expandMacro' _ _ _ e@(ElemLoc _) = [e]
addLocationPrefix :: LabelPrefix -> Set Id -> [Element] -> [Element]
addLocationPrefix lp targets elems = elems'
where
elems' = map (substituteElement sub) elems
sub = M.fromSet (Identifier . (labelPrefixToString lp ++)) targets
type LabelPrefix = [Int]
labelPrefixToString :: LabelPrefix -> String
labelPrefixToString = ('_':) . intercalate "_" . reverse . map show