sifflet-lib-1.0: Sifflet/Foreign/ToHaskell.hs
-- | Exports Sifflet to Haskell
-- Requires haskell-src package.
module Sifflet.Foreign.ToHaskell
(
HaskellOptions(..)
, HasParens(..)
, defaultHaskellOptions
, exportHaskell
, functionsToHsModule
, functionToHsDecl
, exprToHsExp
, valueToHsExp
, prettyModule
)
where
import Char (toUpper)
import Language.Haskell.Parser -- only for reverse engineering
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)
-- Main types and functions
-- | User configurable options for export to Haskell.
-- Currently just a place-holder.
data HaskellOptions = HaskellOptions
deriving (Eq, Show)
-- | The default options for export to Haskell.
defaultHaskellOptions :: HaskellOptions
defaultHaskellOptions = HaskellOptions
-- | Export functions with specified options to a file
-- Work needed: add a declaration "import Sifflet.Data.Number".
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
-- ------------------------------------------------------------------------
-- | Shortcuts for Hs*** data constructors,
-- with lots of defaults for features I'm not using.
-- | There is no source location in the conventional sense.
srcLoc :: SrcLoc
srcLoc = SrcLoc {srcFilename = "", srcLine = 0, srcColumn = 0}
-- {srcFileName = "<unknown", srcLine = 1, srcColumn = 1}
-- | A Haskell module.
hsModule :: String -> [HsImportDecl] -> [HsDecl] -> HsModule
hsModule name importDecls decls =
HsModule srcLoc (Module name)
Nothing -- :: Maybe [HsExportSpec]
importDecls
decls
-- | A Haskell import declaration
hsImportDecl :: String -> HsImportDecl
hsImportDecl name =
HsImportDecl {importLoc = srcLoc,
importModule = Module name,
importQualified = False,
importAs = Nothing,
importSpecs = Nothing}
-- | A function binding (declaration and definition)
hsFunBind :: [HsMatch] -> HsDecl
hsFunBind matches =
HsFunBind matches
-- | Identifier, as the name of a function
hsIdent :: String -> HsName
hsIdent = HsIdent
-- | Symbol, as the name of an operator
hsSymbol :: String -> HsName
hsSymbol = HsSymbol
-- | Pattern variable, as in the argument list of a function
-- (pattern match)
hsPVar :: String -> HsPat
hsPVar = HsPVar . hsIdent
-- | A variable used in an expression (rather than in an argument list)
hsVar :: String -> HsExp
hsVar = HsVar . UnQual . hsIdent
-- | An infix operator application.
-- Probably needs parentheses added.
hsOperate :: HsExp -> HsQOp -> HsExp -> HsExp
hsOperate left qop right =
HsInfixApp left qop right
-- | A prefix function application.
-- Need to work some parentheses in here, probably.
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
-- | An infix operator
hsOp :: String -> HsQOp
-- hsOp name = HsQVarOp (UnQual (HsSymbol name))
hsOp = HsQVarOp . UnQual . hsSymbol
-- | A clause of a function binding
-- hsMatch :: ??
-- ------------------------------------------------------------------------
-- | Converting Sifflet to Haskell syntax tree
-- | Create a module from a module name and Functions.
functionsToHsModule :: String -> Functions -> HsModule
functionsToHsModule mname (Functions fs) =
hsModule mname
[hsImportDecl "Sifflet.Data.Number"] -- sifflet-Haskell library
(map functionToHsDecl fs)
-- | Create a declaration from a Function.
-- Needs work: infer and declare the type of the function.
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) ->
-- forget about type declarations for now
-- ...
HsFunBind [HsMatch srcLoc
(hsIdent fname)
(map hsPVar args)
(HsUnGuardedRhs (exprToHsExp body))
[] -- decls (??)
]
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))
-- ... and somewhere we need to work in HsParen hsExp as needed :-(
valueToHsExp :: Value -> HsExp
valueToHsExp value =
case value of
VBool b -> HsCon (UnQual (HsIdent (if b then "True" else "False")))
VChar c -> HsLit (HsChar c)
-- Should negative numbers get wrapped in parentheses??
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)
-- | Map Sifflet names to Haskell names.
-- Returns the variant HsSymbol for operator names, HsIdent for others
-- (function names, variables, etc.).
-- This might need to be extended with fixity and associativity information,
-- but that can come later when I start to deal with parentheses.
nameToHaskell :: String -> HsName
nameToHaskell name =
if elem name ["+", "-", "*", "/",
"==", "/=", "<", ">", "<=", ">=",
":"]
then HsSymbol name
else
-- some special cases will need to be inserted here,
-- for zero?, positive? negative?, at least;
-- add1, sub1 too.
HsIdent (case name of
"zero?" -> "eqZero"
"positive?" -> "gtZero"
"negative?" -> "ltZero"
_ -> name)
-- ------------------------------------------------------------------------
-- | Simplifying parentheses
-- This belongs elsewhere, since non-Haskelly things can also
-- be instances.
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
-- work needed here ...
_ -> hexp
-- Infix operator application
HsInfixApp left qop right ->
-- This *** needs work *** along the lines of Python.hs
HsInfixApp left qop right
-- Function applications:
-- (f a) b ---> f a b.
-- So why put the parentheses around f a in the first place?
HsApp (HsParen (HsApp hf ha)) hb ->
HsApp (HsApp hf ha) hb
_ -> hexp
-- | Is an expression atomic? Yes if it's a value, a boolean value
-- (i.e., the unary constructor True or False), or a literal; otherwise no.
-- Actually *any* unary constructor could be considered atomic,
-- but I'm not sure how to deal with this. Not urgent,
-- since Sifflet export uses no unary constructors but True and False.
atomic :: HsExp -> Bool
atomic hexp =
case hexp of
HsVar (UnQual (HsIdent _)) -> True -- variable
HsCon (UnQual (HsIdent _)) -> True -- unary constructors: True, False
HsLit _ -> True -- literals
HsList _ -> False -- list
HsIf _ _ _ -> False -- if expression
HsInfixApp _ _ _ -> False
HsApp _ _ -> False
-- well what are the other cases?
_ -> error ("atomic: don't know how to handle: " ++ show hexp)
-- ------------------------------------------------------------------------
-- | Facilities for testing
asModule :: [String] -> String
asModule strings = unlines ("module Test where" : strings)
test1 :: String
test1 = asModule [
-- "foo :: Int -> Int -> Int",
"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))))
-- | Test export of an example function, specified by name
testEF :: String -> IO ()
testEF = testFunBind . getExampleFunction