Skip to content

Commit e7b7a48

Browse files
author
Joshua Grosso
committed
Continue macro expansion
1 parent 91ab515 commit e7b7a48

File tree

13 files changed

+205
-114
lines changed

13 files changed

+205
-114
lines changed

lihsp.cabal

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
-- This file has been generated from package.yaml by hpack version 0.17.0.
1+
-- This file has been generated from package.yaml by hpack version 0.20.0.
22
--
33
-- see: https://github.com/sol/hpack
4+
--
5+
-- hash: 596fc166e921fb1d089e6c0008a31999fb69a79ec742eb8f8582f5c30b048f6a
46

57
name: lihsp
68
version: 0.1.0.0
@@ -40,15 +42,21 @@ library
4042
, parsec
4143
, process
4244
, split
45+
, text
4346
exposed-modules:
4447
Lihsp.AST
4548
Lihsp.Error
49+
Lihsp.Eval
4650
Lihsp.Macros
4751
Lihsp.Normalize
4852
Lihsp.Parse
4953
Lihsp.Parse.AST
5054
Lihsp.Transpile
51-
Lihsp.Utils
55+
Lihsp.Utils.Display
56+
Lihsp.Utils.Recursion
57+
Lihsp.Utils.Resources
58+
other-modules:
59+
Paths_lihsp
5260
default-language: Haskell2010
5361

5462
executable lihsp-exe
@@ -59,16 +67,19 @@ executable lihsp-exe
5967
build-depends:
6068
base >=4.7 && <5
6169
, lihsp
70+
other-modules:
71+
Paths_lihsp
6272
default-language: Haskell2010
6373

6474
test-suite lihsp-test
6575
type: exitcode-stdio-1.0
66-
main-is: Spec.hs
76+
main-is: Lihsp/Spec.hs
6777
hs-source-dirs:
6878
test
6979
ghc-options: -Wall -Werror -Wmissing-import-lists -Wincomplete-record-updates -Wincomplete-uni-patterns -threaded -rtsopts -with-rtsopts=-N
7080
build-depends:
7181
base >=4.7 && <5
82+
, hspec
7283
other-modules:
73-
Lihsp.Spec
84+
Paths_lihsp
7485
default-language: Haskell2010

package.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ library:
2929
- parsec
3030
- process
3131
- split
32+
- text
3233
executables:
3334
lihsp-exe:
3435
main: Main.hs
@@ -41,9 +42,11 @@ executables:
4142
- lihsp
4243
tests:
4344
lihsp-test:
44-
main: Spec.hs
45+
main: Lihsp/Spec.hs
4546
source-dirs: test
4647
ghc-options:
4748
- -threaded
4849
- -rtsopts
4950
- -with-rtsopts=-N
51+
dependencies:
52+
- hspec

resources/macros/Footer.hs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
%%%MACRO_DEFINITION%%%
2+
13
main :: IO ()
2-
main = getArgs >>= aUTOGENERATED_MACRO_FUNCTION >>= print
4+
main = aUTOGENERATED_MACRO_FUNCTION %%%%ARGUMENTS%%% >>= print

src/Lihsp/AST.hs

+29-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ import Control.Lens.TH (makeFieldsNoPrefix)
1313

1414
import Data.Semigroup ((<>))
1515

16-
import Lihsp.Utils
17-
(Bracket(Parentheses, SingleQuotes, SquareBrackets),
18-
Delimiter(Commas, Newlines, Pipes, Spaces), delimit, isOperator,
19-
renderBlock, renderPragma, surround)
16+
import Lihsp.Utils.Display
17+
( Bracket(Parentheses, SingleQuotes, SquareBrackets)
18+
, Delimiter(Commas, Newlines, Pipes, Spaces)
19+
, delimit
20+
, isOperator
21+
, renderBlock
22+
, renderPragma
23+
, surround
24+
)
2025

2126
type Identifier = String
2227

@@ -262,3 +267,23 @@ instance Show TypeSynonym where
262267
show typeSynonym =
263268
"type " <> show (typeSynonym ^. alias) <> " = " <>
264269
show (typeSynonym ^. definition)
270+
271+
-- TODO Either replace with `MonoTraversable` or make `Expression` polymorphic
272+
-- (in which case, use `Traversable`, recursion schemes, etc.). The latter
273+
-- would be preferable.
274+
-- TODO Remove the dependency on `Monad` (since the standard `traverse` only
275+
-- requires an `Applicative` instance).
276+
traverseExpression ::
277+
(Monad m) => (Expression -> m Expression) -> Expression -> m Expression
278+
traverseExpression f x =
279+
case x of
280+
EFunctionApplication functionApplication ->
281+
let newArguments =
282+
traverse (traverseExpression f) (functionApplication ^. arguments)
283+
newFunction = traverseExpression f (functionApplication ^. function)
284+
in f =<<
285+
(EFunctionApplication <$>
286+
(FunctionApplication <$> newFunction <*> newArguments))
287+
EIdentifier _ -> f x
288+
ELetBlock _ -> f x
289+
ELiteral _ -> f x

src/Lihsp/Eval.hs

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
3+
module Lihsp.Eval where
4+
5+
import Control.Monad.Except (MonadError, throwError)
6+
import Control.Monad.IO.Class (MonadIO, liftIO)
7+
8+
import Lihsp.Error (Error(MacroError))
9+
10+
import System.Directory (getTemporaryDirectory, removeFile)
11+
import System.Exit (ExitCode(ExitFailure, ExitSuccess))
12+
import System.IO (Handle, hClose, hPutStr, openTempFile)
13+
import System.Process (readProcessWithExitCode)
14+
15+
withTempFile :: (MonadIO m) => FilePath -> (FilePath -> Handle -> m a) -> m a
16+
withTempFile nameTemplate f = do
17+
temporaryDirectory <- liftIO getTemporaryDirectory
18+
(fileName, handle) <- liftIO $ openTempFile temporaryDirectory nameTemplate
19+
result <- f fileName handle
20+
liftIO $ hClose handle
21+
liftIO $ removeFile fileName
22+
return result
23+
24+
execInterpreter :: (MonadError Error m, MonadIO m) => FilePath -> m String
25+
execInterpreter fileName = do
26+
(code, stdout, stderr) <-
27+
liftIO $ readProcessWithExitCode "runhaskell" [fileName] ""
28+
case code of
29+
ExitSuccess -> return stdout
30+
ExitFailure _ -> throwError $ MacroError stderr
31+
32+
evalSource :: (MonadError Error m, MonadIO m) => String -> m String
33+
evalSource source =
34+
withTempFile "TempMaro.hs" $ \fileName handle -> do
35+
liftIO $ hPutStr handle source
36+
execInterpreter fileName

src/Lihsp/Macros.hs

+46-59
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,57 @@
11
{-# LANGUAGE FlexibleContexts #-}
2+
{-# LANGUAGE OverloadedStrings #-}
23

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-
-}
194
module Lihsp.Macros where
205

21-
import Control.Lens.Operators ((&), (.~))
22-
import Control.Monad.Except (MonadError, throwError)
6+
import Control.Lens.Operators ((.~))
7+
import Control.Monad.Except (MonadError)
238
import Control.Monad.IO.Class (MonadIO, liftIO)
249

10+
import Data.Function ((&))
2511
import Data.Semigroup ((<>))
12+
import Data.Text (pack, replace, unpack)
2613

27-
import Lihsp.AST (MacroDefinition, name)
14+
import Lihsp.AST (Expression, MacroDefinition, name, traverseExpression)
2815
import Lihsp.Error (Error(MacroError))
16+
import Lihsp.Eval (evalSource)
2917
import qualified Lihsp.Parse as Parse (Expression)
3018
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

src/Lihsp/Normalize.hs

+41-17
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
1+
{-# LANGUAGE ApplicativeDo #-}
12
{-# LANGUAGE FlexibleContexts #-}
23
{-# LANGUAGE LambdaCase #-}
34
{-# LANGUAGE TupleSections #-}
45

56
module Lihsp.Normalize where
67

8+
import Control.Lens.Operators ((%~), (^.))
79
import Control.Monad ((>=>))
810
import Control.Monad.Except (MonadError, throwError)
911

12+
import Data.Function ((&))
13+
1014
import Lihsp.AST
11-
(ArgumentList(ArgumentList), DataDeclaration(DataDeclaration),
12-
Expression(EFunctionApplication, EIdentifier, ELetBlock, ELiteral),
13-
FunctionApplication(FunctionApplication),
14-
FunctionDefinition(FunctionDefinition),
15-
Import(ImportItem, ImportType), ImportList(ImportList),
16-
LanguagePragma(LanguagePragma), LetBlock(LetBlock),
17-
Literal(LChar, LInt, LList), MacroDefinition(MacroDefinition),
18-
QualifiedImport(QualifiedImport),
19-
RestrictedImport(RestrictedImport),
20-
Statement(SDataDeclaration, SFunctionDefinition, SLanguagePragma,
21-
SMacroDefinition, SModuleDeclaration, SQualifiedImport,
22-
SRestrictedImport, STypeSynonym, STypeclassInstance,
23-
SUnrestrictedImport),
24-
TypeDefinition(ProperType, TypeConstructor),
25-
TypeSynonym(TypeSynonym), TypeclassInstance(TypeclassInstance))
15+
( ArgumentList(ArgumentList)
16+
, DataDeclaration(DataDeclaration)
17+
, Expression(EFunctionApplication, EIdentifier, ELetBlock, ELiteral)
18+
, FunctionApplication(FunctionApplication)
19+
, FunctionDefinition(FunctionDefinition)
20+
, Import(ImportItem, ImportType)
21+
, ImportList(ImportList)
22+
, LanguagePragma(LanguagePragma)
23+
, LetBlock(LetBlock)
24+
, Literal(LChar, LInt, LList)
25+
, MacroDefinition(MacroDefinition)
26+
, QualifiedImport(QualifiedImport)
27+
, RestrictedImport(RestrictedImport)
28+
, Statement(SDataDeclaration, SFunctionDefinition, SLanguagePragma,
29+
SMacroDefinition, SModuleDeclaration, SQualifiedImport,
30+
SRestrictedImport, STypeSynonym, STypeclassInstance,
31+
SUnrestrictedImport)
32+
, TypeDefinition(ProperType, TypeConstructor)
33+
, TypeSynonym(TypeSynonym)
34+
, TypeclassInstance(TypeclassInstance)
35+
, arguments
36+
, function
37+
)
2638

2739
import Lihsp.Error (Error(NormalizeError))
2840
import qualified Lihsp.Parse as Parse
29-
(Expression(LiteralChar, LiteralInt, LiteralList, SExpression,
30-
Symbol))
41+
( Expression(LiteralChar, LiteralInt, LiteralList, SExpression,
42+
Symbol)
43+
)
3144

3245
normalizeExpression :: (MonadError Error m) => Parse.Expression -> m Expression
3346
normalizeExpression (Parse.LiteralChar char) = return $ ELiteral (LChar char)
@@ -141,3 +154,14 @@ normalizeStatement _ = throwError $ NormalizeError "0008"
141154

142155
normalizeProgram :: (MonadError Error m) => [Parse.Expression] -> m [Statement]
143156
normalizeProgram = traverse normalizeStatement
157+
158+
bottomUpTraverse :: (Applicative m) => (Expression -> m Expression) -> Expression -> m Expression
159+
bottomUpTraverse f x =
160+
case x of
161+
EFunctionApplication functionApplication ->
162+
f . EFunctionApplication <$>
163+
(FunctionApplication <$> f (functionApplication ^. function) <*>
164+
traverse f (functionApplication ^. arguments))
165+
EIdentifier _ -> f x
166+
ELetBlock _ -> f x
167+
ELiteral _ -> f x

0 commit comments

Comments
 (0)