summaryrefslogtreecommitdiff
path: root/src/Language/Fiddle/Compiler.hs
blob: 768c569992d3bcc6f38c06470d7f616adfd79a20 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
module Language.Fiddle.Compiler where

import Control.Monad (when)
import Control.Monad.RWS (RWS, RWST, evalRWS, mapRWS, runRWS)
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Control.Monad.Writer
import Data.Default
import Language.Fiddle.Ast
import Language.Fiddle.Types
import Text.Parsec (SourcePos, sourceColumn, sourceLine, sourceName)
import System.IO (hPutStrLn, stderr)

data Level = Error | Warning | Info

data Diagnostic = Diagnostic Level String SourceSpan

-- Compilation monad. Has diagnostics. Optionally produces a value.
-- newtype Compile s a = Compile (s -> (s, [Diagnostic], Maybe a))

newtype Compile s a = Compile (MaybeT (RWS () [Diagnostic] s) a)
  deriving (Functor, Applicative, Monad)

compilationFailure :: Compile s a
compilationFailure = Compile $ MaybeT (return Nothing)

instance MonadWriter [Diagnostic] (Compile s) where
  tell = Compile . tell
  listen (Compile fn) = Compile $ listen fn
  pass (Compile fn) = Compile $ pass fn

instance MonadState s (Compile s) where
  get = Compile get
  put s = Compile $ put s
  state fn = Compile $ state fn

-- Runs a sub-compilation routine with the given state, but discards the
-- resulting state in favor of the original state.
subCompile :: s' -> Compile s' a -> Compile s (s', a)
subCompile s' (Compile mtrws) = Compile $ do
  let (a, s, w) = runRWS (runMaybeT mtrws) () s'
  tell w
  MaybeT $ return $ fmap (s,) a

-- mapMaybeT (mapRWS (\(a, _, w) -> (a, s', w))) mtrws

-- Saves the state, runs the routine, then restores the state.
pushState :: Compile s a -> Compile s a
pushState cp = do
  s <- get
  snd <$> subCompile s cp

-- Runs a compilation routine. It produces diagnostics and maybe a result.
-- Generally if the diagnostics contain an error, the result will be Nothing,
-- but if only Warnings are generated, then Just something will be returned.
--
-- Note that there is no actual type-level mechanism restricting this function
-- from returning something even if the diagnostics contain errors, but it
-- generally wouldn't make much sense for this to be the case.
compile :: Compile s a -> s -> ([Diagnostic], Maybe a)
compile (Compile fn) initState = do
  let (a, _, w) = runRWS (runMaybeT fn) () initState in (w, a)

compile_ :: (Default s) => Compile s a -> ([Diagnostic], Maybe a)
compile_ c = compile c def

hoistMaybe :: Maybe a -> Compile s a
hoistMaybe = Compile . MaybeT . return

newtype DiagnosticFormat = DiagnosticFormat (Diagnostic -> String)

coloredFormat :: DiagnosticFormat
coloredFormat = DiagnosticFormat $ \(Diagnostic level message (SourceSpan pos1 pos2)) ->
  execWriter $ do
    case level of
      Error -> tell "\x1b[01;31mError "
      Warning -> tell "\x1b[01;33mWarn "
      Info -> tell "\x1b[01;37mInfo "

    tell "\x1b[0m"
    tell (sourceName pos1)
    tell "("
    tellPos pos1
    when (pos2 /= pos1) $ do
      tell "-"
      tellPos pos2
    tell "): "
    tell (unwords $ words message)
  where
    tellPos pos = do
      tell (show $ sourceLine pos)
      tell ":"
      tell (show $ sourceColumn pos)

diagnosticToString :: DiagnosticFormat -> Diagnostic -> String
diagnosticToString (DiagnosticFormat f) = f

printDiagnostic :: Diagnostic -> IO ()
printDiagnostic d =
  hPutStrLn stderr (diagnosticToString coloredFormat d)

fromMayberOrFail :: SourceSpan -> String -> Maybe a -> Compile s a
fromMayberOrFail sourceSpan err Nothing = do
  tell [Diagnostic Error err sourceSpan]
  compilationFailure
fromMayberOrFail _ _ (Just a) = return a