packages feed

berp-0.0.1: src/Berp/Compile/PySyntaxUtils.hs

{-# LANGUAGE TypeSynonymInstances #-}

-----------------------------------------------------------------------------
-- |
-- Module      : Berp.Compile.PySyntaxUtils
-- Copyright   : (c) 2010 Bernie Pope
-- License     : BSD-style
-- Maintainer  : florbitous@gmail.com
-- Stability   : experimental
-- Portability : ghc
--
-- Utilities for processing Python syntax.
--
-----------------------------------------------------------------------------

module Berp.Compile.PySyntaxUtils where

import Language.Python.Common.AST as Py
import Data.Set as Set 
import Data.List (intersperse, foldl')
import Data.Monoid
import Berp.Compile.Monad (Scope (..), emptyScope)
import Berp.Compile.IdentString (ToIdentString (..), IdentString (..))
import Berp.Compile.VarSet (VarSet)

data InterpreterStmt = InterpreterStmt Py.SuiteSpan

isEmptySuite :: Suite a -> Bool
isEmptySuite [] = True
isEmptySuite _ = False

-- Incomplete
assignTargets :: [ExprSpan] -> VarSet 
assignTargets = foldl' addTarget mempty
   where
   addTarget :: VarSet -> ExprSpan -> VarSet
   addTarget set var@(Var {}) = Set.insert (varToString var) set
   addTarget set _other = set

varToString :: Show a => Expr a -> IdentString
varToString v@(Var {}) = toIdentString $ var_ident v
varToString other = error $ "fatal error: varToString called on non variable argument" ++ show other

{-
toIdentString :: Ident a -> IdentString
toIdentString (Ident { ident_string = name }) = IdentString name
-}

paramIdent :: Parameter a -> Ident a
paramIdent = param_name 

topBindings :: SuiteSpan -> Either String Scope
topBindings stmts
   | not $ Set.null nonLocals 
        = Left $ "These variables are declared nonlocal at the top level: " ++ prettyVarSet nonLocals
   | otherwise = Right $ emptyScope { localVars = locals, globalVars = globals }
   where
   (locals, nonLocals, globals) = termBindings stmts 
   
funBindings :: DefinedVars t => [ParameterSpan] -> t -> Either String Scope 
funBindings params term 
   = case allDisjoint paramIdents nonLocals globals of
        Nothing -> Right $ emptyScope 
           { localVars = locals \\ paramIdents
           , paramVars = paramIdents 
           , globalVars = globals } 
        Just error -> Left error
   where
   paramIdents = fromList $ Prelude.map (toIdentString . paramIdent) params 
   (locals, nonLocals, globals) = termBindings term 

termBindings :: DefinedVars t => t -> (VarSet, VarSet, VarSet)
termBindings term 
   = (theseLocals, theseNonLocals, theseGlobals) 
   where
   varBindings = definedVars term 
   theseGlobals = globals varBindings
   theseNonLocals = nonlocals varBindings
   theseLocals = ((assigned varBindings \\ theseGlobals) \\ theseNonLocals) 

allDisjoint :: VarSet -> VarSet -> VarSet -> Maybe String 
allDisjoint params nonlocals globals 
   = if not (Set.null ps_ns)
        then Just $ "These variables are parameters and declared nonlocal: " ++ prettyVarSet ps_ns 
        else if not (Set.null ps_gs)
                then Just $ "These variables are parameters and declared global: " ++ prettyVarSet ps_gs
                else if not (Set.null ns_gs)
                        then Just $ "These variables are declared nonlocal and global: " ++ prettyVarSet ns_gs
                        else Nothing
   where
   ps_ns = params `intersection` nonlocals
   ps_gs = params `intersection` globals
   ns_gs = nonlocals `intersection` globals 

prettyVarSet :: VarSet -> String
prettyVarSet = concat . intersperse "," . Prelude.map fromIdentString . toList 

data BindingSets
   = BindingSets { assigned :: VarSet, nonlocals :: VarSet, globals :: VarSet }

instance Monoid BindingSets where
   mempty = BindingSets { assigned = empty, nonlocals = empty, globals = empty }
   mappend x y
      = BindingSets
        { assigned = assigned x `mappend` assigned y 
        , nonlocals = nonlocals x `mappend` nonlocals y
        , globals = globals x `mappend` globals y }

class DefinedVars t where
   definedVars :: t -> BindingSets 

instance DefinedVars t => DefinedVars [t] where
   definedVars = mconcat . Prelude.map definedVars 

instance (DefinedVars t1, DefinedVars t2) => DefinedVars (t1, t2) where
   definedVars (x,y) = definedVars x `mappend` definedVars y

instance DefinedVars (StatementSpan) where
   definedVars (While { while_body = b, while_else = e })
      = definedVars b `mappend` definedVars e 
   definedVars (For { for_targets = t, for_body = b, for_else = e })
      = mempty { assigned = assignTargets t} `mappend` definedVars b `mappend` definedVars e 
   -- Any definedVars made inside a function body are not collected.
   -- The function name _is_ collected.
   definedVars (Fun { fun_name = n })
      = mempty { assigned = singleton $ toIdentString n }
   definedVars (Class { class_name = ident, class_body = _b })
      = mempty { assigned = singleton $ toIdentString ident } -- `mappend` definedVars b 
   definedVars (Conditional { cond_guards = g, cond_else = e })     
      = definedVars g `mappend` definedVars e
   definedVars (Assign { assign_to = t })
      = mempty { assigned = assignTargets t }
   definedVars (Decorated { decorated_def = d })
       = definedVars d
   definedVars (Try { try_body = b, try_else = e, try_finally = f})
       = definedVars [b,e,f]
   definedVars (With { with_body = b })
      = definedVars b
   definedVars (Global { global_vars = idents })
      = mempty { globals = fromList $ Prelude.map toIdentString idents } 
   definedVars (NonLocal { nonLocal_vars = idents })
      = mempty { nonlocals = fromList $ Prelude.map toIdentString idents }
   definedVars _other = mempty

instance DefinedVars (Expr a) where
   definedVars = mempty 

-- (currently) variables are not atomic because they are always mutable
-- and reading a variable is an effect. If we add single binding variables
-- then they would be atomic.
isAtomicExpr :: Py.ExprSpan -> Bool
isAtomicExpr (Py.Strings {}) = True
isAtomicExpr (Py.Bool {}) = True
isAtomicExpr (Py.Int {}) = True
isAtomicExpr (Py.Float {}) = True
isAtomicExpr (Py.Imaginary {}) = True
isAtomicExpr (Py.Tuple {}) = True
isAtomicExpr (Py.Paren { paren_expr = e }) = isAtomicExpr e
isAtomicExpr (Py.None {}) = True
isAtomicExpr _other = False