module Sifflet.Foreign.ToScheme (SExpr(..) , Atom(..) , Indent , Exporter , SchemeOptions(..) , defaultSchemeOptions , exprToSExpr , functionNameToSchemeName , valueToSExpr , exprToSchemeRepr , exprToSchemePretty , exprToScheme , inl , sepLines2 , functionsToSExprs , functionsToPrettyScheme , defToSExpr , exportScheme ) where import Paths_sifflet_lib -- generated by Cabal import Data.Number.Sifflet import Sifflet.Foreign.Exporter import Sifflet.Language.Expr import Sifflet.Text.Repr import Sifflet.Text.Pretty import Sifflet.Util -- Scheme S-exprs -- -------------- -- Names beginning with S are generally Scheme things, -- and of course, SExpr also stands for Symbolic Expression. data SExpr = SAtom Atom | SList [SExpr] deriving (Eq, Show) data Atom = SFloat Double | SInt Integer | SSymbol String | SString String | SChar Char | SBool Bool | SFunction Function deriving (Eq, Show) type Indent = Int data SchemeOptions = SchemeOptions { defineWithLambda :: Bool -- ^ use explicit lambda in function definitions, -- (define f (lambda (a b) ...) } deriving (Eq, Show) defaultSchemeOptions :: SchemeOptions defaultSchemeOptions = SchemeOptions {defineWithLambda = False} -- | An SExpr is "flattish" if it is an atom -- or is a list containing only atoms, -- where the empty list () is an atom. flattish :: [SExpr] -> Bool flattish sexprs = case sexprs of [] -> True x:xs -> atom x && flattish xs atom :: SExpr -> Bool atom sexpr = case sexpr of SAtom _ -> True SList [] -> True _ -> False -- Converting Sifflet Exprs to SExprs exprToSExpr :: Expr -> SExpr exprToSExpr expr = case expr of EUndefined -> SAtom (SSymbol "*sifflet-undefined*") ESymbol (Symbol str) -> SAtom (SSymbol (functionNameToSchemeName str)) EBool b -> valueToSExpr (VBool b) EChar c -> valueToSExpr (VChar c) ENumber n -> valueToSExpr (VNumber n) EString s -> valueToSExpr (VString s) EIf cond action altAction -> SList [SAtom (SSymbol "if"), exprToSExpr cond, exprToSExpr action, exprToSExpr altAction] EList exprs -> -- This case is not likely to be used, -- but if it is, the exprs might need to be evaluated, -- so we have to use list instead of quote SList (SAtom (SSymbol "list") : (map exprToSExpr exprs)) ECall fsym args -> SList (exprToSExpr (ESymbol fsym) : map exprToSExpr args) _ -> errcats ["exprToSExpr: extended expr:", show expr] -- Convert Sifflet function names to corresponding Scheme function names. -- There are a few special cases; otherwise, the names are the same. -- In particular, all of these have the same names in Sifflet -- as in standard (R5RS) Scheme: -- +, -, *, -- All of these are defined in the library sifflet.scm, -- with the prefix "sifflet-" (e.g., sifflet-div): -- div, add1, sub1, /, not-equal? -- Notes: 1+ and 1- are commonly found in Scheme implementations, -- but not standard. functionNameToSchemeName :: String -> String functionNameToSchemeName name = case name of "mod" -> "modulo" "add1" -> "sifflet-add1" "sub1" -> "sifflet-sub1" "==" -> "equal?" "/=" -> "sifflet-not-equal?" "null" -> "null?" "head" -> "car" "tail" -> "cdr" ":" -> "cons" _ -> name -- Converting Sifflet Values to SExprs valueToSExpr :: Value -> SExpr valueToSExpr value = case value of VList vs -> SList [SAtom (SSymbol "quote"), SList (map valueToSExpr vs)] _ -> SAtom (case value of VBool b -> SBool b VChar c -> SChar c VNumber (Exact i) -> SInt i VNumber (Inexact x) -> SFloat x VString s -> SString s VFun f -> SFunction f VList _ -> error ("valueToSExpr: Impossible! " ++ "We can't have VList here!") ) -- Converting Exprs to Strings of Scheme code exprToSchemeRepr :: Expr -> String exprToSchemeRepr = repr . exprToSExpr exprToSchemePretty :: Expr -> String exprToSchemePretty = pretty . exprToSExpr exprToScheme :: Expr -> String exprToScheme = exprToSchemePretty -- Converting SExprs to Strings of Scheme code instance Repr SExpr where repr sexpr = case sexpr of SAtom satom -> case satom of SFloat x -> show x SInt i -> show i SSymbol name -> name -- without "" SString str -> show str -- with "" SChar char -> show char SBool False -> "#f" SBool True -> "#t" -- SFunction: is this case really needed? -- Isn't the conversion to Scheme function names -- done in exprToSExpr? SFunction (Function mname _ _ _) -> case mname of Nothing -> error "SExpr/repr: cannot repr unnamed function" Just name -> functionNameToSchemeName name SList exprs -> "(" ++ unwords (map repr exprs) ++ ")" instance Pretty SExpr where pretty = prettyLoop 0 prettyLoop :: Indent -> SExpr -> String prettyLoop ind sexpr = case sexpr of SAtom _ -> repr sexpr SList xs -> if flattish xs then repr sexpr else case xs of [] -> error "prettyLoop: non-flattish xs cannot be []." [SAtom (SSymbol "if"), _, _, _] -> displayList2 ind (ind + 4) xs [SAtom (SSymbol "define"), _, _] -> displayList2 ind (ind + 4) xs [SAtom (SSymbol "lambda"), _, _] -> displayList2 ind (ind + 4) xs SAtom (SSymbol name) : args -> -- If it starts as a symbol, it's probably a function call, -- so put function name and first argument on one line, -- and indent everything following to first argument. case args of [] -> -- no argument displayList1 ind (ind + length name + 2) xs _ -> -- at least one argument, so at least two elements -- in the list displayList2 ind (ind + length name + 2) xs _ -> displayList1 ind (ind + 1) xs -- | Newline and indent inl :: Int -> String inl ind = "\n" ++ replicate ind ' ' -- displayList1 "shows" the first list element on the first line -- and then the rest on succeeding lines, so it must have at -- least one element displayList1 :: Indent -> Indent -> [SExpr] -> String displayList1 ind ind' xs = case xs of [] -> error "displayList1: empty list" x:xs' -> "(" ++ prettyLoop ind x ++ displayTail ind' xs' -- Like displayList1 but "shows" the first *two* list elements -- on the first line. displayList2 :: Indent -> Indent -> [SExpr] -> String displayList2 ind ind' xs = case xs of x0:x1:xs' -> "(" ++ prettyLoop ind x0 ++ " " ++ prettyLoop ind' x1 ++ displayTail ind' xs' _ -> error "displayList2: list is too short" -- displayTail "shows" the tail of an SExpr which is a list, -- it assumes the initial "(" and first element have already -- been "shown" displayTail :: Indent -> [SExpr] -> String displayTail ind xs = case xs of [] -> ")" -- to prevent final ")" from being on a line by itself x:[] -> inl ind ++ prettyLoop ind x ++ ")" x:xs' -> inl ind ++ prettyLoop ind x ++ displayTail ind xs' -- Converting Sifflet definitions to Scheme definitions. -- | Convert Sifflet Functions to Scheme SExprs functionsToSExprs :: SchemeOptions -> Functions -> [SExpr] functionsToSExprs options (Functions fs) = map (defToSExpr options . functionToDef) fs -- | Convert Sifflet Functions to pretty Scheme functionsToPrettyScheme :: SchemeOptions -> Functions -> String functionsToPrettyScheme options = sepLines2 . map pretty . functionsToSExprs options -- | Convert a FunctionDefTuple to a Scheme SExpr. -- Use the form (define (name . args) body) -- except when there are zero arguments, which becomes a -- Scheme constant rather than a function, -- use (define name expr). defToSExpr :: SchemeOptions -> FunctionDefTuple -> SExpr defToSExpr options (name, args, _atypes, _rtype, body) = let asym = SAtom . SSymbol sdefine = asym "define" sname = asym name sbody = exprToSExpr body in case args of [] -> SList [sdefine, sname, sbody] _:_ -> let argAtoms = map asym args in if defineWithLambda options then let slambda = asym "lambda" sargs = SList argAtoms slambdaArgsBody = SList [slambda, sargs, sbody] in SList [sdefine, sname, slambdaArgsBody] else let snameArgs = SList (sname : argAtoms) in SList [sdefine, snameArgs, sbody] -- | Export functions to a Scheme file. -- This, too, could use an extra "explicit lambda" argument, -- like defToSExpr. exportScheme :: SchemeOptions -> Exporter exportScheme options functions path = do { let header = ";;; File: " ++ path ++ "\n;;; Generated by the Sifflet->Scheme exporter." ; lib <- schemeLibSiffletPath >>= readFile ; writeFile path (sepLines2 [header, functionsToPrettyScheme options functions, lib]) } -- | The path to the "sifflet.scm" file. schemeLibSiffletPath :: IO FilePath -- getDataFileName is provided by Cabal schemeLibSiffletPath = getDataFileName "sifflet.scm"