module ASTUtil where
{-----------------------------
This is a collection of utility functions I've made. Most of them are
nothing special, but they do make things easier and cleaner in the rest 
of the code. Many of them imitate more general functions specialized for
AST nodes.

See ASTTranslate.hs for more.

Originally created by Sage LaTorra for Summer of Code 2006.
------------------------------}
import ASTDefinition

{-Removes a given character from a string.-}

removeChar :: Char -> String -> String
removeChar _ []         = []
removeChar todrop instr = case ((head instr)==todrop) of
    True  -> (tail instr)
    False -> (head instr):(removeChar todrop (tail instr))


--Makes a list of strings from a list of lists of strings.

makeList :: [[String]] -> [String]
makeList []    = []
makeList alist = (head alist)++(makeList (tail alist))


--Take a string and return a list of strings with each element being a word in that string. 
--The second argument is what's already been collected (allowing you to start the list of words with your own list, if need be)
--To just get the contents of the string broken into words, just call makeWords instring [""]

makeWords :: String -> [String] -> [String]
makeWords [] curout   = curout
makeWords inst curout = if ((head inst)==' ') then (makeWords (drop 2 inst) ([(head (tail inst))]:curout)) else (makeWords (tail inst) (((head curout)++[(head inst)]):(tail curout)))


--drop the leading character if it matches the first argument of the call

dropLeadingChar :: Char -> String -> String
dropLeadingChar _ []           = []
dropLeadingChar todrop astring = if ((head astring)==todrop) then (tail astring) else astring 


--Returns the first instance of a given type of node from a list of nodes. 

extractNodetype :: P5AST -> [P5AST] -> P5AST
extractNodetype _ []       = (AbstractNode "UnknownAbs" [])
extractNodetype node nlist = if (matchOnType node (head nlist)) then (head nlist) else (extractNodetype node (tail nlist))


extractNodetypeSkip :: Int -> P5AST -> [P5AST] -> P5AST
extractNodetypeSkip 0 node nlist = extractNodetype node nlist
extractNodetypeSkip _ _ []      = (AbstractNode "UnknownAbs" [])
extractNodetypeSkip skips node nlist = if (matchOnType node (head nlist)) then (extractNodetypeSkip (skips-1) node (tail nlist)) else (extractNodetypeSkip skips node (tail nlist))

--Get just the kids of a given node ([] in case of literal node).

extractKids :: P5AST -> [P5AST]
extractKids (AbstractNode atype kids)    = kids
extractKids (Heredoc doc start end kids) = kids
extractKids (LiteralNode _ _ _)          = []


--When called on the kids of a node that make up a function call, this function extracts the second arguement (the first thing after a comma)

getSecondArg :: [P5AST] -> P5AST
getSecondArg []   = (AbstractNode "UnknownAbs" [])
getSecondArg list = if (matchWithoutEnc (head list) (AbstractNode "listelem" [])) then (dropComma (head list)) else (getSecondArg (tail list))


--Drops the comma from an argument to a function (since the literal comma gets stuck in with with argument)

dropComma :: P5AST -> P5AST
dropComma (AbstractNode "listelem" kids) = (head (tail kids))
dropComma (AbstractNode atype kids)      = (AbstractNode atype kids)
dropComma (Heredoc doc start end kids)   = (Heredoc doc start end kids)
dropComma (LiteralNode atype enc uni)    = (LiteralNode atype enc uni)


--Extracts the first text node (based only on type, not other fields)

extractText :: [P5AST] -> P5AST
extractText []   = (LiteralNode "text" "1" "")
extractText kids = if ((getLType (head kids))=="text") then (head kids) else (extractText (tail kids)) 


--Match based only on subtype

matchOnType :: P5AST -> P5AST -> Bool
matchOnType (AbstractNode atype1 kids1) (AbstractNode atype2 kids2) = if (atype1==atype2) then True else False
matchOnType (LiteralNode atype1 _ _) (LiteralNode atype2 _ _)       = if (atype1==atype2) then True else False
matchOnType _ _                                                     = False 


--Get the type of a literal node

getLType :: P5AST -> String
getLType (AbstractNode sometype kids)   = "UnknownLit"
getLType (Heredoc _ start end kids)     = "UnknownLit"
getLType (LiteralNode sometype enc uni) = sometype


--Join a list of lists of nodes into a list of nodes

join :: [[P5AST]] -> [P5AST]
join []    = []
join lists = (head lists)++(join (tail lists))


--Turn a list of strings into a single string

joinString :: [String] -> String
joinString []   = []
joinString strs = (head strs)++(joinString (tail strs))


--Extract the uni field from a node

extractUni :: P5AST -> String
extractUni (LiteralNode _ _ uni) = uni
extractUni (AbstractNode _ _)    = "" 
extractUni (Heredoc _ _ _ _)     = "" 


{-A simple search convenience function, returns true when the given node is in the list
matches based on type (Abstract or Literal) subtype (Junk, Op_leave, Sigil, etc.) and, in
the case of literal nodes, the uni part of the node.-}

isIn :: P5AST -> [P5AST] -> Bool
isIn _ []      = False
isIn node list = if (matchWithoutEnc (head list) node) then True else (isIn node (tail list))


{-Searches for the given list of nodes in another list allows any number of nodes between the nodes being searched for matches like isIn
Useage: isInOrder [NodesBeingSearchedFor] [SearchTarget]-}

isInOrder :: [P5AST] -> [P5AST] -> Bool
isInOrder [] []      = True
isInOrder _ []       = False
isInOrder [] _       = True
isInOrder nodes list = if (matchWithoutEnc (head list) (head nodes)) then (isInOrder (tail nodes) (tail list)) else (isInOrder nodes (tail list))


--Like isInOrder, except the nodes have to follow directly after one another.

isInSequence :: [P5AST] -> [P5AST] -> Bool
isInSequence _ []       = True
isInSequence [] _       = False
isInSequence nodes list = if (allMatch nodes list) then True else (isInSequence (tail nodes) list) 


{-This function is just like isInSequence, but it only matches on type -}

isInSequenceType :: [P5AST] -> [P5AST] -> Bool
isInSequenceType _ []       = True
isInSequenceType [] _       = False
isInSequenceType nodes list = if (allMatchType nodes list) then True else (isInSequence (tail nodes) list)


{-Just like allMatch, but only matching on type-}

allMatchType :: [P5AST] -> [P5AST] -> Bool
allMatchType [] [] = True
allMatchType _ [] = False
allMatchType [] _ = False
allMatchType list1 list2 = case [(matchOnType (head list1) (LiteralNode "junk" "" "")), (matchOnType (head list2) (LiteralNode "junk" "" ""))] of
    [True, True]   -> (allMatch (tail list1) (tail list2))
    [True, False]  -> (allMatch (tail list1) list2)
    [False, True]  -> (allMatch list1 (tail list2))
    [False, False] -> if (matchOnType (head list1) (head list2)) then (allMatch (tail list1) (tail list2)) else False


--Make sure two lists match

allMatch :: [P5AST] -> [P5AST] -> Bool
allMatch [] []       = True
allMatch _ []        = False
allMatch [] _        = False
allMatch list1 list2 = case [(matchOnType (head list1) (LiteralNode "junk" "" "")), (matchOnType (head list2) (LiteralNode "junk" "" ""))] of
    [True, True]   -> (allMatch (tail list1) (tail list2))
    [True, False]  -> (allMatch (tail list1) list2)
    [False, True]  -> (allMatch list1 (tail list2))
    [False, False] -> if (matchWithoutEnc (head list1) (head list2)) then (allMatch (tail list1) (tail list2)) else False


{-Matches nodes based on type (Heredoc, Abstract or Literal) subtype (Junk, Op_leave, PNothing, etc)
and (in the case of literal nodes) on the uni field. Used in the above search functions.-}

matchWithoutEnc :: P5AST -> P5AST -> Bool
matchWithoutEnc (LiteralNode type1 _ uni1) (LiteralNode type2 _ uni2)             = if (and [(uni1==uni2), (type1==type2)]) then True else False
matchWithoutEnc (AbstractNode type1 kids1) (AbstractNode type2 kids2)             = if (type1 == type2) then True else False
matchWithoutEnc (Heredoc doc1 start1 end1 kids1) (Heredoc doc2 start2 end2 kids2) = if (and [(matchWithoutEnc start1 start2),(matchWithoutEnc end1 end2), doc1==doc2]) then True else False
matchWithoutEnc _ _                                                               = False
