{-# LANGUAGE TemplateHaskell, QuasiQuotes #-} {-| Module : Morloc.CodeGenerator.Grammars.Translator.Python3 Description : Python3 translator Copyright : (c) Zebulun Arendsee, 2020 License : GPL-3 Maintainer : zbwrnz@gmail.com Stability : experimental -} module Morloc.CodeGenerator.Grammars.Translator.Python3 ( translate , preprocess ) where import Morloc.CodeGenerator.Namespace import Morloc.CodeGenerator.Serial (isSerializable, prettySerialOne, serialAstToType) import Morloc.CodeGenerator.Grammars.Common import Morloc.Data.Doc import Morloc.Quasi import qualified Morloc.Config as MC import qualified Morloc.Monad as MM import qualified Morloc.Data.Text as MT import qualified System.FilePath as SF import qualified Data.Char as DC -- tree rewrites preprocess :: ExprM Many -> MorlocMonad (ExprM Many) preprocess = invertExprM translate :: [Source] -> [ExprM One] -> MorlocMonad MDoc translate srcs es = do -- setup library paths lib <- fmap pretty $ MM.asks MC.configLibrary -- translate sources includeDocs <- mapM translateSource (unique . catMaybes . map srcPath $ srcs) -- diagnostics liftIO . putDoc $ (vsep $ map prettyExprM es) -- translate each manifold tree, rooted on a call from nexus or another pool mDocs <- mapM translateManifold es -- make code for dispatching to manifolds let dispatch = makeDispatch es return $ makePool lib includeDocs mDocs dispatch -- create an internal variable based on a unique id letNamer :: Int -> MDoc letNamer i = "a" <> viaShow i -- create namer for manifold positional arguments bndNamer :: Int -> MDoc bndNamer i = "x" <> viaShow i -- create a name for a manifold based on a unique id manNamer :: Int -> MDoc manNamer i = "m" <> viaShow i -- FIXME: should definitely use namespaces here, not `import *` translateSource :: Path -> MorlocMonad MDoc translateSource (Path s) = do (Path lib) <- MM.asks configLibrary let moduleStr = pretty . MT.liftToText (map DC.toLower) . MT.replace "/" "." . MT.stripPrefixIfPresent "/" -- strip the leading slash (if present) . MT.stripPrefixIfPresent "./" -- no path if relative to here . MT.stripPrefixIfPresent lib -- make the path relative to the library . MT.liftToText SF.dropExtensions $ s return $ "from" <+> moduleStr <+> "import *" tupleKey :: Int -> MDoc -> MDoc tupleKey i v = [idoc|#{v}[#{pretty i}]|] selectAccessor :: NamType -> MT.Text -> MorlocMonad (MDoc -> MDoc -> MDoc) selectAccessor NamTable "dict" = return recordAccess selectAccessor NamRecord _ = return recordAccess selectAccessor NamTable _ = return objectAccess selectAccessor NamObject _ = return objectAccess recordAccess :: MDoc -> MDoc -> MDoc recordAccess record field = record <> "[" <> dquotes field <> "]" objectAccess :: MDoc -> MDoc -> MDoc objectAccess object field = object <> "." <> field serialize :: MDoc -> SerialAST One -> MorlocMonad (MDoc, [MDoc]) serialize v0 s0 = do (ms, v1) <- serialize' v0 s0 t <- serialAstToType s0 schema <- typeSchema t let v2 = "mlc_serialize" <> tupled [v1, schema] return (v2, ms) where serialize' :: MDoc -> SerialAST One -> MorlocMonad ([MDoc], MDoc) serialize' v s | isSerializable s = return ([], v) | otherwise = construct v s construct :: MDoc -> SerialAST One -> MorlocMonad ([MDoc], MDoc) construct v (SerialPack _ (One (p, s))) = do unpacker <- case typePackerReverse p of [] -> MM.throwError . SerializationError $ "No unpacker found" (src:_) -> return . pretty . srcName $ src serialize' [idoc|#{unpacker}(#{v})|] s construct v (SerialList s) = do idx <- fmap pretty $ MM.getCounter let v' = "s" <> idx (before, x) <- serialize' [idoc|i#{idx}|] s let push = [idoc|#{v'}.append(#{x})|] lst = vsep [ [idoc|#{v'} = []|] , nest 4 (vsep ([idoc|for i#{idx} in #{v}:|] : before ++ [push])) ] return ([lst], v') construct v (SerialTuple ss) = do (befores, ss') <- fmap unzip $ zipWithM (\i s -> construct (tupleKey i v) s) [0..] ss idx <- fmap pretty $ MM.getCounter let v' = "s" <> idx x = [idoc|#{v'} = #{tupled ss'}|] return (concat befores ++ [x], v'); construct v (SerialObject namType (PV _ _ constructor) _ rs) = do accessField <- selectAccessor namType constructor (befores, ss') <- fmap unzip $ mapM (\(PV _ _ k,s) -> serialize' (accessField v (pretty k)) s) rs idx <- fmap pretty $ MM.getCounter let v' = "s" <> idx entries = zipWith (\(PV _ _ key) val -> pretty key <> "=" <> val) (map fst rs) ss' decl = [idoc|#{v'} = dict#{tupled (entries)};|] return (concat befores ++ [decl], v'); construct _ s = MM.throwError . SerializationError . render $ "construct: " <> prettySerialOne s deserialize :: MDoc -> SerialAST One -> MorlocMonad (MDoc, [MDoc]) deserialize v0 s0 | isSerializable s0 = do t <- serialAstToType s0 schema <- typeSchema t let deserializing = [idoc|mlc_deserialize(#{v0}, #{schema});|] return (deserializing, []) | otherwise = do idx <- fmap pretty $ MM.getCounter t <- serialAstToType s0 schema <- typeSchema t let rawvar = "s" <> idx deserializing = [idoc|#{rawvar} = mlc_deserialize(#{v0}, #{schema});|] (x, befores) <- check rawvar s0 return (x, deserializing:befores) where check :: MDoc -> SerialAST One -> MorlocMonad (MDoc, [MDoc]) check v s | isSerializable s = return (v, []) | otherwise = construct v s construct :: MDoc -> SerialAST One -> MorlocMonad (MDoc, [MDoc]) construct v (SerialPack _ (One (p, s'))) = do packer <- case typePackerForward p of [] -> MM.throwError . SerializationError $ "No packer found" (x:_) -> return . pretty . srcName $ x (x, before) <- check v s' let deserialized = [idoc|#{packer}(#{x})|] return (deserialized, before) construct v (SerialList s) = do idx <- fmap pretty $ MM.getCounter let v' = "s" <> idx (x, before) <- check [idoc|i#{idx}|] s let push = [idoc|#{v'}.append(#{x});|] lst = vsep [ [idoc|#{v'} = [];|] , nest 4 (vsep ([idoc|for i#{idx} in #{v}:|] : before ++ [push])) ] return (v', [lst]) construct v (SerialTuple ss) = do (ss', befores) <- fmap unzip $ zipWithM (\i s -> check (tupleKey i v) s) [0..] ss idx <- fmap pretty $ MM.getCounter let v' = "s" <> idx x = [idoc|#{v'} = #{tupled ss'};|] return (v', concat befores ++ [x]); construct v (SerialObject namType (PV _ _ constructor) _ rs) = do idx <- fmap pretty $ MM.getCounter accessField <- selectAccessor namType constructor (ss', befores) <- fmap unzip $ mapM (\(PV _ _ k,s) -> check (accessField v (pretty k)) s) rs let v' = "s" <> idx entries = zipWith (\(PV _ _ key) val -> pretty key <> "=" <> val) (map fst rs) ss' decl = [idoc|#{v'} = #{pretty constructor}#{tupled entries};|] return (v', concat befores ++ [decl]); construct _ s = MM.throwError . SerializationError . render $ "deserializeDescend: " <> prettySerialOne s -- break a call tree into manifolds translateManifold :: ExprM One -> MorlocMonad MDoc translateManifold m0@(ManifoldM _ args0 _) = do MM.startCounter (vsep . punctuate line . (\(x,_,_)->x)) <$> f args0 m0 where f :: [Argument] -> ExprM One -> MorlocMonad ( [MDoc] -- completely generated manifolds , MDoc -- a tag for the returned expression , [MDoc] -- lines to precede the returned expression ) f pargs m@(ManifoldM (metaId->i) args e) = do (ms', e', rs') <- f args e let mname = manNamer i def = "def" <+> mname <> tupled (map makeArgument args) <> ":" mdoc = nest 4 (vsep $ def:rs' ++ [e']) call <- return $ case (splitArgs args pargs, nargsTypeM (typeOfExprM m)) of ((rs, []), _) -> mname <> tupled (map makeArgument rs) -- covers #1, #2 and #4 (([], _ ), _) -> mname ((rs, vs), _) -> makeLambda vs (mname <> tupled (map makeArgument (rs ++ vs))) -- covers #5 return (mdoc : ms', call, []) f _ (PoolCallM _ _ cmds args) = do let call = "_morloc_foreign_call(" <> list(map dquotes cmds ++ map makeArgument args) <> ")" return ([], call, []) f _ (ForeignInterfaceM _ _) = MM.throwError . CallTheMonkeys $ "Foreign interfaces should have been resolved before passed to the translators" f args (LetM i e1 e2) = do (ms1', e1', rs1) <- (f args) e1 (ms2', e2', rs2) <- (f args) e2 let rs = rs1 ++ [ letNamer i <+> "=" <+> e1' ] ++ rs2 return (ms1' ++ ms2', e2', rs) f args (AppM (SrcM _ src) xs) = do (mss', xs', rss') <- mapM (f args) xs |>> unzip3 return (concat mss', pretty (srcName src) <> tupled xs', concat rss') f _ (AppM _ _) = error "Can only apply functions" f _ (SrcM _ src) = return ([], pretty (srcName src), []) f _ (LamM _ _) = undefined -- FIXME: this is defined in R f _ (BndVarM _ i) = return ([], bndNamer i, []) f _ (LetVarM _ i) = return ([], letNamer i, []) f args (AccM e k) = do (ms, e', ps) <- f args e x <- case typeOfTypeM (typeOfExprM e) of (Just (NamP r (PV _ _ v) _ _)) -> selectAccessor r v <*> pure e' <*> pure (pretty k) _ -> MM.throwError . CallTheMonkeys $ "Bad record access" return (ms, x, ps) f args (ListM _ es) = do (mss', es', rss') <- mapM (f args) es |>> unzip3 return (concat mss', list es', concat rss') f args (TupleM _ es) = do (mss', es', rss') <- mapM (f args) es |>> unzip3 return (concat mss', tupled es', concat rss') f args (RecordM _ entries) = do (mss', es', rss') <- mapM (f args . snd) entries |>> unzip3 let entries' = zipWith (\k v -> pretty k <> "=" <> v) (map fst entries) es' return (concat mss', "OrderedDict" <> tupled entries', concat rss') f _ (LogM _ x) = return ([], if x then "True" else "False", []) f _ (NumM _ x) = return ([], viaShow x, []) f _ (StrM _ x) = return ([], dquotes $ pretty x, []) f _ (NullM _) = return ([], "None", []) f args (SerializeM s e) = do (ms, e', rs1) <- f args e (serialized, rs2) <- serialize e' s return (ms, serialized, rs1 ++ rs2) f args (DeserializeM s e) = do (ms, e', rs1) <- f args e (deserialized, rs2) <- deserialize e' s return (ms, deserialized, rs1 ++ rs2) f args (ReturnM e) = do (ms, e', rs) <- f args e return (ms, "return(" <> e' <> ")", rs) translateManifold _ = error "Every ExprM object must start with a Manifold term" makeLambda :: [Argument] -> MDoc -> MDoc makeLambda args body = "lambda" <+> hsep (punctuate "," (map makeArgument args)) <> ":" <+> body makeArgument :: Argument -> MDoc makeArgument (SerialArgument v _) = bndNamer v makeArgument (NativeArgument v _) = bndNamer v makeArgument (PassThroughArgument v) = bndNamer v makeDispatch :: [ExprM One] -> MDoc makeDispatch ms = align . vsep $ [ align . vsep $ ["dispatch = {", indent 4 (vsep $ map entry ms), "}"] , "f = dispatch[cmdID]" ] where entry :: ExprM One -> MDoc entry (ManifoldM (metaId->i) _ _) = pretty i <> ":" <+> manNamer i <> "," entry _ = error "Expected ManifoldM" typeSchema :: TypeP -> MorlocMonad MDoc typeSchema t0 = f <$> type2jsontype t0 where f :: JsonType -> MDoc f (VarJ v) = lst [var v, "None"] f (ArrJ v ts) = lst [var v, lst (map f ts)] f (NamJ "dict" es) = lst [dquotes "dict", dict (map entry es)] f (NamJ "record" es) = lst [dquotes "record", dict (map entry es)] f (NamJ v es) = lst [pretty v, dict (map entry es)] entry :: (MT.Text, JsonType) -> MDoc entry (v, t) = pretty v <> "=" <> f t dict :: [MDoc] -> MDoc dict xs = "OrderedDict" <> lst xs lst :: [MDoc] -> MDoc lst xs = encloseSep "(" ")" "," xs var :: MT.Text -> MDoc var v = dquotes (pretty v) makePool :: MDoc -> [MDoc] -> [MDoc] -> MDoc -> MDoc makePool lib includeDocs manifolds dispatch = [idoc|#!/usr/bin/env python import sys import subprocess import json from pymorlocinternals import (mlc_serialize, mlc_deserialize) from collections import OrderedDict sys.path = ["#{lib}"] + sys.path #{vsep includeDocs} def _morloc_foreign_call(args): try: sysObj = subprocess.run( args, stdout=subprocess.PIPE, check=True ) except subprocess.CalledProcessError as e: sys.exit(str(e)) return(sysObj.stdout.decode("ascii")) #{vsep manifolds} if __name__ == '__main__': try: cmdID = int(sys.argv[1]) except IndexError: sys.exit("Internal error in {}: no manifold id found".format(sys.argv[0])) except ValueError: sys.exit("Internal error in {}: expected integer manifold id".format(sys.argv[0])) try: #{dispatch} except KeyError: sys.exit("Internal error in {}: no manifold found with id={}".format(sys.argv[0], cmdID)) result = f(*sys.argv[2:]) print(result) |]