module Sifflet.Foreign.ToHaskell
(
HaskellOptions(..)
, HasParens(..)
, defaultHaskellOptions
, exportHaskell
, functionsToHsModule
, functionToHsDecl
, exprToHsExp
, valueToHsExp
, prettyModule
)
where
import Char (toUpper)
import Language.Haskell.Parser
import Language.Haskell.Syntax
import qualified Language.Haskell.Pretty as HsPretty
import Sifflet.Foreign.Exporter
import Sifflet.Language.Expr
import Sifflet.Examples
import System.FilePath (dropExtension, takeFileName)
data HaskellOptions = HaskellOptions
deriving (Eq, Show)
defaultHaskellOptions :: HaskellOptions
defaultHaskellOptions = HaskellOptions
exportHaskell :: HaskellOptions -> Exporter
exportHaskell _options functions path =
let header = "-- File: " ++ path ++
"\n-- Generated by the Sifflet->Haskell exporter.\n\n"
in writeFile path (header ++
hspp (simplifyParens
(functionsToHsModule
(pathToModuleName path)
functions)))
pathToModuleName :: FilePath -> String
pathToModuleName path =
case dropExtension (takeFileName path) of
[] -> "Test"
c : cs -> toUpper c : cs
srcLoc :: SrcLoc
srcLoc = SrcLoc {srcFilename = "", srcLine = 0, srcColumn = 0}
hsModule :: String -> [HsImportDecl] -> [HsDecl] -> HsModule
hsModule name importDecls decls =
HsModule srcLoc (Module name)
Nothing
importDecls
decls
hsImportDecl :: String -> HsImportDecl
hsImportDecl name =
HsImportDecl {importLoc = srcLoc,
importModule = Module name,
importQualified = False,
importAs = Nothing,
importSpecs = Nothing}
hsFunBind :: [HsMatch] -> HsDecl
hsFunBind matches =
HsFunBind matches
hsIdent :: String -> HsName
hsIdent = HsIdent
hsSymbol :: String -> HsName
hsSymbol = HsSymbol
hsPVar :: String -> HsPat
hsPVar = HsPVar . hsIdent
hsVar :: String -> HsExp
hsVar = HsVar . UnQual . hsIdent
hsOperate :: HsExp -> HsQOp -> HsExp -> HsExp
hsOperate left qop right =
HsInfixApp left qop right
hsCall :: HsExp -> [HsExp] -> HsExp
hsCall hfunc hargs =
case hargs of
[] ->
case hfunc of
HsVar (UnQual (HsIdent name)) -> hfunc
_ -> error ("hsCall: unexpected form of unary function: " ++
show hfunc)
a : [] -> HsApp hfunc a
a : as -> hsCall (HsApp hfunc a) as
hsOp :: String -> HsQOp
hsOp = HsQVarOp . UnQual . hsSymbol
functionsToHsModule :: String -> Functions -> HsModule
functionsToHsModule mname (Functions fs) =
hsModule mname
[hsImportDecl "Sifflet.Data.Number"]
(map functionToHsDecl fs)
functionToHsDecl :: Function -> HsDecl
functionToHsDecl (Function mname atypes rtype impl) =
case (mname, impl) of
(Nothing, _) -> error "functionToHsDecl: function has no name"
(_, Primitive _) -> error "functionToHsDecl: function is primitive"
(Just fname, Compound args body) ->
HsFunBind [HsMatch srcLoc
(hsIdent fname)
(map hsPVar args)
(HsUnGuardedRhs (exprToHsExp body))
[]
]
exprToHsExp :: Expr -> HsExp
exprToHsExp expr =
case expr of
EUndefined -> hsVar "undefined"
ESymbol (Symbol s) -> hsVar s
ELit v -> valueToHsExp v
EIf c a b ->
HsIf (exprToHsExp c) (exprToHsExp a) (exprToHsExp b)
EList es -> HsList (map exprToHsExp es)
ECall (Symbol fname) args ->
case nameToHaskell fname of
HsSymbol opName ->
case args of
[left, right] ->
HsParen (hsOperate (exprToHsExp left)
(hsOp opName)
(exprToHsExp right))
_ -> error "exprToHsExp: operation does not have 2 operands"
HsIdent funcName ->
HsParen (hsCall (hsVar funcName) (map exprToHsExp args))
valueToHsExp :: Value -> HsExp
valueToHsExp value =
case value of
VBool b -> HsCon (UnQual (HsIdent (if b then "True" else "False")))
VChar c -> HsLit (HsChar c)
VInt i -> HsLit (HsInt i)
VFloat x -> HsLit (HsFrac (toRational x))
VStr s -> HsLit (HsString s)
VFun _ -> error "valueToHsLiteral: I don't know how to convert a VFun"
VList vs -> HsList (map valueToHsExp vs)
nameToHaskell :: String -> HsName
nameToHaskell name =
if elem name ["+", "-", "*", "/",
"==", "/=", "<", ">", "<=", ">=",
":"]
then HsSymbol name
else
HsIdent (case name of
"zero?" -> "eqZero"
"positive?" -> "gtZero"
"negative?" -> "ltZero"
_ -> name)
class HasParens a where
simplifyParens :: a -> a
instance HasParens HsModule where
simplifyParens (HsModule locus name exportDecls importDecls decls) =
HsModule locus name exportDecls importDecls (map simplifyParens decls)
instance HasParens HsDecl where
simplifyParens decl =
case decl of
HsFunBind [HsMatch locus fname args
(HsUnGuardedRhs body)
[]] ->
HsFunBind [HsMatch locus fname args
(HsUnGuardedRhs (simplifyParens body))
[]]
_ ->
decl
instance HasParens HsExp where
simplifyParens hexp =
let t = simplifyParens
ut = unpar . t
unpar e =
case e of
HsParen e' -> e'
_ -> e
in case hexp of
HsIf c a b -> HsIf (ut c) (ut a) (ut b)
HsList es -> HsList (map t es)
HsParen e ->
if atomic e then e
else case e of
_ -> hexp
HsInfixApp left qop right ->
HsInfixApp left qop right
HsApp (HsParen (HsApp hf ha)) hb ->
HsApp (HsApp hf ha) hb
_ -> hexp
atomic :: HsExp -> Bool
atomic hexp =
case hexp of
HsVar (UnQual (HsIdent _)) -> True
HsCon (UnQual (HsIdent _)) -> True
HsLit _ -> True
HsList _ -> False
HsIf _ _ _ -> False
HsInfixApp _ _ _ -> False
HsApp _ _ -> False
_ -> error ("atomic: don't know how to handle: " ++ show hexp)
asModule :: [String] -> String
asModule strings = unlines ("module Test where" : strings)
test1 :: String
test1 = asModule [
"foo x y = x + y"]
test2 :: String
test2 = asModule [
"foo1 x = bar (codd x)",
"foo2 = bar . codd"]
prettyDS :: [String] -> IO ()
prettyDS declStrings = prettyModule (asModule declStrings)
prettyES :: String -> IO ()
prettyES expString = prettyModule (asModule ["x = " ++ expString])
hspp :: (HsPretty.Pretty a) => a -> String
hspp = HsPretty.prettyPrint
prettyModule :: String -> IO ()
prettyModule string =
case parseModule string of
ParseOk m -> putStrLn (hspp m)
ParseFailed loc msg -> putStrLn (show loc ++ ": " ++ msg)
prettyE :: Expr -> IO ()
prettyE expr =
putStrLn (hspp (exprToHsExp expr))
prettyV :: Value -> IO ()
prettyV value =
putStrLn (hspp (valueToHsExp value))
testParse :: String -> ParseResult HsModule
testParse string = parseModule string
testCallPrefix :: IO ()
testCallPrefix = prettyE $ ECall (Symbol "mod") [ELit (VInt 7), ELit (VInt 5)]
testCallInfix :: IO ()
testCallInfix = prettyE $ ECall (Symbol "+") [ELit (VInt 7), ELit (VInt 5)]
testFunBind :: Function -> IO ()
testFunBind f = putStrLn (hspp (simplifyParens (functionToHsDecl f)))
testExportModule :: String -> [Function] -> IO ()
testExportModule moduleName fs =
putStrLn (hspp (simplifyParens
(functionsToHsModule moduleName (Functions fs))))
testEF :: String -> IO ()
testEF = testFunBind . getExampleFunction