packages feed

Pugs-6.2.13.1: src/Pugs/Parser/Util.hs

{-# OPTIONS_GHC -fglasgow-exts -fallow-overlapping-instances #-}
module Pugs.Parser.Util where

import Pugs.Internals
import Pugs.AST
import Pugs.Types
import Pugs.Lexer
import Pugs.Rule
import Pugs.Parser.Types
import qualified Data.Map as Map
import qualified Data.Set as Set

grammaticalCategories :: [String]
grammaticalCategories = ["prefix_circumfix_meta_operator:","infix_circumfix_meta_operator:","prefix_postfix_meta_operator:","postfix_prefix_meta_operator:","infix_postfix_meta_operator:","statement_modifier:","statement_control:","scope_declarator:","trait_auxiliary:","trait_verb:","regex_mod_external:","regex_mod_internal:","regex_assertion:","regex_backslash:","regex_metachar:","postcircumfix:","circumfix:","postfix:","infix:","prefix:","quote:","term:"]


retBlockWith :: (Exp -> Exp) -> BlockInfo -> RuleParser BlockInfo
retBlockWith f bi = return bi{ bi_body = f (bi_body bi) }

-- around a block body we save the package and the current lexical pad
-- at the start, so that they can be restored after parsing the body
localBlock :: RuleParser Exp -> RuleParser BlockInfo
localBlock m = do
    state   <- get

    -- XXX - Perhaps clone the protopad right here, for $_ etc?
    compPad <- newMPad (s_protoPad state)

    -- traceM $ "Gen:" ++ show compPad
    let env     = s_env state
        lexPads = (PCompiling compPad:envLexPads env)
        protoVars = Map.map (const compPad) (padEntries $ s_protoPad state)

    put state
        { s_closureTraits   = (id : s_closureTraits state)
        , s_env             = env
            { envLexPads = lexPads  -- enter the scope
            , envCompPad = Just compPad
            }
        , s_protoPad        = emptyPad
        , s_knownVars       = s_knownVars state `Map.union` protoVars
        }

    body    <- m
    state'  <- get

    -- Remove from knownVars the bindings belonging to this scope.
    let outerKnownVars = Map.filter (/= compPad) (s_knownVars state')
        outerOuterVars = Map.delete compPad (s_outerVars state')
        (traits, outerTraits) = case s_closureTraits state' of
            (t:ts)  -> (t, ts)
            _       -> (id, [])

    put state
        { s_env = (s_env state')
            { envPackage = envPackage env
            , envLexical = envLexical env
            , envLexPads = envLexPads env
            , envCompPad = envCompPad env
            }
        , s_closureTraits = outerTraits
        , s_knownVars     = outerKnownVars
        , s_outerVars     = outerOuterVars
        , s_protoPad      = emptyPad
        }

    -- Re-read compile time refs into the new protos at end of scope.
    newPad <- return $! unsafePerformSTM $! do
        curPad  <- readMPad compPad
        entries <- forM (padToList curPad) $ \(var, entry) -> do
            proto   <- readPadEntry entry
            let newEntry = entry{ pe_proto = proto }
            return (newEntry `seq` (var, newEntry))
        let newPad = listToPad (length entries `seq` entries)
        writeMPad compPad newPad
        return newPad
    return $ MkBlockInfo{ bi_pad = newPad, bi_body = body, bi_traits = traits }

ruleParamList :: ParensOption -> RuleParser a -> RuleParser (Maybe [[a]])
ruleParamList wantParens parse = rule "parameter list" $ do
    (formal, hasParens) <- f $
        (((try parse) `sepEndBy` lexeme (oneOf ",;")) `sepEndBy` invColon)
    case formal of
        [[]]   -> return $ if hasParens then Just [[], []] else Nothing
        [args] -> return $ Just [[], args]
        [_,_]  -> return $ Just formal
        _      -> fail "Only one invocant list allowed"
    where
    f = case wantParens of
        ParensOptional  -> maybeParensBool
        ParensMandatory -> \x -> do rv <- parens x; return (rv, True)
    invColon = do
        ch <- oneOf ":;"
        -- Compare:
        --   sub foo (: $a)   # vs.
        --   sub foo (:$a)
        lookAhead $ (many1 space <|> string ")")
        whiteSpace
        return ch
        
maybeParensBool :: RuleParser a -> RuleParser (a, Bool)
maybeParensBool p = choice
    [ do rv <- parens p; return (rv, True)
    , do rv <- p; return (rv, False)
    ]


{-| Wraps a call to @&Pugs::Internals::check_for_io_leak@ around the input
    expression. @&Pugs::Internals::check_for_io_leak@ should @die()@ if the
    expression returned an IO handle. -}
-- Please remember to edit Prelude.pm, too, if you rename the name of the
-- checker function.
checkForIOLeak :: VCode -> Exp
checkForIOLeak code =
    App (_Var "&Pugs::Internals::check_for_io_leak") Nothing [ Val $ VCode code ]
    
defaultParamFor :: SubType -> [Param]
defaultParamFor SubBlock    = [] -- defaultScalarParam]
defaultParamFor SubPointy   = []
defaultParamFor _           = [defaultArrayParam]

extractNamedPlaceholders :: SubType -> Maybe [Param] -> Exp -> (Exp, [Var], [Param])
extractNamedPlaceholders SubBlock formal body = (fun, names', params)
    where
    (fun, names) = extractPlaceholderVars body Set.empty
    names' | isJust formal
           = sortNames (Set.delete varTopic names)
           | otherwise
           = sortNames names
    params = map nameToParam names' ++ (maybe [] id formal)
extractNamedPlaceholders SubPointy formal body = (body, [], maybe [] id formal)
extractNamedPlaceholders SubMethod formal body = (body, [], maybe [] id formal)
extractNamedPlaceholders _ formal body = (body, names', params)
    where
    (_, names) = extractPlaceholderVars body Set.empty
    names' | isJust formal
           = sortNames (Set.delete varTopic names)
           | otherwise
           = sortNames (Set.filter (== varTopic) names)
    params = map nameToParam names' ++ (maybe [] id formal)

sortNames :: Set Var -> [Var]
sortNames = sortBy (\x y -> v_name x `compare` v_name y) . Set.toList

nameToParam :: Var -> Param
nameToParam name = MkOldParam
    { isInvocant    = False
    , isOptional    = False
    , isNamed       = False
    , isLValue      = True
    , isWritable    = (name == varTopic)
    , isLazy        = False
    , paramName     = name
    , paramContext  = CxtItem $ typeOfSigilVar name
    , paramDefault  = Noop
    }

_percentUnderscore :: Var
_percentUnderscore = cast "%_"

paramsFor :: SubType -> Maybe [Param] -> [Param] -> [Param]
paramsFor SubMethod formal params 
    | isNothing (find ((_percentUnderscore ==) . paramName) params)
    = paramsFor SubRoutine formal params ++ [defaultHashParam]
paramsFor styp Nothing []       = defaultParamFor styp
paramsFor _ _ params            = params

processFormals :: Monad m => [[Exp]] -> m (Maybe Exp, [Exp])
processFormals formal = case formal of
    []      -> return (Nothing, [])
    [args]  -> return (Nothing, unwind args)
    [invs,args] | [inv] <- unwind invs -> return (Just inv, unwind args)
    _                   -> fail "Only one invocant allowed"
    where
    unwind :: [Exp] -> [Exp]
    unwind [] = []
    unwind ((Syn "," list):xs) = unwind list ++ unwind xs
    unwind x  = x

-- | A Param representing the default (unnamed) invocant of a method on the given type.
selfParam :: Type -> Param
selfParam typ = MkOldParam
    { isInvocant    = True
    , isOptional    = False
    , isNamed       = False
    , isLValue      = True
    , isWritable    = True
    , isLazy        = False
    , paramName     = cast "$__SELF__"
    , paramContext  = CxtItem typ
    , paramDefault  = Noop
    }

hashComposerCheck :: Exp -> RuleParser Bool
hashComposerCheck exp
    | Ann (Prag [MkPrag "eol-block" _]) _ <- exp = do
        when isHash $
            fail "Closing hash curly may not terminate a line;\nplease add a comma or a semicolon to disambiguate"
        return False
    | otherwise = return isHash
    where
    isHash = doCheck (possiblyUnwrap exp)

    possiblyUnwrap (Ann _ exp) = possiblyUnwrap exp
    possiblyUnwrap (Syn "block" [exp]) = unwrap exp
    possiblyUnwrap (App (Val (VCode (MkCode { subType = SubBlock, subBody = fun }))) Nothing []) = unwrap fun
    possiblyUnwrap x = x
    
    isHashOrPair (Ann _ exp)            = isHashOrPair exp
    isHashOrPair (App (Var var) _ _)    = (var == cast "&pair") || (var == cast "&infix:=>") 
    isHashOrPair (Syn "%{}" _)          = True
    isHashOrPair (Var var)              = v_sigil var == SHash
    isHashOrPair _                      = False
    
    doCheck Noop                   = True
    doCheck (Syn "," (subexp:_))   = isHashOrPair subexp
    doCheck exp                    = isHashOrPair exp

tryLookAhead :: RuleParser a -> RuleParser b -> RuleParser a
tryLookAhead rule after = try $ do
    rv <- rule
    lookAhead after
    return rv

makeVar :: String -> Exp
makeVar (s:"<>") =
    makeVarWithSigil s $ _Var "$/"
makeVar (s:rest) | all (`elem` "1234567890") rest =
    makeVarWithSigil s $ Syn "[]" [_Var "$/", Val $ VInt (read rest)]
makeVar (s:'<':'<':name) =
    makeVarWithSigil s $ Syn "{}" [_Var "$/", doSplitStr shellWords (init (init name))]
makeVar (s:'\171':name) =
    makeVarWithSigil s $ Syn "{}" [_Var "$/", doSplitStr shellWords (init name)]
makeVar (s:'<':name) =
    makeVarWithSigil s $ Syn "{}" [_Var "$/", doSplitStr perl6Words (init name)]
makeVar var = _Var var

makeVarWithSigil :: Char -> Exp -> Exp
makeVarWithSigil '$' x = x
makeVarWithSigil s   x = Syn (s:"{}") [x]

-- | splits the string into expressions on whitespace.
-- Implements the <> operator at parse-time.
doSplitStr :: (String -> [String]) -> String -> Exp
doSplitStr f str = case f str of
    []  -> Syn "," []
    [x] -> Val (VStr x)
    xs  -> Syn "," $ map (Val . VStr) xs

perl6Words :: String -> [String]
perl6Words s
    | [] <- findSpace = []
    | otherwise       = w : words s''
    where
    (w, s'')  = break isBreakingSpace findSpace
    findSpace = dropWhile isBreakingSpace s

isBreakingSpace :: Char -> Bool
isBreakingSpace '\x09'  = True
isBreakingSpace '\x0a'  = True
isBreakingSpace '\x0d'  = True
isBreakingSpace '\x20'  = True
isBreakingSpace _       = False

followedBy, tryFollowedBy :: RuleParser a -> RuleParser b -> RuleParser a
followedBy rule after = do
    rv <- rule
    after
    return rv

tryFollowedBy = (try .) . followedBy

-- XXX - Naive implementation of << 1 '2' 3 >>, only used in $<< 'foo' >> so far

data ShellWordsState = MkShellWordsState
    { s_escape  :: Bool
    , s_quote   :: (Maybe Char)
    , s_cur     :: Maybe String
    , s_acc     :: [String]
    }

shellWords :: String -> [String]
shellWords = postProc . foldl doShellWords (MkShellWordsState False Nothing Nothing [])
    where
    doShellWords state ch
        | s_escape state
        = normalChar{ s_escape = False }
        | '\\' <- ch
        = state{ s_escape = True }
        | Just q <- s_quote state
        = if ch == q then closeQuote else normalChar
        | isBreakingSpace ch
        = nextWord
        | '"' <- ch     = beginQuote
        | '\'' <- ch    = beginQuote
        | otherwise     = normalChar
        where
        cur = s_cur state
        acc = s_acc state
        normalChar = state{ s_cur = Just (maybe [ch] (ch:) cur) }
        beginQuote = state{ s_quote = Just ch }
        closeQuote = state{ s_quote = Nothing, s_cur = Just (maybe "" id cur) }
        nextWord   = state{ s_acc = maybe acc (:acc) cur, s_cur = Nothing }
    postProc MkShellWordsState{ s_cur = cur, s_acc = acc } = reverse (map reverse acc')
        where
        acc' = maybe acc (:acc) cur