Skip to content

Commit 2cc9def

Browse files
committed
Lua: split Init module into more modules.
The module has grown unwieldy and is therefore split into three internal Haskell modules, `Init`, `Module`, and `Run`.
1 parent 684668d commit 2cc9def

File tree

7 files changed

+285
-228
lines changed

7 files changed

+285
-228
lines changed

pandoc-lua-engine/pandoc-lua-engine.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ library
7171
, Text.Pandoc.Lua.Filter
7272
, Text.Pandoc.Lua.Global
7373
, Text.Pandoc.Lua.Init
74+
, Text.Pandoc.Lua.Module
7475
, Text.Pandoc.Lua.Marshal.Chunks
7576
, Text.Pandoc.Lua.Marshal.CommonState
7677
, Text.Pandoc.Lua.Marshal.Context
@@ -99,6 +100,7 @@ library
99100
, Text.Pandoc.Lua.Module.Utils
100101
, Text.Pandoc.Lua.Orphans
101102
, Text.Pandoc.Lua.PandocLua
103+
, Text.Pandoc.Lua.Run
102104
, Text.Pandoc.Lua.SourcePos
103105
, Text.Pandoc.Lua.Writer.Classic
104106
, Text.Pandoc.Lua.Writer.Scaffolding

pandoc-lua-engine/src/Text/Pandoc/Lua.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ module Text.Pandoc.Lua
2121
, getEngine
2222
) where
2323

24+
import Text.Pandoc.Lua.Custom (loadCustom)
2425
import Text.Pandoc.Lua.Engine (getEngine, applyFilter)
2526
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
26-
import Text.Pandoc.Lua.Init (runLua, runLuaNoEnv)
27-
import Text.Pandoc.Lua.Custom (loadCustom)
27+
import Text.Pandoc.Lua.Run (runLua, runLuaNoEnv)
2828
import Text.Pandoc.Lua.Orphans ()

pandoc-lua-engine/src/Text/Pandoc/Lua/Custom.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import HsLua as Lua hiding (Operation (Div))
1818
import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback)
1919
import Text.Pandoc.Error (PandocError)
2020
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
21-
import Text.Pandoc.Lua.Init (runLuaWith)
2221
import Text.Pandoc.Lua.Marshal.Format (peekExtensionsConfig)
2322
import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc)
2423
import Text.Pandoc.Lua.Marshal.WriterOptions (pushWriterOptions)
2524
import Text.Pandoc.Lua.PandocLua (unPandocLua)
25+
import Text.Pandoc.Lua.Run (runLuaWith)
2626
import Text.Pandoc.Readers (Reader (..))
2727
import Text.Pandoc.Sources (ToSources(..))
2828
import Text.Pandoc.Scripting (CustomComponents (..))

pandoc-lua-engine/src/Text/Pandoc/Lua/Engine.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import Text.Pandoc.Class (PandocMonad)
2121
import Text.Pandoc.Definition (Pandoc)
2222
import Text.Pandoc.Filter (Environment (..))
2323
import Text.Pandoc.Error (PandocError (PandocFilterError, PandocLuaError))
24+
import Text.Pandoc.Lua.Custom (loadCustom)
2425
import Text.Pandoc.Lua.Filter (runFilterFile)
2526
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
26-
import Text.Pandoc.Lua.Init (runLua)
27-
import Text.Pandoc.Lua.Custom (loadCustom)
27+
import Text.Pandoc.Lua.Run (runLua)
2828
import Text.Pandoc.Lua.Orphans ()
2929
import Text.Pandoc.Scripting (ScriptingEngine (..))
3030
import qualified Text.Pandoc.UTF8 as UTF8

pandoc-lua-engine/src/Text/Pandoc/Lua/Init.hs

Lines changed: 36 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -2,246 +2,59 @@
22
{-# LANGUAGE OverloadedStrings #-}
33
{-# LANGUAGE RankNTypes #-}
44
{- |
5-
Module : Text.Pandoc.Lua
6-
Copyright : Copyright © 2017-2024 Albert Krewinkel
7-
License : GNU GPL, version 2 or above
8-
5+
Module : Text.Pandoc.Lua.Init
6+
Copyright : © 2017-2024 Albert Krewinkel
7+
License : GPL-2.0-or-later
98
Maintainer : Albert Krewinkel <[email protected]>
10-
Stability : alpha
119
1210
Functions to initialize the Lua interpreter.
1311
-}
1412
module Text.Pandoc.Lua.Init
15-
( runLua
16-
, runLuaNoEnv
17-
, runLuaWith
13+
( initLua
14+
, userInit
1815
) where
1916

20-
import Control.Monad (forM, forM_, when)
21-
import Control.Monad.Catch (throwM, try)
22-
import Control.Monad.Trans (MonadIO (..))
23-
import Data.Maybe (catMaybes)
24-
import Data.Version (makeVersion)
25-
import HsLua as Lua hiding (status, try)
26-
import Text.Pandoc.Class (PandocMonad (..), report)
17+
import Control.Monad (when)
18+
import Control.Monad.Catch (throwM)
19+
import HsLua as Lua hiding (status)
20+
import Text.Pandoc.Class (report)
2721
import Text.Pandoc.Data (readDataFile)
2822
import Text.Pandoc.Error (PandocError (PandocLuaError))
2923
import Text.Pandoc.Logging (LogMessage (ScriptingWarning))
30-
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
31-
import Text.Pandoc.Lua.Marshal.List (pushPandocList, pushListModule)
24+
import Text.Pandoc.Lua.Module (initModules)
3225
import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua)
3326
import Text.Pandoc.Lua.SourcePos (luaSourcePos)
34-
import qualified Data.ByteString.Char8 as Char8
3527
import qualified Data.Text as T
36-
import qualified Lua.LPeg as LPeg
37-
import qualified HsLua.Aeson
38-
import qualified HsLua.Module.DocLayout as Module.Layout
39-
import qualified HsLua.Module.Path as Module.Path
40-
import qualified HsLua.Module.Zip as Module.Zip
41-
import qualified Text.Pandoc.Lua.Module.CLI as Pandoc.CLI
42-
import qualified Text.Pandoc.Lua.Module.Format as Pandoc.Format
43-
import qualified Text.Pandoc.Lua.Module.Image as Pandoc.Image
44-
import qualified Text.Pandoc.Lua.Module.JSON as Pandoc.JSON
45-
import qualified Text.Pandoc.Lua.Module.Log as Pandoc.Log
46-
import qualified Text.Pandoc.Lua.Module.MediaBag as Pandoc.MediaBag
47-
import qualified Text.Pandoc.Lua.Module.Pandoc as Module.Pandoc
48-
import qualified Text.Pandoc.Lua.Module.Scaffolding as Pandoc.Scaffolding
49-
import qualified Text.Pandoc.Lua.Module.Structure as Pandoc.Structure
50-
import qualified Text.Pandoc.Lua.Module.System as Pandoc.System
51-
import qualified Text.Pandoc.Lua.Module.Template as Pandoc.Template
52-
import qualified Text.Pandoc.Lua.Module.Text as Pandoc.Text
53-
import qualified Text.Pandoc.Lua.Module.Types as Pandoc.Types
54-
import qualified Text.Pandoc.Lua.Module.Utils as Pandoc.Utils
5528
import qualified Text.Pandoc.UTF8 as UTF8
5629

57-
-- | Run the Lua interpreter, using pandoc's default way of environment
58-
-- initialization.
59-
runLua :: (PandocMonad m, MonadIO m)
60-
=> LuaE PandocError a -> m (Either PandocError a)
61-
runLua action = do
62-
runPandocLuaWith Lua.run . try $ do
63-
initLuaState
64-
liftPandocLua action
65-
66-
runLuaWith :: (PandocMonad m, MonadIO m)
67-
=> GCManagedState -> LuaE PandocError a -> m (Either PandocError a)
68-
runLuaWith luaState action = do
69-
runPandocLuaWith (withGCManagedState luaState) . try $ do
70-
initLuaState
71-
liftPandocLua action
72-
73-
-- | Like 'runLua', but ignores all environment variables like @LUA_PATH@.
74-
runLuaNoEnv :: (PandocMonad m, MonadIO m)
75-
=> LuaE PandocError a -> m (Either PandocError a)
76-
runLuaNoEnv action = do
77-
runPandocLuaWith Lua.run . try $ do
78-
liftPandocLua $ do
79-
-- This is undocumented, but works -- the code is adapted from the
80-
-- `lua.c` sources for the default interpreter.
81-
Lua.pushboolean True
82-
Lua.setfield Lua.registryindex "LUA_NOENV"
83-
initLuaState
84-
liftPandocLua action
85-
86-
-- | Modules that are loaded at startup and assigned to fields in the
87-
-- pandoc module.
88-
--
89-
-- Note that @pandoc.List@ is not included here for technical reasons;
90-
-- it must be handled separately.
91-
loadedModules :: [Module PandocError]
92-
loadedModules =
93-
[ Pandoc.CLI.documentedModule
94-
, Pandoc.Format.documentedModule
95-
, Pandoc.Image.documentedModule
96-
, Pandoc.JSON.documentedModule
97-
, Pandoc.Log.documentedModule
98-
, Pandoc.MediaBag.documentedModule
99-
, Pandoc.Scaffolding.documentedModule
100-
, Pandoc.Structure.documentedModule
101-
, Pandoc.System.documentedModule
102-
, Pandoc.Template.documentedModule
103-
, Pandoc.Text.documentedModule
104-
, Pandoc.Types.documentedModule
105-
, Pandoc.Utils.documentedModule
106-
, Module.Layout.documentedModule { moduleName = "pandoc.layout" }
107-
`allSince` [2,18]
108-
, Module.Path.documentedModule { moduleName = "pandoc.path" }
109-
`allSince` [2,12]
110-
, Module.Zip.documentedModule { moduleName = "pandoc.zip" }
111-
`allSince` [3,0]
112-
]
113-
where
114-
allSince mdl version = mdl
115-
{ moduleFunctions = map (`since` makeVersion version) $ moduleFunctions mdl
116-
}
117-
118-
-- | Initialize the lua state with all required values
119-
initLuaState :: PandocLua ()
120-
initLuaState = do
30+
-- | Initialize Lua with all default and pandoc-specific libraries and default
31+
-- globals.
32+
initLua :: PandocLua ()
33+
initLua = do
12134
liftPandocLua Lua.openlibs
12235
setWarnFunction
123-
initPandocModule
124-
initJsonMetatable
125-
installLpegSearcher
126-
setGlobalModules
127-
loadInitScript "init.lua"
128-
where
129-
initPandocModule :: PandocLua ()
130-
initPandocModule = liftPandocLua $ do
131-
-- Push module table
132-
registerModule Module.Pandoc.documentedModule
133-
-- load modules and add them to the `pandoc` module table.
134-
forM_ loadedModules $ \mdl -> do
135-
registerModule mdl
136-
-- pandoc.text must be require-able as 'text' for backwards compat.
137-
when (moduleName mdl == "pandoc.text") $ do
138-
getfield registryindex loaded
139-
pushvalue (nth 2)
140-
setfield (nth 2) "text"
141-
pop 1 -- _LOADED
142-
-- Shorten name, drop everything before the first dot (if any).
143-
let fieldname (Name mdlname) = Name .
144-
maybe mdlname snd . Char8.uncons . snd $
145-
Char8.break (== '.') mdlname
146-
Lua.setfield (nth 2) (fieldname $ moduleName mdl)
147-
-- pandoc.List is low-level and must be opened differently.
148-
requirehs "pandoc.List" (const pushListModule)
149-
setfield (nth 2) "List"
150-
-- assign module to global variable
151-
Lua.setglobal "pandoc"
152-
153-
loadInitScript :: FilePath -> PandocLua ()
154-
loadInitScript scriptFile = do
155-
script <- readDataFile scriptFile
156-
status <- liftPandocLua $ Lua.dostring script
157-
when (status /= Lua.OK) . liftPandocLua $ do
158-
err <- popException
159-
let prefix = "Couldn't load '" <> T.pack scriptFile <> "':\n"
160-
throwM . PandocLuaError . (prefix <>) $ case err of
161-
PandocLuaError msg -> msg
162-
_ -> T.pack $ show err
163-
164-
setGlobalModules :: PandocLua ()
165-
setGlobalModules = liftPandocLua $ do
166-
let globalModules =
167-
[ ("lpeg", LPeg.luaopen_lpeg_ptr) -- must be loaded first
168-
, ("re", LPeg.luaopen_re_ptr) -- re depends on lpeg
169-
]
170-
loadedBuiltInModules <- fmap catMaybes . forM globalModules $
171-
\(pkgname, luaopen) -> do
172-
Lua.pushcfunction luaopen
173-
usedBuiltIn <- Lua.pcall 0 1 Nothing >>= \case
174-
OK -> do -- all good, loading succeeded
175-
-- register as loaded module so later modules can rely on this
176-
Lua.getfield Lua.registryindex Lua.loaded
177-
Lua.pushvalue (Lua.nth 2)
178-
Lua.setfield (Lua.nth 2) pkgname
179-
Lua.pop 1 -- pop _LOADED
180-
return True
181-
_ -> do -- built-in library failed, load system lib
182-
Lua.pop 1 -- ignore error message
183-
-- Try loading via the normal package loading mechanism.
184-
Lua.getglobal "require"
185-
Lua.pushName pkgname
186-
Lua.call 1 1 -- Throws an exception if loading failed again!
187-
return False
188-
189-
-- Module on top of stack. Register as global
190-
Lua.setglobal pkgname
191-
return $ if usedBuiltIn then Just pkgname else Nothing
192-
193-
-- Remove module entry from _LOADED table in registry if we used a
194-
-- built-in library. This ensures that later calls to @require@ will
195-
-- prefer the shared library, if any.
196-
forM_ loadedBuiltInModules $ \pkgname -> do
197-
Lua.getfield Lua.registryindex Lua.loaded
198-
Lua.pushnil
199-
Lua.setfield (Lua.nth 2) pkgname
200-
Lua.pop 1 -- registry
201-
202-
installLpegSearcher :: PandocLua ()
203-
installLpegSearcher = liftPandocLua $ do
204-
Lua.getglobal' "package.searchers"
205-
Lua.pushHaskellFunction $ Lua.state >>= liftIO . LPeg.lpeg_searcher
206-
Lua.rawseti (Lua.nth 2) . (+1) . fromIntegral =<< Lua.rawlen (Lua.nth 2)
207-
Lua.pop 1 -- remove 'package.searchers' from stack
208-
209-
-- | Setup the metatable that's assigned to Lua tables that were created
210-
-- from/via JSON arrays.
211-
initJsonMetatable :: PandocLua ()
212-
initJsonMetatable = liftPandocLua $ do
213-
pushPandocList (const pushnil) []
214-
getmetatable top
215-
setfield registryindex HsLua.Aeson.jsonarray
216-
Lua.pop 1
217-
218-
-- | Evaluate a @'PandocLua'@ computation, running all contained Lua
219-
-- operations.
220-
runPandocLuaWith :: (PandocMonad m, MonadIO m)
221-
=> (forall b. LuaE PandocError b -> IO b)
222-
-> PandocLua a
223-
-> m a
224-
runPandocLuaWith runner pLua = do
225-
origState <- getCommonState
226-
globals <- defaultGlobals
227-
(result, newState) <- liftIO . runner . unPandocLua $ do
228-
putCommonState origState
229-
liftPandocLua $ setGlobals globals
230-
r <- pLua
231-
c <- getCommonState
232-
return (r, c)
233-
putCommonState newState
234-
return result
235-
236-
-- | Global variables which should always be set.
237-
defaultGlobals :: PandocMonad m => m [Global]
238-
defaultGlobals = do
239-
commonState <- getCommonState
240-
return
241-
[ PANDOC_API_VERSION
242-
, PANDOC_STATE commonState
243-
, PANDOC_VERSION
244-
]
36+
initModules
37+
liftPandocLua userInit
38+
39+
-- | User-controlled initialization, e.g., running the user's init script.
40+
userInit :: LuaE PandocError ()
41+
userInit = runInitScript
42+
43+
-- | Run the @init.lua@ data file as a Lua script.
44+
runInitScript :: LuaE PandocError ()
45+
runInitScript = runDataFileScript "init.lua"
46+
47+
-- | Get a data file and run it as a Lua script.
48+
runDataFileScript :: FilePath -> LuaE PandocError ()
49+
runDataFileScript scriptFile = do
50+
script <- unPandocLua $ readDataFile scriptFile
51+
status <- Lua.dostring script
52+
when (status /= Lua.OK) $ do
53+
err <- popException
54+
let prefix = "Couldn't load '" <> T.pack scriptFile <> "':\n"
55+
throwM . PandocLuaError . (prefix <>) $ case err of
56+
PandocLuaError msg -> msg
57+
_ -> T.pack $ show err
24558

24659
setWarnFunction :: PandocLua ()
24760
setWarnFunction = liftPandocLua . setwarnf' $ \msg -> do

0 commit comments

Comments
 (0)