start to do better error-handling
Getty Ritter
1 year, 11 months ago
1 | {-# LANGUAGE DeriveAnyClass #-} | |
1 | 2 | {-# LANGUAGE OverloadedStrings #-} |
2 | 3 | |
3 | 4 | module Main where |
4 | 5 | |
6 | import qualified Control.Exception.Base as Exn | |
5 | 7 | import qualified Data.ByteString.Char8 as BS |
6 | 8 | import qualified Data.TeLML as TeLML |
7 | 9 | import qualified Data.Text as Text |
8 | 10 | import qualified Data.Text.Encoding as Text |
11 | import qualified Data.Text.IO as Text | |
9 | 12 | import qualified HsLua.Core as Lua |
10 | 13 | import qualified System.Console.GetOpt as Opt |
11 | 14 | import qualified System.Environment as Sys |
48 | 51 | (_, _, errors) -> |
49 | 52 | error (unlines errors) |
50 | 53 | |
54 | data Error | |
55 | = LuaError Lua.Exception | |
56 | | TeLMLError Exn.SomeException | |
57 | deriving (Show) | |
58 | ||
59 | instance Exn.Exception Error where | |
60 | displayException (LuaError err) = Exn.displayException err | |
61 | displayException (TeLMLError err) = Exn.displayException err | |
62 | ||
63 | instance Lua.LuaError Error where | |
64 | popException = do | |
65 | err <- Lua.changeErrorType Lua.popException | |
66 | return (LuaError err) | |
67 | ||
68 | pushException (LuaError err) = do | |
69 | Lua.changeErrorType (Lua.pushException err) | |
70 | pushException (TeLMLError err) = do | |
71 | let str = BS.pack (Exn.displayException err) | |
72 | Lua.pushstring str | |
73 | ||
74 | luaException = LuaError . Lua.luaException | |
75 | ||
76 | type LuaM r = Lua.LuaE Error r | |
77 | ||
78 | throw :: Exn.Exception e => e -> LuaM a | |
79 | throw = | |
80 | Lua.liftIO . Exn.throwIO . TeLMLError . Exn.toException | |
81 | ||
51 | 82 | main :: IO () |
52 | 83 | main = do |
53 | 84 | options <- parseOpts |
60 | 91 | luaSource <- case optTagFile options of |
61 | 92 | Nothing -> return "" |
62 | 93 | Just f -> BS.readFile f |
63 | result <- Lua.run (luaMain luaSource telml) | |
64 | print result | |
94 | result <- Lua.runEither (luaMain luaSource telml) | |
95 | case result of | |
96 | Right msg -> Text.putStr msg | |
97 | Left err -> putStrLn (Exn.displayException err) | |
65 | 98 | return () |
66 | 99 | |
67 |
luaMain :: BS.ByteString -> TeLML.Document -> Lua |
|
100 | luaMain :: BS.ByteString -> TeLML.Document -> LuaM Text.Text | |
68 | 101 | luaMain luaSource doc = do |
102 | Lua.openbase | |
103 | Lua.pop 1 | |
104 | ||
69 | 105 | Lua.newtable |
70 |
Lua.setglobal "telml" |
|
106 | Lua.setglobal "telml" | |
71 | 107 | _ <- Lua.dostring luaSource |
72 | 108 | telml <- Lua.getglobal "telml" |
73 | 109 | if telml /= Lua.TypeTable |
75 | 111 | else return () |
76 | 112 | handleDoc doc |
77 | 113 | |
78 | standardTags :: Text.Text -> [Text.Text] -> Maybe Text.Text | |
114 | data BuiltinArityMismatch = BuiltinArityMismatch | |
115 | { bamExpected :: Int, | |
116 | bamProvided :: Int, | |
117 | bamTagName :: Text.Text | |
118 | } | |
119 | deriving (Show) | |
120 | ||
121 | instance Exn.Exception BuiltinArityMismatch where | |
122 | displayException bam = | |
123 | concat | |
124 | [ "Tag `\\", | |
125 | Text.unpack (bamTagName bam), | |
126 | "`: expected ", | |
127 | show (bamExpected bam), | |
128 | " argument(s), got ", | |
129 | show (bamProvided bam) | |
130 | ] | |
131 | ||
132 | data NoSuchTag = NoSuchTag {nstName :: Text.Text} deriving (Show) | |
133 | ||
134 | instance Exn.Exception NoSuchTag where | |
135 | displayException nst = | |
136 | "No such tag: `" ++ Text.unpack (nstName nst) ++ "`" | |
137 | ||
138 | data NotAFunction = NotAFunction | |
139 | {nafName :: Text.Text, nafActual :: Lua.Type} | |
140 | deriving (Show) | |
141 | ||
142 | instance Exn.Exception NotAFunction where | |
143 | displayException naf = | |
144 | concat | |
145 | [ "Lua definition of `telml.", | |
146 | Text.unpack (nafName naf), | |
147 | "` not a function, found ", | |
148 | go (nafActual naf), | |
149 | " instead" | |
150 | ] | |
151 | where | |
152 | go Lua.TypeNil = "nil" | |
153 | go Lua.TypeBoolean = "boolean" | |
154 | go Lua.TypeLightUserdata = "userdata (light)" | |
155 | go Lua.TypeNumber = "number" | |
156 | go Lua.TypeString = "string" | |
157 | go Lua.TypeTable = "table" | |
158 | go Lua.TypeFunction = "function" | |
159 | go Lua.TypeUserdata = "userdata" | |
160 | go Lua.TypeThread = "thread" | |
161 | go Lua.TypeNone = "something unspeakable" | |
162 | ||
163 | standardTags :: Text.Text -> [Text.Text] -> LuaM Text.Text | |
79 | 164 | standardTags n ps = |
80 | 165 | case (n, ps) of |
81 | ("em", [r]) -> Just ("<em>" <> r <> "</em>") | |
82 | ("strong", [r]) -> Just ("<strong>" <> r <> "</strong>") | |
83 | ("li", [r]) -> Just ("<li>" <> r <> "</li>") | |
84 | _ -> Nothing | |
85 | ||
86 | handleDoc :: TeLML.Document -> Lua.LuaE Lua.Exception Text.Text | |
166 | -- \em to produce italics | |
167 | ("em", [r]) -> pure ("<em>" <> r <> "</em>") | |
168 | ("em", _) -> throw (BuiltinArityMismatch 1 (length ps) n) | |
169 | -- \strong to produce bolding | |
170 | ("strong", [r]) -> pure ("<strong>" <> r <> "</strong>") | |
171 | ("strong", _) -> throw (BuiltinArityMismatch 1 (length ps) n) | |
172 | -- \li to produce list items | |
173 | ("li", [r]) -> pure ("<li>" <> r <> "</li>") | |
174 | ("li", _) -> throw (BuiltinArityMismatch 1 (length ps) n) | |
175 | _ -> throw (NoSuchTag n) | |
176 | ||
177 | handleDoc :: TeLML.Document -> LuaM Text.Text | |
87 | 178 | handleDoc = fmap mconcat . sequence . map handleFrag |
88 | 179 | |
89 |
handleFrag :: TeLML.Fragment -> Lua |
|
180 | handleFrag :: TeLML.Fragment -> LuaM Text.Text | |
90 | 181 | handleFrag (TeLML.TextFrag text) = return text |
91 | 182 | handleFrag (TeLML.TagFrag tag) = handleTag tag |
92 | 183 | |
93 |
handleTag :: TeLML.Tag -> Lua |
|
184 | handleTag :: TeLML.Tag -> LuaM Text.Text | |
94 | 185 | handleTag (TeLML.Tag n ps) = do |
95 | 186 | ps' <- mapM handleDoc ps |
96 | 187 | Lua.pushstring (Text.encodeUtf8 n) |
97 | 188 | typ <- Lua.gettable 1 |
98 | 189 | case typ of |
99 | Lua.TypeNil -> case standardTags n ps' of | |
100 | Just r -> return r | |
101 |
|
|
190 | Lua.TypeNil -> standardTags n ps' | |
102 | 191 | Lua.TypeFunction -> do |
103 | 192 | mapM_ (Lua.pushstring . Text.encodeUtf8) ps' |
104 | 193 | Lua.call (Lua.NumArgs (fromIntegral (length ps'))) 1 |
105 | 194 | result <- Lua.tostring 2 |
106 | 195 | case result of |
107 | Nothing -> error "expected string" | |
108 | Just r -> return (Text.decodeUtf8 r) | |
109 |
|
|
196 | Nothing -> do | |
197 | actualtyp <- Lua.ltype 2 | |
198 | error ("expected string, got" ++ show actualtyp) | |
199 | Just r -> do | |
200 | return (Text.decodeUtf8 r) | |
201 | _ -> throw (NotAFunction n typ) |