@@ -10,7 +10,7 @@ import Data.Aeson
10import Data.Aeson.Types
10import Data.Aeson.Types
11import Data.Char
11import Data.Char
12import Data.FileEmbed (embedFile)
12import Data.FileEmbed (embedFile)
13−import Data.List (delete, isPrefixOf, partition)
13+import Data.List (delete, intercalate, isPrefixOf, nub, partition, (\\))
14import Data.Maybe
14import Data.Maybe
15import Data.Monoid ((<>))
15import Data.Monoid ((<>))
16import Data.Version
16import Data.Version
@@ -19,8 +19,10 @@ import Control.Monad.Supply.Class
19import Text.Printf
19import Text.Printf
21import System.Environment
21import System.Environment
22−import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getModificationTime)
22+import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getCurrentDirectory, getModificationTime)
23import System.FilePath ((</>), joinPath, splitDirectories, takeDirectory)
23import System.FilePath ((</>), joinPath, splitDirectories, takeDirectory)
24+import System.FilePath.Find
25import Data.Text (Text)
27import Data.Text (Text)
26import qualified Data.Text as T
28import qualified Data.Text as T
@@ -29,6 +31,8 @@ import qualified Data.Text.Lazy as L
29import qualified Data.Text.Lazy.Encoding as L
31import qualified Data.Text.Lazy.Encoding as L
30import qualified Data.ByteString as B
32import qualified Data.ByteString as B
34+import Development.GitRev
32import Language.PureScript.AST.Literals
36import Language.PureScript.AST.Literals
33import Language.PureScript.CoreFn
37import Language.PureScript.CoreFn
34import Language.PureScript.CoreFn.FromJSON
38import Language.PureScript.CoreFn.FromJSON
@@ -39,6 +43,8 @@ import CodeGen.IL.Printer
40import Tests
44import Tests
46+data Command = Build | Run (Maybe String)
42parseJson :: Text -> Value
48parseJson :: Text -> Value
43parseJson text
49parseJson text
44 | Just fileJson <- decode . L.encodeUtf8 $ L.fromStrict text = fileJson
50 | Just fileJson <- decode . L.encodeUtf8 $ L.fromStrict text = fileJson
@@ -48,40 +54,98 @@ jsonToModule :: Value -> Module Ann
48jsonToModule value =
54jsonToModule value =
49 case parse moduleFromJSON value of
55 case parse moduleFromJSON value of
50 Success (_, r) -> r
56 Success (_, r) -> r
51− _ -> error "failed"
57+ _ -> error "Failed parsing corefn. Psgo currently supports purescript <= 0.13.8"
59+splitArgs :: [String] -> ([String], Maybe String, [String])
61+ let (opts, files) = partition (isPrefixOf "--") args
62+ opts' = (map . map) toLower opts
63+ (moduleName, files') =
65+ then (Nothing, files)
67+ if "--run" `elem` opts' && length (files) >= 1
69+ let file0 = head files
72+ "Main.main" -> (Just "Main", files')
73+ "Test.Main.main" -> (Just "Test.Main", files')
74+ _ -> (Nothing, files)
75+ else (Nothing, files)
76+ in (opts, moduleName, files')
53main :: IO ()
78main :: IO ()
55 args <- getArgs
80 args <- getArgs
56− if null args
81+ -- let (opts, files) = partition (isPrefixOf "--") args
57− then return ()
82+ let (opts, moduleName, files) = splitArgs args
83+ opts' = (map . map) toLower opts
84+ if "--tests" `elem` opts'
59− let (opts, files) = partition (isPrefixOf "--") args
60− opts' = (map . map) toLower opts
61− if "--tests" `elem` opts'
87+ if "--help" `elem` opts' || "--version" `elem` opts'
62− then runTests
88+ then do
64− when ("--makefile" `elem` opts') $
89+ when ("--help" `elem` opts') $ do
65− B.writeFile "Makefile" $(embedFile "support/Makefile")
66− when ("--help" `elem` opts') $
67 putStrLn help
90 putStrLn help
91+ when ("--version" `elem` opts') $ do
92+ let branch = $(gitBranch)
93+ details | branch == "golang" = "master, commit " ++ $(gitHash)
95+ putStrLn $ details ++ if $(gitDirty) then " (DIRTY)" else ""
68− when (not $ null files) $ do
97+ if null files
99+ currentDir <- getCurrentDirectory
100+ processFiles opts' [currentDir]
102+ processFiles opts' files
103+ if "--run" `elem` opts
104+ then runBuildTool $ Run moduleName
105+ else when ("--no-build" `notElem` opts) $
109+ processFiles :: [String] -> [FilePath] -> IO ()
110+ processFiles opts [file] = do
111+ isDir <- doesDirectoryExist file
113+ files <- find always (fileName ==? corefn) file
115+ then errorNoCorefnFiles
116+ else generateFiles opts files
117+ else generateFiles opts [file]
118+ processFiles opts files = do
119+ generateFiles opts files
120+ basePath :: [FilePath] -> FilePath
69− let filepath = takeDirectory (head files)
122+ let filepath = takeDirectory (head files) in
70− baseOutpath = joinPath $ (init $ splitDirectories filepath) ++ [outdir]
123+ joinPath $ (init $ splitDirectories filepath)
71− writeRuntimeFiles baseOutpath
124+ generateFiles :: [String] -> [FilePath] -> IO ()
125+ generateFiles opts files = do
126+ let baseOutpath = basePath files
127+ writeSupportFiles baseOutpath
72− Par.mapM (generateCode opts' baseOutpath) files
128+ Par.mapM (generateCode opts baseOutpath) files
73− return ()
129+ return ()
130+ runBuildTool :: Command -> IO ()
131+ runBuildTool cmd = do
132+ project <- projectEnv
135+ callProcess "go" ["build", T.unpack project </> modPrefix' </> "Main"]
136+ Run moduleName -> do
137+ callProcess "go" ["run", T.unpack project </> modPrefix' </> fromMaybe "Main" moduleName]
74− return ()
138+ return ()
76generateCode :: [String] -> FilePath -> FilePath -> IO ()
140generateCode :: [String] -> FilePath -> FilePath -> IO ()
77generateCode opts baseOutpath jsonFile = do
141generateCode opts baseOutpath jsonFile = do
78 jsonModTime <- getModificationTime jsonFile
142 jsonModTime <- getModificationTime jsonFile
79 let filepath = takeDirectory jsonFile
143 let filepath = takeDirectory jsonFile
80 dirparts = splitDirectories $ filepath
144 dirparts = splitDirectories $ filepath
81− mname = (\c -> if c == '.' then '_' else c) <$> last dirparts
145+ mname = last dirparts
82 basedir = joinPath $ init dirparts
146 basedir = joinPath $ init dirparts
83− mname' = moduleNameToIL' $ T.pack mname
147+ mname' = T.pack mname
84− possibleFileName = basedir </> outdir </> (T.unpack mname') </> implFileName mname'
148+ possibleFileName = basedir </> (T.unpack mname') </> implFileName mname'
85 exists <- doesFileExist possibleFileName
149 exists <- doesFileExist possibleFileName
86 if exists
150 if exists
@@ -93,9 +157,10 @@ generateCode opts baseOutpath jsonFile = do
93transpile :: [String] -> FilePath -> FilePath -> IO ()
157transpile :: [String] -> FilePath -> FilePath -> IO ()
94transpile opts baseOutpath jsonFile = do
158transpile opts baseOutpath jsonFile = do
95 jsonText <- T.decodeUtf8 <$> B.readFile jsonFile
159 jsonText <- T.decodeUtf8 <$> B.readFile jsonFile
160+ project <- projectEnv
96 let module' = jsonToModule $ parseJson jsonText
161 let module' = jsonToModule $ parseJson jsonText
97− ((_, foreigns, asts, implHeader, implFooter), _) <- runSupplyT 5 (moduleToIL module' Nothing)
162+ ((_, foreigns, asts, implHeader, implFooter), _) <- runSupplyT 5 (moduleToIL module' project)
98− let mn = moduleNameToIL $ moduleName module'
163+ let mn = moduleNameToIL' $ moduleName module'
99 implementation = prettyPrintIL asts
164 implementation = prettyPrintIL asts
100 outpath = joinPath [baseOutpath, T.unpack mn]
165 outpath = joinPath [baseOutpath, T.unpack mn]
101 implPath = outpath </> implFileName mn
166 implPath = outpath </> implFileName mn
@@ -103,28 +168,83 @@ transpile opts baseOutpath jsonFile = do
103 putStrLn implPath
168 putStrLn implPath
104 B.writeFile implPath $ T.encodeUtf8 (implHeader <> implementation <> implFooter)
169 B.writeFile implPath $ T.encodeUtf8 (implHeader <> implementation <> implFooter)
106−writeRuntimeFiles :: FilePath -> IO ()
171+writeSupportFiles :: FilePath -> IO ()
107−writeRuntimeFiles baseOutpath = do
172+writeSupportFiles baseOutpath = do
108− createDirectoryIfMissing True $ baseOutpath </> "purescript"
173+ currentDir <- getCurrentDirectory
109− let runtimeSource = baseOutpath </> "purescript" </> "purescript.go"
174+ let outputdir = T.pack $ baseOutpath \\ currentDir
110− runtimeExists <- doesFileExist runtimeSource
175+ ffiOutpath = currentDir </> subdir
111− when (not runtimeExists) $ do
176+ createDirectoryIfMissing True baseOutpath
112− B.writeFile runtimeSource $(embedFile "runtime/purescript.go")
177+ createDirectoryIfMissing True ffiOutpath
113−
178+ writeModuleFile outputdir currentDir $(embedFile "support/go.mod.working")
114−outdir :: FilePath
179+ writeModuleFile outputdir baseOutpath $(embedFile "support/go.mod.output")
115−outdir = "src"
180+ writeModuleFile outputdir ffiOutpath $(embedFile "support/go.mod.ffi-loader")
181+ writeLoaderFile ffiOutpath $(embedFile "support/ffi-loader.go")
183+ writeModuleFile :: Text -> FilePath -> B.ByteString -> IO ()
184+ writeModuleFile outputdir path modText = do
185+ let goModSource = path </> "go.mod"
186+ goModSourceExists <- doesFileExist goModSource
187+ when (not goModSourceExists) $ do
188+ project <- projectEnv
189+ let project' = T.unpack project
190+ outputdir' = tail $ T.unpack outputdir
191+ modText' = T.replace "$PROJECT" project $ T.decodeUtf8 modText
192+ replaceLoader = project' </> ffiLoader' <> "=." </> subdir
193+ replaceOutput = project' </> modPrefix' <> "=." </> outputdir'
194+ B.writeFile goModSource $ T.encodeUtf8 modText'
195+ callProcess "go" ["mod", "edit", "-replace", replaceLoader]
196+ callProcess "go" ["mod", "edit", "-replace", replaceOutput]
197+ callProcess "go" ["clean", "-modcache"]
198+ writeLoaderFile :: FilePath -> B.ByteString -> IO ()
199+ writeLoaderFile ffiOutpath loaderText = do
200+ let loaderSource = ffiOutpath </> "ffi_loader.go"
201+ loaderSourceExists <- doesFileExist loaderSource
202+ when (not loaderSourceExists) $ do
203+ B.writeFile loaderSource loaderText
117implFileName :: Text -> FilePath
205implFileName :: Text -> FilePath
118−implFileName mn = T.unpack $ mn <> ".go"
206+implFileName mn = ((\c -> if c == '.' then '_' else c) <$> T.unpack mn) <> ".go"
208+projectEnv :: IO Text
210+ T.pack . fromMaybe defaultProject <$> lookupEnv goproject
120help :: String
212help :: String
121help = "Usage: psgo OPTIONS COREFN-FILES\n\
213help = "Usage: psgo OPTIONS COREFN-FILES\n\
122− \ PureScript-to-go compiler\n\n\
214+ \ PureScript to native (via go) compiler\n\n\
123 \Available options:\n\
215 \Available options:\n\
124 \ --help Show this help text\n\n\
216 \ --help Show this help text\n\n\
125− \ --makefile Generate a GNU Makefile which can be used for compiling\n\
217+ \ --version Show the version number\n\n\
126− \ a PureScript program and libraries to a native binary via\n\
218+ \ --run Run the generated go code directly, without building an\n\
127− \ purs corefn output and C++\n\n\
219+ \ executable\n\
220+ \ --no-build Generate go source files, but do not build an executable\n\
128 \ --tests Run test cases (under construction)\n\n\
221 \ --tests Run test cases (under construction)\n\n\
129 \See also:\n\
222 \See also:\n\
130 \ purs compile --help\n"
223 \ purs compile --help\n"
226+corefn = "corefn.json"
232+goproject = "GOPROJECT"
234+defaultProject :: String
235+defaultProject = "project.localhost"
238+modPrefix' = T.unpack modPrefix
241+ffiLoader' = T.unpack ffiLoader
244+subdir = T.unpack modLabel
246+errorNoCorefnFiles :: IO ()
247+errorNoCorefnFiles = do
248+ ioError . userError $ "no compiled purescript '" <> corefn <> "' files found –\n" <>
249+ " make sure to use the '--codegen corefn' option with your purs\n" <>
250+ " project build tool"