134 Commits

Author SHA1 Message Date
crumbtoo
77f2f900d8 core driver 2024-02-01 15:24:16 -07:00
crumbtoo
ff5a5af9bc -ddump-eval 2024-02-01 12:14:43 -07:00
crumbtoo
7a6518583f debug tags 2024-02-01 11:57:37 -07:00
crumbtoo
dda0e17358 -ddump-ast 2024-02-01 11:37:52 -07:00
crumbtoo
46f0393a03 *R functions 2024-02-01 10:37:51 -07:00
crumbtoo
1803a1e058 formatting 2024-02-01 09:05:58 -07:00
crumbtoo
ccf17faff8 driver progress 2024-01-30 16:19:03 -07:00
crumbtoo
14df00039f error messages 2024-01-30 15:56:45 -07:00
crumbtoo
ba099b7028 organisation and cleaning
organisation and tidying
2024-01-30 14:04:43 -07:00
crumbtoo
e962bacd2e fixup! ttg boilerplate 2024-01-30 13:04:23 -07:00
crumbtoo
f0c652b861 fixup! ttg boilerplate 2024-01-30 13:03:07 -07:00
crumbtoo
6a41e123ea ttg boilerplate 2024-01-30 13:01:01 -07:00
crumbtoo
fbea3d6f3d let layout 2024-01-28 19:41:36 -07:00
crumbtoo
ab979cb934 i should've made a lisp man this sucks 2024-01-28 19:33:05 -07:00
crumbtoo
7d42f9b641 at long last
more

no more undefineds
2024-01-28 18:30:12 -07:00
crumbtoo
fdaa2a1afd abandon ship 2024-01-28 17:02:32 -07:00
crumbtoo
83dda869f8 show 2024-01-28 16:24:08 -07:00
crumbtoo
c74c192645 idk 2024-01-26 19:19:41 -07:00
crumbtoo
e00e4d3418 it's also a comonad. lol. 2024-01-26 17:53:05 -07:00
crumbtoo
8d0f324c63 oh my god guys!!! Located is a lax semimonoidal endofunctor on the category Hask!!!
![abstractionjak](https://media.discordapp.net/attachments/1101767463579951154/1200248978642567168/3877820-20SoyBooru.png?ex=65c57df8&is=65b308f8&hm=67da3acb61861cab6156df014b397d78fb8815fa163f2e992474d545beb668ba&=&format=webp&quality=lossless&width=880&height=868)
2024-01-26 17:25:59 -07:00
crumbtoo
6a6076f26e some 2024-01-26 15:12:10 -07:00
crumbtoo
559fd49f2b minor changes
putting this on hold; implementing TTG first
2024-01-25 15:52:56 -07:00
crumbtoo
bb3f73836c nearing release :3 2024-01-25 13:18:04 -07:00
crumbtoo
eeeac9cc85 named constr tests 2024-01-25 13:02:12 -07:00
crumbtoo
4f39dd36f1 resolve named data in case exprs 2024-01-25 12:39:57 -07:00
crumbtoo
4c99e44c04 temporary pragma system 2024-01-25 11:15:09 -07:00
crumbtoo
170e4e36ae new tag syntax; preparing for Core patterns
new tag syntax; preparing for data names
2024-01-24 11:34:09 -07:00
crumbtoo
d52a366c1b small fixups 2024-01-24 11:03:51 -07:00
crumbtoo
0025d33069 stable enough for a demo hey? 2024-01-24 10:14:44 -07:00
crumbtoo
7c474cc064 minor docs 2024-01-24 09:49:27 -07:00
crumbtoo
fbef645746 checklist 2024-01-24 09:39:06 -07:00
crumbtoo
c8199a9dd1 minor docs 2024-01-24 09:31:57 -07:00
crumbtoo
3d45e12676 infer letrec expressions 2024-01-23 21:09:25 -07:00
crumbtoo
22b5b47795 letrec 2024-01-23 20:19:16 -07:00
crumbtoo
cefdf6ffae allow uppercase sc names in preperation for Rlp2Core 2024-01-22 12:45:42 -07:00
crumbtoo
e3b18c8915 errors! 2024-01-22 12:20:05 -07:00
crumbtoo
692d22afb9 msgenvelope 2024-01-22 10:26:33 -07:00
crumbtoo
c146e1c450 errorful parser
small
2024-01-22 10:14:30 -07:00
crumbtoo
5a659d22dd errorful parser 2024-01-22 09:55:58 -07:00
crumbtoo
1a881399ab when the "Test suite rlp-test: PASS" hits
i'm like atlas and the world is writing two lines of code
2024-01-21 14:02:28 -07:00
crumbtoo
257d02da87 RlpcError -> IsRlpcError 2024-01-21 11:53:41 -07:00
crumbtoo
f47f325e34 compiles (kill me)
man
2024-01-19 15:52:47 -07:00
crumbtoo
f22d4238f5 Merge branch 'dev' into frontend-parser 2024-01-17 10:28:59 -07:00
crumbtoo
4e1c9dd750 rename rlp 2024-01-17 10:19:48 -07:00
crumbtoo
d6ac991105 renamerlp 2024-01-17 10:19:16 -07:00
crumbtoo
d5663c1aad remove debug flags 2024-01-17 10:11:48 -07:00
crumbtoo
7e6bee3d4a infix exprs 2024-01-17 10:08:57 -07:00
crumbtoo
5ec625e0fd i really need to learn git proper 2024-01-15 15:20:04 -07:00
crumbtoo
9196e20e08 Merge branch 'frontend-parser' into happy-frontend 2024-01-15 15:18:29 -07:00
crumbtoo
a1a50bd013 now we're fucking GETTING SOMEWHERE 2024-01-15 14:58:26 -07:00
crumbtoo
1c035d092a works 2024-01-15 13:31:15 -07:00
crumbtoo
c0236dc079 oh my god 2024-01-15 11:11:43 -07:00
crumbtoo
9a4f24ec10 Merge commit '4f66e71' into happy-frontend 2024-01-15 11:06:37 -07:00
crumbtoo
4f66e71b9a FIX REAL 2024-01-15 11:05:10 -07:00
crumbtoo
bdf74ac6c9 cool 2024-01-15 10:35:11 -07:00
crumbtoo
3dfadc17ec fixy 2024-01-15 10:33:09 -07:00
crumbtoo
c92d8fac65 we're so back 2024-01-15 09:44:26 -07:00
crumbtoo
a38381f6ca version bounds 2024-01-15 07:53:40 -07:00
crumbtoo
6390ca80d8 see previous commit and scale back the part where i'm joking 2024-01-15 07:47:23 -07:00
crumbtoo
17ddf3530c kitten i'll be honest mommy's about to kill herself 2024-01-15 07:47:23 -07:00
crumbtoo
e597ecbfc6 okay layouts kinda 2024-01-15 07:47:23 -07:00
crumbtoo
2496589346 aagh 2024-01-15 07:47:23 -07:00
crumbtoo
681a394312 man this sucks 2024-01-15 07:47:23 -07:00
crumbtoo
aff1c6b4c6 decent starting point 2024-01-15 07:47:23 -07:00
crumbtoo
bec376b7c7 threaded lexer 2024-01-15 07:47:23 -07:00
crumbtoo
eaa04c4a59 its fine 2024-01-15 07:47:23 -07:00
crumbtoo
ea2fb4dcaa tysigs 2024-01-15 07:47:23 -07:00
crumbtoo
ab2cb59526 i did not realise my fs is case insensitive 2024-01-15 07:47:23 -07:00
crumbtoo
ec4902b2d4 layout
layouts

oh my layouts
2024-01-15 07:47:23 -07:00
crumbtoo
1fc45b70b4 replace uses of many+satisfy with takeWhileP 2024-01-15 07:47:23 -07:00
crumbtoo
4b9a570c72 finally in a decent state 2024-01-15 07:47:23 -07:00
crumbtoo
65b967689c decls fix 2024-01-15 07:47:23 -07:00
crumbtoo
ed60ec8b32 aaaaa 2024-01-15 07:47:23 -07:00
crumbtoo
d0dbdbbd9b cool 2024-01-15 07:47:23 -07:00
crumbtoo
cae0939f0c where 2024-01-15 07:47:23 -07:00
crumbtoo
3292998c42 expr fixups 2024-01-15 07:47:23 -07:00
crumbtoo
84c1122995 infix decl 2024-01-15 07:47:21 -07:00
crumbtoo
97ce9b48ae labels 2024-01-15 07:46:23 -07:00
crumbtoo
936f24148f works 2024-01-15 07:46:23 -07:00
crumbtoo
2a159232c7 fixation fufilled - back to work! 2024-01-15 07:46:23 -07:00
crumbtoo
4ee9785239 Show1 instances 2024-01-15 07:46:20 -07:00
crumbtoo
cbe4276061 goofy 2024-01-15 07:44:17 -07:00
crumbtoo
c5c06fa6cb something 2024-01-15 07:44:17 -07:00
crumbtoo
0f04e2decf application and lits
appl
2024-01-15 07:44:17 -07:00
crumbtoo
6130a91668 oh boy am i going to hate this code in 12 hours 2024-01-15 07:44:17 -07:00
crumbtoo
c15f9b6546 4:00 AM psychopath code 2024-01-15 07:44:17 -07:00
crumbtoo
bb6aca094c grammar reference 2024-01-15 07:44:17 -07:00
crumbtoo
245b12a96e add version bounds 2024-01-15 07:43:59 -07:00
crumbtoo
cb9ec43c14 tysigs 2024-01-10 15:11:26 -07:00
crumbtoo
8ad967fac0 i did not realise my fs is case insensitive 2024-01-10 14:33:03 -07:00
crumbtoo
55dbc9de70 layout
layouts

oh my layouts
2024-01-10 14:23:37 -07:00
crumbtoo
05226373ee replace uses of many+satisfy with takeWhileP 2024-01-10 11:33:27 -07:00
crumbtoo
981c5d8a83 finally in a decent state 2024-01-10 11:26:17 -07:00
crumbtoo
86cd1075ca decls fix 2024-01-10 11:03:06 -07:00
crumbtoo
1d43c1d304 aaaaa 2024-01-10 10:46:53 -07:00
crumbtoo
4b44f57066 cool 2024-01-09 22:57:14 -07:00
crumbtoo
90a9594e8f where 2024-01-09 14:24:51 -07:00
crumbtoo
074350768c expr fixups 2024-01-09 12:26:53 -07:00
crumbtoo
37d9e6f219 infix decl 2024-01-09 11:39:26 -07:00
crumbtoo
cb7cdf7ed7 labels 2024-01-08 20:14:18 -07:00
crumbtoo
2f783d96e8 works 2024-01-08 18:56:14 -07:00
crumbtoo
a71c099fe0 fixation fufilled - back to work! 2024-01-08 13:39:12 -07:00
crumbtoo
d1e64eb12d Show1 instances 2024-01-03 10:04:42 -07:00
crumbtoo
f31726b43d goofy 2024-01-02 08:43:34 -07:00
crumbtoo
8aa9bb843f something 2024-01-02 08:04:49 -07:00
crumbtoo
9a357a99b7 application and lits
appl
2024-01-02 07:04:27 -07:00
crumbtoo
060d48f9e1 oh boy am i going to hate this code in 12 hours 2024-01-02 06:26:48 -07:00
crumbtoo
bf4abeb8b4 4:00 AM psychopath code 2024-01-02 05:34:11 -07:00
crumbtoo
7ed565fc24 grammar reference 2024-01-02 02:33:31 -07:00
crumbtoo
832767575c lex \ instead of \\ 2023-12-29 18:43:09 -07:00
crumbtoo
1dc695f640 Compiler.JustRun 2023-12-29 14:20:53 -07:00
crumbtoo
b941347f82 fix hm tests 2023-12-29 13:54:09 -07:00
crumbtoo
35446533d7 type-checked quasiquoters 2023-12-29 13:47:42 -07:00
crumbtoo
e80acbcd28 errorful (it's not good) 2023-12-28 15:55:55 -07:00
crumbtoo
cb5692248f back and medicated! 2023-12-28 15:55:55 -07:00
crumbtoo
1164b13a1e kinda sorta typechecking 2023-12-28 15:55:55 -07:00
crumbtoo
b6945a64eb i'm on an airplane rn, my eyelids grow heavy, and i forgot my medication. should this be my final commit (of the week): gootbye 2023-12-28 15:55:55 -07:00
crumbtoo
526bf0734e RlpcError 2023-12-28 15:55:24 -07:00
crumbtoo
c2960e4acc Name = Text
Name = Text
2023-12-20 15:41:41 -07:00
crumbtoo
07be32c618 parse programs (with type sigs :D) 2023-12-20 14:49:40 -07:00
crumbtoo
5c9bf40e40 parse programs (with types :D) 2023-12-20 14:42:35 -07:00
crumbtoo
fe90c9afb0 parse types 2023-12-20 14:13:17 -07:00
crumbtoo
414312cf98 parse type sigs; program type sigs 2023-12-20 14:13:17 -07:00
crumbtoo
6f522d34ff TyInt -> TyCon "Int#" 2023-12-20 14:12:45 -07:00
crumbtoo
d954734660 LitE -> Lit 2023-12-18 15:42:41 -07:00
crumbtoo
52b7723ea0 LitE -> Lit 2023-12-18 15:38:26 -07:00
crumbtoo
ac6f826141 small 2023-12-18 15:37:32 -07:00
crumbtoo
e222dae6ac infer nonrec let binds
infer nonrec let binds
2023-12-18 15:37:32 -07:00
crumbtoo
e9e1c075db type IsString + test unification error 2023-12-18 15:37:32 -07:00
crumbtoo
0470912983 comments and better type errors 2023-12-18 15:37:32 -07:00
crumbtoo
f7e850c61a hindley milner inference :D 2023-12-18 15:37:27 -07:00
crumbtoo
78f88e085f infer 2023-12-18 15:36:32 -07:00
crumbtoo
20c936f317 commentary 2023-12-18 15:36:32 -07:00
crumbtoo
136e3687b0 Literal -> Lit, LitE -> Lit 2023-12-18 15:36:17 -07:00
42 changed files with 2808 additions and 573 deletions

18
.ghci Normal file
View File

@@ -0,0 +1,18 @@
:set -XOverloadedStrings
:set -package process
:{
import System.Exit qualified
import System.Process qualified
_reload_and_make _ = do
p <- System.Process.spawnCommand "make -f Makefile_happysrcs"
r <- System.Process.waitForProcess p
case r of
System.Exit.ExitSuccess -> pure ":reload"
_ -> pure ""
:}
:def! r _reload_and_make

19
CHANGELOG.md Normal file
View File

@@ -0,0 +1,19 @@
# unreleased
* New tag syntax:
```hs
case x of
{ 1 -> something
; 2 -> another
}
```
is now written as
```hs
case x of
{ <1> -> something
; <2> -> another
}
```
# Release 1.0.0

25
Makefile_happysrcs Normal file
View File

@@ -0,0 +1,25 @@
HAPPY = happy
HAPPY_OPTS = -a -g -c -i/tmp/t.info
ALEX = alex
ALEX_OPTS = -g
SRC = src
CABAL_BUILD = dist-newstyle/build/x86_64-osx/ghc-9.6.2/rlp-0.1.0.0/build
all: parsers lexers
parsers: $(CABAL_BUILD)/Rlp/Parse.hs $(CABAL_BUILD)/Core/Parse.hs
lexers: $(CABAL_BUILD)/Rlp/Lex.hs $(CABAL_BUILD)/Core/Lex.hs
$(CABAL_BUILD)/Rlp/Parse.hs: $(SRC)/Rlp/Parse.y
$(HAPPY) $(HAPPY_OPTS) $< -o $@
$(CABAL_BUILD)/Rlp/Lex.hs: $(SRC)/Rlp/Lex.x
$(ALEX) $(ALEX_OPTS) $< -o $@
$(CABAL_BUILD)/Core/Parse.hs: $(SRC)/Core/Parse.y
$(HAPPY) $(HAPPY_OPTS) $< -o $@
$(CABAL_BUILD)/Core/Lex.hs: $(SRC)/Core/Lex.x
$(ALEX) $(ALEX_OPTS) $< -o $@

View File

@@ -23,19 +23,19 @@ $ cabal test --test-show-details=direct
$ rlpc -ddump-eval examples/factorial.hs
# Compile and evaluate t.hs, with evaluation info dumped to t.log
$ rlpc -ddump-eval -l t.log t.hs
# Print the raw structure describing the compiler options and die
# Print the raw structure describing the compiler options
# (option parsing still must succeed in order to print)
$ rlpc -ddump-opts t.hs
```
### Potential Features
Listed in order of importance.
- [ ] ADTs
- [ ] First-class functions
- [x] ADTs
- [x] First-class functions
- [ ] Higher-kinded types
- [ ] Typeclasses
- [ ] Parametric polymorphism
- [ ] Hindley-Milner type inference
- [x] Parametric polymorphism
- [x] Hindley-Milner type inference
- [ ] Newtype coercion
- [ ] Parallelism
@@ -66,32 +66,61 @@ Listed in order of importance.
- [ ] TCO
- [ ] DCE
- [ ] Frontend
- [ ] High-level language
- [ ] AST
- [ ] Lexer
- [ ] Parser
- [x] High-level language
- [x] AST
- [x] Lexer
- [x] Parser
- [ ] Translation to the core language
- [ ] Constraint solver
- [ ] `do`-notation
- [x] CLI
- [ ] Documentation
- [ ] State transition rules
- [x] State transition rules
- [ ] How does the evaluation model work?
- [ ] The Hindley-Milner type system
- [ ] CLI usage
- [ ] Tail call optimisation
- [x] Parsing rlp
- [ ] Parsing rlp
- [ ] Trees That Grow
- [ ] Tests
- [x] Generic example programs
- [ ] Parser
### December Release Plan
- [ ] Tests
### ~~December Release Plan~~
- [x] Tests
- [ ] Core lexer
- [ ] Core parser
- [ ] Evaluation model
- [x] Evaluation model
- [ ] Benchmarks
- [ ] Stable Core lexer
- [ ] Stable Core parser
- [ ] Stable evaluation model
- [ ] Garbage Collection
- [x] Stable Core lexer
- [x] Stable Core parser
- [x] Stable evaluation model
- [x] Garbage Collection
- [ ] Stable documentation for the evaluation model
### January Release Plan
- [ ] Beta rl' to Core
- [ ] UX improvements
- [ ] Actual compiler errors -- no more unexceptional `error` calls
- [ ] Better CLI dump flags
- [ ] Annotate the AST with token positions for errors
- [ ] More examples
### March Release Plan
- [ ] Tests
- [ ] rl' parser
- [ ] rl' lexer
### Indefinite Release Plan
This list is more concrete than the milestones, but likely further in the future
than the other release plans.
- [ ] Stable rl' to Core
- [ ] Core polish
- [ ] Better, stable parser
- [ ] Better, stable lexer
- [ ] Less hacky handling of named data
- [ ] Less hacky pragmas
- [ ] GM to LLVM

17
app/CoreDriver.hs Normal file
View File

@@ -0,0 +1,17 @@
module CoreDriver
( driver
)
where
--------------------------------------------------------------------------------
import Compiler.RLPC
import Control.Monad
import Core.Lex
import Core.Parse
import GM
--------------------------------------------------------------------------------
driver :: RLPCIO ()
driver = forFiles_ $ \f ->
withSource f (lexCoreR >=> parseCoreProgR >=> evalProgR)

View File

@@ -7,12 +7,19 @@ import Options.Applicative hiding (ParseError)
import Control.Monad
import Control.Monad.Reader
import Data.HashSet qualified as S
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as TIO
import Data.List
import System.IO
import System.Exit (exitSuccess)
import Core
import TI
import GM
import Lens.Micro.Mtl
import CoreDriver qualified
import RlpDriver qualified
----------------------------------------------------------------------------------
optParser :: ParserInfo RLPCOptions
@@ -34,9 +41,15 @@ options = RLPCOptions
{- -d -}
<*> fmap S.fromList # many # option debugFlagReader
( short 'd'
<> help "dump evaluation logs"
<> help "pass debug flags"
<> metavar "DEBUG FLAG"
)
{- -f -}
<*> fmap S.fromList # many # option compilerFlagReader
( short 'f'
<> help "pass compilation flags"
<> metavar "COMPILATION FLAG"
)
{- --evaluator, -e -}
<*> option evaluatorReader
( long "evaluator"
@@ -52,96 +65,45 @@ options = RLPCOptions
\triggering the garbage collector"
<> value 50
)
<*> option languageReader
( long "language"
<> short 'x'
)
<*> some (argument str $ metavar "FILES...")
where
infixr 9 #
f # x = f x
languageReader :: ReadM Language
languageReader = maybeReader $ \case
"rlp" -> Just LanguageRlp
"core" -> Just LanguageCore
_ -> Nothing
debugFlagReader :: ReadM DebugFlag
debugFlagReader = str
compilerFlagReader :: ReadM CompilerFlag
compilerFlagReader = str
evaluatorReader :: ReadM Evaluator
evaluatorReader = maybeReader $ \case
"gm" -> Just EvaluatorGM
"tim" -> Just EvaluatorTI
"ti" -> Just EvaluatorTI
_ -> Nothing
mmany :: (Alternative f, Monoid m) => f m -> f m
mmany v = liftA2 (<>) v (mmany v)
debugFlagReader :: ReadM DebugFlag
debugFlagReader = maybeReader $ \case
"dump-eval" -> Just DDumpEval
"dump-opts" -> Just DDumpOpts
"dump-ast" -> Just DDumpAST
_ -> Nothing
----------------------------------------------------------------------------------
-- temp
data CompilerError = CompilerError String
deriving Show
instance Exception CompilerError
main :: IO ()
main = do
opts <- execParser optParser
(_, es) <- evalRLPCIO opts driver
forM_ es $ \ (CompilerError e) -> print $ "warning: " <> e
pure ()
void $ evalRLPCIO opts driver
driver :: RLPCIO CompilerError ()
driver = sequence_
[ dshowFlags
, ddumpAST
, ddumpEval
]
dshowFlags :: RLPCIO CompilerError ()
dshowFlags = whenFlag flagDDumpOpts do
ask >>= liftIO . print
ddumpAST :: RLPCIO CompilerError ()
ddumpAST = whenFlag flagDDumpAST $ forFiles_ \o f -> do
liftIO $ withFile f ReadMode $ \h -> do
s <- hGetContents h
case parseProg o s of
Right (a,_) -> hPutStrLn stderr $ show a
Left e -> error "todo errors lol"
ddumpEval :: RLPCIO CompilerError ()
ddumpEval = whenFlag flagDDumpEval do
fs <- view rlpcInputFiles
forM_ fs $ \f -> liftIO (readFile f) >>= doProg
where
doProg :: String -> RLPCIO CompilerError ()
doProg s = ask >>= \o -> case parseProg o s of
-- TODO: error handling
Left e -> addFatal . CompilerError $ show e
Right (a,_) -> do
log <- view rlpcLogFile
dumpEval <- chooseEval
case log of
Just f -> liftIO $ withFile f WriteMode $ dumpEval a
Nothing -> liftIO $ dumpEval a stderr
-- choose the appropriate model based on the compiler opts
chooseEval = do
ev <- view rlpcEvaluator
pure $ case ev of
EvaluatorGM -> v GM.hdbgProg
EvaluatorTI -> v TI.hdbgProg
where v f p h = f p h *> pure ()
parseProg :: RLPCOptions
-> String
-> Either SrcError (Program', [SrcError])
parseProg o = evalRLPC o . (lexCore >=> parseCoreProg)
forFiles_ :: (Monad m)
=> (RLPCOptions -> FilePath -> RLPCT e m a)
-> RLPCT e m ()
forFiles_ k = do
fs <- view rlpcInputFiles
o <- ask
forM_ fs (k o)
driver :: RLPCIO ()
driver = view rlpcLanguage >>= \case
LanguageCore -> CoreDriver.driver
LanguageRlp -> RlpDriver.driver

11
app/RlpDriver.hs Normal file
View File

@@ -0,0 +1,11 @@
module RlpDriver
( driver
)
where
--------------------------------------------------------------------------------
import Compiler.RLPC
--------------------------------------------------------------------------------
driver :: RLPCIO ()
driver = undefined

View File

@@ -1,16 +1,24 @@
The *G-Machine*
===============
The G-Machine (graph machine) is the current heart of rlpc, until we potentially
move onto a STG (spineless tagless graph machine) or a TIM (three-instruction
machine). rl' source code is desugared into Core; a dumbed-down subset of rl',
and then compiled to G-Machine code, which is then finally translated to the
desired target.
**********
Motivation
**********
Our initial model, the *Template Instantiator* (TI) was a very
straightforward solution to compilation, but its core design has a major
Achilles' heel, being that Compilation is interleaved with evaluation -- The
heap nodes for supercombinators hold uninstantiated expressions, i.e. raw ASTs
straight from the parser. When a supercombinator is found on the stack during
evaluation, the template expression is instantiated (compiled) on the spot.
Our initial model, the *Template Instantiator* (TI) was a very straightforward
solution to compilation, but its core design has a major Achilles' heel, being
that compilation is interleaved with evaluation -- The heap nodes for
supercombinators hold uninstantiated expressions, i.e. raw ASTs straight from
the parser. When a supercombinator is found on the stack during evaluation, the
template expression is instantiated (compiled) on the spot. This makes
translation to an assembly difficult, undermining the point of an intermediate
language.
.. math::
\transrule
@@ -31,7 +39,7 @@ evaluation, the template expression is instantiated (compiled) on the spot.
\text{where } h' = \mathtt{instantiateU} \; e \; a_n \; h \; g
}
The process of instantiating a supercombinator goes something like this
The process of instantiating a supercombinator goes something like this:
1. Augment the environment with bindings to the arguments.
@@ -52,13 +60,17 @@ The process of instantiating a supercombinator goes something like this
Instantiating the supercombinator's body in this way is the root of our
Achilles' heel. Traversing a tree structure is a very non-linear task unfit for
an assembly target. The goal of our new G-Machine is to compile a *linear
sequence of instructions* which instantiate the expression at execution.
sequence of instructions* which, **when executed**, build up a graph
representing the code.
**************************
Trees and Vines, in Theory
**************************
WIP.
Rather than instantiating an expression at runtime -- traversing the AST and
building a graph -- we want to compile all expressions at compile-time,
generating a linear sequence of instructions which may be executed to build the
graph.
**************************
Evaluation: Slurping Vines
@@ -100,5 +112,3 @@ The way around this is quite simple: simply offset the stack when w
:end-before: -- << [ref/compileC]
:caption: src/GM.hs

View File

@@ -2,16 +2,21 @@ Lexing, Parsing, and Layouts
============================
The C-style languages of my previous experiences have all had quite trivial
lexical analysis stages, peaking in complexity when I streamed tokens lazily in
C. The task of tokenising a C-style language is very simple in description: you
ignore all whitespace and point out what you recognise. If you don't recognise
something, check if it's a literal or an identifier. Should it be neither,
return an error.
lexical analysis stages: you ignore all whitespace and point out the symbols you
recognise. If you don't recognise something, check if it's a literal or an
identifier. Should it be neither, return an error.
On paper, both lexing and parsing a Haskell-like language seem to pose a few
In contrast, both lexing and parsing a Haskell-like language poses a number of
greater challenges. Listed by ascending intimidation factor, some of the
potential roadblocks on my mind before making an attempt were:
* Context-sensitive keywords; Haskell allows for some words to be used as
identifiers in appropriate contexts, such as :code:`family`, :code:`role`,
:code:`as`. Reading a note_ found in `GHC's lexer`_, it appears that keywords
are only considered in bodies for which their use is relevant, e.g.
:code:`family` and :code:`role` in type declarations, :code:`as` after
:code:`case`; :code:`if`, :code:`then`, and :code:`else` in expressions, etc.
* Operators; Haskell has not only user-defined infix operators, but user-defined
precedence levels and associativities. I recall using an algorithm that looked
up infix, prefix, postfix, and even mixfix operators up in a global table to
@@ -19,17 +24,9 @@ potential roadblocks on my mind before making an attempt were:
stored in the table). I never modified the table at runtime, however this
could be a very nice solution for Haskell.
* Context-sensitive keywords; Haskell allows for some words to be used as identifiers in
appropriate contexts, such as :code:`family`, :code:`role`, :code:`as`.
Reading a note_ found in `GHC's lexer`_,
it appears that keywords are only considered in bodies for which their use is
relevant, e.g. :code:`family` and :code:`role` in type declarations,
:code:`as` after :code:`case`; :code:`if`, :code:`then`, and :code:`else` in
expressions, etc.
* Whitespace sensitivity; While I was comfortable with the idea of a system
similar to Python's INDENT/DEDENT tokens, Haskell seemed to use whitespace to
section code in a way that *felt* different.
similar to Python's INDENT/DEDENT tokens, Haskell's layout system is based on
alignment and is very generous with line-folding.
.. _note: https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/coding-style#2-using-notes
.. _GHC's lexer: https://gitlab.haskell.org/ghc/ghc/-/blob/master/compiler/GHC/Parser/Lexer.x#L1133
@@ -45,9 +42,9 @@ We will compare and contrast with Python's lexical analysis. Much to my dismay,
Python uses newlines and indentation to separate statements and resolve scope
instead of the traditional semicolons and braces found in C-style languages (we
may generally refer to these C-style languages as *explicitly-sectioned*).
Internally during tokenisation, when the Python lexer begins a new line, they
compare the indentation of the new line with that of the previous and apply the
following rules:
Internally during tokenisation, when the Python lexer encounters a new line, the
indentation of the new line is compared with that of the previous and the
following rules are applied:
1. If the new line has greater indentation than the previous, insert an INDENT
token and push the new line's indentation level onto the indentation stack
@@ -60,44 +57,37 @@ following rules:
3. If the indentation is equal, insert a NEWLINE token to terminate the previous
line, and leave it at that!
Parsing Python with the INDENT, DEDENT, and NEWLINE tokens is identical to
parsing a language with braces and semicolons. This is a solution pretty in line
with Python's philosophy of the "one correct answer" (TODO: this needs a
source). In developing our *layout* rules, we will follow in the pattern of
translating the whitespace-sensitive source language to an explicitly sectioned
language.
On the parser's end, the INDENT, DEDENT, and NEWLINE tokens are identical to
braces and semicolons. In developing our *layout* rules, we will follow in the
pattern of translating the whitespace-sensitive source language to an explicitly
sectioned language.
But What About Haskell?
***********************
We saw that Python, the most notable example of an implicitly sectioned
language, is pretty simple to lex. Why then am I so afraid of Haskell's layouts?
To be frank, I'm far less scared after asking myself this -- however there are
certainly some new complexities that Python needn't concern. Haskell has
implicit line *continuation*: forms written over multiple lines; indentation
styles often seen in Haskell are somewhat esoteric compared to Python's
"s/[{};]//".
Parsing Haskell -- and thus rl' -- is only slightly more complex than Python,
but the design is certainly more sensitive.
.. code-block:: haskell
-- line continuation
-- line folds
something = this is a
single expression
-- an extremely common style found in haskell
data Python = Users
{ are :: Crying
, right :: About
, now :: Sorry
data Some = Data
{ is :: Presented
, in :: This
, silly :: Style
}
-- another formatting oddity
-- another style oddity
-- note that this is not a single
-- continued line! `look at`,
-- `this`, and `alignment` are all
-- separate expressions!
-- `this odd`, and `alignment` are all
-- discrete items!
anotherThing = do look at
this
this odd
alignment
But enough fear, lets actually think about implementation. Firstly, some
@@ -233,3 +223,4 @@ References
* `Haskell syntax reference
<https://www.haskell.org/onlinereport/haskell2010/haskellch10.html>`_

View File

@@ -1,6 +0,0 @@
The *Template Instantiator*
====================================
WIP. This will hopefully be expanded into a thorough walkthrough of the state
machine.

View File

@@ -0,0 +1,5 @@
Type Inference in rl'
=====================
rl' implements type inference via the Hindley-Milner type system.

View File

@@ -13,7 +13,7 @@ author = 'madeleine sydney slaga'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = ['sphinx.ext.imgmath']
extensions = ['sphinx.ext.imgmath', 'sphinx.ext.graphviz']
# templates_path = ['_templates']
exclude_patterns = []
@@ -32,6 +32,7 @@ html_theme = 'alabaster'
imgmath_latex_preamble = r'''
\usepackage{amsmath}
\usepackage{tabularray}
\usepackage{syntax}
\newcommand{\transrule}[2]
{\begin{tblr}{|rrrlc|}

View File

@@ -0,0 +1,67 @@
The Complete Syntax of rl'
==========================
WIP.
Provided is the complete syntax of rl' in (pseudo) EBNF. {A} represents zero or
more A's, [A] means optional A, and terminals are wrapped in 'single-quotes'.
.. math
:nowrap:
\setlength{\grammarparsep}{20pt plus 1pt minus 1pt}
\setlength{\grammarindent}{12em}
\begin{grammar}
<Decl> ::= <InfixDecl>
\alt <DataDecl>
\alt <TypeSig>
\alt <FunDef>
<InfixDecl> ::= <InfixWord> `litint' <Name>
<InfixWord> ::= `infix'
\alt `infixl'
\alt `infixr'
<DataDecl> ::= `data' `conname' {}
\end{grammar}
.. code-block:: bnf
Decl ::= InfixDecl
| DataDecl
| TypeSig
| FunDef
InfixDecl ::= InfixWord 'litint' Operator
InfixWord ::= 'infix'
| 'infixl'
| 'infixr'
DataDecl ::= 'data' 'conname' {'name'} '=' Data
DataCons ::= 'conname' {Type1} ['|' DataCons]
TypeSig ::= Var '::' Type
FunDef ::= Var {Pat1} '=' Expr
Type ::= Type1 {Type1}
-- note that (->) is right-associative,
-- and extends as far as possible
| Type '->' Type
Type1 ::= '(' Type ')'
| 'conname'
Pat ::= 'conname' Pat1 {Pat1}
| Pat 'consym' Pat
Pat1 ::= Literal
| 'conname'
| '(' Pat ')'
Literal ::= 'litint'
Var ::= 'varname'
| '(' 'varsym' ')'
Con ::= 'conname'
| '(' 'consym' ')'

View File

@@ -0,0 +1,17 @@
rl' Inference Rules
===================
.. rubric::
[Var]
.. math::
\frac{x : \tau \in \Gamma}
{\Gamma \vdash x : \tau}
.. rubric::
[App]
.. math::
\frac{\Gamma \vdash f : \alpha \to \beta \qquad \Gamma \vdash x : \alpha}
{\Gamma \vdash f x : \beta}

View File

@@ -1,6 +1,6 @@
fac n = case (==#) n 0 of
{ 1 -> 1
; 0 -> (*#) n (fac ((-#) n 1))
{ <1> -> 1
; <0> -> (*#) n (fac ((-#) n 1))
};
main = fac 3;

View File

@@ -2,8 +2,8 @@ nil = Pack{0 0};
cons x y = Pack{1 2} x y;
list = cons 1 (cons 2 (cons 3 nil));
sum l = case l of
{ 0 -> 0
; 1 x xs -> (+#) x (sum xs)
{ <0> -> 0
; <1> x xs -> (+#) x (sum xs)
};
main = sum list;

View File

@@ -0,0 +1,105 @@
Programming Language Checklist
by Colin McMillen, Jason Reed, and Elly Fong-Jones, 2011-10-10.
You appear to be advocating a new:
[x] functional [ ] imperative [ ] object-oriented [ ] procedural [ ] stack-based
[ ] "multi-paradigm" [x] lazy [ ] eager [x] statically-typed [ ] dynamically-typed
[x] pure [ ] impure [ ] non-hygienic [ ] visual [x] beginner-friendly
[ ] non-programmer-friendly [ ] completely incomprehensible
programming language. Your language will not work. Here is why it will not work.
You appear to believe that:
[ ] Syntax is what makes programming difficult
[x] Garbage collection is free [x] Computers have infinite memory
[x] Nobody really needs:
[x] concurrency [x] a REPL [x] debugger support [x] IDE support [x] I/O
[x] to interact with code not written in your language
[ ] The entire world speaks 7-bit ASCII
[ ] Scaling up to large software projects will be easy
[ ] Convincing programmers to adopt a new language will be easy
[ ] Convincing programmers to adopt a language-specific IDE will be easy
[ ] Programmers love writing lots of boilerplate
[ ] Specifying behaviors as "undefined" means that programmers won't rely on them
[ ] "Spooky action at a distance" makes programming more fun
Unfortunately, your language (has/lacks):
[x] comprehensible syntax [ ] semicolons [x] significant whitespace [ ] macros
[ ] implicit type conversion [ ] explicit casting [x] type inference
[ ] goto [ ] exceptions [x] closures [x] tail recursion [ ] coroutines
[ ] reflection [ ] subtyping [ ] multiple inheritance [x] operator overloading
[x] algebraic datatypes [x] recursive types [x] polymorphic types
[ ] covariant array typing [x] monads [ ] dependent types
[x] infix operators [x] nested comments [ ] multi-line strings [ ] regexes
[ ] call-by-value [x] call-by-name [ ] call-by-reference [ ] call-cc
The following philosophical objections apply:
[ ] Programmers should not need to understand category theory to write "Hello, World!"
[ ] Programmers should not develop RSI from writing "Hello, World!"
[ ] The most significant program written in your language is its own compiler
[x] The most significant program written in your language isn't even its own compiler
[x] No language spec
[x] "The implementation is the spec"
[ ] The implementation is closed-source [ ] covered by patents [ ] not owned by you
[ ] Your type system is unsound [ ] Your language cannot be unambiguously parsed
[ ] a proof of same is attached
[ ] invoking this proof crashes the compiler
[x] The name of your language makes it impossible to find on Google
[x] Interpreted languages will never be as fast as C
[ ] Compiled languages will never be "extensible"
[ ] Writing a compiler that understands English is AI-complete
[ ] Your language relies on an optimization which has never been shown possible
[ ] There are less than 100 programmers on Earth smart enough to use your language
[ ] ____________________________ takes exponential time
[ ] ____________________________ is known to be undecidable
Your implementation has the following flaws:
[ ] CPUs do not work that way
[ ] RAM does not work that way
[ ] VMs do not work that way
[ ] Compilers do not work that way
[ ] Compilers cannot work that way
[ ] Shift-reduce conflicts in parsing seem to be resolved using rand()
[ ] You require the compiler to be present at runtime
[ ] You require the language runtime to be present at compile-time
[ ] Your compiler errors are completely inscrutable
[ ] Dangerous behavior is only a warning
[ ] The compiler crashes if you look at it funny
[x] The VM crashes if you look at it funny
[x] You don't seem to understand basic optimization techniques
[x] You don't seem to understand basic systems programming
[ ] You don't seem to understand pointers
[ ] You don't seem to understand functions
Additionally, your marketing has the following problems:
[x] Unsupported claims of increased productivity
[x] Unsupported claims of greater "ease of use"
[ ] Obviously rigged benchmarks
[ ] Graphics, simulation, or crypto benchmarks where your code just calls
handwritten assembly through your FFI
[ ] String-processing benchmarks where you just call PCRE
[ ] Matrix-math benchmarks where you just call BLAS
[x] Noone really believes that your language is faster than:
[x] assembly [x] C [x] FORTRAN [x] Java [x] Ruby [ ] Prolog
[ ] Rejection of orthodox programming-language theory without justification
[x] Rejection of orthodox systems programming without justification
[ ] Rejection of orthodox algorithmic theory without justification
[ ] Rejection of basic computer science without justification
Taking the wider ecosystem into account, I would like to note that:
[x] Your complex sample code would be one line in: examples/
[ ] We already have an unsafe imperative language
[ ] We already have a safe imperative OO language
[x] We already have a safe statically-typed eager functional language
[ ] You have reinvented Lisp but worse
[ ] You have reinvented Javascript but worse
[ ] You have reinvented Java but worse
[ ] You have reinvented C++ but worse
[ ] You have reinvented PHP but worse
[ ] You have reinvented PHP better, but that's still no justification
[ ] You have reinvented Brainfuck but non-ironically
In conclusion, this is what I think of you:
[ ] You have some interesting ideas, but this won't fly.
[x] This is a bad language, and you should feel bad for inventing it.
[ ] Programming in this language is an adequate punishment for inventing it.

View File

@@ -7,11 +7,12 @@ license: GPL-2.0-only
-- license-file: LICENSE
author: crumbtoo
maintainer: crumb@disroot.org
-- copyright:
copyright: Madeleine Sydney Ślaga
category: Language
build-type: Simple
extra-doc-files: README.md
-- extra-source-files:
tested-with: GHC==9.6.2
common warnings
-- ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-unused-top-binds
@@ -22,53 +23,79 @@ library
, TI
, GM
, Compiler.RLPC
, Compiler.RlpcError
, Compiler.JustRun
, Core.Syntax
, Core.Examples
, Core.Utils
, Core.TH
other-modules: Data.Heap
, Core.HindleyMilner
, Control.Monad.Errorful
, Rlp.Syntax
-- , Rlp.Parse.Decls
, Rlp.Parse
, Rlp.Parse.Associate
, Rlp.Lex
, Rlp.Parse.Types
, Compiler.Types
, Data.Heap
, Data.Pretty
, Core.Parse
, Core.Lex
, Control.Monad.Errorful
, Core2Core
, RLP.Syntax
, Control.Monad.Utils
build-tool-depends: happy:happy, alex:alex
-- other-extensions:
build-depends: base ^>=4.18.0.0
, containers
, microlens
, microlens-th
, mtl
, template-haskell
build-depends: base >=4.17 && <4.20
-- required for happy
, array
, data-default-class
, unordered-containers
, hashable
, pretty
, recursion-schemes
, megaparsec
, text
, array >= 0.5.5 && < 0.6
, containers >= 0.6.7 && < 0.7
, template-haskell >= 2.20.0 && < 2.21
, pretty >= 1.1.3 && < 1.2
, data-default >= 0.7.1 && < 0.8
, data-default-class >= 0.1.2 && < 0.2
, hashable >= 1.4.3 && < 1.5
, mtl >= 2.3.1 && < 2.4
, text >= 2.0.2 && < 2.1
, megaparsec >= 9.6.1 && < 9.7
, microlens >= 0.4.13 && < 0.5
, microlens-mtl >= 0.2.0 && < 0.3
, microlens-platform >= 0.4.3 && < 0.5
, microlens-th >= 0.4.3 && < 0.5
, unordered-containers >= 0.2.20 && < 0.3
, recursion-schemes >= 5.2.2 && < 5.3
, data-fix >= 0.3.2 && < 0.4
, utf8-string >= 1.0.2 && < 1.1
, extra >= 1.7.0 && < 2
, semigroupoids
, comonad
, lens
, text-ansi
hs-source-dirs: src
default-language: GHC2021
default-extensions:
OverloadedStrings
TypeFamilies
LambdaCase
executable rlpc
import: warnings
main-is: Main.hs
-- other-modules:
-- other-extensions:
build-depends: base ^>=4.18.0.0
other-modules: RlpDriver
, CoreDriver
build-depends: base >=4.17.0.0 && <4.20.0.0
, rlp
, optparse-applicative
, microlens
, microlens-mtl
, mtl
, unordered-containers
, optparse-applicative >= 0.18.1 && < 0.19
, microlens >= 0.4.13 && < 0.5
, microlens-mtl >= 0.2.0 && < 0.3
, mtl >= 2.3.1 && < 2.4
, unordered-containers >= 0.2.20 && < 0.3
, text >= 2.0.2 && < 2.1
hs-source-dirs: app
default-language: GHC2021
@@ -84,7 +111,9 @@ test-suite rlp-test
, rlp
, QuickCheck
, hspec ==2.*
, microlens
other-modules: Arith
, GMSpec
, Core.HindleyMilnerSpec
build-tool-depends: hspec-discover:hspec-discover

48
src/Compiler/JustRun.hs Normal file
View File

@@ -0,0 +1,48 @@
{-|
Module : Compiler.JustRun
Description : No-BS, high-level wrappers for major pipeline pieces.
A collection of wrapper functions to demo processes such as lexing, parsing,
type-checking, and evaluation. This module intends to export "no-BS" functions
that use Prelude types such as @Either@ and @String@ rather than more complex
types such as @RLPC@ or @Text@.
-}
module Compiler.JustRun
( justLexSrc
, justParseSrc
, justTypeCheckSrc
)
where
----------------------------------------------------------------------------------
import Core.Lex
import Core.Parse
import Core.HindleyMilner
import Core.Syntax (Program')
import Compiler.RLPC
import Control.Arrow ((>>>))
import Control.Monad ((>=>))
import Data.Text qualified as T
import Data.Function ((&))
import GM
----------------------------------------------------------------------------------
justLexSrc :: String -> Either [MsgEnvelope RlpcError] [CoreToken]
justLexSrc s = lexCoreR (T.pack s)
& fmap (map $ \ (Located _ _ _ t) -> t)
& rlpcToEither
justParseSrc :: String -> Either [MsgEnvelope RlpcError] Program'
justParseSrc s = parse (T.pack s)
& rlpcToEither
where parse = lexCoreR >=> parseCoreProgR
justTypeCheckSrc :: String -> Either [MsgEnvelope RlpcError] Program'
justTypeCheckSrc s = typechk (T.pack s)
& rlpcToEither
where typechk = lexCoreR >=> parseCoreProgR >=> checkCoreProgR
rlpcToEither :: RLPC a -> Either [MsgEnvelope RlpcError] a
rlpcToEither r = case evalRLPC def r of
(Just a, _) -> Right a
(Nothing, es) -> Left es

View File

@@ -11,95 +11,110 @@ errors and the family of RLPC monads.
-- only used for mtl instances
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveGeneric, DerivingStrategies, DerivingVia #-}
{-# LANGUAGE BlockArguments, ViewPatterns #-}
module Compiler.RLPC
( RLPC
, RLPCT
, RLPCIO
, RLPCOptions(RLPCOptions)
, addFatal
, addWound
, MonadErrorful
, Severity(..)
, Evaluator(..)
, evalRLPCT
, evalRLPCIO
, evalRLPC
, rlpcLogFile
, rlpcDebugOpts
, rlpcEvaluator
, rlpcInputFiles
, DebugFlag(..)
, whenFlag
, flagDDumpEval
, flagDDumpOpts
, flagDDumpAST
, def
(
-- * Rlpc Monad transformer
RLPCT(RLPCT),
-- ** Special cases
RLPC, RLPCIO
-- ** Running
, runRLPCT
, evalRLPCT, evalRLPCIO, evalRLPC
-- * Rlpc options
, Language(..), Evaluator(..)
, DebugFlag(..), CompilerFlag(..)
-- ** Lenses
, rlpcLogFile, rlpcDFlags, rlpcEvaluator, rlpcInputFiles, rlpcLanguage
-- * Misc. MTL-style functions
, liftErrorful, hoistRlpcT
-- * Misc. Rlpc Monad -related types
, RLPCOptions(RLPCOptions), IsRlpcError(..), RlpcError(..)
, MsgEnvelope(..), Severity(..)
, addDebugMsg
, whenDFlag, whenFFlag
-- * Misc. Utilities
, forFiles_, withSource
-- * Convenient re-exports
, addFatal, addWound, def
)
where
----------------------------------------------------------------------------------
import Control.Arrow ((>>>))
import Control.Exception
import Control.Monad
import Control.Monad.Reader
import Control.Monad.State (MonadState(state))
import Control.Monad.Errorful
import Control.Monad.IO.Class
import Compiler.RlpcError
import Compiler.Types
import Data.Functor.Identity
import Data.Default.Class
import Data.Foldable
import GHC.Generics (Generic)
import Data.Maybe
import Data.Hashable (Hashable)
import Data.HashSet (HashSet)
import Data.HashSet qualified as S
import Data.Coerce
import Lens.Micro
import Lens.Micro.TH
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Text.ANSI qualified as Ansi
import Text.PrettyPrint hiding ((<>))
import Lens.Micro.Platform
import Lens.Micro.Platform.Internal
import System.Exit
----------------------------------------------------------------------------------
-- TODO: fancy errors
newtype RLPCT e m a = RLPCT {
runRLPCT :: ReaderT RLPCOptions (ErrorfulT e m) a
newtype RLPCT m a = RLPCT {
runRLPCT :: ReaderT RLPCOptions (ErrorfulT (MsgEnvelope RlpcError) m) a
}
-- TODO: incorrect ussage of MonadReader. RLPC should have its own
-- environment access functions
deriving (Functor, Applicative, Monad, MonadReader RLPCOptions)
deriving ( Functor, Applicative, Monad
, MonadReader RLPCOptions, MonadErrorful (MsgEnvelope RlpcError))
deriving instance (MonadIO m) => MonadIO (RLPCT e m)
rlpc :: (IsRlpcError e, Monad m)
=> (RLPCOptions -> (Maybe a, [MsgEnvelope e]))
-> RLPCT m a
rlpc f = RLPCT . ReaderT $ \opt ->
ErrorfulT . pure $ f opt & _2 . each . mapped %~ liftRlpcError
instance MonadTrans (RLPCT e) where
lift = RLPCT . lift . lift
type RLPC = RLPCT Identity
instance (MonadState s m) => MonadState s (RLPCT e m) where
state = lift . state
type RLPCIO = RLPCT IO
type RLPC e = RLPCT e Identity
type RLPCIO e = RLPCT e IO
evalRLPCT :: RLPCOptions
-> RLPCT e m a
-> m (Either e (a, [e]))
evalRLPCT o = runRLPCT >>> flip runReaderT o >>> runErrorfulT
instance (MonadIO m) => MonadIO (RLPCT m) where
evalRLPC :: RLPCOptions
-> RLPC e a
-> Either e (a, [e])
evalRLPC o m = coerce $ evalRLPCT o m
-> RLPC a
-> (Maybe a, [MsgEnvelope RlpcError])
evalRLPC opt r = runRLPCT r
& flip runReaderT opt
& runErrorful
evalRLPCIO :: (Exception e)
=> RLPCOptions
-> RLPCIO e a
-> IO (a, [e])
evalRLPCIO o m = do
m' <- evalRLPCT o m
case m' of
-- TODO: errors
Left e -> throwIO e
Right a -> pure a
evalRLPCT :: RLPCOptions
-> RLPCT m a
-> m (Maybe a, [MsgEnvelope RlpcError])
evalRLPCT opt r = runRLPCT r
& flip runReaderT opt
& runErrorfulT
liftErrorful :: (Monad m, IsRlpcError e) => ErrorfulT (MsgEnvelope e) m a -> RLPCT m a
liftErrorful e = RLPCT $ lift (fmap liftRlpcError `mapErrorful` e)
hoistRlpcT :: (forall a. m a -> n a)
-> RLPCT m a -> RLPCT n a
hoistRlpcT f rma = RLPCT $ ReaderT $ \opt ->
ErrorfulT $ f $ evalRLPCT opt rma
data RLPCOptions = RLPCOptions
{ _rlpcLogFile :: Maybe FilePath
, _rlpcDebugOpts :: DebugOpts
, _rlpcDFlags :: HashSet DebugFlag
, _rlpcFFlags :: HashSet CompilerFlag
, _rlpcEvaluator :: Evaluator
, _rlpcHeapTrigger :: Int
, _rlpcLanguage :: Language
, _rlpcInputFiles :: [FilePath]
}
deriving Show
@@ -107,58 +122,117 @@ data RLPCOptions = RLPCOptions
data Evaluator = EvaluatorGM | EvaluatorTI
deriving Show
data Severity = Error
| Warning
| Debug
deriving Show
-- temporary until we have a new doc building system
type ErrorDoc = String
class Diagnostic e where
errorDoc :: e -> ErrorDoc
instance (Monad m) => MonadErrorful e (RLPCT e m) where
addWound = RLPCT . lift . addWound
addFatal = RLPCT . lift . addFatal
data Language = LanguageRlp | LanguageCore
deriving Show
----------------------------------------------------------------------------------
instance Default RLPCOptions where
def = RLPCOptions
{ _rlpcLogFile = Nothing
, _rlpcDebugOpts = mempty
, _rlpcDFlags = mempty
, _rlpcFFlags = mempty
, _rlpcEvaluator = EvaluatorGM
, _rlpcHeapTrigger = 200
, _rlpcInputFiles = []
, _rlpcLanguage = LanguageRlp
}
type DebugOpts = HashSet DebugFlag
-- debug flags are passed with -dFLAG
type DebugFlag = Text
data DebugFlag = DDumpEval
| DDumpOpts
| DDumpAST
deriving (Show, Eq, Generic)
instance Hashable DebugFlag
type CompilerFlag = Text
makeLenses ''RLPCOptions
pure []
whenFlag :: (MonadReader s m) => SimpleGetter s Bool -> m () -> m ()
whenFlag l m = asks (^. l) >>= \a -> if a then m else pure ()
addDebugMsg :: (Monad m, IsText e) => Text -> e -> RLPCT m ()
addDebugMsg tag e = addWound . debugMsg tag $ Text [e ^. unpacked . packed]
-- there's probably a better way to write this. my current knowledge of lenses
-- is too weak.
flagGetter :: DebugFlag -> SimpleGetter RLPCOptions Bool
flagGetter d = to $ \s -> s ^. rlpcDebugOpts & S.member d
-- TODO: rewrite this with prisms once microlens-pro drops :3
whenDFlag :: (Monad m) => DebugFlag -> RLPCT m () -> RLPCT m ()
whenDFlag f m = do
-- mfw no `At` instance for HashSet
fs <- view rlpcDFlags
let a = S.member f fs
when a m
flagDDumpEval :: SimpleGetter RLPCOptions Bool
flagDDumpEval = flagGetter DDumpEval
whenFFlag :: (Monad m) => CompilerFlag -> RLPCT m () -> RLPCT m ()
whenFFlag f m = do
-- mfw no `At` instance for HashSet
fs <- view rlpcFFlags
let a = S.member f fs
when a m
flagDDumpOpts :: SimpleGetter RLPCOptions Bool
flagDDumpOpts = flagGetter DDumpOpts
--------------------------------------------------------------------------------
flagDDumpAST :: SimpleGetter RLPCOptions Bool
flagDDumpAST = flagGetter DDumpAST
evalRLPCIO :: RLPCOptions -> RLPCIO a -> IO a
evalRLPCIO opt r = do
(ma,es) <- evalRLPCT opt r
putRlpcErrs opt es
case ma of
Just x -> pure x
Nothing -> die "Failed, no code compiled."
putRlpcErrs :: RLPCOptions -> [MsgEnvelope RlpcError] -> IO ()
putRlpcErrs opts = filter byTag
>>> traverse_ (putStrLn . ('\n':) . prettyRlpcMsg)
where
dflags = opts ^. rlpcDFlags
byTag :: MsgEnvelope RlpcError -> Bool
byTag (view msgSeverity -> SevDebug t) =
t `S.member` dflags
prettyRlpcMsg :: MsgEnvelope RlpcError -> String
prettyRlpcMsg m@(view msgSeverity -> SevDebug _) = prettyRlpcDebugMsg m
prettyRlpcMsg m = render $ docRlpcErr m
prettyRlpcDebugMsg :: MsgEnvelope RlpcError -> String
prettyRlpcDebugMsg msg =
T.unpack . foldMap mkLine $ [ t' | t <- ts, t' <- T.lines t ]
where
mkLine s = "-d" <> tag <> ": " <> s <> "\n"
Text ts = msg ^. msgDiagnostic
SevDebug tag = msg ^. msgSeverity
docRlpcErr :: MsgEnvelope RlpcError -> Doc
docRlpcErr msg = header
$$ nest 2 bullets
$$ source
where
source = vcat $ zipWith (<+>) rule srclines
where
rule = repeat (ttext . Ansi.blue . Ansi.bold $ "|")
srclines = ["", "<problematic source code>", ""]
filename = msgColour "<input>"
pos = msgColour $ tshow (msg ^. msgSpan . srcspanLine)
<> ":"
<> tshow (msg ^. msgSpan . srcspanColumn)
header = ttext $ filename <> msgColour ":" <> pos <> msgColour ": "
<> errorColour "error" <> msgColour ":"
bullets = let Text ts = msg ^. msgDiagnostic
in vcat $ hang "" 2 . ttext . msgColour <$> ts
msgColour = Ansi.white . Ansi.bold
errorColour = Ansi.red . Ansi.bold
ttext = text . T.unpack
tshow :: (Show a) => a -> Text
tshow = T.pack . show
--------------------------------------------------------------------------------
forFiles_ :: (Monad m)
=> (FilePath -> RLPCT m a)
-> RLPCT m ()
forFiles_ k = do
fs <- view rlpcInputFiles
forM_ fs k
-- TODO: catch any exceptions, i.e. non-existent files should be handled by the
-- compiler
withSource :: (MonadIO m) => FilePath -> (Text -> RLPCT m a) -> RLPCT m a
withSource f k = liftIO (T.readFile f) >>= k

77
src/Compiler/RlpcError.hs Normal file
View File

@@ -0,0 +1,77 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
module Compiler.RlpcError
( IsRlpcError(..)
, MsgEnvelope(..)
, Severity(..)
, RlpcError(..)
, msgSpan
, msgDiagnostic
, msgSeverity
, liftRlpcErrors
, errorMsg
, debugMsg
-- * Located Comonad
, Located(..)
, SrcSpan(..)
)
where
----------------------------------------------------------------------------------
import Control.Monad.Errorful
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Exts (IsString(..))
import Lens.Micro.Platform
import Lens.Micro.Platform.Internal
import Compiler.Types
----------------------------------------------------------------------------------
data MsgEnvelope e = MsgEnvelope
{ _msgSpan :: SrcSpan
, _msgDiagnostic :: e
, _msgSeverity :: Severity
}
deriving (Functor, Show)
newtype RlpcError = Text [Text]
deriving Show
instance IsString RlpcError where
fromString = Text . pure . T.pack
class IsRlpcError e where
liftRlpcError :: e -> RlpcError
instance IsRlpcError RlpcError where
liftRlpcError = id
data Severity = SevWarning
| SevError
| SevDebug Text
deriving Show
makeLenses ''MsgEnvelope
liftRlpcErrors :: (Functor m, IsRlpcError e)
=> ErrorfulT e m a
-> ErrorfulT RlpcError m a
liftRlpcErrors = mapErrorful liftRlpcError
instance (IsRlpcError e) => IsRlpcError (MsgEnvelope e) where
liftRlpcError msg = msg ^. msgDiagnostic & liftRlpcError
errorMsg :: SrcSpan -> e -> MsgEnvelope e
errorMsg s e = MsgEnvelope
{ _msgSpan = s
, _msgDiagnostic = e
, _msgSeverity = SevError
}
debugMsg :: Text -> e -> MsgEnvelope e
debugMsg tag e = MsgEnvelope
-- TODO: not pretty, but it is a debug message after all
{ _msgSpan = SrcSpan 0 0 0 0
, _msgDiagnostic = e
, _msgSeverity = SevDebug tag
}

78
src/Compiler/Types.hs Normal file
View File

@@ -0,0 +1,78 @@
module Compiler.Types
( SrcSpan(..)
, srcspanLine, srcspanColumn, srcspanAbs, srcspanLen
, Located(..)
, (<<~), (<~>)
-- * Re-exports
, Comonad
, Apply
, Bind
)
where
--------------------------------------------------------------------------------
import Control.Comonad
import Data.Functor.Apply
import Data.Functor.Bind
import Control.Lens hiding ((<<~))
--------------------------------------------------------------------------------
-- | Token wrapped with a span (line, column, absolute, length)
data Located a = Located SrcSpan a
deriving (Show, Functor)
instance Apply Located where
liftF2 f (Located sa p) (Located sb q)
= Located (sa <> sb) (p `f` q)
instance Bind Located where
Located sa a >>- k = Located (sa <> sb) b
where
Located sb b = k a
instance Comonad Located where
extract (Located _ a) = a
extend ck w@(Located p _) = Located p (ck w)
data SrcSpan = SrcSpan
!Int -- ^ Line
!Int -- ^ Column
!Int -- ^ Absolute
!Int -- ^ Length
deriving Show
tupling :: Iso' SrcSpan (Int, Int, Int, Int)
tupling = iso (\ (SrcSpan a b c d) -> (a,b,c,d))
(\ (a,b,c,d) -> SrcSpan a b c d)
srcspanLine, srcspanColumn, srcspanAbs, srcspanLen :: Lens' SrcSpan Int
srcspanLine = tupling . _1
srcspanColumn = tupling . _2
srcspanAbs = tupling . _3
srcspanLen = tupling . _4
instance Semigroup SrcSpan where
SrcSpan la ca aa sa <> SrcSpan lb cb ab sb = SrcSpan l c a s where
l = min la lb
c = min ca cb
a = min aa ab
s = case aa `compare` ab of
EQ -> max sa sb
LT -> max sa (ab + lb - aa)
GT -> max sb (aa + la - ab)
-- | A synonym for '(<<=)' with a tighter precedence and left-associativity for
-- use with '(<~>)' in a sort of, comonadic pseudo-applicative style.
(<<~) :: (Comonad w) => (w a -> b) -> w a -> w b
(<<~) = (<<=)
infixl 4 <<~
-- | Similar to '(<*>)', but with a cokleisli arrow.
(<~>) :: (Comonad w, Bind w) => w (w a -> b) -> w a -> w b
mc <~> ma = mc >>- \f -> ma =>> f
infixl 4 <~>

View File

@@ -1,65 +1,83 @@
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TupleSections, PatternSynonyms #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE UndecidableInstances #-}
module Control.Monad.Errorful
( ErrorfulT
, runErrorfulT
( ErrorfulT(..)
, Errorful
, pattern Errorful
, errorful
, runErrorful
, mapErrorful
, MonadErrorful(..)
)
where
----------------------------------------------------------------------------------
import Control.Monad.State.Strict
import Control.Monad.Reader
import Control.Monad.Trans
import Data.Functor.Identity
import Data.Coerce
import Data.HashSet (HashSet)
import Data.HashSet qualified as H
import Lens.Micro
----------------------------------------------------------------------------------
newtype ErrorfulT e m a = ErrorfulT { runErrorfulT :: m (Either e (a, [e])) }
newtype ErrorfulT e m a = ErrorfulT { runErrorfulT :: m (Maybe a, [e]) }
type Errorful e = ErrorfulT e Identity
pattern Errorful :: (Either e (a, [e])) -> Errorful e a
pattern Errorful :: (Maybe a, [e]) -> Errorful e a
pattern Errorful a = ErrorfulT (Identity a)
runErrorful :: Errorful e a -> Either e (a, [e])
errorful :: (Applicative m) => (Maybe a, [e]) -> ErrorfulT e m a
errorful = ErrorfulT . pure
runErrorful :: Errorful e a -> (Maybe a, [e])
runErrorful m = coerce (runErrorfulT m)
class (Applicative m) => MonadErrorful e m | m -> e where
addWound :: e -> m ()
addFatal :: e -> m a
-- not sure if i want to add this yet...
-- catchWound :: m a -> (e -> m a) -> m a
addWound :: e -> m ()
addFatal :: e -> m a
instance (Applicative m) => MonadErrorful e (ErrorfulT e m) where
addWound e = ErrorfulT $ pure . Right $ ((), [e])
addFatal e = ErrorfulT $ pure . Left $ e
addWound e = ErrorfulT $ pure (Just (), [e])
addFatal e = ErrorfulT $ pure (Nothing, [e])
instance MonadTrans (ErrorfulT e) where
lift m = ErrorfulT (Right . (,[]) <$> m)
lift m = ErrorfulT ((\x -> (Just x,[])) <$> m)
instance (MonadIO m) => MonadIO (ErrorfulT e m) where
liftIO = lift . liftIO
instance (Functor m) => Functor (ErrorfulT e m) where
fmap f (ErrorfulT m) = ErrorfulT $ fmap (_1 %~ f) <$> m
fmap f (ErrorfulT m) = ErrorfulT (m & mapped . _1 . _Just %~ f)
instance (Applicative m) => Applicative (ErrorfulT e m) where
pure a = ErrorfulT (pure . Right $ (a, []))
pure a = ErrorfulT . pure $ (Just a, [])
m <*> a = ErrorfulT (m' `apply` a')
where
m' = runErrorfulT m
a' = runErrorfulT a
-- TODO: strict concatenation
apply = liftA2 $ liftA2 (\ (f,e1) (x,e2) -> (f x, e1 ++ e2))
ErrorfulT m <*> ErrorfulT n = ErrorfulT $ m `apply` n where
apply :: m (Maybe (a -> b), [e]) -> m (Maybe a, [e]) -> m (Maybe b, [e])
apply = liftA2 $ \ (mf,e1) (ma,e2) -> (mf <*> ma, e1 <> e2)
instance (Monad m) => Monad (ErrorfulT e m) where
ErrorfulT m >>= k = ErrorfulT $ do
m' <- m
case m' of
Right (a,es) -> runErrorfulT (k a)
Left e -> pure (Left e)
(a,es) <- m
case a of
Just x -> runErrorfulT (k x)
Nothing -> pure (Nothing, es)
mapErrorful :: (Functor m) => (e -> e') -> ErrorfulT e m a -> ErrorfulT e' m a
mapErrorful f (ErrorfulT m) = ErrorfulT $
m & mapped . _2 . mapped %~ f
-- when microlens-pro drops we can write this as
-- mapErrorful f = coerced . mapped . _2 . mapped %~ f
-- lol
--------------------------------------------------------------------------------
-- daily dose of n^2 instances
instance (Monad m, MonadErrorful e m) => MonadErrorful e (ReaderT r m) where
addWound = lift . addWound
addFatal = lift . addFatal

View File

@@ -0,0 +1,21 @@
module Control.Monad.Utils
( mapAccumLM
)
where
----------------------------------------------------------------------------------
import Data.Tuple (swap)
import Control.Monad.State
----------------------------------------------------------------------------------
-- | Monadic variant of @mapAccumL@
mapAccumLM :: forall m t s a b. (Monad m, Traversable t)
=> (s -> a -> m (s, b))
-> s
-> t a
-> m (s, t b)
mapAccumLM k s t = swap <$> runStateT (traverse k' t) s
where
k' :: a -> StateT s m b
k' a = StateT $ fmap swap <$> flip k a

View File

@@ -4,18 +4,18 @@ Description : Core examples (may eventually be unit tests)
-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
module Core.Examples
( fac3
, sumList
, constDivZero
, idCase
) where
module Core.Examples where
----------------------------------------------------------------------------------
import Core.Syntax
import Core.TH
----------------------------------------------------------------------------------
-- TODO: my shitty lexer isn't inserting semicolons
-- fac3 = undefined
-- sumList = undefined
-- constDivZero = undefined
-- idCase = undefined
---
letrecExample :: Program'
letrecExample = [coreProg|
@@ -142,8 +142,8 @@ simple1 = [coreProg|
caseBool1 :: Program'
caseBool1 = [coreProg|
_if c x y = case c of
{ 1 -> x
; 0 -> y
{ <1> -> x
; <0> -> y
};
false = Pack{0 0};
@@ -155,8 +155,8 @@ caseBool1 = [coreProg|
fac3 :: Program'
fac3 = [coreProg|
fac n = case (==#) n 0 of
{ 1 -> 1
; 0 -> (*#) n (fac ((-#) n 1))
{ <1> -> 1
; <0> -> (*#) n (fac ((-#) n 1))
};
main = fac 3;
@@ -170,8 +170,8 @@ sumList = [coreProg|
cons x y = Pack{1 2} x y;
list = cons 1 (cons 2 (cons 3 nil));
sum l = case l of
{ 0 -> 0
; 1 x xs -> (+#) x (sum xs)
{ <0> -> 0
; <1> x xs -> (+#) x (sum xs)
};
main = sum list;
|]
@@ -187,34 +187,61 @@ idCase = [coreProg|
id x = x;
main = id (case Pack{1 0} of
{ 1 -> (+#) 2 3
{ <1> -> (+#) 2 3
})
|]
corePrelude :: Module Name
corePrelude = Module (Just ("Prelude", [])) $
-- non-primitive defs
[coreProg|
id x = x;
k x y = x;
k1 x y = y;
s f g x = f x (g x);
compose f g x = f (g x);
twice f x = f (f x);
fst p = casePair# p k;
snd p = casePair# p k1;
head l = caseList# l abort# k;
tail l = caseList# l abort# k1;
_length_cc x xs = (+#) 1 (length xs);
length l = caseList# l 0 length_cc;
-- NOTE: the GM primitive (==#) returns an untyped constructor with tag 1 for
-- true, and 0 for false. See: GM.boxBool
namedBoolCase :: Program'
namedBoolCase = [coreProg|
{-# PackData True 1 0 #-}
{-# PackData False 0 0 #-}
main = case (==#) 1 1 of
{ True -> 123
; False -> 456
}
|]
<>
-- primitive constructors need some specialised wiring:
Program
[ ScDef "False" [] $ Con 0 0
, ScDef "True" [] $ Con 1 0
, ScDef "MkPair" [] $ Con 0 2
, ScDef "Nil" [] $ Con 1 0
, ScDef "Cons" [] $ Con 2 2
]
namedConsCase :: Program'
namedConsCase = [coreProg|
{-# PackData Nil 0 0 #-}
{-# PackData Cons 1 2 #-}
Nil = Pack{0 0};
Cons = Pack{1 2};
foldr f z l = case l of
{ Nil -> z
; Cons x xs -> f x (foldr f z xs)
};
list = Cons 1 (Cons 2 (Cons 3 Nil));
main = foldr (+#) 0 list
|]
-- corePrelude :: Module Name
-- corePrelude = Module (Just ("Prelude", [])) $
-- -- non-primitive defs
-- [coreProg|
-- id x = x;
-- k x y = x;
-- k1 x y = y;
-- s f g x = f x (g x);
-- compose f g x = f (g x);
-- twice f x = f (f x);
-- fst p = casePair# p k;
-- snd p = casePair# p k1;
-- head l = caseList# l abort# k;
-- tail l = caseList# l abort# k1;
-- _length_cc x xs = (+#) 1 (length xs);
-- length l = caseList# l 0 length_cc;
-- |]
-- <>
-- -- primitive constructors need some specialised wiring:
-- Program
-- [ ScDef "False" [] $ Con 0 0
-- , ScDef "True" [] $ Con 1 0
-- , ScDef "MkPair" [] $ Con 0 2
-- , ScDef "Nil" [] $ Con 1 0
-- , ScDef "Cons" [] $ Con 2 2
-- ]
--}

279
src/Core/HindleyMilner.hs Normal file
View File

@@ -0,0 +1,279 @@
{-|
Module : Core.HindleyMilner
Description : Hindley-Milner type system
-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Core.HindleyMilner
( Context'
, infer
, check
, checkCoreProg
, checkCoreProgR
, TypeError(..)
, HMError
)
where
----------------------------------------------------------------------------------
import Lens.Micro
import Lens.Micro.Mtl
import Lens.Micro.Platform
import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import Data.HashMap.Strict qualified as H
import Data.Foldable (traverse_)
import Compiler.RLPC
import Control.Monad (foldM, void, forM)
import Control.Monad.Errorful (Errorful, addFatal)
import Control.Monad.State
import Control.Monad.Utils (mapAccumLM)
import Text.Printf
import Core.Syntax
----------------------------------------------------------------------------------
-- | Annotated typing context -- I have a feeling we're going to want this in the
-- future.
type Context b = [(b, Type)]
-- | Unannotated typing context, AKA our beloved Γ.
type Context' = Context Name
-- TODO: Errorful monad?
-- | Type error enum.
data TypeError
-- | Two types could not be unified
= TyErrCouldNotUnify Type Type
-- | @x@ could not be unified with @t@ because @x@ occurs in @t@
| TyErrRecursiveType Name Type
-- | Untyped, potentially undefined variable
| TyErrUntypedVariable Name
| TyErrMissingTypeSig Name
deriving (Show, Eq)
instance IsRlpcError TypeError where
liftRlpcError = \case
-- todo: use anti-parser instead of show
TyErrCouldNotUnify t u -> Text
[ T.pack $ printf "Could not match type `%s` with `%s`."
(show t) (show u)
, "Expected: " <> tshow t
, "Got: " <> tshow u
]
TyErrUntypedVariable n -> Text
[ "Untyped (likely undefined) variable `" <> n <> "`"
]
TyErrRecursiveType t x -> Text
[ T.pack $ printf "recursive type error lol"
]
where tshow = T.pack . show
-- | Synonym for @Errorful [TypeError]@. This means an @HMError@ action may
-- throw any number of fatal or nonfatal errors. Run with @runErrorful@.
type HMError = Errorful TypeError
-- TODO: better errors. Errorful-esque, with cummulative errors instead of
-- instantly dying.
-- | Assert that an expression unifies with a given type
--
-- >>> let e = [coreProg|3|]
-- >>> check [] (TyCon "Bool") e
-- Left (TyErrCouldNotUnify (TyCon "Bool") (TyCon "Int#"))
-- >>> check [] (TyCon "Int#") e
-- Right ()
check :: Context' -> Type -> Expr' -> HMError ()
check g t1 e = do
t2 <- infer g e
void $ unify [(t1,t2)]
-- | Typecheck program. I plan to allow for *some* inference in the future, but
-- in the mean time all top-level binders must have a type annotation.
checkCoreProg :: Program' -> HMError ()
checkCoreProg p = scDefs
& traverse_ k
where
scDefs = p ^. programScDefs
g = gatherTypeSigs p
k :: ScDef' -> HMError ()
k sc = case lookup scname g of
Just t -> check g t (sc ^. _rhs)
Nothing -> addFatal $ TyErrMissingTypeSig scname
where scname = sc ^. _lhs._1
-- | @checkCoreProgR p@ returns @p@ if @p@ successfully typechecks.
checkCoreProgR :: (Applicative m) => Program' -> RLPCT m Program'
checkCoreProgR p = undefined
{-# WARNING checkCoreProgR "unimpl" #-}
-- | Infer the type of an expression under some context.
--
-- >>> let g1 = [("id", TyVar "a" :-> TyVar "a")]
-- >>> let g2 = [("id", (TyVar "a" :-> TyVar "a") :-> TyVar "a" :-> TyVar "a")]
-- >>> infer g1 [coreExpr|id 3|]
-- Right TyInt
-- >>> infer g2 [coreExpr|id 3|]
-- Left (TyErrCouldNotUnify (TyVar "a" :-> TyVar "a") TyInt)
infer :: Context' -> Expr' -> HMError Type
infer g e = do
(t,cs) <- gather g e
-- apply all unified constraints
foldr (uncurry subst) t <$> unify cs
-- | A @Constraint@ between two types describes the requirement that the pair
-- must unify
type Constraint = (Type, Type)
-- | Type of an expression under some context, and gather the constraints
-- necessary to unify. Note that this is not the same as @infer@, as the
-- expression will likely be given a fresh type variable along with a
-- constraint, rather than the solved type.
--
-- For example, if the context says "@id@ has type a -> a," in an application of
-- @id 3@, the whole application is assigned type @$a0@ and the constraint that
-- @id@ must unify with type @Int -> $a0@ is generated.
--
-- >>> gather [("id", TyVar "a" :-> TyVar "a")] [coreExpr|id 3|]
-- (TyVar "$a0",[(TyVar "a" :-> TyVar "a",TyInt :-> TyVar "$a0")])
gather :: Context' -> Expr' -> HMError (Type, [Constraint])
gather = \g e -> runStateT (go g e) ([],0) <&> \ (t,(cs,_)) -> (t,cs) where
go :: Context' -> Expr' -> StateT ([Constraint], Int) HMError Type
go g = \case
Lit (IntL _) -> pure TyInt
Var k -> lift $ maybe e pure $ lookup k g
where e = addFatal $ TyErrUntypedVariable k
App f x -> do
tf <- go g f
tx <- go g x
tfx <- uniqueVar
addConstraint tf (tx :-> tfx)
pure tfx
Let NonRec bs e -> do
g' <- buildLetContext g bs
go g' e
Let Rec bs e -> do
g' <- buildLetrecContext g bs
go g' e
Lam bs e -> case bs of
[x] -> do
tx <- uniqueVar
let g' = (x,tx) : g
te <- go g' e
pure (tx :-> te)
-- TODO lambda, case
buildLetrecContext :: Context' -> [Binding']
-> StateT ([Constraint], Int) HMError Context'
buildLetrecContext g bs = do
let f ag (k := _) = do
n <- uniqueVar
pure ((k,n) : ag)
rg <- foldM f g bs
let k ag (k := v) = do
t <- go rg v
pure ((k,t) : ag)
foldM k g bs
-- | augment a context with the inferred types of each binder. the returned
-- context is linearly accumulated, meaning that the context used to infer each binder
-- will include the inferred types of all previous binder
buildLetContext :: Context' -> [Binding']
-> StateT ([Constraint], Int) HMError Context'
buildLetContext = foldM k where
k :: Context' -> Binding' -> StateT ([Constraint], Int) HMError Context'
k g (x := y) = do
ty <- go g y
pure ((x,ty) : g)
uniqueVar :: StateT ([Constraint], Int) HMError Type
uniqueVar = do
n <- use _2
_2 %= succ
pure (TyVar . T.pack $ '$' : 'a' : show n)
addConstraint :: Type -> Type -> StateT ([Constraint], Int) HMError ()
addConstraint t u = _1 %= ((t, u):)
-- | Unify a list of constraints, meaning that pairs between types are turned
-- into pairs of type variables and types. A useful thought model is to think of
-- it as solving an equation such that the unknown variable is the left-hand
-- side.
unify :: [Constraint] -> HMError Context'
unify = go mempty where
go :: Context' -> [Constraint] -> HMError Context'
-- nothing left! return accumulated context
go g [] = pure g
go g (c:cs) = case c of
-- primitives may of course unify with themselves
(TyInt, TyInt) -> go g cs
-- `x` unifies with `x`
(TyVar t, TyVar u) | t == u -> go g cs
-- a type variable `x` unifies with an arbitrary type `t` if `t` does
-- not reference `x`
(TyVar x, t) -> unifyTV g x t cs
(t, TyVar x) -> unifyTV g x t cs
-- two functions may be unified if their domain and codomain unify
(a :-> b, x :-> y) -> go g $ (a,x) : (b,y) : cs
-- anything else is a failure :(
(t,u) -> addFatal $ TyErrCouldNotUnify t u
unifyTV :: Context' -> Name -> Type -> [Constraint] -> HMError Context'
unifyTV g x t cs | occurs t = addFatal $ TyErrRecursiveType x t
| otherwise = go g' substed
where
g' = (x,t) : g
substed = cs & each . both %~ subst x t
occurs (a :-> b) = occurs a || occurs b
occurs (TyVar y)
| x == y = True
occurs _ = False
gatherTypeSigs :: Program b -> Context b
gatherTypeSigs p = p ^. programTypeSigs
& H.toList
-- | The expression @subst x t e@ substitutes all occurences of @x@ in @e@ with
-- @t@
--
-- >>> subst "a" (TyCon "Int") (TyVar "a")
-- TyCon "Int"
-- >>> subst "a" (TyCon "Int") (TyVar "a" :-> TyVar "a")
-- TyCon "Int" :-> TyCon "Int"
subst :: Name -> Type -> Type -> Type
subst x t (TyVar y) | x == y = t
subst x t (a :-> b) = subst x t a :-> subst x t b
subst _ _ e = e
--------------------------------------------------------------------------------
demoContext :: Context'
demoContext =
[ ("fix", (TyVar "a" :-> TyVar "a") :-> TyVar "a")
, ("add", TyInt :-> TyInt :-> TyInt)
, ("==", TyInt :-> TyInt :-> TyCon "Bool")
, ("True", TyCon "Bool")
, ("False", TyCon "Bool")
]
pprintType :: Type -> String
pprintType (s :-> t) = "(" <> pprintType s <> " -> " <> pprintType t <> ")"
pprintType TyFun = "(->)"
pprintType (TyVar x) = x ^. unpacked
pprintType (TyCon t) = t ^. unpacked

View File

@@ -3,8 +3,10 @@
Module : Core.Lex
Description : Lexical analysis for the core language
-}
{-# LANGUAGE OverloadedStrings #-}
module Core.Lex
( lexCore
, lexCoreR
, lexCore'
, CoreToken(..)
, SrcError(..)
@@ -15,13 +17,19 @@ module Core.Lex
where
import Data.Char (chr)
import Debug.Trace
import Data.Text (Text)
import Data.Text qualified as T
import Data.String (IsString(..))
import Data.Functor.Identity
import Core.Syntax
import Compiler.RLPC
-- TODO: unify Located definitions
import Compiler.RlpcError hiding (Located(..))
import Lens.Micro
import Lens.Micro.TH
}
%wrapper "monad"
%wrapper "monad-strict-text"
$whitechar = [ \t\n\r\f\v]
$special = [\(\)\,\;\[\]\{\}]
@@ -59,6 +67,8 @@ $white_no_nl = $white # $nl
@decimal = $digit+
@alttag = "<" $digit+ ">"
rlp :-
<0>
@@ -68,6 +78,7 @@ rlp :-
"{" { constTok TokenLBrace }
"}" { constTok TokenRBrace }
";" { constTok TokenSemicolon }
"::" { constTok TokenHasType }
"@" { constTok TokenTypeApp }
"{-#" { constTok TokenLPragma `andBegin` pragma }
@@ -80,17 +91,19 @@ rlp :-
"where" { constTok TokenWhere }
"Pack" { constTok TokenPack } -- temp
"\\" { constTok TokenLambda }
"\" { constTok TokenLambda }
"λ" { constTok TokenLambda }
"=" { constTok TokenEquals }
"->" { constTok TokenArrow }
@alttag { lexWith ( TokenAltTag . read @Int . T.unpack
. T.drop 1 . T.init ) }
@varname { lexWith TokenVarName }
@conname { lexWith TokenConName }
@varsym { lexWith TokenVarSym }
@consym { lexWith TokenConSym }
@decimal { lexWith (TokenLitInt . read @Int) }
@decimal { lexWith (TokenLitInt . read @Int . T.unpack) }
$white { skip }
\n { skip }
@@ -128,16 +141,18 @@ data CoreToken = TokenLet
| TokenConName Name
| TokenVarSym Name
| TokenConSym Name
| TokenAltTag Tag
| TokenEquals
| TokenLParen
| TokenRParen
| TokenLBrace
| TokenRBrace
| TokenSemicolon
| TokenHasType
| TokenTypeApp
| TokenLPragma
| TokenRPragma
| TokenWord String
| TokenWord Text
| TokenEOF
deriving Show
@@ -155,25 +170,26 @@ data SrcErrorType = SrcErrLexical String
type Lexer = AlexInput -> Int -> Alex (Located CoreToken)
lexWith :: (String -> CoreToken) -> Lexer
lexWith f (AlexPn _ y x,_,_,s) l = pure $ Located y x l (f $ take l s)
lexWith :: (Text -> CoreToken) -> Lexer
lexWith f (AlexPn _ y x,_,_,s) l = pure $ Located y x l (f $ T.take l s)
-- | The main lexer driver.
lexCore :: String -> RLPC SrcError [Located CoreToken]
lexCore :: Text -> RLPC [Located CoreToken]
lexCore s = case m of
Left e -> addFatal err
where err = SrcError
{ _errSpan = (0,0,0) -- TODO: location
, _errSeverity = Error
, _errDiagnostic = SrcErrLexical e
}
Left e -> error "core lex error"
Right ts -> pure ts
where
m = runAlex s lexStream
lexCoreR :: forall m. (Applicative m) => Text -> RLPCT m [Located CoreToken]
lexCoreR = hoistRlpcT generalise . lexCore
where
generalise :: forall a. Identity a -> m a
generalise (Identity a) = pure a
-- | @lexCore@, but the tokens are stripped of location info. Useful for
-- debugging
lexCore' :: String -> RLPC SrcError [CoreToken]
lexCore' :: Text -> RLPC [CoreToken]
lexCore' s = fmap f <$> lexCore s
where f (Located _ _ _ t) = t
@@ -188,6 +204,14 @@ data ParseError = ParErrLexical String
| ParErrParse
deriving Show
-- TODO:
instance IsRlpcError SrcError where
liftRlpcError = Text . pure . T.pack . show
-- TODO:
instance IsRlpcError ParseError where
liftRlpcError = Text . pure . T.pack . show
alexEOF :: Alex (Located CoreToken)
alexEOF = Alex $ \ st@(AlexState { alex_pos = AlexPn _ y x }) ->
Right (st, Located y x 0 TokenEOF)

View File

@@ -3,12 +3,13 @@
Module : Core.Parse
Description : Parser for the Core language
-}
{-# LANGUAGE OverloadedStrings, ViewPatterns #-}
module Core.Parse
( parseCore
, parseCoreExpr
, parseCoreProg
, parseCoreProgR
, module Core.Lex -- temp convenience
, parseTmp
, SrcError
, Module
)
@@ -16,10 +17,19 @@ module Core.Parse
import Control.Monad ((>=>))
import Data.Foldable (foldl')
import Data.Functor.Identity
import Core.Syntax
import Core.Lex
import Compiler.RLPC
import Control.Monad
import Lens.Micro
import Data.Default.Class (def)
import Data.Hashable (Hashable)
import Data.List.Extra
import Data.Text.IO qualified as TIO
import Data.Text (Text)
import Data.Text qualified as T
import Data.HashMap.Strict qualified as H
}
%name parseCore Module
@@ -27,7 +37,7 @@ import Data.Default.Class (def)
%name parseCoreProg StandaloneProgram
%tokentype { Located CoreToken }
%error { parseError }
%monad { RLPC SrcError }
%monad { RLPC } { happyBind } { happyPure }
%token
let { Located _ _ _ TokenLet }
@@ -43,6 +53,7 @@ import Data.Default.Class (def)
varsym { Located _ _ _ (TokenVarSym $$) }
conname { Located _ _ _ (TokenConName $$) }
consym { Located _ _ _ (TokenConSym $$) }
alttag { Located _ _ _ (TokenAltTag $$) }
word { Located _ _ _ (TokenWord $$) }
'λ' { Located _ _ _ TokenLambda }
'->' { Located _ _ _ TokenArrow }
@@ -55,6 +66,7 @@ import Data.Default.Class (def)
'{-#' { Located _ _ _ TokenLPragma }
'#-}' { Located _ _ _ TokenRPragma }
';' { Located _ _ _ TokenSemicolon }
'::' { Located _ _ _ TokenHasType }
eof { Located _ _ _ TokenEOF }
%%
@@ -71,16 +83,46 @@ StandaloneProgram :: { Program Name }
StandaloneProgram : Program eof { $1 }
Program :: { Program Name }
Program : ScDefs { Program $1 }
Program : ScTypeSig ';' Program { insTypeSig $1 $3 }
| ScTypeSig OptSemi { singletonTypeSig $1 }
| ScDef ';' Program { insScDef $1 $3 }
| ScDef OptSemi { singletonScDef $1 }
| TLPragma Program {% doTLPragma $1 $2 }
| TLPragma {% doTLPragma $1 mempty }
TLPragma :: { Pragma }
: '{-#' Words '#-}' { Pragma $2 }
Words :: { [Text] }
: Words word { $1 `snoc` $2 }
| word { [$1] }
OptSemi :: { () }
OptSemi : ';' { () }
| {- epsilon -} { () }
ScTypeSig :: { (Name, Type) }
ScTypeSig : Var '::' Type { ($1,$3) }
ScDefs :: { [ScDef Name] }
ScDefs : ScDef ';' ScDefs { $1 : $3 }
| ScDef ';' { [$1] }
| ScDef { [$1] }
| {- epsilon -} { [] }
ScDef :: { ScDef Name }
ScDef : Var ParList '=' Expr { ScDef $1 $2 $4 }
-- hack to allow constructors to be compiled into scs
| Con ParList '=' Expr { ScDef $1 $2 $4 }
Type :: { Type }
Type : Type1 { $1 }
Type1 :: { Type }
Type1 : '(' Type ')' { $2 }
| Type1 '->' Type { $1 :-> $3 }
-- do we want to allow symbolic names for tyvars and tycons?
| varname { TyVar $1 }
| conname { TyCon $1 }
ParList :: { [Name] }
ParList : Var ParList { $1 : $2 }
@@ -120,22 +162,15 @@ Alters : Alter ';' Alters { $1 : $3 }
| Alter { [$1] }
Alter :: { Alter Name }
Alter : litint ParList '->' Expr { Alter (AltData $1) $2 $4 }
Alter : alttag ParList '->' Expr { Alter (AltTag $1) $2 $4 }
| Con ParList '->' Expr { Alter (AltData $1) $2 $4 }
Expr1 :: { Expr Name }
Expr1 : litint { LitE $ IntL $1 }
Expr1 : litint { Lit $ IntL $1 }
| Id { Var $1 }
| PackCon { $1 }
| ExprPragma { $1 }
| '(' Expr ')' { $2 }
ExprPragma :: { Expr Name }
ExprPragma : '{-#' Words '#-}' {% exprPragma $2 }
Words :: { [String] }
Words : word Words { $1 : $2 }
| word { [$1] }
PackCon :: { Expr Name }
PackCon : pack '{' litint litint '}' { Con $3 $4 }
@@ -161,34 +196,64 @@ Con : '(' consym ')' { $2 }
{
parseError :: [Located CoreToken] -> RLPC SrcError a
parseError (Located y x l _ : _) = addFatal err
where err = SrcError
{ _errSpan = (y,x,l)
, _errSeverity = Error
, _errDiagnostic = SrcErrParse
}
parseError :: [Located CoreToken] -> RLPC a
parseError (Located y x l t : _) =
error $ show y <> ":" <> show x
<> ": parse error at token `" <> show t <> "'"
parseTmp :: IO (Module Name)
parseTmp = do
s <- readFile "/tmp/t.hs"
case parse s of
Left e -> error (show e)
Right (ts,_) -> pure ts
{-# WARNING parseError "unimpl" #-}
exprPragma :: [String] -> RLPC (Expr Name)
exprPragma ("AST" : e) = undefined
exprPragma _ = undefined
{-# WARNING exprPragma "unimpl" #-}
astPragma :: [String] -> RLPC (Expr Name)
astPragma _ = undefined
{-# WARNING astPragma "unimpl" #-}
insTypeSig :: (Hashable b) => (b, Type) -> Program b -> Program b
insTypeSig ts = programTypeSigs %~ uncurry H.insert ts
singletonTypeSig :: (Hashable b) => (b, Type) -> Program b
singletonTypeSig ts = insTypeSig ts mempty
insScDef :: (Hashable b) => ScDef b -> Program b -> Program b
insScDef sc = programScDefs %~ (sc:)
singletonScDef :: (Hashable b) => ScDef b -> Program b
singletonScDef sc = insScDef sc mempty
parseCoreProgR :: forall m. (Monad m) => [Located CoreToken] -> RLPCT m Program'
parseCoreProgR = ddumpast <=< (hoistRlpcT generalise . parseCoreProg)
where
parse = evalRLPC def . (lexCore >=> parseCore)
generalise :: forall a. Identity a -> m a
generalise (Identity a) = pure a
exprPragma :: [String] -> RLPC SrcError (Expr Name)
exprPragma ("AST" : e) = astPragma e
exprPragma _ = addFatal err
where err = SrcError
{ _errSpan = (0,0,0) -- TODO: span
, _errSeverity = Warning
, _errDiagnostic = SrcErrUnknownPragma "" -- TODO: missing pragma
}
ddumpast :: Program' -> RLPCT m Program'
ddumpast p = do
addDebugMsg "dump-ast" . show $ p
pure p
astPragma :: [String] -> RLPC SrcError (Expr Name)
astPragma = pure . read . unwords
happyBind :: RLPC a -> (a -> RLPC b) -> RLPC b
happyBind m k = m >>= k
happyPure :: a -> RLPC a
happyPure a = pure a
doTLPragma :: Pragma -> Program' -> RLPC Program'
-- TODO: warn unrecognised pragma
doTLPragma (Pragma []) p = pure p
doTLPragma (Pragma pr) p = case pr of
-- TODO: warn on overwrite
["PackData", n, readt -> t, readt -> a] ->
pure $ p & programDataTags . at n ?~ (t,a)
readt :: (Read a) => Text -> a
readt = read . T.unpack
}

View File

@@ -4,11 +4,21 @@ Description : Core ASTs and the like
-}
{-# LANGUAGE PatternSynonyms, OverloadedStrings #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DerivingStrategies, DerivingVia #-}
-- for recursion-schemes
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable
, TemplateHaskell, TypeFamilies #-}
module Core.Syntax
( Expr(..)
, ExprF(..)
, ExprF'(..)
, Type(..)
, Literal(..)
, pattern TyInt
, Lit(..)
, pattern (:$)
, pattern (:@)
, pattern (:->)
, Binding(..)
, AltCon(..)
, pattern (:=)
@@ -20,7 +30,11 @@ module Core.Syntax
, Module(..)
, Program(..)
, Program'
, Pragma(..)
, unliftScDef
, programScDefs
, programTypeSigs
, programDataTags
, Expr'
, ScDef'
, Alter'
@@ -32,40 +46,55 @@ module Core.Syntax
----------------------------------------------------------------------------------
import Data.Coerce
import Data.Pretty
import GHC.Generics
import Data.List (intersperse)
import Data.Function ((&))
import Data.Functor.Foldable
import Data.Functor.Foldable.TH (makeBaseFunctor)
import Data.String
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as H
import Data.Hashable
import Data.Text qualified as T
import Data.Char
import GHC.Generics
-- Lift instances for the Core quasiquoters
import Language.Haskell.TH.Syntax (Lift)
import Lens.Micro.TH (makeLenses)
import Lens.Micro
----------------------------------------------------------------------------------
data Expr b = Var Name
| Con Tag Int -- Con Tag Arity
| Con Tag Int -- ^ Con Tag Arity
| Case (Expr b) [Alter b]
| Lam [b] (Expr b)
| Let Rec [Binding b] (Expr b)
| App (Expr b) (Expr b)
| LitE Literal
| Type Type
| Lit Lit
deriving (Show, Read, Lift)
deriving instance (Eq b) => Eq (Expr b)
data Type = TyInt
| TyFun
data Type = TyFun
| TyVar Name
| TyApp Type Type
| TyConApp TyCon [Type]
| TyCon Name
deriving (Show, Read, Lift, Eq)
type TyCon = Name
pattern TyInt :: Type
pattern TyInt = TyCon "Int#"
infixl 2 :$
pattern (:$) :: Expr b -> Expr b -> Expr b
pattern (:$) :: Expr b -> Expr b -> Expr b
pattern f :$ x = App f x
infixl 2 :@
pattern (:@) :: Type -> Type -> Type
pattern f :@ x = TyApp f x
infixr 1 :->
pattern (:->) :: Type -> Type -> Type
pattern a :-> b = TyApp (TyApp TyFun a) b
{-# COMPLETE Binding :: Binding #-}
{-# COMPLETE (:=) :: Binding #-}
data Binding b = Binding b (Expr b)
@@ -74,7 +103,7 @@ data Binding b = Binding b (Expr b)
deriving instance (Eq b) => Eq (Binding b)
infixl 1 :=
pattern (:=) :: b -> (Expr b) -> (Binding b)
pattern (:=) :: b -> Expr b -> Binding b
pattern k := v = Binding k v
data Alter b = Alter AltCon [b] (Expr b)
@@ -82,32 +111,48 @@ data Alter b = Alter AltCon [b] (Expr b)
deriving instance (Eq b) => Eq (Alter b)
newtype Pragma = Pragma [T.Text]
data Rec = Rec
| NonRec
deriving (Show, Read, Eq, Lift)
data AltCon = AltData Tag
| AltLiteral Literal
data AltCon = AltData Name
| AltTag Tag
| AltLit Lit
| Default
deriving (Show, Read, Eq, Lift)
data Literal = IntL Int
newtype Lit = IntL Int
deriving (Show, Read, Eq, Lift)
type Name = String
type Name = T.Text
type Tag = Int
data ScDef b = ScDef b [b] (Expr b)
deriving (Show, Lift)
unliftScDef :: ScDef b -> Expr b
unliftScDef (ScDef _ as e) = Lam as e
data Module b = Module (Maybe (Name, [Name])) (Program b)
deriving (Show, Lift)
newtype Program b = Program [ScDef b]
deriving (Show, Lift)
data Program b = Program
{ _programScDefs :: [ScDef b]
, _programTypeSigs :: HashMap b Type
-- map constructors to their tag and arity
, _programDataTags :: HashMap b (Tag, Int)
}
deriving (Show, Lift, Generic)
deriving (Semigroup, Monoid)
via Generically (Program b)
programScDefs :: Lens' (Program b) [ScDef b]
programScDefs = lens coerce (const coerce)
makeLenses ''Program
makeBaseFunctor ''Expr
pure []
type ExprF' = ExprF Name
type Program' = Program Name
type Expr' = Expr Name
@@ -116,13 +161,14 @@ type Alter' = Alter Name
type Binding' = Binding Name
instance IsString (Expr b) where
fromString = Var
fromString = Var . fromString
instance Semigroup (Program b) where
(<>) = coerce $ (<>) @[ScDef b]
instance Monoid (Program b) where
mempty = Program []
instance IsString Type where
fromString "" = error "IsString Type string may not be empty"
fromString s
| isUpper c = TyCon . fromString $ s
| otherwise = TyVar . fromString $ s
where (c:_) = s
----------------------------------------------------------------------------------
@@ -155,5 +201,10 @@ instance HasLHS (Alter b) (Alter b) (AltCon, [b]) (AltCon, [b]) where
instance HasLHS (ScDef b) (ScDef b) (b, [b]) (b, [b]) where
_lhs = lens
(\ (ScDef n as _) -> (n,as))
(\ (ScDef _ _ e) (n',as') -> (ScDef n' as' e))
(\ (ScDef _ _ e) (n',as') -> ScDef n' as' e)
instance HasLHS (Binding b) (Binding b) b b where
_lhs = lens
(\ (k := _) -> k)
(\ (_ := e) k' -> k' := e)

View File

@@ -5,62 +5,46 @@ Description : Core quasiquoters
module Core.TH
( coreExpr
, coreProg
, core
, coreProgT
)
where
----------------------------------------------------------------------------------
import Language.Haskell.TH
import Language.Haskell.TH.Syntax hiding (Module)
import Language.Haskell.TH.Syntax hiding (Module)
import Language.Haskell.TH.Quote
import Control.Monad ((>=>))
import Control.Monad.IO.Class
import Control.Arrow ((>>>))
import Compiler.RLPC
import Data.Default.Class (def)
import Data.Text (Text)
import Data.Text qualified as T
import Core.Parse
import Core.Lex
import Core.Syntax
import Core.HindleyMilner (checkCoreProgR)
----------------------------------------------------------------------------------
core :: QuasiQuoter
core = QuasiQuoter
{ quoteExp = qCore
, quotePat = error "core quasiquotes may only be used in expressions"
, quoteType = error "core quasiquotes may only be used in expressions"
, quoteDec = error "core quasiquotes may only be used in expressions"
}
coreProg :: QuasiQuoter
coreProg = QuasiQuoter
{ quoteExp = qCoreProg
, quotePat = error "core quasiquotes may only be used in expressions"
, quoteType = error "core quasiquotes may only be used in expressions"
, quoteDec = error "core quasiquotes may only be used in expressions"
}
coreProg = mkqq $ lexCoreR >=> parseCoreProgR
coreExpr :: QuasiQuoter
coreExpr = QuasiQuoter
{ quoteExp = qCoreExpr
coreExpr = mkqq $ lexCoreR >=> parseCoreExpr
-- | Type-checked @coreProg@
coreProgT :: QuasiQuoter
coreProgT = mkqq $ lexCoreR >=> parseCoreProgR >=> checkCoreProgR
mkqq :: (Lift a) => (Text -> RLPC a) -> QuasiQuoter
mkqq p = QuasiQuoter
{ quoteExp = mkq p
, quotePat = error "core quasiquotes may only be used in expressions"
, quoteType = error "core quasiquotes may only be used in expressions"
, quoteDec = error "core quasiquotes may only be used in expressions"
}
qCore :: String -> Q Exp
qCore s = case parse s of
Left e -> error (show e)
Right (m,ts) -> lift m
where
parse = evalRLPC def . (lexCore >=> parseCore)
qCoreExpr :: String -> Q Exp
qCoreExpr s = case parseExpr s of
Left e -> error (show e)
Right (m,ts) -> lift m
where
parseExpr = evalRLPC def . (lexCore >=> parseCoreExpr)
qCoreProg :: String -> Q Exp
qCoreProg s = case parseProg s of
Left e -> error (show e)
Right (m,ts) -> lift m
where
parseProg = evalRLPC def . (lexCore >=> parseCoreProg)
mkq :: (Lift a) => (Text -> RLPC a) -> String -> Q Exp
mkq parse s = case evalRLPC def (parse $ T.pack s) of
(Just a, _) -> lift a
(Nothing, _) -> error "todo: aaahhbbhjhbdjhabsjh"

View File

@@ -1,16 +1,10 @@
-- for recursion schemes
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
-- for recursion schemes
{-# LANGUAGE TemplateHaskell, TypeFamilies #-}
module Core.Utils
( bindersOf
, rhssOf
( programRhss
, programGlobals
, isAtomic
, insertModule
-- , insertModule
, extractProgram
, freeVariables
, ExprF(..)
)
where
----------------------------------------------------------------------------------
@@ -19,35 +13,32 @@ import Data.Functor.Foldable
import Data.Set (Set)
import Data.Set qualified as S
import Core.Syntax
import Lens.Micro
import GHC.Exts (IsList(..))
----------------------------------------------------------------------------------
bindersOf :: (IsList l, Item l ~ b) => [Binding b] -> l
bindersOf bs = fromList $ fmap f bs
where f (k := _) = k
programGlobals :: Traversal' (Program b) b
programGlobals = programScDefs . each . _lhs . _1
rhssOf :: (IsList l, Item l ~ Expr b) => [Binding b] -> l
rhssOf = fromList . fmap f
where f (_ := v) = v
programRhss :: Traversal' (Program b) (Expr b)
programRhss = programScDefs . each . _rhs
isAtomic :: Expr b -> Bool
isAtomic (Var _) = True
isAtomic (LitE _) = True
isAtomic (Lit _) = True
isAtomic _ = False
----------------------------------------------------------------------------------
-- TODO: export list awareness
insertModule :: Module b -> Program b -> Program b
insertModule (Module _ m) p = p <> m
-- insertModule :: Module b -> Program b -> Program b
-- insertModule (Module _ p) = programScDefs %~ (<>m)
extractProgram :: Module b -> Program b
extractProgram (Module _ p) = p
----------------------------------------------------------------------------------
makeBaseFunctor ''Expr
freeVariables :: Expr' -> Set Name
freeVariables = cata go
where
@@ -56,8 +47,8 @@ freeVariables = cata go
-- TODO: collect free vars in rhss of bs
go (LetF _ bs e) = (e `S.union` esFree) `S.difference` ns
where
es = rhssOf bs :: [Expr']
ns = bindersOf bs
es = bs ^.. each . _rhs :: [Expr']
ns = S.fromList $ bs ^.. each . _lhs
-- TODO: this feels a little wrong. maybe a different scheme is
-- appropriate
esFree = foldMap id $ freeVariables <$> es

View File

@@ -1,3 +1,4 @@
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE LambdaCase #-}
module Core2Core
( core2core
@@ -15,10 +16,12 @@ import Data.Set (Set)
import Data.Set qualified as S
import Data.List
import Control.Monad.Writer
import Control.Monad.State
import Control.Monad.State.Lazy
import Control.Arrow ((>>>))
import Data.Text qualified as T
import Data.HashMap.Strict (HashMap)
import Numeric (showHex)
import Lens.Micro
import Lens.Micro.Platform
import Core.Syntax
import Core.Utils
----------------------------------------------------------------------------------
@@ -27,26 +30,39 @@ core2core :: Program' -> Program'
core2core p = undefined
gmPrep :: Program' -> Program'
gmPrep p = p' <> Program caseScs
gmPrep p = p & appFloater (floatNonStrictCases globals)
& tagData
where
rhss :: Applicative f => (Expr z -> f (Expr z)) -> Program z -> f (Program z)
rhss = programScDefs . each . _rhs
globals = p ^.. programScDefs . each . _lhs . _1
& S.fromList
-- i kinda don't like that we're calling floatNonStrictCases twice tbh
p' = p & rhss %~ fst . runFloater . floatNonStrictCases globals
caseScs = (p ^.. rhss)
<&> snd . runFloater . floatNonStrictCases globals
& mconcat
tagData :: Program' -> Program'
tagData p = let ?dt = p ^. programDataTags
in p & programRhss %~ cata go where
go :: (?dt :: HashMap Name (Tag, Int)) => ExprF' Expr' -> Expr'
go (CaseF e as) = Case e (tagAlts <$> as)
go x = embed x
tagAlts :: (?dt :: HashMap Name (Tag, Int)) => Alter' -> Alter'
tagAlts (Alter (AltData c) bs e) = Alter (AltTag tag) bs e
where tag = case ?dt ^. at c of
Just (t,_) -> t
-- TODO: errorful
Nothing -> error $ "unknown constructor " <> show c
tagAlts x = x
-- | Auxilary type used in @floatNonSrictCases@
type Floater = StateT [Name] (Writer [ScDef'])
appFloater :: (Expr' -> Floater Expr') -> Program' -> Program'
appFloater fl p = p & traverseOf programRhss fl
& runFloater
& \ (me,floats) -> me & programScDefs %~ (<>floats)
runFloater :: Floater a -> (a, [ScDef'])
runFloater = flip evalStateT ns >>> runWriter
where
ns = [ "$nonstrict_case_" ++ showHex n "" | n <- [0..] ]
ns = [ T.pack $ "$nonstrict_case_" ++ showHex n "" | n <- [0..] ]
-- TODO: formally define a "strict context" and reference that here
-- the returned ScDefs are guaranteed to be free of non-strict cases.
@@ -55,7 +71,7 @@ floatNonStrictCases g = goE
where
goE :: Expr' -> Floater Expr'
goE (Var k) = pure (Var k)
goE (LitE l) = pure (LitE l)
goE (Lit l) = pure (Lit l)
goE (Case e as) = pure (Case e as)
goE (Let Rec bs e) = Let Rec <$> bs' <*> goE e
where bs' = travBs goE bs
@@ -77,7 +93,7 @@ floatNonStrictCases g = goE
goC (f :$ x) = (:$) <$> goC f <*> goC x
goC (Let r bs e) = Let r <$> bs' <*> goE e
where bs' = travBs goC bs
goC (LitE l) = pure (LitE l)
goC (Lit l) = pure (Lit l)
goC (Var k) = pure (Var k)
goC (Con t as) = pure (Con t as)

View File

@@ -8,6 +8,7 @@ Description : The G-Machine
module GM
( hdbgProg
, evalProg
, evalProgR
, Node(..)
, gmEvalProg
, finalStateOf
@@ -22,7 +23,10 @@ import Data.Maybe (fromMaybe, mapMaybe)
import Data.Monoid (Endo(..))
import Data.Tuple (swap)
import Lens.Micro
import Lens.Micro.Extras (view)
import Lens.Micro.TH
import Lens.Micro.Platform (packed, unpacked)
import Lens.Micro.Platform.Internal (IsText(..))
import Text.Printf
import Text.PrettyPrint hiding ((<>))
import Text.PrettyPrint.HughesPJ (maybeParens)
@@ -31,6 +35,7 @@ import System.IO (Handle, hPutStrLn)
import Data.String (IsString)
import Data.Heap
import Debug.Trace
import Compiler.RLPC
import Core2Core
import Core
----------------------------------------------------------------------------------
@@ -153,6 +158,21 @@ hdbgProg p hio = do
[resAddr] = final ^. gmStack
res = hLookupUnsafe resAddr h
evalProgR :: (Monad m) => Program' -> RLPCT m (Node, Stats)
evalProgR p = do
(renderOut . showState) `traverse_` states
renderOut . showStats $ sts
pure (res, sts)
where
renderOut r = addDebugMsg "dump-eval" $ render r ++ "\n"
states = eval . compile $ p
final = last states
sts = final ^. gmStats
-- the address of the result should be the one and only stack entry
[resAddr] = final ^. gmStack
res = hLookupUnsafe resAddr (final ^. gmHeap)
eval :: GmState -> [GmState]
eval st = st : rest
where
@@ -281,7 +301,7 @@ step st = case head (st ^. gmCode) of
m = st ^. gmEnv
s = st ^. gmStack
h = st ^. gmHeap
n' = show n
n' = show n ^. packed
-- Core Rule 2. (no sharing)
-- pushIntI :: Int -> GmState
@@ -582,7 +602,7 @@ compiledPrims =
binop k i = (k, 2, [Push 1, Eval, Push 1, Eval, i, Update 2, Pop 2, Unwind])
buildInitialHeap :: Program' -> (GmHeap, Env)
buildInitialHeap (Program ss) = mapAccumL allocateSc mempty compiledScs
buildInitialHeap (view programScDefs -> ss) = mapAccumL allocateSc mempty compiledScs
where
compiledScs = fmap compileSc ss <> compiledPrims
@@ -612,12 +632,13 @@ buildInitialHeap (Program ss) = mapAccumL allocateSc mempty compiledScs
| k `elem` domain = [Push n]
| otherwise = [PushGlobal k]
where
n = fromMaybe (error $ "undeclared var: " <> k) $ lookupN k g
n = fromMaybe err $ lookupN k g
err = error $ "undeclared var: " <> (k ^. unpacked)
domain = f `mapMaybe` g
f (NameKey n, _) = Just n
f _ = Nothing
compileC _ (LitE l) = compileCL l
compileC _ (Lit l) = compileCL l
-- >> [ref/compileC]
compileC g (App f x) = compileC g x
@@ -657,20 +678,21 @@ buildInitialHeap (Program ss) = mapAccumL allocateSc mempty compiledScs
compileC _ (Con t n) = [PushConstr t n]
compileC _ (Case _ _) =
error "case expressions may not appear in non-strict contexts :/"
error "GM compiler found a non-strict case expression, which should\
\ have been floated by Core2Core.gmPrep. This is a bug!"
compileC _ _ = error "yet to be implemented!"
compileCL :: Literal -> Code
compileCL :: Lit -> Code
compileCL (IntL n) = [PushInt n]
compileEL :: Literal -> Code
compileEL :: Lit -> Code
compileEL (IntL n) = [PushInt n]
-- compile an expression in a strict context such that a pointer to the
-- expression is left on top of the stack in WHNF
compileE :: Env -> Expr' -> Code
compileE _ (LitE l) = compileEL l
compileE _ (Lit l) = compileEL l
compileE g (Let NonRec bs e) =
-- we use compileE instead of compileC
mconcat binders <> compileE g' e <> [Slide d]
@@ -720,12 +742,16 @@ buildInitialHeap (Program ss) = mapAccumL allocateSc mempty compiledScs
compileD g as = fmap (compileA g) as
compileA :: Env -> Alter' -> (Tag, Code)
compileA g (Alter (AltData t) as e) = (t, [Split n] <> c <> [Slide n])
compileA g (Alter (AltTag t) as e) = (t, [Split n] <> c <> [Slide n])
where
n = length as
binds = (NameKey <$> as) `zip` [0..]
g' = binds ++ argOffset n g
c = compileE g' e
compileA _ (Alter _ as e) = error "GM.compileA found an untagged\
\ constructor, which should have\
\ been handled by Core2Core.gmPrep.\
\ This is a bug!"
inlineOp1 :: Env -> Instr -> Expr' -> Code
inlineOp1 g i a = compileE g a <> [i]
@@ -738,8 +764,8 @@ buildInitialHeap (Program ss) = mapAccumL allocateSc mempty compiledScs
argOffset :: Int -> Env -> Env
argOffset n = each . _2 %~ (+n)
idPack :: Tag -> Int -> String
idPack t n = printf "Pack{%d %d}" t n
showCon :: (IsText a) => Tag -> Int -> a
showCon t n = printf "Pack{%d %d}" t n ^. packed
----------------------------------------------------------------------------------
@@ -855,12 +881,12 @@ showNodeAt = showNodeAtP 0
showNodeAtP :: Int -> GmState -> Addr -> Doc
showNodeAtP p st a = case hLookup a h of
Just (NNum n) -> int n <> "#"
Just (NGlobal _ _) -> text name
Just (NGlobal _ _) -> textt name
where
g = st ^. gmEnv
name = case lookup a (swap <$> g) of
Just (NameKey n) -> n
Just (ConstrKey t n) -> idPack t n
Just (ConstrKey t n) -> showCon t n
_ -> errTxtInvalidAddress
-- TODO: left-associativity
Just (NAp f x) -> pprec $ showNodeAtP (p+1) st f
@@ -877,7 +903,7 @@ showNodeAtP p st a = case hLookup a h of
pprec = maybeParens (p > 0)
showSc :: GmState -> (Name, Addr) -> Doc
showSc st (k,a) = "Supercomb " <> qquotes (text k) <> colon
showSc st (k,a) = "Supercomb " <> qquotes (textt k) <> colon
$$ code
where
code = case hLookup a (st ^. gmHeap) of
@@ -900,6 +926,9 @@ showInstr (CaseJump alts) = "CaseJump" $$ nest pprTabstop alternatives
alternatives = foldr (\a acc -> showAlt a $$ acc) mempty alts
showInstr i = text $ show i
textt :: (IsText a) => a -> Doc
textt t = t ^. unpacked & text
----------------------------------------------------------------------------------
lookupN :: Name -> Env -> Maybe Addr
@@ -975,7 +1004,8 @@ resultOf p = do
h = st ^. gmHeap
resultOfExpr :: Expr' -> Maybe Node
resultOfExpr e = resultOf $ Program
[ ScDef "main" [] e
]
resultOfExpr e = resultOf $
mempty & programScDefs .~
[ ScDef "main" [] e
]

View File

@@ -1,59 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module RLP.Syntax
( RlpExpr
)
where
----------------------------------------------------------------------------------
import Data.Text (Text)
import Lens.Micro
import Core (HasRHS(..), HasLHS(..))
----------------------------------------------------------------------------------
newtype RlpProgram b = RlpProgram [Decl b]
data Decl b = InfixD InfixAssoc Int VarId
| FunD VarId [Pat b] (RlpExpr b)
| DataD ConId [ConId] [ConAlt]
data ConAlt = ConAlt ConId [ConId]
data InfixAssoc = Assoc | AssocL | AssocR
data RlpExpr b = LetE [Bind b] (RlpExpr b)
| VarE VarId
| ConE ConId
| LamE [Pat b] (RlpExpr b)
| CaseE (RlpExpr b) [Alt b]
| IfE (RlpExpr b) (RlpExpr b) (RlpExpr b)
| AppE (RlpExpr b) (RlpExpr b)
| LitE (Lit b)
-- do we want guards?
data Alt b = AltA (Pat b) (RlpExpr b)
data Bind b = PatB (Pat b) (RlpExpr b)
| FunB VarId [Pat b] (RlpExpr b)
data VarId = NameVar Text
| SymVar Text
data ConId = NameCon Text
| SymCon Text
data Pat b = VarP VarId
| LitP (Lit b)
| ConP ConId [Pat b]
data Lit b = IntL Int
| CharL Char
| ListL [RlpExpr b]
-- instance HasLHS Alt Alt Pat Pat where
-- _lhs = lens
-- (\ (AltA p _) -> p)
-- (\ (AltA _ e) p' -> AltA p' e)
-- instance HasRHS Alt Alt RlpExpr RlpExpr where
-- _rhs = lens
-- (\ (AltA _ e) -> e)
-- (\ (AltA p _) e' -> AltA p e')

364
src/Rlp/Lex.x Normal file
View File

@@ -0,0 +1,364 @@
{
{-# LANGUAGE ViewPatterns, LambdaCase #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
module Rlp.Lex
( P(..)
, RlpToken(..)
, Located(..)
, lexToken
, lexStream
, lexDebug
, lexCont
, popLexState
, programInitState
, runP'
)
where
import Codec.Binary.UTF8.String (encodeChar)
import Control.Monad
import Control.Monad.Errorful
import Core.Syntax (Name)
import Data.Functor.Identity
import Data.Char (digitToInt)
import Data.Monoid (First)
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as T
import Data.Word
import Data.Default
import Lens.Micro.Mtl
import Lens.Micro
import Debug.Trace
import Rlp.Parse.Types
}
$whitechar = [ \t\n\r\f\v]
$nl = [\n\r]
$white_no_nl = $white # $nl
$lower = [a-z \_]
$upper = [A-Z]
$alpha = [$lower $upper]
$digit = 0-9
$special = [\(\)\,\;\[\]\{\}]
$namechar = [$alpha $digit \' \#]
$asciisym = [\!\#\$\%\&\*\+\.\/\<\=\>\?\@\\\^\|\-\~\:]
@decimal = $digit+
@varname = $lower $namechar*
@conname = $upper $namechar*
@consym = \: $asciisym*
@varsym = $asciisym+
@reservedname =
case|data|do|import|in|let|letrec|module|of|where
|infixr|infixl|infix
@reservedop =
"=" | \\ | "->" | "|" | "::"
rlp :-
-- everywhere: skip whitespace
$white_no_nl+ ;
-- everywhere: skip comments
-- TODO: don't treat operators like (-->) as comments
"--".* ;
-- we are indentation-sensitive! do not skip NLs!. upon encountering a newline,
-- we check indentation and potentially insert extra tokens. search this file
-- for the definition of `doBol`
<0> \n { beginPush bol }
<layout>
{
}
-- layout keywords
<0>
{
"let" { constToken TokenLet `thenBeginPush` layout_let }
}
-- scan various identifiers and reserved words. order is important here!
<0>
{
@reservedname { tokenWith lexReservedName }
@conname { tokenWith TokenConName }
@varname { tokenWith TokenVarName }
@reservedop { tokenWith lexReservedOp }
@consym { tokenWith TokenConSym }
@varsym { tokenWith TokenVarSym }
}
-- literals -- currently this is just unsigned integer literals
<0>
{
@decimal { tokenWith (TokenLitInt . readInt) }
}
-- control characters
<0>
{
"(" { constToken TokenLParen }
")" { constToken TokenRParen }
"{" { explicitLBrace }
"}" { explicitRBrace }
";" { constToken TokenSemicolon }
}
-- consume all whitespace leaving us at the beginning of the next non-empty
-- line. we then compare the indentation of that line to the enclosing layout
-- context and proceed accordingly
<bol>
{
$whitechar ;
\n ;
() { doBol }
}
<layout_let>
{
\n { beginPush bol }
"{" { explicitLBrace }
"in" { constToken TokenIn `thenDo` (popLexState *> popLayout) }
() { doLayout }
}
<layout_top>
{
\n ;
"{" { explicitLBrace `thenDo` popLexState }
() { doLayout }
}
{
lexReservedName :: Text -> RlpToken
lexReservedName = \case
"data" -> TokenData
"case" -> TokenCase
"of" -> TokenOf
"let" -> TokenLet
"in" -> TokenIn
"infix" -> TokenInfix
"infixl" -> TokenInfixL
"infixr" -> TokenInfixR
lexReservedOp :: Text -> RlpToken
lexReservedOp = \case
"=" -> TokenEquals
"::" -> TokenHasType
"|" -> TokenPipe
-- | @andBegin@, with the subtle difference that the start code is set
-- /after/ the action
thenBegin :: LexerAction a -> Int -> LexerAction a
thenBegin act c inp l = do
a <- act inp l
psLexState . _head .= c
pure a
thenBeginPush :: LexerAction a -> Int -> LexerAction a
thenBeginPush act c inp l = do
a <- act inp l
pushLexState c
pure a
andBegin :: LexerAction a -> Int -> LexerAction a
andBegin act c inp l = do
psLexState . _head .= c
act inp l
beginPush :: Int -> LexerAction (Located RlpToken)
beginPush n _ _ = pushLexState n >> lexToken
alexGetByte :: AlexInput -> Maybe (Word8, AlexInput)
alexGetByte inp = case inp ^. aiBytes of
[] -> do
(c,t) <- T.uncons (inp ^. aiSource)
let (b:bs) = encodeChar c
-- tail the source
inp' = inp & aiSource .~ t
-- record the excess bytes for successive calls
& aiBytes .~ bs
-- report the previous char
& aiPrevChar .~ c
-- update the position
& aiPos %~ \ (ln,col,a) ->
if c == '\n'
then (ln+1, 1, a+1)
else (ln, col+1, a+1)
pure (b, inp')
_ -> Just (head bs, inp')
where
(bs, inp') = inp & aiBytes <<%~ drop 1
getInput :: P AlexInput
getInput = use psInput
getLexState :: P Int
getLexState = use (psLexState . singular _head)
alexInputPrevChar :: AlexInput -> Char
alexInputPrevChar = view aiPrevChar
pushLexState :: Int -> P ()
pushLexState n = psLexState %= (n:)
readInt :: Text -> Int
readInt = T.foldl f 0 where
f n c = 10*n + digitToInt c
constToken :: RlpToken -> LexerAction (Located RlpToken)
constToken t inp l = do
pos <- use (psInput . aiPos)
pure (Located (spanFromPos pos l) t)
tokenWith :: (Text -> RlpToken) -> LexerAction (Located RlpToken)
tokenWith tf inp l = do
pos <- getPos
let t = tf (T.take l $ inp ^. aiSource)
pure (Located (spanFromPos pos l) t)
getPos :: P Position
getPos = use (psInput . aiPos)
alexEOF :: P (Located RlpToken)
alexEOF = do
inp <- getInput
pos <- getPos
pure (Located (spanFromPos pos 0) TokenEOF)
runP' :: P a -> Text -> (ParseState, [MsgEnvelope RlpParseError], Maybe a)
runP' p s = runP p st where
st = initParseState [layout_top,0] s
lexToken :: P (Located RlpToken)
lexToken = do
inp <- getInput
c <- getLexState
st <- use id
-- traceM $ "st: " <> show st
case alexScan inp c of
AlexEOF -> pure $ Located (spanFromPos (inp^.aiPos) 0) TokenEOF
AlexSkip inp' l -> do
psInput .= inp'
lexToken
AlexToken inp' l act -> do
psInput .= inp'
act inp l
AlexError inp' -> addFatalHere 1 RlpParErrLexical
lexCont :: (Located RlpToken -> P a) -> P a
lexCont = (lexToken >>=)
lexStream :: P [RlpToken]
lexStream = do
t <- lexToken
case t of
Located _ TokenEOF -> pure [TokenEOF]
Located _ t -> (t:) <$> lexStream
lexDebug :: (Located RlpToken -> P a) -> P a
lexDebug k = do
t <- lexToken
traceM $ "token: " <> show t
k t
lexTest :: Text -> Maybe [RlpToken]
lexTest s = runP' lexStream s ^. _3
indentLevel :: P Int
indentLevel = do
pos <- use (psInput . aiPos)
pure (pos ^. _2)
insertToken :: RlpToken -> P (Located RlpToken)
insertToken t = do
pos <- use (psInput . aiPos)
pure (Located (spanFromPos pos 0) t)
popLayout :: P Layout
popLayout = do
-- traceM "pop layout"
ctx <- preuse (psLayoutStack . _head)
psLayoutStack %= (drop 1)
case ctx of
Just l -> pure l
Nothing -> error "popLayout: layout stack empty! this is a bug."
pushLayout :: Layout -> P ()
pushLayout l = do
-- traceM "push layout"
psLayoutStack %= (l:)
popLexState :: P ()
popLexState = do
psLexState %= tail
insertSemicolon, insertLBrace, insertRBrace :: P (Located RlpToken)
insertSemicolon = {- traceM "inserting semi" >> -} insertToken TokenSemicolonV
insertLBrace = {- traceM "inserting lbrace" >> -} insertToken TokenLBraceV
insertRBrace = {- traceM "inserting rbrace" >> -} insertToken TokenRBraceV
cmpLayout :: P Ordering
cmpLayout = do
i <- indentLevel
ctx <- preuse (psLayoutStack . _head)
case ctx of
Just (Implicit n) -> pure (i `compare` n)
_ -> pure GT
doBol :: LexerAction (Located RlpToken)
doBol inp l = do
off <- cmpLayout
i <- indentLevel
traceM $ "i: " <> show i
-- important that we pop the lex state lest we find our lexer diverging
popLexState
case off of
-- the line is aligned with the previous. it therefore belongs to the
-- same list
EQ -> insertSemicolon
-- the line is indented further than the previous, so we assume it is a
-- line continuation. ignore it and move on!
GT -> lexToken
-- the line is indented less than the previous, pop the layout stack and
-- insert a closing brace.
LT -> popLayout >> insertRBrace
thenDo :: LexerAction a -> P b -> LexerAction a
thenDo act p inp l = act inp l <* p
explicitLBrace :: LexerAction (Located RlpToken)
explicitLBrace inp l = do
pushLayout Explicit
constToken TokenLBrace inp l
explicitRBrace :: LexerAction (Located RlpToken)
explicitRBrace inp l = do
popLayout
constToken TokenRBrace inp l
doLayout :: LexerAction (Located RlpToken)
doLayout _ _ = do
i <- indentLevel
-- traceM $ "doLayout: i: " <> show i
pushLayout (Implicit i)
popLexState
insertLBrace
programInitState :: Text -> ParseState
programInitState = initParseState [layout_top,0]
}

244
src/Rlp/Parse.y Normal file
View File

@@ -0,0 +1,244 @@
{
{-# LANGUAGE LambdaCase, ViewPatterns #-}
module Rlp.Parse
( parseRlpProg
, parseRlpProgR
, parseRlpExpr
, parseRlpExprR
)
where
import Compiler.RlpcError
import Compiler.RLPC
import Rlp.Lex
import Rlp.Syntax
import Rlp.Parse.Types
import Rlp.Parse.Associate
import Lens.Micro.Platform
import Data.List.Extra
import Data.Fix
import Data.Functor.Const
import Data.Functor.Apply
import Data.Functor.Bind
import Control.Comonad
import Data.Functor
import Data.Semigroup.Traversable
import Data.Text (Text)
import Data.Text qualified as T
import Data.Void
}
%name parseRlpProg StandaloneProgram
%name parseRlpExpr StandaloneExpr
%monad { P }
%lexer { lexCont } { Located _ TokenEOF }
%error { parseError }
%errorhandlertype explist
%tokentype { Located RlpToken }
%token
varname { Located _ (TokenVarName _) }
conname { Located _ (TokenConName _) }
consym { Located _ (TokenConSym _) }
varsym { Located _ (TokenVarSym _) }
data { Located _ TokenData }
litint { Located _ (TokenLitInt _) }
'=' { Located _ TokenEquals }
'|' { Located _ TokenPipe }
'::' { Located _ TokenHasType }
';' { Located _ TokenSemicolon }
'(' { Located _ TokenLParen }
')' { Located _ TokenRParen }
'->' { Located _ TokenArrow }
vsemi { Located _ TokenSemicolonV }
'{' { Located _ TokenLBrace }
'}' { Located _ TokenRBrace }
vlbrace { Located _ TokenLBraceV }
vrbrace { Located _ TokenRBraceV }
infixl { Located _ TokenInfixL }
infixr { Located _ TokenInfixR }
infix { Located _ TokenInfix }
let { Located _ TokenLet }
in { Located _ TokenIn }
%nonassoc '='
%right '->'
%right in
%%
StandaloneProgram :: { RlpProgram RlpcPs }
StandaloneProgram : '{' Decls '}' {% mkProgram $2 }
| VL DeclsV VR {% mkProgram $2 }
StandaloneExpr :: { RlpExpr RlpcPs }
: VL Expr VR { extract $2 }
VL :: { () }
VL : vlbrace { () }
VR :: { () }
VR : vrbrace { () }
| error { () }
Decls :: { [Decl' RlpcPs] }
Decls : Decl ';' Decls { $1 : $3 }
| Decl ';' { [$1] }
| Decl { [$1] }
DeclsV :: { [Decl' RlpcPs] }
DeclsV : Decl VS Decls { $1 : $3 }
| Decl VS { [$1] }
| Decl { [$1] }
| {- epsilon -} { [] }
VS :: { Located RlpToken }
VS : ';' { $1 }
| vsemi { $1 }
Decl :: { Decl' RlpcPs }
: FunDecl { $1 }
| TySigDecl { $1 }
| DataDecl { $1 }
| InfixDecl { $1 }
TySigDecl :: { Decl' RlpcPs }
: Var '::' Type { (\e -> TySigD [extract e]) <<~ $1 <~> $3 }
InfixDecl :: { Decl' RlpcPs }
: InfixWord litint InfixOp { $1 =>> \w ->
InfixD (extract $1) (extractInt $ extract $2)
(extract $3) }
InfixWord :: { Located Assoc }
: infixl { $1 \$> InfixL }
| infixr { $1 \$> InfixR }
| infix { $1 \$> Infix }
DataDecl :: { Decl' RlpcPs }
: data Con TyParams '=' DataCons { $1 \$> DataD (extract $2) $3 $5 }
TyParams :: { [PsName] }
: {- epsilon -} { [] }
| TyParams varname { $1 `snoc` (extractName . extract $ $2) }
DataCons :: { [ConAlt RlpcPs] }
: DataCons '|' DataCon { $1 `snoc` $3 }
| DataCon { [$1] }
DataCon :: { ConAlt RlpcPs }
: Con Type1s { ConAlt (extract $1) $2 }
Type1s :: { [RlpType' RlpcPs] }
: {- epsilon -} { [] }
| Type1s Type1 { $1 `snoc` $2 }
Type1 :: { RlpType' RlpcPs }
: '(' Type ')' { $2 }
| conname { fmap ConT (mkPsName $1) }
| varname { fmap VarT (mkPsName $1) }
Type :: { RlpType' RlpcPs }
: Type '->' Type { FunT <<~ $1 <~> $3 }
| Type1 { $1 }
FunDecl :: { Decl' RlpcPs }
FunDecl : Var Params '=' Expr { $4 =>> \e ->
FunD (extract $1) $2 e Nothing }
Params :: { [Pat' RlpcPs] }
Params : {- epsilon -} { [] }
| Params Pat1 { $1 `snoc` $2 }
Pat1 :: { Pat' RlpcPs }
: Var { fmap VarP $1 }
| Lit { LitP <<= $1 }
Expr :: { RlpExpr' RlpcPs }
: Expr1 InfixOp Expr { $2 =>> \o ->
OAppE (extract o) $1 $3 }
| Expr1 { $1 }
| LetExpr { $1 }
LetExpr :: { RlpExpr' RlpcPs }
: let layout1(Binding) in Expr { $1 \$> LetE $2 $4 }
layout1(p) : '{' layout_list1(';',p) '}' { $2 }
| VL layout_list1(VS,p) VR { $2 }
layout_list1(sep,p) : p { [$1] }
| layout_list1(sep,p) sep p { $1 `snoc` $3 }
Binding :: { Binding' RlpcPs }
: Pat1 '=' Expr { PatB <<~ $1 <~> $3 }
Expr1 :: { RlpExpr' RlpcPs }
: '(' Expr ')' { $1 .> $2 <. $3 }
| Lit { fmap LitE $1 }
| Var { fmap VarE $1 }
InfixOp :: { Located PsName }
: consym { mkPsName $1 }
| varsym { mkPsName $1 }
-- TODO: microlens-pro save me microlens-pro (rewrite this with prisms)
Lit :: { Lit' RlpcPs }
: litint { $1 <&> (IntL . (\ (TokenLitInt n) -> n)) }
Var :: { Located PsName }
Var : varname { mkPsName $1 }
Con :: { Located PsName }
: conname { mkPsName $1 }
{
parseRlpExprR = undefined
parseRlpProgR :: (Monad m) => Text -> RLPCT m (RlpProgram RlpcPs)
parseRlpProgR s = liftErrorful $ pToErrorful parseRlpProg st
where
st = programInitState s
mkPsName :: Located RlpToken -> Located PsName
mkPsName = fmap extractName
extractName :: RlpToken -> PsName
extractName = \case
TokenVarName n -> n
TokenConName n -> n
TokenConSym n -> n
TokenVarSym n -> n
_ -> error "mkPsName: not an identifier"
extractInt :: RlpToken -> Int
extractInt (TokenLitInt n) = n
extractInt _ = error "extractInt: ugh"
mkProgram :: [Decl' RlpcPs] -> P (RlpProgram RlpcPs)
mkProgram ds = do
pt <- use psOpTable
pure $ RlpProgram (associate pt <$> ds)
parseError :: (Located RlpToken, [String]) -> P a
parseError ((Located ss t), exp) = addFatal $
errorMsg ss (RlpParErrUnexpectedToken t exp)
mkInfixD :: Assoc -> Int -> PsName -> P (Decl' RlpcPs)
mkInfixD a p n = do
let opl :: Lens' ParseState (Maybe OpInfo)
opl = psOpTable . at n
opl <~ (use opl >>= \case
Just o -> addWoundHere l e >> pure (Just o) where
e = RlpParErrDuplicateInfixD n
l = T.length n
Nothing -> pure (Just (a,p))
)
pos <- use (psInput . aiPos)
pure $ Located (spanFromPos pos 0) (InfixD a p n)
intOfToken :: Located RlpToken -> Int
intOfToken (Located _ (TokenLitInt n)) = n
}

View File

@@ -0,0 +1,19 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms, ViewPatterns, ImplicitParams #-}
module Rlp.Parse.Associate
{-# WARNING "temporarily unimplemented" #-}
( associate
)
where
--------------------------------------------------------------------------------
import Data.HashMap.Strict qualified as H
import Data.Functor.Foldable
import Data.Functor.Const
import Lens.Micro
import Rlp.Parse.Types
import Rlp.Syntax
--------------------------------------------------------------------------------
associate x y = y
{-# WARNING associate "temporarily undefined" #-}

270
src/Rlp/Parse/Types.hs Normal file
View File

@@ -0,0 +1,270 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ImplicitParams, ViewPatterns, PatternSynonyms #-}
{-# LANGUAGE LambdaCase #-}
module Rlp.Parse.Types
(
-- * Trees That Grow
RlpcPs
-- * Parser monad and state
, P(..), ParseState(..), Layout(..), OpTable, OpInfo
, initParseState, initAlexInput
, pToErrorful
-- ** Lenses
, psLayoutStack, psLexState, psInput, psOpTable
-- * Other parser types
, RlpToken(..), AlexInput(..), Position(..), spanFromPos, LexerAction
, Located(..), PsName
-- ** Lenses
, aiPrevChar, aiSource, aiBytes, aiPos, posLine, posColumn
, (<<~), (<~>)
-- * Error handling
, MsgEnvelope(..), RlpcError(..), RlpParseError(..)
, addFatal, addWound, addFatalHere, addWoundHere
)
where
--------------------------------------------------------------------------------
import Core.Syntax (Name)
import Control.Monad
import Control.Monad.State.Strict
import Control.Monad.Errorful
import Compiler.RlpcError
import Data.Text (Text)
import Data.Maybe
import Data.Fix
import Data.Functor.Foldable
import Data.Functor.Const
import Data.Functor.Classes
import Data.HashMap.Strict qualified as H
import Data.Void
import Data.Word (Word8)
import Data.Text qualified as T
import Lens.Micro.TH
import Lens.Micro
import Rlp.Syntax
import Compiler.Types
--------------------------------------------------------------------------------
-- | Phantom type identifying rlpc's parser phase
data RlpcPs
type instance XRec RlpcPs f = Located (f RlpcPs)
type instance IdP RlpcPs = PsName
type instance XFunD RlpcPs = ()
type instance XDataD RlpcPs = ()
type instance XInfixD RlpcPs = ()
type instance XTySigD RlpcPs = ()
type instance XXDeclD RlpcPs = ()
type instance XLetE RlpcPs = ()
type instance XVarE RlpcPs = ()
type instance XLamE RlpcPs = ()
type instance XCaseE RlpcPs = ()
type instance XIfE RlpcPs = ()
type instance XAppE RlpcPs = ()
type instance XLitE RlpcPs = ()
type instance XParE RlpcPs = ()
type instance XOAppE RlpcPs = ()
type PsName = Text
--------------------------------------------------------------------------------
spanFromPos :: Position -> Int -> SrcSpan
spanFromPos (l,c,a) s = SrcSpan l c a s
{-# INLINE spanFromPos #-}
type LexerAction a = AlexInput -> Int -> P a
data AlexInput = AlexInput
{ _aiPrevChar :: Char
, _aiSource :: Text
, _aiBytes :: [Word8]
, _aiPos :: Position
}
deriving Show
type Position =
( Int -- ^ line
, Int -- ^ column
, Int -- ^ Absolutely
)
posLine :: Lens' Position Int
posLine = _1
posColumn :: Lens' Position Int
posColumn = _2
posAbsolute :: Lens' Position Int
posAbsolute = _3
data RlpToken
-- literals
= TokenLitInt Int
-- identifiers
| TokenVarName Name
| TokenConName Name
| TokenVarSym Name
| TokenConSym Name
-- reserved words
| TokenData
| TokenCase
| TokenOf
| TokenLet
| TokenIn
| TokenInfixL
| TokenInfixR
| TokenInfix
-- reserved ops
| TokenArrow
| TokenPipe
| TokenHasType
| TokenLambda
| TokenEquals
-- control symbols
| TokenSemicolon
| TokenLBrace
| TokenRBrace
| TokenLParen
| TokenRParen
-- 'virtual' control symbols, inserted by the lexer without any correlation
-- to a specific part of the input
| TokenSemicolonV
| TokenLBraceV
| TokenRBraceV
| TokenEOF
deriving (Show)
newtype P a = P {
runP :: ParseState
-> (ParseState, [MsgEnvelope RlpParseError], Maybe a)
}
deriving (Functor)
pToErrorful :: (Applicative m)
=> P a -> ParseState -> ErrorfulT (MsgEnvelope RlpParseError) m a
pToErrorful p st = ErrorfulT $ pure (ma,es) where
(_,es,ma) = runP p st
instance Applicative P where
pure a = P $ \st -> (st, [], pure a)
liftA2 = liftM2
instance Monad P where
p >>= k = P $ \st ->
let (st',es,ma) = runP p st
in case ma of
Just a -> runP (k a) st'
& _2 %~ (es<>)
Nothing -> (st',es,Nothing)
{-# INLINE (>>=) #-}
instance MonadState ParseState P where
state f = P $ \st ->
let (a,st') = f st
in (st', [], Just a)
instance MonadErrorful (MsgEnvelope RlpParseError) P where
addWound e = P $ \st -> (st, [e], Just ())
addFatal e = P $ \st -> (st, [e], Nothing)
data ParseState = ParseState
{ _psLayoutStack :: [Layout]
, _psLexState :: [Int]
, _psInput :: AlexInput
, _psOpTable :: OpTable
}
deriving Show
data Layout = Explicit
| Implicit Int
deriving (Show, Eq)
type OpTable = H.HashMap Name OpInfo
type OpInfo = (Assoc, Int)
-- data WithLocation a = WithLocation [String] a
data RlpParseError = RlpParErrOutOfBoundsPrecedence Int
| RlpParErrDuplicateInfixD Name
| RlpParErrLexical
| RlpParErrUnexpectedToken RlpToken [String]
deriving (Show)
instance IsRlpcError RlpParseError where
liftRlpcError = \case
RlpParErrOutOfBoundsPrecedence n ->
Text [ "Illegal precedence in infixity declaration"
, "rl' currently only allows precedences between 0 and 9."
]
RlpParErrDuplicateInfixD s ->
Text [ "Conflicting infixity declarations for operator "
<> tshow s
]
RlpParErrLexical ->
Text [ "Unknown lexical error :(" ]
RlpParErrUnexpectedToken t exp ->
Text [ "Unexpected token " <> tshow t
, "Expected: " <> tshow exp
]
where
tshow :: (Show a) => a -> T.Text
tshow = T.pack . show
----------------------------------------------------------------------------------
makeLenses ''AlexInput
makeLenses ''ParseState
addWoundHere :: Int -> RlpParseError -> P ()
addWoundHere l e = P $ \st ->
let e' = MsgEnvelope
{ _msgSpan = let pos = psInput . aiPos
in SrcSpan (st ^. pos . posLine)
(st ^. pos . posColumn)
(st ^. pos . posAbsolute)
l
, _msgDiagnostic = e
, _msgSeverity = SevError
}
in (st, [e'], Just ())
addFatalHere :: Int -> RlpParseError -> P a
addFatalHere l e = P $ \st ->
let e' = MsgEnvelope
{ _msgSpan = let pos = psInput . aiPos
in SrcSpan (st ^. pos . posLine)
(st ^. pos . posColumn)
(st ^. pos . posAbsolute)
l
, _msgDiagnostic = e
, _msgSeverity = SevError
}
in (st, [e'], Nothing)
initParseState :: [Int] -> Text -> ParseState
initParseState ls s = ParseState
{ _psLayoutStack = []
-- IMPORTANT: the initial state is `bol` to begin the top-level layout,
-- which then returns to state 0 which continues the normal lexing process.
, _psLexState = ls
, _psInput = initAlexInput s
, _psOpTable = mempty
}
initAlexInput :: Text -> AlexInput
initAlexInput s = AlexInput
{ _aiPrevChar = '\0'
, _aiSource = s
, _aiBytes = []
, _aiPos = (1,1,0)
}

244
src/Rlp/Syntax.hs Normal file
View File

@@ -0,0 +1,244 @@
-- recursion-schemes
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable
, TemplateHaskell, TypeFamilies #-}
{-# LANGUAGE OverloadedStrings, PatternSynonyms #-}
{-# LANGUAGE TypeFamilies, TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances, ImpredicativeTypes #-}
module Rlp.Syntax
(
-- * AST
RlpProgram(..)
, Decl(..), Decl', RlpExpr(..), RlpExpr'
, Pat(..), Pat'
, Assoc(..)
, Lit(..), Lit'
, RlpType(..), RlpType'
, ConAlt(..)
, Binding(..), Binding'
-- * Trees That Grow boilerplate
-- ** Extension points
, IdP, XRec, UnXRec(..), MapXRec(..)
-- *** Decl
, XFunD, XTySigD, XInfixD, XDataD, XXDeclD
-- *** RlpExpr
, XLetE, XVarE, XLamE, XCaseE, XIfE, XAppE, XLitE
, XParE, XOAppE, XXRlpExprE
-- ** Pattern synonyms
-- *** Decl
, pattern FunD, pattern TySigD, pattern InfixD, pattern DataD
-- *** RlpExpr
, pattern LetE, pattern VarE, pattern LamE, pattern CaseE, pattern IfE
, pattern AppE, pattern LitE, pattern ParE, pattern OAppE
, pattern XRlpExprE
)
where
----------------------------------------------------------------------------------
import Data.Text (Text)
import Data.Text qualified as T
import Data.String (IsString(..))
import Data.Functor.Foldable.TH (makeBaseFunctor)
import Data.Functor.Classes
import Data.Kind (Type)
import Lens.Micro
import Lens.Micro.TH
import Core.Syntax hiding (Lit, Type, Binding, Binding')
import Core (HasRHS(..), HasLHS(..))
----------------------------------------------------------------------------------
data RlpModule p = RlpModule
{ _rlpmodName :: Text
, _rlpmodProgram :: RlpProgram p
}
-- | dear god.
type PhaseShow p =
( Show (XRec p Pat), Show (XRec p RlpExpr)
, Show (XRec p Lit), Show (IdP p)
, Show (XRec p RlpType)
, Show (XRec p Binding)
)
newtype RlpProgram p = RlpProgram [Decl' p]
deriving instance (PhaseShow p, Show (XRec p Decl)) => Show (RlpProgram p)
data RlpType p = FunConT
| FunT (RlpType' p) (RlpType' p)
| AppT (RlpType' p) (RlpType' p)
| VarT (IdP p)
| ConT (IdP p)
type RlpType' p = XRec p RlpType
deriving instance (PhaseShow p)
=> Show (RlpType p)
data Decl p = FunD' (XFunD p) (IdP p) [Pat' p] (RlpExpr' p) (Maybe (Where p))
| TySigD' (XTySigD p) [IdP p] (RlpType' p)
| DataD' (XDataD p) (IdP p) [IdP p] [ConAlt p]
| InfixD' (XInfixD p) Assoc Int (IdP p)
| XDeclD' !(XXDeclD p)
deriving instance
( Show (XFunD p), Show (XTySigD p)
, Show (XDataD p), Show (XInfixD p)
, Show (XXDeclD p)
, PhaseShow p
)
=> Show (Decl p)
type family XFunD p
type family XTySigD p
type family XDataD p
type family XInfixD p
type family XXDeclD p
pattern FunD :: (XFunD p ~ ())
=> (IdP p) -> [Pat' p] -> (RlpExpr' p) -> (Maybe (Where p))
-> Decl p
pattern TySigD :: (XTySigD p ~ ()) => [IdP p] -> (RlpType' p) -> Decl p
pattern DataD :: (XDataD p ~ ()) => (IdP p) -> [IdP p] -> [ConAlt p] -> Decl p
pattern InfixD :: (XInfixD p ~ ()) => Assoc -> Int -> (IdP p) -> Decl p
pattern XDeclD :: (XXDeclD p ~ ()) => Decl p
pattern FunD n as e wh = FunD' () n as e wh
pattern TySigD ns t = TySigD' () ns t
pattern DataD n as cs = DataD' () n as cs
pattern InfixD a p n = InfixD' () a p n
pattern XDeclD = XDeclD' ()
type Decl' p = XRec p Decl
data Assoc = InfixL
| InfixR
| Infix
deriving (Show)
data ConAlt p = ConAlt (IdP p) [RlpType' p]
deriving instance (Show (IdP p), Show (XRec p RlpType)) => Show (ConAlt p)
data RlpExpr p = LetE' (XLetE p) [Binding' p] (RlpExpr' p)
| VarE' (XVarE p) (IdP p)
| LamE' (XLamE p) [Pat p] (RlpExpr' p)
| CaseE' (XCaseE p) (RlpExpr' p) [(Alt p, Where p)]
| IfE' (XIfE p) (RlpExpr' p) (RlpExpr' p) (RlpExpr' p)
| AppE' (XAppE p) (RlpExpr' p) (RlpExpr' p)
| LitE' (XLitE p) (Lit p)
| ParE' (XParE p) (RlpExpr' p)
| OAppE' (XOAppE p) (IdP p) (RlpExpr' p) (RlpExpr' p)
| XRlpExprE' !(XXRlpExprE p)
type family XLetE p
type family XVarE p
type family XLamE p
type family XCaseE p
type family XIfE p
type family XAppE p
type family XLitE p
type family XParE p
type family XOAppE p
type family XXRlpExprE p
pattern LetE :: (XLetE p ~ ()) => [Binding' p] -> RlpExpr' p -> RlpExpr p
pattern VarE :: (XVarE p ~ ()) => IdP p -> RlpExpr p
pattern LamE :: (XLamE p ~ ()) => [Pat p] -> RlpExpr' p -> RlpExpr p
pattern CaseE :: (XCaseE p ~ ()) => RlpExpr' p -> [(Alt p, Where p)] -> RlpExpr p
pattern IfE :: (XIfE p ~ ()) => RlpExpr' p -> RlpExpr' p -> RlpExpr' p -> RlpExpr p
pattern AppE :: (XAppE p ~ ()) => RlpExpr' p -> RlpExpr' p -> RlpExpr p
pattern LitE :: (XLitE p ~ ()) => Lit p -> RlpExpr p
pattern ParE :: (XParE p ~ ()) => RlpExpr' p -> RlpExpr p
pattern OAppE :: (XOAppE p ~ ()) => IdP p -> RlpExpr' p -> RlpExpr' p -> RlpExpr p
pattern XRlpExprE :: (XXRlpExprE p ~ ()) => RlpExpr p
pattern LetE bs e = LetE' () bs e
pattern VarE n = VarE' () n
pattern LamE as e = LamE' () as e
pattern CaseE e as = CaseE' () e as
pattern IfE c a b = IfE' () c a b
pattern AppE f x = AppE' () f x
pattern LitE l = LitE' () l
pattern ParE e = ParE' () e
pattern OAppE n a b = OAppE' () n a b
pattern XRlpExprE = XRlpExprE' ()
deriving instance
( Show (XLetE p), Show (XVarE p), Show (XLamE p)
, Show (XCaseE p), Show (XIfE p), Show (XAppE p)
, Show (XLitE p), Show (XParE p), Show (XOAppE p)
, Show (XXRlpExprE p)
, PhaseShow p
) => Show (RlpExpr p)
type RlpExpr' p = XRec p RlpExpr
class UnXRec p where
unXRec :: XRec p f -> f p
class MapXRec p where
mapXRec :: (f p -> f' p') -> XRec p f -> XRec p' f'
type family XRec p (f :: Type -> Type) = (r :: Type) | r -> p f
type family IdP p
type Where p = [Binding p]
-- do we want guards?
data Alt p = AltA (Pat' p) (RlpExpr' p)
deriving instance (PhaseShow p) => Show (Alt p)
data Binding p = PatB (Pat' p) (RlpExpr' p)
| FunB (IdP p) [Pat' p] (RlpExpr' p)
type Binding' p = XRec p Binding
deriving instance (Show (XRec p Pat), Show (XRec p RlpExpr), Show (IdP p)
) => Show (Binding p)
data Pat p = VarP (IdP p)
| LitP (Lit' p)
| ConP (IdP p) [Pat' p]
deriving instance (PhaseShow p) => Show (Pat p)
type Pat' p = XRec p Pat
data Lit p = IntL Int
| CharL Char
| ListL [RlpExpr' p]
deriving instance (PhaseShow p) => Show (Lit p)
type Lit' p = XRec p Lit
-- instance HasLHS Alt Alt Pat Pat where
-- _lhs = lens
-- (\ (AltA p _) -> p)
-- (\ (AltA _ e) p' -> AltA p' e)
-- instance HasRHS Alt Alt RlpExpr RlpExpr where
-- _rhs = lens
-- (\ (AltA _ e) -> e)
-- (\ (AltA p _) e' -> AltA p e')
makeBaseFunctor ''RlpExpr
-- showsTernaryWith :: (Int -> x -> ShowS)
-- -> (Int -> y -> ShowS)
-- -> (Int -> z -> ShowS)
-- -> String -> Int
-- -> x -> y -> z
-- -> ShowS
-- showsTernaryWith sa sb sc name p a b c = showParen (p > 10)
-- $ showString name
-- . showChar ' ' . sa 11 a
-- . showChar ' ' . sb 11 b
-- . showChar ' ' . sc 11 c
--------------------------------------------------------------------------------
makeLenses ''RlpModule

View File

@@ -6,6 +6,7 @@ module Arith
) where
----------------------------------------------------------------------------------
import Data.Functor.Classes (eq1)
import Lens.Micro
import Core.Syntax
import GM
import Test.QuickCheck
@@ -70,13 +71,13 @@ instance Arbitrary ArithExpr where
-- coreResult = evalCore (toCore e)
toCore :: ArithExpr -> Program'
toCore expr = Program
toCore expr = mempty & programScDefs .~
[ ScDef "id" ["x"] $ Var "x"
, ScDef "main" [] $ go expr
]
where
go :: ArithExpr -> Expr'
go (IntA n) = LitE (IntL n)
go (IntA n) = Lit (IntL n)
go (NegateA e) = "negate#" :$ go e
go (IdA e) = "id" :$ go e
go (a :+ b) = f "+#" a b

View File

@@ -0,0 +1,62 @@
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
module Core.HindleyMilnerSpec
( spec
)
where
----------------------------------------------------------------------------------
import Core.Syntax
import Core.TH (coreExpr)
import Core.HindleyMilner
import Control.Monad.Errorful
import Data.Either (isLeft)
import Test.Hspec
----------------------------------------------------------------------------------
-- TODO: more tests. preferrably property-based. lol.
spec :: Spec
spec = do
it "should infer `id 3` :: Int" $
let g = [ ("id", "a" :-> "a") ]
in infer' g [coreExpr|id 3|] `shouldBe` Right TyInt
it "should not infer `id 3` when `id` is specialised to `a -> a`" $
let g = [ ("id", ("a" :-> "a") :-> "a" :-> "a") ]
in infer' g [coreExpr|id 3|] `shouldSatisfy` isLeft
-- TODO: property-based tests for let
it "should infer `let x = 3 in id x` :: Int" $
let g = [ ("id", "a" :-> "a") ]
e = [coreExpr|let {x = 3} in id x|]
in infer' g e `shouldBe` Right TyInt
it "should infer `let x = 3; y = 2 in (+#) x y` :: Int" $
let g = [ ("+#", TyInt :-> TyInt :-> TyInt) ]
e = [coreExpr|let {x=3;y=2} in (+#) x y|]
in infer' g e `shouldBe` Right TyInt
it "should find `3 :: Bool` contradictory" $
let e = [coreExpr|3|]
in check' [] (TyCon "Bool") e `shouldSatisfy` isLeft
it "should infer `fix ((+#) 1)` :: Int" $
let g = [ ("fix", ("a" :-> "a") :-> "a")
, ("+#", TyInt :-> TyInt :-> TyInt) ]
e = [coreExpr|fix ((+#) 1)|]
in infer' g e `shouldBe` Right TyInt
it "should infer mutually recursively defined lists" $
let g = [ ("cons", TyInt :-> TyCon "IntList" :-> TyCon "IntList") ]
e :: Expr'
e = [coreExpr|letrec { as = cons 1 bs; bs = cons 2 as } in as|]
in infer' g e `shouldBe` Right (TyCon "IntList")
infer' :: Context' -> Expr' -> Either [TypeError] Type
infer' g e = case runErrorful $ infer g e of
(Just t, _) -> Right t
(Nothing, es) -> Left es
check' :: Context' -> Type -> Expr' -> Either [TypeError] ()
check' g t e = case runErrorful $ check g t e of
(Just t, _) -> Right ()
(Nothing, es) -> Left es

View File

@@ -27,15 +27,22 @@ spec = do
in coreRes `shouldBe` arithRes
describe "test programs" $ do
it "fac 3" $ do
it "fac 3" $
resultOf Ex.fac3 `shouldBe` Just (NNum 6)
it "sum [1,2,3]" $ do
it "sum [1,2,3]" $
resultOf Ex.sumList `shouldBe` Just (NNum 6)
it "k 3 ((/#) 1 0)" $ do
it "k 3 ((/#) 1 0)" $
resultOf Ex.constDivZero `shouldBe` Just (NNum 3)
it "id (case ... of { ... })" $ do
it "id (case ... of { ... })" $
resultOf Ex.idCase `shouldBe` Just (NNum 5)
it "bool pattern matching with named constructors" $
resultOf Ex.namedBoolCase `shouldBe` Just (NNum 123)
it "list pattern matching with named constructors" $
resultOf Ex.namedConsCase `shouldBe` Just (NNum 6)