|
1 | 1 | {-# LANGUAGE FlexibleContexts #-}
|
| 2 | +{-# LANGUAGE OverloadedStrings #-} |
2 | 3 |
|
3 |
| -{- |
4 |
| -The below module is most definitely a Work In Progress. Yay! |
5 |
| -
|
6 |
| -Macros edit the parse tree, which the compiler converts to an AST which is transpiled into Eta. |
7 |
| -1. When macros are expanded, the code forms passed in as its arguments are represented as a list of parse expressions (lists, symbols, literals, etc.). |
8 |
| - The output of the macro programs is rendered as a list and inserted back into the file. |
9 |
| -2. Step 1 is repeated until no more macros are available for expansion. |
10 |
| -3. The whole file is then tranpsiled into Eta as usual. |
11 |
| -
|
12 |
| -MACRO EXPANSION STRATEGY |
13 |
| -This is just a very, very rough hypothesis. It's probably broken, and I have no idea how it would be adapted to work with macros defined in multiple files or mutually-recursive macros (or even self-recursive macros). You have been warned. |
14 |
| -I'm assuming macros should be expanded inner-to-outer (since Lisps let allow for using macros inside macros, e.g. `with-gensyms` and its ilk). |
15 |
| -1. Read the file top-to-bottom, collecting macro definitions. |
16 |
| - For each macro definition, check if already-defined macros are being used inside the definition. If so, expand their usages and repeat until there are no macro usages left in each definition. |
17 |
| -2. Expand the deepest macro usages inside each non-`defmacro` expression. Repeat until there are no macro usages left. |
18 |
| --} |
19 | 4 | module Lihsp.Macros where
|
20 | 5 |
|
21 |
| -import Control.Lens.Operators ((&), (.~)) |
22 |
| -import Control.Monad.Except (MonadError, throwError) |
| 6 | +import Control.Lens.Operators ((.~)) |
| 7 | +import Control.Monad.Except (MonadError) |
23 | 8 | import Control.Monad.IO.Class (MonadIO, liftIO)
|
24 | 9 |
|
| 10 | +import Data.Function ((&)) |
25 | 11 | import Data.Semigroup ((<>))
|
| 12 | +import Data.Text (pack, replace, unpack) |
26 | 13 |
|
27 |
| -import Lihsp.AST (MacroDefinition, name) |
| 14 | +import Lihsp.AST (Expression, MacroDefinition, name, traverseExpression) |
28 | 15 | import Lihsp.Error (Error(MacroError))
|
| 16 | +import Lihsp.Eval (evalSource) |
29 | 17 | import qualified Lihsp.Parse as Parse (Expression)
|
30 | 18 | import Lihsp.Parse (parseProgram)
|
31 |
| - |
32 |
| -import Paths_lihsp (getDataFileName) |
33 |
| - |
34 |
| -import System.Directory (getTemporaryDirectory, removeFile) |
35 |
| -import System.Exit (ExitCode(ExitFailure, ExitSuccess)) |
36 |
| -import System.IO (Handle, hClose, hPutStr, openTempFile) |
37 |
| -import System.Process (readProcessWithExitCode) |
38 |
| - |
39 |
| -withTempFile :: (MonadIO m) => FilePath -> (FilePath -> Handle -> m a) -> m a |
40 |
| -withTempFile template f = do |
41 |
| - temporaryDirectory <- liftIO getTemporaryDirectory |
42 |
| - (fileName, handle) <- liftIO $ openTempFile temporaryDirectory template |
43 |
| - result <- f fileName handle |
44 |
| - liftIO $ hClose handle |
45 |
| - liftIO $ removeFile fileName |
46 |
| - return result |
47 |
| - |
48 |
| -generateMacroProgram :: MacroDefinition -> IO String |
49 |
| -generateMacroProgram macroDefinition = do |
50 |
| - let macroSource = macroDefinition & name .~ "aUTOGENERATED_MACRO_FUNCTION" |
51 |
| - footer <- getDataFileName "resources/macros/Footer.hs" >>= readFile |
52 |
| - header <- getDataFileName "resources/macros/Header.hs" >>= readFile |
53 |
| - return $ header <> show macroSource <> footer |
54 |
| - |
55 |
| -execInterpreter :: (MonadError Error m, MonadIO m) => FilePath -> m String |
56 |
| -execInterpreter fileName = do |
57 |
| - (code, stdout, stderr) <- |
58 |
| - liftIO $ readProcessWithExitCode "runhaskell" [fileName] "" |
59 |
| - case code of |
60 |
| - ExitSuccess -> return stdout |
61 |
| - ExitFailure _ -> throwError $ MacroError stderr |
62 |
| - |
63 |
| -runMacro :: |
64 |
| - (MonadError Error m, MonadIO m) => MacroDefinition -> m [Parse.Expression] |
65 |
| -runMacro macroSource = |
66 |
| - withTempFile "TempMacro.hs" $ \fileName handle -> do |
67 |
| - macroProgram <- liftIO $ generateMacroProgram macroSource |
68 |
| - liftIO $ hPutStr handle macroProgram |
69 |
| - result <- execInterpreter fileName |
70 |
| - parseProgram result |
| 19 | +import Lihsp.Utils.Resources (readDataFile) |
| 20 | + |
| 21 | +generateMacroProgram :: |
| 22 | + (MonadIO m) => MacroDefinition -> [Expression] -> m String |
| 23 | +generateMacroProgram macroDefinition applicationArguments = do |
| 24 | + fileHeader <- liftIO $ readDataFile "resources/macros/Header.hs" |
| 25 | + fileFooter <- liftIO getFileFooter |
| 26 | + return $ fileHeader <> show fileContents <> fileFooter |
| 27 | + where |
| 28 | + getFileFooter = |
| 29 | + let insertApplicationArguments = |
| 30 | + let applicationArgumentsPlaceholder = "%%%ARGUMENTS%%%" |
| 31 | + in unpack . |
| 32 | + replace |
| 33 | + applicationArgumentsPlaceholder |
| 34 | + (pack $ show applicationArguments) . |
| 35 | + pack |
| 36 | + insertDefinitionBody = |
| 37 | + let definitionBodyPlaceholder = "%%%MACRO_DEFINITION%%%" |
| 38 | + in unpack . |
| 39 | + replace definitionBodyPlaceholder (pack $ show macroDefinition) . |
| 40 | + pack |
| 41 | + in insertApplicationArguments . insertDefinitionBody <$> |
| 42 | + readDataFile "resources/macros/Footer.hs" |
| 43 | + |
| 44 | +expandMacroCall :: |
| 45 | + (MonadError Error m, MonadIO m) |
| 46 | + => MacroDefinition |
| 47 | + -> [Expression] |
| 48 | + -> m [Parse.Expression] |
| 49 | +expandMacroCall macroDefinition args = |
| 50 | + generateMacroProgram macroDefinition args >>= evalSource >>= parseProgram |
| 51 | + |
| 52 | +expandMacros :: Expression -> IO Expression |
| 53 | +expandMacros = |
| 54 | + traverseExpression $ \expression -> |
| 55 | + case macroApplication expression of |
| 56 | + Just (macroName, arguments) -> expandMacroCall |
| 57 | + Nothing -> return expression |
0 commit comments