xcffib-1.6.0: generator/Data/XCB/Python/AST.hs
{-
- Copyright 2024 Tycho Andersen
-
- This module exists as a small AST to replace language-python, which seems
- largely unmaintained at this point. This is not really intended to be a
- complete python grammer, mostly it is just enough to be what xcffib needs to
- generate its trees.
-
- It has no annotations as in language-python, because xcffib doesn't use them.
-
- The grammar not complete: it cannot express anything other than empty
- dictionaries, cannot express sets at all, cannot do async, etc. all because
- these features of the language are unused by xcffib.
-
- The grammar is not sound: it has one "op" class, representing both unary and
- binary operators.
-
- Use at your own risk :)
-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
module Data.XCB.Python.AST (
Expr(..),
Suite,
Statement(..),
Ident,
Op(..),
Pretty(..),
prettyText,
PseudoExpr(..)
) where
import Prelude hiding ((<>))
import Data.Maybe
import Text.PrettyPrint
type Suite = [Statement]
type Ident = String
-- we don't use that many operations :)
data Op
= Plus
| Minus
| Multiply
| FloorDivide
| BinaryAnd
| ShiftRight
| ShiftLeft
| Invert
| Equality
| LessThan
| Modulo
deriving (Eq, Ord, Show)
data Statement
= Import
{ import_item :: Ident }
-- this is only a one level dotted import; other levels are not supported
| FromImport
{ from_module :: Ident
, from_item :: Ident
}
-- we skip While, For, AsyncFor, etc. because we don't use them
| Fun
{ fun_name :: Ident
-- only named variables, no default values, kwargs, etc.
, fun_args :: [Ident]
, fun_body :: Suite
}
| Decorated
{ decorations :: Ident
-- only decorated functions/methods are supported
, fun_name :: Ident
, fun_args :: [Ident]
, fun_body :: Suite
}
-- skip AsyncFun...
| Class
{ class_name :: Ident
-- same as functions, only identifiers allowed
, class_args :: [Ident]
, class_body :: Suite
}
| Conditional
{ if_cond :: Expr
, if_body :: Suite
, else_body :: Suite
}
| Assign
{ assign_to :: Expr
, assign_expr :: Expr
}
| AugmentedAssign
{ aug_assign_to :: Expr
, aug_assign_op :: Op
, aug_assign_expr :: Expr
}
-- skip AnnotatedAssign, Decorated
| Return
{ return_expr :: Maybe (Expr) }
-- skip Try, With, AsyncWith
| Pass {}
-- skip Break, Continue, Delete
| StmtExpr
{ stmt_expr :: Expr }
-- skip Global, NonLocal, Assert, Print, Exec
| Raise { raise_exception :: Ident }
deriving (Eq, Ord, Show)
data Expr
= Var { var :: Ident }
| Int { int_value :: Int }
| Bool { bool_value :: Bool }
| None
| Strings { strings_strings :: [String] }
| Call
{ call_fun :: Expr
, call_args :: [Expr]
}
| CondExpr
{ ce_true_branch :: Expr
, ce_conditon :: Expr
, ce_false_branch :: Expr
}
| Subscript
{ subscriptee :: Expr
, subscript_expr :: Expr
}
| BinaryOp
{ operator :: Op
, binop_left :: Expr
, binop_right :: Expr
}
| UnaryOp
{ operator :: Op
, unop_arg :: Expr
}
| Dot
{ dot_expr :: Expr
, dot_attribute :: Ident
}
| Tuple { tuple_exprs :: [Expr] }
| EmptyDict {} -- an empty dictionary
| Paren { paren_expr :: Expr }
deriving (Eq, Ord, Show)
prettyText :: Pretty a => a -> String
prettyText = render . pretty
class Pretty a where
pretty :: a -> Doc
instance Pretty Op where
pretty Plus = text "+"
pretty Minus = text "-"
pretty Multiply = text "*"
pretty FloorDivide = text "//"
pretty BinaryAnd = text "&"
pretty ShiftRight = text ">>"
pretty ShiftLeft = text "<<"
pretty Invert = text "~"
pretty Equality = text "=="
pretty LessThan = text "<"
pretty Modulo = text "%"
_reserved :: [String]
_reserved = [ "None"
, "def"
, "class"
, "and"
, "or"
]
-- | Sanitize identifiers.
instance Pretty Ident where
pretty s | s `elem` _reserved = text $ "_" ++ s
pretty s | isInt s = text $ "_" ++ s
where
isInt str = isJust $ ((maybeRead str) :: Maybe Int)
maybeRead = fmap fst . listToMaybe . reads
pretty s = text s
instance Pretty Suite where
pretty stmts = vcat $ map pretty stmts
indent :: Doc -> Doc
indent = nest 4
instance Pretty Statement where
pretty (Import item) = text "import" <+> pretty item
pretty (FromImport source item) = text "from" <+> text source <+> text "import" <+> pretty item
pretty (Fun name args bod) = text "def" <+> pretty name <> (parens (addCommas args)) <> colon
$+$ indent (pretty bod)
pretty (Decorated decorator name args bod) = text "@" <> text decorator $+$ pretty (Fun name args bod)
pretty (Class name [] body) = text "class" <+> pretty name <> colon $+$ indent (pretty body)
pretty (Class name superclasses body) = text "class" <+> pretty name <> parens (addCommas superclasses) <> colon $+$ indent (pretty body)
pretty (Conditional cond if_ else_) = text "if" <+> pretty cond <> colon $+$ indent (pretty if_) $+$ pretty else_
pretty (Assign to expr) = pretty to <+> text "=" <+> pretty expr
pretty (AugmentedAssign to op expr) = pretty to <+> pretty op <> text "=" <+> pretty expr
pretty (Return (Just expr)) = text "return" <+> pretty expr
pretty (Return Nothing) = text "return"
pretty Pass = text "pass"
pretty (StmtExpr expr) = pretty expr
pretty (Raise exc) = text "raise" <+> pretty exc
class PseudoExpr a where
getExpr :: a -> Expr
instance PseudoExpr String where
getExpr s = Var s
instance PseudoExpr Expr where
getExpr = id
addCommas :: PseudoExpr a => [a] -> Doc
addCommas exprs = hsep $ punctuate (text ",") (map (pretty . getExpr) exprs)
instance Pretty Expr where
pretty (Var v) = pretty v
pretty (Int i) = integer (toInteger i)
pretty (Bool True) = text "True"
pretty (Bool False) = text "False"
pretty None = text "None"
pretty (Strings xs) = hcat (map text xs)
pretty (Call fun args) = pretty fun <> lparen <> addCommas args <> rparen
pretty (CondExpr trueB cond falseB) = pretty trueB <+> text "if" <+> pretty cond <+> text "else" <+> pretty falseB
pretty (Subscript thing expr) = pretty thing <> brackets (pretty expr)
pretty (BinaryOp op left right) = pretty left <+> pretty op <+> pretty right
pretty (UnaryOp op arg) = pretty op <> pretty arg
pretty (Dot thing attr) = pretty thing <> text "." <> pretty attr
pretty (Tuple [expr]) = pretty expr <> comma -- one element still needs to be a tuple
pretty (Tuple exprs) = addCommas exprs
pretty EmptyDict = text "{}"
pretty (Paren expr) = lparen <> pretty expr <> rparen