-- | Abstract syntax tree and pretty-printing for Python. -- Works for Python 2 and 3. -- A lot of the data structures are inspired by the language-python package; -- I have chosen not to have language-python as a dependency of sifflet-lib, -- however, because it would be overkill and still allows to little control -- over pretty-printing of Python expressionsw. module Sifflet.Foreign.Python (PyPretty(..) , PModule(..) , PStatement(..) , alterParens , ret , condS , var , ident , char , fun , operatorTable ) where import Data.List (intercalate) import qualified Data.Map as M import Sifflet.Language.Expr import Sifflet.Text.Pretty class PyPretty a where pyPretty :: a -> String pyPrettyList :: String -> String -> String -> [a] -> String pyPrettyList pre tween post xs = pre ++ intercalate tween (map pyPretty xs) ++ post pyPrettyParens :: (PyPretty a) => [a] -> String pyPrettyParens = pyPrettyList "(" ", " ")" instance PyPretty Symbol where pyPretty = pretty instance PyPretty Operator where pyPretty = pretty -- | Python module -- essentially a list of statements; -- should it also have a name? data PModule = PModule [PStatement] deriving (Eq, Show) instance PyPretty PModule where pyPretty (PModule ss) = sepLines2 (map pyPretty ss) -- | Python statement data PStatement = PReturn Expr | PImport String -- ^ import statement | PCondS Expr PStatement PStatement -- ^ if condition action alt-action | PFun Symbol [Symbol] PStatement -- ^ function name, formal parameters, body deriving (Eq, Show) instance PyPretty PStatement where pyPretty s = case s of PReturn e -> "return " ++ pyPretty e PImport modName -> "import " ++ modName PCondS c a b -> sepLines ["if " ++ pyPretty c ++ ":", indentLine 4 (pyPretty a), "else:", indentLine 4 (pyPretty b)] PFun fid params body -> sepLines ["def " ++ pyPretty fid ++ pyPrettyParens params ++ ":", indentLine 4 (pyPretty body)] -- | Expr as an instance of PyPretty. -- This instance is only for Exprs as Python exprs, -- for export to Python! It will conflict with the -- one in ToHaskell.hs (or Haskell.hs). -- -- The EOp case needs work to deal with precedences -- and avoid unnecessary parens. -- Note that this instance declaration is for *Python* Exprs. -- Haskell Exprs of course should not be pretty-printed -- the same way! instance PyPretty Expr where pyPretty pexpr = case pexpr of EUndefined -> "undefined" EChar _ -> error ("Python pyPretty of Expr: " ++ "EChar should have been converted to " ++ "EString") EList _ -> error ("Python pyPretty of Expr: " ++ "EList should have been converted to " ++ "ECall li ...") EIf c a b -> unwords [pyPretty a, "if", pyPretty c, "else", pyPretty b] EGroup e -> pyPrettyParens [e] ESymbol vid -> pyPretty vid ENumber n -> show n EBool b -> show b EString s -> show s ECall fexpr argExprs -> concat [pyPretty fexpr, pyPrettyParens argExprs] EOp op left right -> unwords [pyPretty left, pyPretty op, pyPretty right] alterParens :: (Expr -> Expr) -> PStatement -> PStatement alterParens t s = case s of PReturn e -> PReturn (t e) PCondS c a b -> PCondS (t c) (alterParens t a) (alterParens t b) PFun fid params b -> PFun fid params (alterParens t b) _ -> s -- | Python return statement ret :: Expr -> PStatement ret pexpr = PReturn pexpr -- | Python if STATEMENT -- This is the if STATEMENT: -- if c: -- a -- else: -- b -- -- But do I need this at all? condS :: Expr -> Expr -> Expr -> PStatement condS c a b = PCondS c (ret a) (ret b) -- PExpr smart constructors -- | Python variable var :: String -> Expr var name = ESymbol (Symbol name) -- | Python identifier ident :: String -> Symbol ident s = Symbol s -- | Python character expression = string expression with one character char :: Char -> Expr char c = EString [c] -- | Python function formal parameter param :: String -> Symbol param name = Symbol name -- | Defines function definition fun :: String -> [String] -> Expr -> PStatement fun fname paramNames bodyExpr = PFun (ident fname) (map param paramNames) (ret bodyExpr) -- | Binary operators -- Precedence levels are rather *informally* described in -- The Python Language Reference, -- http://docs.python.org/reference/. -- I am adopting the infixr levels from Haskell, -- which seem to be consistent with Python, -- at least for the operators that Sifflet uses. -- -- | Operator information -- Arithmetic operators: -- + and - have lower precedence than *, /, //, % -- | Comparison operators have precedence lower than any arithmetic -- operator. Here, I've specified associative = False, -- because association doesn't even make sense (well, it does in Python -- but not in other languages); -- (a == b) == c is in general not well typed. operatorTable :: M.Map String Operator operatorTable = M.fromList (map (\ op -> (opName op, op)) [ (Operator "*" 7 True GroupLtoR) -- times , (Operator "//" 7 False GroupLtoR) -- int div , (Operator "/" 7 False GroupLtoR) -- float div , (Operator "%" 7 False GroupLtoR) -- mod , (Operator "+" 6 True GroupLtoR) -- plus , (Operator "-" 6 False GroupLtoR) -- minus , (Operator "==" 4 False GroupNone) -- eq , (Operator "!=" 4 False GroupNone) -- ne , (Operator ">" 4 False GroupNone) -- gt , (Operator ">=" 4 False GroupNone) -- ge , (Operator "<" 4 False GroupNone) -- lt , (Operator "<=" 4 False GroupNone) -- le ])