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