aboutsummaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Lua
diff options
context:
space:
mode:
authorYan Pas <yanp.bugz@gmail.com>2018-10-07 18:10:01 +0300
committerYan Pas <yanp.bugz@gmail.com>2018-10-07 18:10:01 +0300
commit27467189ab184c5d098e244e01f7d1bfdb0d4d45 (patch)
treed1fb96ebbc49ee0c4e73ef354feddd521690d545 /src/Text/Pandoc/Lua
parent4f3dd3b1af7217214287ab886147c5e33a54774d (diff)
parentbd8a66394bc25b52dca9ffd963a560a4ca492f9c (diff)
downloadpandoc-27467189ab184c5d098e244e01f7d1bfdb0d4d45.tar.gz
Merge branch 'master' into groff_reader
Diffstat (limited to 'src/Text/Pandoc/Lua')
-rw-r--r--src/Text/Pandoc/Lua/Filter.hs144
-rw-r--r--src/Text/Pandoc/Lua/Init.hs15
-rw-r--r--src/Text/Pandoc/Lua/Module/MediaBag.hs2
-rw-r--r--src/Text/Pandoc/Lua/Module/Pandoc.hs35
-rw-r--r--src/Text/Pandoc/Lua/Module/Utils.hs34
-rw-r--r--src/Text/Pandoc/Lua/Packages.hs32
-rw-r--r--src/Text/Pandoc/Lua/StackInstances.hs254
-rw-r--r--src/Text/Pandoc/Lua/Util.hs186
8 files changed, 341 insertions, 361 deletions
diff --git a/src/Text/Pandoc/Lua/Filter.hs b/src/Text/Pandoc/Lua/Filter.hs
index 264066305..d17f9a969 100644
--- a/src/Text/Pandoc/Lua/Filter.hs
+++ b/src/Text/Pandoc/Lua/Filter.hs
@@ -1,6 +1,33 @@
-{-# LANGUAGE NoImplicitPrelude #-}
+{-
+Copyright © 2012-2018 John MacFarlane <jgm@berkeley.edu>
+ 2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-}
{-# LANGUAGE FlexibleContexts #-}
-
+{-# LANGUAGE NoImplicitPrelude #-}
+{- |
+Module : Text.Pandoc.Lua.Filter
+Copyright : © 2012–2018 John MacFarlane,
+ © 2017-2018 Albert Krewinkel
+License : GNU GPL, version 2 or above
+Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
+Stability : alpha
+
+Types and functions for running Lua filters.
+-}
module Text.Pandoc.Lua.Filter ( LuaFilterFunction
, LuaFilter
, tryFilter
@@ -12,62 +39,58 @@ module Text.Pandoc.Lua.Filter ( LuaFilterFunction
, inlineElementNames
) where
import Prelude
-import Control.Monad (mplus, unless, when, (>=>))
+import Control.Monad (mplus, (>=>))
import Control.Monad.Catch (finally)
-import Text.Pandoc.Definition
+import Data.Data (Data, DataType, dataTypeConstrs, dataTypeName, dataTypeOf,
+ showConstr, toConstr, tyconUQname)
import Data.Foldable (foldrM)
import Data.Map (Map)
-import qualified Data.Map as Map
-import qualified Foreign.Lua as Lua
-import Foreign.Lua (FromLuaStack (peek), Lua, StackIndex,
- Status (OK), ToLuaStack (push))
+import Foreign.Lua (Lua, Peekable, Pushable)
+import Text.Pandoc.Definition
+import Text.Pandoc.Lua.StackInstances ()
import Text.Pandoc.Walk (walkM, Walkable)
-import Data.Data (Data, DataType, dataTypeConstrs, dataTypeName, dataTypeOf,
- showConstr, toConstr, tyconUQname)
-import Text.Pandoc.Lua.StackInstances()
-import Text.Pandoc.Lua.Util (typeCheck)
-type FunctionMap = Map String LuaFilterFunction
-
-newtype LuaFilterFunction = LuaFilterFunction { functionIndex :: Int }
-
-instance ToLuaStack LuaFilterFunction where
- push = pushFilterFunction
-
-instance FromLuaStack LuaFilterFunction where
- peek = registerFilterFunction
-
-newtype LuaFilter = LuaFilter FunctionMap
-
-instance FromLuaStack LuaFilter where
- peek idx =
- let constrs = metaFilterName : pandocFilterNames
- ++ blockElementNames
- ++ inlineElementNames
- fn c acc = do
- Lua.getfield idx c
- filterFn <- Lua.tryLua (peek (-1))
- Lua.pop 1
+import qualified Data.Map.Strict as Map
+import qualified Foreign.Lua as Lua
+import qualified Text.Pandoc.Lua.Util as LuaUtil
+
+-- | Filter function stored in the registry
+newtype LuaFilterFunction = LuaFilterFunction Lua.Reference
+
+-- | Collection of filter functions (at most one function per element
+-- constructor)
+newtype LuaFilter = LuaFilter (Map String LuaFilterFunction)
+
+instance Peekable LuaFilter where
+ peek idx = do
+ let constrs = metaFilterName
+ : pandocFilterNames
+ ++ blockElementNames
+ ++ inlineElementNames
+ let go constr acc = do
+ Lua.getfield idx constr
+ filterFn <- registerFilterFunction
return $ case filterFn of
- Left _ -> acc
- Right f -> (c, f) : acc
- in LuaFilter . Map.fromList <$> foldrM fn [] constrs
-
--- | Push the filter function to the top of the stack.
+ Nothing -> acc
+ Just fn -> Map.insert constr fn acc
+ LuaFilter <$> foldrM go Map.empty constrs
+
+-- | Register the function at the top of the stack as a filter function in the
+-- registry.
+registerFilterFunction :: Lua (Maybe LuaFilterFunction)
+registerFilterFunction = do
+ isFn <- Lua.isfunction Lua.stackTop
+ if isFn
+ then Just . LuaFilterFunction <$> Lua.ref Lua.registryindex
+ else Nothing <$ Lua.pop 1
+
+-- | Retrieve filter function from registry and push it to the top of the stack.
pushFilterFunction :: LuaFilterFunction -> Lua ()
-pushFilterFunction lf =
- -- The function is stored in a lua registry table, retrieve it from there.
- Lua.rawgeti Lua.registryindex (functionIndex lf)
-
-registerFilterFunction :: StackIndex -> Lua LuaFilterFunction
-registerFilterFunction idx = do
- isFn <- Lua.isfunction idx
- unless isFn . Lua.throwLuaError $ "Not a function at index " ++ show idx
- Lua.pushvalue idx
- refIdx <- Lua.ref Lua.registryindex
- return $ LuaFilterFunction refIdx
-
-elementOrList :: FromLuaStack a => a -> Lua [a]
+pushFilterFunction (LuaFilterFunction fnRef) =
+ Lua.getref Lua.registryindex fnRef
+
+
+elementOrList :: Peekable a => a -> Lua [a]
elementOrList x = do
let topOfStack = Lua.stackTop
elementUnchanged <- Lua.isnil topOfStack
@@ -77,12 +100,10 @@ elementOrList x = do
mbres <- Lua.peekEither topOfStack
case mbres of
Right res -> [res] <$ Lua.pop 1
- Left _ -> do
- typeCheck Lua.stackTop Lua.TypeTable
- Lua.toList topOfStack `finally` Lua.pop 1
+ Left _ -> Lua.peekList topOfStack `finally` Lua.pop 1
-- | Try running a filter for the given element
-tryFilter :: (Data a, FromLuaStack a, ToLuaStack a)
+tryFilter :: (Data a, Peekable a, Pushable a)
=> LuaFilter -> a -> Lua [a]
tryFilter (LuaFilter fnMap) x =
let filterFnName = showConstr (toConstr x)
@@ -96,14 +117,11 @@ tryFilter (LuaFilter fnMap) x =
-- called with given element as argument and is expected to return an element.
-- Alternatively, the function can return nothing or nil, in which case the
-- element is left unchanged.
-runFilterFunction :: ToLuaStack a => LuaFilterFunction -> a -> Lua ()
+runFilterFunction :: Pushable a => LuaFilterFunction -> a -> Lua ()
runFilterFunction lf x = do
pushFilterFunction lf
- push x
- z <- Lua.pcall 1 1 Nothing
- when (z /= OK) $ do
- let addPrefix = ("Error while running filter function: " ++)
- Lua.throwTopMessageAsError' addPrefix
+ Lua.push x
+ LuaUtil.callWithTraceback 1 1
walkMWithLuaFilter :: LuaFilter -> Pandoc -> Lua Pandoc
walkMWithLuaFilter f =
@@ -156,7 +174,7 @@ metaFilterName = "Meta"
pandocFilterNames :: [String]
pandocFilterNames = ["Pandoc", "Doc"]
-singleElement :: FromLuaStack a => a -> Lua a
+singleElement :: Peekable a => a -> Lua a
singleElement x = do
elementUnchanged <- Lua.isnil (-1)
if elementUnchanged
@@ -167,6 +185,6 @@ singleElement x = do
Right res -> res <$ Lua.pop 1
Left err -> do
Lua.pop 1
- Lua.throwLuaError $
+ Lua.throwException $
"Error while trying to get a filter's return " ++
"value from lua stack.\n" ++ err
diff --git a/src/Text/Pandoc/Lua/Init.hs b/src/Text/Pandoc/Lua/Init.hs
index c8c7fdfbd..35611d481 100644
--- a/src/Text/Pandoc/Lua/Init.hs
+++ b/src/Text/Pandoc/Lua/Init.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE NoImplicitPrelude #-}
{-
Copyright © 2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -16,6 +15,7 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
+{-# LANGUAGE NoImplicitPrelude #-}
{- |
Module : Text.Pandoc.Lua
Copyright : Copyright © 2017-2018 Albert Krewinkel
@@ -40,7 +40,7 @@ import Control.Monad.Trans (MonadIO (..))
import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr)
import Data.IORef (newIORef, readIORef)
import Data.Version (Version (versionBranch))
-import Foreign.Lua (Lua, LuaException (..))
+import Foreign.Lua (Lua)
import GHC.IO.Encoding (getForeignEncoding, setForeignEncoding, utf8)
import Paths_pandoc (version)
import Text.Pandoc.Class (PandocIO, getCommonState, getUserDataDir, getMediaBag,
@@ -54,17 +54,22 @@ import qualified Foreign.Lua as Lua
import qualified Foreign.Lua.Module.Text as Lua
import qualified Text.Pandoc.Definition as Pandoc
+-- | Lua error message
+newtype LuaException = LuaException String deriving (Show)
+
-- | Run the lua interpreter, using pandoc's default way of environment
--- initalization.
+-- initialization.
runPandocLua :: Lua a -> PandocIO (Either LuaException a)
runPandocLua luaOp = do
luaPkgParams <- luaPackageParams
enc <- liftIO $ getForeignEncoding <* setForeignEncoding utf8
- res <- liftIO $ Lua.runLuaEither (initLuaState luaPkgParams *> luaOp)
+ res <- liftIO $ Lua.runEither (initLuaState luaPkgParams *> luaOp)
liftIO $ setForeignEncoding enc
newMediaBag <- liftIO (readIORef (luaPkgMediaBag luaPkgParams))
setMediaBag newMediaBag
- return res
+ return $ case res of
+ Left (Lua.Exception msg) -> Left (LuaException msg)
+ Right x -> Right x
-- | Generate parameters required to setup pandoc's lua environment.
luaPackageParams :: PandocIO LuaPackageParams
diff --git a/src/Text/Pandoc/Lua/Module/MediaBag.hs b/src/Text/Pandoc/Lua/Module/MediaBag.hs
index f48fe56c5..150c06cc8 100644
--- a/src/Text/Pandoc/Lua/Module/MediaBag.hs
+++ b/src/Text/Pandoc/Lua/Module/MediaBag.hs
@@ -87,7 +87,7 @@ mediaDirectoryFn mbRef = do
zipWithM_ addEntry [1..] dirContents
return 1
where
- addEntry :: Int -> (FilePath, MimeType, Int) -> Lua ()
+ addEntry :: Lua.Integer -> (FilePath, MimeType, Int) -> Lua ()
addEntry idx (fp, mimeType, contentLength) = do
Lua.newtable
Lua.push "path" *> Lua.push fp *> Lua.rawset (-3)
diff --git a/src/Text/Pandoc/Lua/Module/Pandoc.hs b/src/Text/Pandoc/Lua/Module/Pandoc.hs
index 8cb630d7b..769b04b9e 100644
--- a/src/Text/Pandoc/Lua/Module/Pandoc.hs
+++ b/src/Text/Pandoc/Lua/Module/Pandoc.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE NoImplicitPrelude #-}
{-
Copyright © 2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -17,6 +16,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE NoImplicitPrelude #-}
{- |
Module : Text.Pandoc.Lua.Module.Pandoc
Copyright : Copyright © 2017-2018 Albert Krewinkel
@@ -36,13 +36,12 @@ import Control.Monad (when)
import Data.Default (Default (..))
import Data.Maybe (fromMaybe)
import Data.Text (pack)
-import Foreign.Lua (ToLuaStack, FromLuaStack, Lua, NumResults, Optional, liftIO)
+import Foreign.Lua (Lua, NumResults, Optional, Peekable, Pushable)
import System.Exit (ExitCode (..))
import Text.Pandoc.Class (runIO)
import Text.Pandoc.Definition (Block, Inline)
import Text.Pandoc.Lua.Filter (walkInlines, walkBlocks, LuaFilter)
import Text.Pandoc.Lua.StackInstances ()
-import Text.Pandoc.Lua.Util (addFunction, addValue, loadScriptFromDataDir)
import Text.Pandoc.Walk (Walkable)
import Text.Pandoc.Options (ReaderOptions (readerExtensions))
import Text.Pandoc.Process (pipeProcess)
@@ -51,19 +50,20 @@ import Text.Pandoc.Readers (Reader (..), getReader)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BSL
import qualified Foreign.Lua as Lua
+import qualified Text.Pandoc.Lua.Util as LuaUtil
-- | Push the "pandoc" on the lua stack. Requires the `list` module to be
-- loaded.
pushModule :: Maybe FilePath -> Lua NumResults
pushModule datadir = do
- loadScriptFromDataDir datadir "pandoc.lua"
- addFunction "read" readDoc
- addFunction "pipe" pipeFn
- addFunction "walk_block" walkBlock
- addFunction "walk_inline" walkInline
+ LuaUtil.loadScriptFromDataDir datadir "pandoc.lua"
+ LuaUtil.addFunction "read" readDoc
+ LuaUtil.addFunction "pipe" pipeFn
+ LuaUtil.addFunction "walk_block" walkBlock
+ LuaUtil.addFunction "walk_inline" walkInline
return 1
-walkElement :: (ToLuaStack a, Walkable [Inline] a, Walkable [Block] a)
+walkElement :: (Pushable a, Walkable [Inline] a, Walkable [Block] a)
=> a -> LuaFilter -> Lua a
walkElement x f = walkInlines f x >>= walkBlocks f
@@ -81,7 +81,8 @@ readDoc content formatSpecOrNil = do
Right (reader, es) ->
case reader of
TextReader r -> do
- res <- liftIO $ runIO $ r def{ readerExtensions = es } (pack content)
+ res <- Lua.liftIO . runIO $
+ r def{ readerExtensions = es } (pack content)
case res of
Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc
Left s -> Lua.raiseError (show s) -- error while reading
@@ -93,7 +94,7 @@ pipeFn :: String
-> BL.ByteString
-> Lua NumResults
pipeFn command args input = do
- (ec, output) <- liftIO $ pipeProcess Nothing command args input
+ (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input
case ec of
ExitSuccess -> 1 <$ Lua.push output
ExitFailure n -> Lua.raiseError (PipeError command n output)
@@ -104,26 +105,26 @@ data PipeError = PipeError
, pipeErrorOutput :: BL.ByteString
}
-instance FromLuaStack PipeError where
+instance Peekable PipeError where
peek idx =
PipeError
<$> (Lua.getfield idx "command" *> Lua.peek (-1) <* Lua.pop 1)
<*> (Lua.getfield idx "error_code" *> Lua.peek (-1) <* Lua.pop 1)
<*> (Lua.getfield idx "output" *> Lua.peek (-1) <* Lua.pop 1)
-instance ToLuaStack PipeError where
+instance Pushable PipeError where
push pipeErr = do
Lua.newtable
- addValue "command" (pipeErrorCommand pipeErr)
- addValue "error_code" (pipeErrorCode pipeErr)
- addValue "output" (pipeErrorOutput pipeErr)
+ LuaUtil.addField "command" (pipeErrorCommand pipeErr)
+ LuaUtil.addField "error_code" (pipeErrorCode pipeErr)
+ LuaUtil.addField "output" (pipeErrorOutput pipeErr)
pushPipeErrorMetaTable
Lua.setmetatable (-2)
where
pushPipeErrorMetaTable :: Lua ()
pushPipeErrorMetaTable = do
v <- Lua.newmetatable "pandoc pipe error"
- when v $ addFunction "__tostring" pipeErrorMessage
+ when v $ LuaUtil.addFunction "__tostring" pipeErrorMessage
pipeErrorMessage :: PipeError -> Lua BL.ByteString
pipeErrorMessage (PipeError cmd errorCode output) = return $ mconcat
diff --git a/src/Text/Pandoc/Lua/Module/Utils.hs b/src/Text/Pandoc/Lua/Module/Utils.hs
index 7fa4616be..030d6af95 100644
--- a/src/Text/Pandoc/Lua/Module/Utils.hs
+++ b/src/Text/Pandoc/Lua/Module/Utils.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE NoImplicitPrelude #-}
{-
Copyright © 2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -16,6 +15,7 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
+{-# LANGUAGE NoImplicitPrelude #-}
{- |
Module : Text.Pandoc.Lua.Module.Utils
Copyright : Copyright © 2017-2018 Albert Krewinkel
@@ -33,15 +33,16 @@ module Text.Pandoc.Lua.Module.Utils
import Prelude
import Control.Applicative ((<|>))
import Data.Default (def)
-import Foreign.Lua (FromLuaStack, Lua, LuaInteger, NumResults)
+import Foreign.Lua (Peekable, Lua, NumResults)
import Text.Pandoc.Class (runIO, setUserDataDir)
import Text.Pandoc.Definition (Pandoc, Meta, MetaValue, Block, Inline)
import Text.Pandoc.Lua.StackInstances ()
-import Text.Pandoc.Lua.Util (addFunction, popValue)
+import Text.Pandoc.Lua.Util (addFunction)
import qualified Data.Digest.Pure.SHA as SHA
import qualified Data.ByteString.Lazy as BSL
import qualified Foreign.Lua as Lua
+import qualified Text.Pandoc.Builder as B
import qualified Text.Pandoc.Filter.JSON as JSONFilter
import qualified Text.Pandoc.Shared as Shared
@@ -49,6 +50,7 @@ import qualified Text.Pandoc.Shared as Shared
pushModule :: Maybe FilePath -> Lua NumResults
pushModule mbDatadir = do
Lua.newtable
+ addFunction "blocks_to_inlines" blocksToInlines
addFunction "hierarchicalize" hierarchicalize
addFunction "normalize_date" normalizeDate
addFunction "run_json_filter" (runJSONFilter mbDatadir)
@@ -57,6 +59,14 @@ pushModule mbDatadir = do
addFunction "to_roman_numeral" toRomanNumeral
return 1
+-- | Squashes a list of blocks into inlines.
+blocksToInlines :: [Block] -> Lua.Optional [Inline] -> Lua [Inline]
+blocksToInlines blks optSep = do
+ let sep = case Lua.fromOptional optSep of
+ Just x -> B.fromList x
+ Nothing -> Shared.defaultBlocksSeparator
+ return $ B.toList (Shared.blocksToInlinesWithSep sep blks)
+
-- | Convert list of Pandoc blocks into (hierarchical) list of Elements.
hierarchicalize :: [Block] -> Lua [Shared.Element]
hierarchicalize = return . Shared.hierarchicalize
@@ -79,7 +89,7 @@ runJSONFilter mbDatadir doc filterFile optArgs = do
Just x -> return x
Nothing -> do
Lua.getglobal "FORMAT"
- (:[]) <$> popValue
+ (:[]) <$> Lua.popValue
filterRes <- Lua.liftIO . runIO $ do
setUserDataDir mbDatadir
JSONFilter.apply def args filterFile doc
@@ -111,18 +121,18 @@ data AstElement
| MetaValueElement MetaValue
deriving (Show)
-instance FromLuaStack AstElement where
+instance Peekable AstElement where
peek idx = do
- res <- Lua.tryLua $ (PandocElement <$> Lua.peek idx)
- <|> (InlineElement <$> Lua.peek idx)
- <|> (BlockElement <$> Lua.peek idx)
- <|> (MetaElement <$> Lua.peek idx)
- <|> (MetaValueElement <$> Lua.peek idx)
+ res <- Lua.try $ (PandocElement <$> Lua.peek idx)
+ <|> (InlineElement <$> Lua.peek idx)
+ <|> (BlockElement <$> Lua.peek idx)
+ <|> (MetaElement <$> Lua.peek idx)
+ <|> (MetaValueElement <$> Lua.peek idx)
case res of
Right x -> return x
- Left _ -> Lua.throwLuaError
+ Left _ -> Lua.throwException
"Expected an AST element, but could not parse value as such."
-- | Convert a number < 4000 to uppercase roman numeral.
-toRomanNumeral :: LuaInteger -> Lua String
+toRomanNumeral :: Lua.Integer -> Lua String
toRomanNumeral = return . Shared.toRomanNumeral . fromIntegral
diff --git a/src/Text/Pandoc/Lua/Packages.hs b/src/Text/Pandoc/Lua/Packages.hs
index 59637826e..5cf11f5c5 100644
--- a/src/Text/Pandoc/Lua/Packages.hs
+++ b/src/Text/Pandoc/Lua/Packages.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE NoImplicitPrelude #-}
{-
Copyright © 2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -16,8 +15,9 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
-{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE ScopedTypeVariables #-}
{- |
Module : Text.Pandoc.Lua.Packages
Copyright : Copyright © 2017-2018 Albert Krewinkel
@@ -35,12 +35,11 @@ module Text.Pandoc.Lua.Packages
import Prelude
import Control.Monad (forM_)
-import Data.ByteString.Char8 (unpack)
+import Data.ByteString (ByteString)
import Data.IORef (IORef)
import Foreign.Lua (Lua, NumResults, liftIO)
import Text.Pandoc.Class (CommonState, readDataFile, runIO, setUserDataDir)
import Text.Pandoc.MediaBag (MediaBag)
-import Text.Pandoc.Lua.Util (dostring')
import qualified Foreign.Lua as Lua
import Text.Pandoc.Lua.Module.Pandoc as Pandoc
@@ -57,14 +56,10 @@ data LuaPackageParams = LuaPackageParams
-- | Insert pandoc's package loader as the first loader, making it the default.
installPandocPackageSearcher :: LuaPackageParams -> Lua ()
installPandocPackageSearcher luaPkgParams = do
- luaVersion <- Lua.getglobal "_VERSION" *> Lua.peek (-1)
- if luaVersion == "Lua 5.1"
- then Lua.getglobal' "package.loaders"
- else Lua.getglobal' "package.searchers"
+ Lua.getglobal' "package.searchers"
shiftArray
Lua.pushHaskellFunction (pandocPackageSearcher luaPkgParams)
- Lua.wrapHaskellFunction
- Lua.rawseti (-2) 1
+ Lua.rawseti (Lua.nthFromTop 2) 1
Lua.pop 1 -- remove 'package.searchers' from stack
where
shiftArray = forM_ [4, 3, 2, 1] $ \i -> do
@@ -86,7 +81,6 @@ pandocPackageSearcher luaPkgParams pkgName =
where
pushWrappedHsFun f = do
Lua.pushHaskellFunction f
- Lua.wrapHaskellFunction
return 1
searchPureLuaLoader = do
let filename = pkgName ++ ".lua"
@@ -97,21 +91,19 @@ pandocPackageSearcher luaPkgParams pkgName =
Lua.push ("no file '" ++ filename ++ "' in pandoc's datadir")
return 1
-loadStringAsPackage :: String -> String -> Lua NumResults
+loadStringAsPackage :: String -> ByteString -> Lua NumResults
loadStringAsPackage pkgName script = do
- status <- dostring' script
+ status <- Lua.dostring script
if status == Lua.OK
then return (1 :: NumResults)
else do
- msg <- Lua.peek (-1) <* Lua.pop 1
- Lua.push ("Error while loading ``" ++ pkgName ++ "`.\n" ++ msg)
- Lua.lerror
- return (2 :: NumResults)
+ msg <- Lua.popValue
+ Lua.raiseError ("Error while loading `" <> pkgName <> "`.\n" <> msg)
--- | Get the string representation of the pandoc module
-dataDirScript :: Maybe FilePath -> FilePath -> IO (Maybe String)
+-- | Get the ByteString representation of the pandoc module.
+dataDirScript :: Maybe FilePath -> FilePath -> IO (Maybe ByteString)
dataDirScript datadir moduleFile = do
res <- runIO $ setUserDataDir datadir >> readDataFile moduleFile
return $ case res of
Left _ -> Nothing
- Right s -> Just (unpack s)
+ Right s -> Just s
diff --git a/src/Text/Pandoc/Lua/StackInstances.hs b/src/Text/Pandoc/Lua/StackInstances.hs
index 3298079c5..931b8c225 100644
--- a/src/Text/Pandoc/Lua/StackInstances.hs
+++ b/src/Text/Pandoc/Lua/StackInstances.hs
@@ -1,4 +1,8 @@
-{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE LambdaCase #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
{-
Copyright © 2012-2018 John MacFarlane <jgm@berkeley.edu>
2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -17,10 +21,6 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
{- |
Module : Text.Pandoc.Lua.StackInstances
Copyright : © 2012-2018 John MacFarlane
@@ -37,148 +37,125 @@ module Text.Pandoc.Lua.StackInstances () where
import Prelude
import Control.Applicative ((<|>))
import Control.Monad (when)
-import Control.Monad.Catch (finally)
import Data.Data (showConstr, toConstr)
-import Data.Foldable (forM_)
-import Foreign.Lua (FromLuaStack (peek), Lua, LuaInteger, LuaNumber, StackIndex,
- ToLuaStack (push), Type (..), throwLuaError, tryLua)
+import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
import Text.Pandoc.Definition
import Text.Pandoc.Extensions (Extensions)
-import Text.Pandoc.Lua.Util (getTable, getTag, pushViaConstructor, typeCheck)
+import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor)
import Text.Pandoc.Options (ReaderOptions (..), TrackChanges)
-import Text.Pandoc.Shared (Element (Blk, Sec), safeRead)
+import Text.Pandoc.Shared (Element (Blk, Sec))
-import qualified Foreign.Lua as Lua
import qualified Data.Set as Set
+import qualified Foreign.Lua as Lua
import qualified Text.Pandoc.Lua.Util as LuaUtil
-defineHowTo :: String -> Lua a -> Lua a
-defineHowTo ctx op = op `Lua.modifyLuaError` (("Could not " ++ ctx ++ ": ") ++)
-
-instance ToLuaStack Pandoc where
+instance Pushable Pandoc where
push (Pandoc meta blocks) =
pushViaConstructor "Pandoc" blocks meta
-instance FromLuaStack Pandoc where
+instance Peekable Pandoc where
peek idx = defineHowTo "get Pandoc value" $ do
- typeCheck idx Lua.TypeTable
- blocks <- getTable idx "blocks"
- meta <- Lua.getfield idx "meta" *> (Lua.peek Lua.stackTop `finally` Lua.pop 1)
+ blocks <- LuaUtil.rawField idx "blocks"
+ meta <- LuaUtil.rawField idx "meta"
return $ Pandoc meta blocks
-instance ToLuaStack Meta where
+instance Pushable Meta where
push (Meta mmap) =
pushViaConstructor "Meta" mmap
-instance FromLuaStack Meta where
- peek idx = defineHowTo "get Meta value" $ do
- typeCheck idx Lua.TypeTable
- Meta <$> peek idx
+instance Peekable Meta where
+ peek idx = defineHowTo "get Meta value" $
+ Meta <$> Lua.peek idx
-instance ToLuaStack MetaValue where
+instance Pushable MetaValue where
push = pushMetaValue
-instance FromLuaStack MetaValue where
+instance Peekable MetaValue where
peek = peekMetaValue
-instance ToLuaStack Block where
+instance Pushable Block where
push = pushBlock
-instance FromLuaStack Block where
+instance Peekable Block where
peek = peekBlock
-- Inline
-instance ToLuaStack Inline where
+instance Pushable Inline where
push = pushInline
-instance FromLuaStack Inline where
+instance Peekable Inline where
peek = peekInline
-- Citation
-instance ToLuaStack Citation where
+instance Pushable Citation where
push (Citation cid prefix suffix mode noteNum hash) =
pushViaConstructor "Citation" cid mode prefix suffix noteNum hash
-instance FromLuaStack Citation where
+instance Peekable Citation where
peek idx = do
- id' <- getTable idx "id"
- prefix <- getTable idx "prefix"
- suffix <- getTable idx "suffix"
- mode <- getTable idx "mode"
- num <- getTable idx "note_num"
- hash <- getTable idx "hash"
+ id' <- LuaUtil.rawField idx "id"
+ prefix <- LuaUtil.rawField idx "prefix"
+ suffix <- LuaUtil.rawField idx "suffix"
+ mode <- LuaUtil.rawField idx "mode"
+ num <- LuaUtil.rawField idx "note_num"
+ hash <- LuaUtil.rawField idx "hash"
return $ Citation id' prefix suffix mode num hash
-instance ToLuaStack Alignment where
- push = push . show
-instance FromLuaStack Alignment where
- peek idx = safeRead' =<< peek idx
-
-instance ToLuaStack CitationMode where
- push = push . show
-instance FromLuaStack CitationMode where
- peek idx = safeRead' =<< peek idx
-
-instance ToLuaStack Format where
- push (Format f) = push f
-instance FromLuaStack Format where
- peek idx = Format <$> peek idx
-
-instance ToLuaStack ListNumberDelim where
- push = push . show
-instance FromLuaStack ListNumberDelim where
- peek idx = safeRead' =<< peek idx
-
-instance ToLuaStack ListNumberStyle where
- push = push . show
-instance FromLuaStack ListNumberStyle where
- peek idx = safeRead' =<< peek idx
-
-instance ToLuaStack MathType where
- push = push . show
-instance FromLuaStack MathType where
- peek idx = safeRead' =<< peek idx
-
-instance ToLuaStack QuoteType where
- push = push . show
-instance FromLuaStack QuoteType where
- peek idx = safeRead' =<< peek idx
-
-instance ToLuaStack Double where
- push = push . (realToFrac :: Double -> LuaNumber)
-instance FromLuaStack Double where
- peek = fmap (realToFrac :: LuaNumber -> Double) . peek
-
-instance ToLuaStack Int where
- push = push . (fromIntegral :: Int -> LuaInteger)
-instance FromLuaStack Int where
- peek = fmap (fromIntegral :: LuaInteger-> Int) . peek
-
-safeRead' :: Read a => String -> Lua a
-safeRead' s = case safeRead s of
- Nothing -> throwLuaError ("Could not read: " ++ s)
- Just x -> return x
+instance Pushable Alignment where
+ push = Lua.push . show
+instance Peekable Alignment where
+ peek = Lua.peekRead
+
+instance Pushable CitationMode where
+ push = Lua.push . show
+instance Peekable CitationMode where
+ peek = Lua.peekRead
+
+instance Pushable Format where
+ push (Format f) = Lua.push f
+instance Peekable Format where
+ peek idx = Format <$> Lua.peek idx
+
+instance Pushable ListNumberDelim where
+ push = Lua.push . show
+instance Peekable ListNumberDelim where
+ peek = Lua.peekRead
+
+instance Pushable ListNumberStyle where
+ push = Lua.push . show
+instance Peekable ListNumberStyle where
+ peek = Lua.peekRead
+
+instance Pushable MathType where
+ push = Lua.push . show
+instance Peekable MathType where
+ peek = Lua.peekRead
+
+instance Pushable QuoteType where
+ push = Lua.push . show
+instance Peekable QuoteType where
+ peek = Lua.peekRead
-- | Push an meta value element to the top of the lua stack.
pushMetaValue :: MetaValue -> Lua ()
pushMetaValue = \case
MetaBlocks blcks -> pushViaConstructor "MetaBlocks" blcks
- MetaBool bool -> push bool
+ MetaBool bool -> Lua.push bool
MetaInlines inlns -> pushViaConstructor "MetaInlines" inlns
MetaList metalist -> pushViaConstructor "MetaList" metalist
MetaMap metamap -> pushViaConstructor "MetaMap" metamap
- MetaString str -> push str
+ MetaString str -> Lua.push str
-- | Interpret the value at the given stack index as meta value.
peekMetaValue :: StackIndex -> Lua MetaValue
peekMetaValue idx = defineHowTo "get MetaValue" $ do
-- Get the contents of an AST element.
- let elementContent :: FromLuaStack a => Lua a
- elementContent = peek idx
+ let elementContent :: Peekable a => Lua a
+ elementContent = Lua.peek idx
luatype <- Lua.ltype idx
case luatype of
- TypeBoolean -> MetaBool <$> peek idx
- TypeString -> MetaString <$> peek idx
- TypeTable -> do
- tag <- tryLua $ getTag idx
+ Lua.TypeBoolean -> MetaBool <$> Lua.peek idx
+ Lua.TypeString -> MetaString <$> Lua.peek idx
+ Lua.TypeTable -> do
+ tag <- Lua.try $ LuaUtil.getTag idx
case tag of
Right "MetaBlocks" -> MetaBlocks <$> elementContent
Right "MetaBool" -> MetaBool <$> elementContent
@@ -186,16 +163,16 @@ peekMetaValue idx = defineHowTo "get MetaValue" $ do
Right "MetaInlines" -> MetaInlines <$> elementContent
Right "MetaList" -> MetaList <$> elementContent
Right "MetaString" -> MetaString <$> elementContent
- Right t -> throwLuaError ("Unknown meta tag: " ++ t)
+ Right t -> Lua.throwException ("Unknown meta tag: " <> t)
Left _ -> do
-- no meta value tag given, try to guess.
len <- Lua.rawlen idx
if len <= 0
- then MetaMap <$> peek idx
- else (MetaInlines <$> peek idx)
- <|> (MetaBlocks <$> peek idx)
- <|> (MetaList <$> peek idx)
- _ -> throwLuaError "could not get meta value"
+ then MetaMap <$> Lua.peek idx
+ else (MetaInlines <$> Lua.peek idx)
+ <|> (MetaBlocks <$> Lua.peek idx)
+ <|> (MetaList <$> Lua.peek idx)
+ _ -> Lua.throwException "could not get meta value"
-- | Push an block element to the top of the lua stack.
pushBlock :: Block -> Lua ()
@@ -219,8 +196,7 @@ pushBlock = \case
-- | Return the value at the given index as block if possible.
peekBlock :: StackIndex -> Lua Block
peekBlock idx = defineHowTo "get Block value" $ do
- typeCheck idx Lua.TypeTable
- tag <- getTag idx
+ tag <- LuaUtil.getTag idx
case tag of
"BlockQuote" -> BlockQuote <$> elementContent
"BulletList" -> BulletList <$> elementContent
@@ -239,11 +215,11 @@ peekBlock idx = defineHowTo "get Block value" $ do
"Table" -> (\(capt, aligns, widths, headers, body) ->
Table capt aligns widths headers body)
<$> elementContent
- _ -> throwLuaError ("Unknown block type: " ++ tag)
+ _ -> Lua.throwException ("Unknown block type: " <> tag)
where
-- Get the contents of an AST element.
- elementContent :: FromLuaStack a => Lua a
- elementContent = getTable idx "c"
+ elementContent :: Peekable a => Lua a
+ elementContent = LuaUtil.rawField idx "c"
-- | Push an inline element to the top of the lua stack.
pushInline :: Inline -> Lua ()
@@ -271,8 +247,7 @@ pushInline = \case
-- | Return the value at the given index as inline if possible.
peekInline :: StackIndex -> Lua Inline
peekInline idx = defineHowTo "get Inline value" $ do
- typeCheck idx Lua.TypeTable
- tag <- getTag idx
+ tag <- LuaUtil.getTag idx
case tag of
"Cite" -> uncurry Cite <$> elementContent
"Code" -> withAttr Code <$> elementContent
@@ -295,11 +270,11 @@ peekInline idx = defineHowTo "get Inline value" $ do
"Strong" -> Strong <$> elementContent
"Subscript" -> Subscript <$> elementContent
"Superscript"-> Superscript <$> elementContent
- _ -> throwLuaError ("Unknown inline type: " ++ tag)
+ _ -> Lua.throwException ("Unknown inline type: " <> tag)
where
-- Get the contents of an AST element.
- elementContent :: FromLuaStack a => Lua a
- elementContent = getTable idx "c"
+ elementContent :: Peekable a => Lua a
+ elementContent = LuaUtil.rawField idx "c"
withAttr :: (Attr -> a -> b) -> (LuaAttr, a) -> b
withAttr f (attributes, x) = f (fromLuaAttr attributes) x
@@ -307,25 +282,25 @@ withAttr f (attributes, x) = f (fromLuaAttr attributes) x
-- | Wrapper for Attr
newtype LuaAttr = LuaAttr { fromLuaAttr :: Attr }
-instance ToLuaStack LuaAttr where
+instance Pushable LuaAttr where
push (LuaAttr (id', classes, kv)) =
pushViaConstructor "Attr" id' classes kv
-instance FromLuaStack LuaAttr where
- peek idx = defineHowTo "get Attr value" (LuaAttr <$> peek idx)
+instance Peekable LuaAttr where
+ peek idx = defineHowTo "get Attr value" (LuaAttr <$> Lua.peek idx)
--
-- Hierarchical elements
--
-instance ToLuaStack Element where
- push (Blk blk) = push blk
+instance Pushable Element where
+ push (Blk blk) = Lua.push blk
push (Sec lvl num attr label contents) = do
Lua.newtable
- LuaUtil.addValue "level" lvl
- LuaUtil.addValue "numbering" num
- LuaUtil.addValue "attr" (LuaAttr attr)
- LuaUtil.addValue "label" label
- LuaUtil.addValue "contents" contents
+ LuaUtil.addField "level" lvl
+ LuaUtil.addField "numbering" num
+ LuaUtil.addField "attr" (LuaAttr attr)
+ LuaUtil.addField "label" label
+ LuaUtil.addField "contents" contents
pushSecMetaTable
Lua.setmetatable (-2)
where
@@ -333,7 +308,7 @@ instance ToLuaStack Element where
pushSecMetaTable = do
inexistant <- Lua.newmetatable "PandocElementSec"
when inexistant $ do
- LuaUtil.addValue "t" "Sec"
+ LuaUtil.addField "t" "Sec"
Lua.push "__index"
Lua.pushvalue (-2)
Lua.rawset (-3)
@@ -342,18 +317,13 @@ instance ToLuaStack Element where
--
-- Reader Options
--
-instance ToLuaStack Extensions where
- push exts = push (show exts)
-
-instance ToLuaStack TrackChanges where
- push = push . showConstr . toConstr
+instance Pushable Extensions where
+ push exts = Lua.push (show exts)
-instance ToLuaStack a => ToLuaStack (Set.Set a) where
- push set = do
- Lua.newtable
- forM_ set (`LuaUtil.addValue` True)
+instance Pushable TrackChanges where
+ push = Lua.push . showConstr . toConstr
-instance ToLuaStack ReaderOptions where
+instance Pushable ReaderOptions where
push ro = do
let ReaderOptions
(extensions :: Extensions)
@@ -367,12 +337,12 @@ instance ToLuaStack ReaderOptions where
(stripComments :: Bool)
= ro
Lua.newtable
- LuaUtil.addValue "extensions" extensions
- LuaUtil.addValue "standalone" standalone
- LuaUtil.addValue "columns" columns
- LuaUtil.addValue "tabStop" tabStop
- LuaUtil.addValue "indentedCodeClasses" indentedCodeClasses
- LuaUtil.addValue "abbreviations" abbreviations
- LuaUtil.addValue "defaultImageExtension" defaultImageExtension
- LuaUtil.addValue "trackChanges" trackChanges
- LuaUtil.addValue "stripComments" stripComments
+ LuaUtil.addField "extensions" extensions
+ LuaUtil.addField "standalone" standalone
+ LuaUtil.addField "columns" columns
+ LuaUtil.addField "tabStop" tabStop
+ LuaUtil.addField "indentedCodeClasses" indentedCodeClasses
+ LuaUtil.addField "abbreviations" abbreviations
+ LuaUtil.addField "defaultImageExtension" defaultImageExtension
+ LuaUtil.addField "trackChanges" trackChanges
+ LuaUtil.addField "stripComments" stripComments
diff --git a/src/Text/Pandoc/Lua/Util.hs b/src/Text/Pandoc/Lua/Util.hs
index ea9ec2554..77b27b88e 100644
--- a/src/Text/Pandoc/Lua/Util.hs
+++ b/src/Text/Pandoc/Lua/Util.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE NoImplicitPrelude #-}
{-
Copyright © 2012-2018 John MacFarlane <jgm@berkeley.edu>
2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -18,6 +17,8 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NoImplicitPrelude #-}
{- |
Module : Text.Pandoc.Lua.Util
Copyright : © 2012–2018 John MacFarlane,
@@ -31,101 +32,53 @@ Lua utility functions.
-}
module Text.Pandoc.Lua.Util
( getTag
- , getTable
- , addValue
+ , rawField
+ , addField
, addFunction
- , getRawInt
- , setRawInt
- , addRawInt
- , typeCheck
- , raiseError
- , popValue
- , PushViaCall
- , pushViaCall
+ , addValue
, pushViaConstructor
, loadScriptFromDataDir
- , dostring'
+ , defineHowTo
+ , throwTopMessageAsError'
+ , callWithTraceback
+ , dofileWithTraceback
) where
import Prelude
-import Control.Monad (when)
-import Control.Monad.Catch (finally)
-import Data.ByteString.Char8 (unpack)
-import Foreign.Lua (FromLuaStack (..), NumResults, Lua, NumArgs, StackIndex,
- ToLuaStack (..), ToHaskellFunction)
-import Foreign.Lua.Api (Status, call, pop, rawget, rawgeti, rawset, rawseti)
+import Control.Monad (unless, when)
+import Foreign.Lua ( Lua, NumArgs, NumResults, Peekable, Pushable, StackIndex
+ , Status, ToHaskellFunction )
import Text.Pandoc.Class (readDataFile, runIOorExplode, setUserDataDir)
import qualified Foreign.Lua as Lua
-
--- | Adjust the stack index, assuming that @n@ new elements have been pushed on
--- the stack.
-adjustIndexBy :: StackIndex -> StackIndex -> StackIndex
-adjustIndexBy idx n =
- if idx < 0
- then idx - n
- else idx
+import qualified Text.Pandoc.UTF8 as UTF8
-- | Get value behind key from table at given index.
-getTable :: (ToLuaStack a, FromLuaStack b) => StackIndex -> a -> Lua b
-getTable idx key = do
- push key
- rawget (idx `adjustIndexBy` 1)
- popValue
+rawField :: Peekable a => StackIndex -> String -> Lua a
+rawField idx key = do
+ absidx <- Lua.absindex idx
+ Lua.push key
+ Lua.rawget absidx
+ Lua.popValue
+
+-- | Add a value to the table at the top of the stack at a string-index.
+addField :: Pushable a => String -> a -> Lua ()
+addField = addValue
-- | Add a key-value pair to the table at the top of the stack.
-addValue :: (ToLuaStack a, ToLuaStack b) => a -> b -> Lua ()
+addValue :: (Pushable a, Pushable b) => a -> b -> Lua ()
addValue key value = do
- push key
- push value
- rawset (-3)
+ Lua.push key
+ Lua.push value
+ Lua.rawset (Lua.nthFromTop 3)
-- | Add a function to the table at the top of the stack, using the given name.
addFunction :: ToHaskellFunction a => String -> a -> Lua ()
addFunction name fn = do
Lua.push name
Lua.pushHaskellFunction fn
- Lua.wrapHaskellFunction
Lua.rawset (-3)
--- | Get value behind key from table at given index.
-getRawInt :: FromLuaStack a => StackIndex -> Int -> Lua a
-getRawInt idx key = do
- rawgeti idx key
- popValue
-
--- | Set numeric key/value in table at the given index
-setRawInt :: ToLuaStack a => StackIndex -> Int -> a -> Lua ()
-setRawInt idx key value = do
- push value
- rawseti (idx `adjustIndexBy` 1) key
-
--- | Set numeric key/value in table at the top of the stack.
-addRawInt :: ToLuaStack a => Int -> a -> Lua ()
-addRawInt = setRawInt (-1)
-
-typeCheck :: StackIndex -> Lua.Type -> Lua ()
-typeCheck idx expected = do
- actual <- Lua.ltype idx
- when (actual /= expected) $ do
- expName <- Lua.typename expected
- actName <- Lua.typename actual
- Lua.throwLuaError $ "expected " ++ expName ++ " but got " ++ actName ++ "."
-
-raiseError :: ToLuaStack a => a -> Lua NumResults
-raiseError e = do
- Lua.push e
- fromIntegral <$> Lua.lerror
-
--- | Get, then pop the value at the top of the stack.
-popValue :: FromLuaStack a => Lua a
-popValue = do
- resOrError <- Lua.peekEither (-1)
- pop 1
- case resOrError of
- Left err -> Lua.throwLuaError err
- Right x -> return x
-
-- | Helper class for pushing a single value to the stack via a lua function.
-- See @pushViaCall@.
class PushViaCall a where
@@ -136,11 +89,11 @@ instance PushViaCall (Lua ()) where
Lua.push fn
Lua.rawget Lua.registryindex
pushArgs
- call num 1
+ Lua.call num 1
-instance (ToLuaStack a, PushViaCall b) => PushViaCall (a -> b) where
+instance (Pushable a, PushViaCall b) => PushViaCall (a -> b) where
pushViaCall' fn pushArgs num x =
- pushViaCall' fn (pushArgs *> push x) (num + 1)
+ pushViaCall' fn (pushArgs *> Lua.push x) (num + 1)
-- | Push an value to the stack via a lua function. The lua function is called
-- with all arguments that are passed to this function and is expected to return
@@ -155,26 +108,11 @@ pushViaConstructor pandocFn = pushViaCall ("pandoc." ++ pandocFn)
-- | Load a file from pandoc's data directory.
loadScriptFromDataDir :: Maybe FilePath -> FilePath -> Lua ()
loadScriptFromDataDir datadir scriptFile = do
- script <- fmap unpack . Lua.liftIO . runIOorExplode $
+ script <- Lua.liftIO . runIOorExplode $
setUserDataDir datadir >> readDataFile scriptFile
- status <- dostring' script
- when (status /= Lua.OK) .
- Lua.throwTopMessageAsError' $ \msg ->
- "Couldn't load '" ++ scriptFile ++ "'.\n" ++ msg
-
--- | Load a string and immediately perform a full garbage collection. This is
--- important to keep the program from hanging: If the program contained a call
--- to @require@, the a new loader function was created which then become
--- garbage. If that function is collected at an inopportune times, i.e. when the
--- Lua API is called via a function that doesn't allow calling back into Haskell
--- (getraw, setraw, …), then the function's finalizer, and the full program,
--- will hang.
-dostring' :: String -> Lua Status
-dostring' script = do
- loadRes <- Lua.loadstring script
- if loadRes == Lua.OK
- then Lua.pcall 0 1 Nothing <* Lua.gc Lua.GCCOLLECT 0
- else return loadRes
+ status <- Lua.dostring script
+ when (status /= Lua.OK) $
+ throwTopMessageAsError' (("Couldn't load '" ++ scriptFile ++ "'.\n") ++)
-- | Get the tag of a value. This is an optimized and specialized version of
-- @Lua.getfield idx "tag"@. It only checks for the field on the table at index
@@ -182,8 +120,54 @@ dostring' script = do
-- metatable.
getTag :: StackIndex -> Lua String
getTag idx = do
- top <- Lua.gettop
- hasMT <- Lua.getmetatable idx
- push "tag"
- if hasMT then Lua.rawget (-2) else Lua.rawget (idx `adjustIndexBy` 1)
- peek Lua.stackTop `finally` Lua.settop top
+ -- push metatable or just the table
+ Lua.getmetatable idx >>= \hasMT -> unless hasMT (Lua.pushvalue idx)
+ Lua.push "tag"
+ Lua.rawget (Lua.nthFromTop 2)
+ Lua.tostring Lua.stackTop <* Lua.pop 2 >>= \case
+ Nothing -> Lua.throwException "untagged value"
+ Just x -> return (UTF8.toString x)
+
+-- | Modify the message at the top of the stack before throwing it as an
+-- Exception.
+throwTopMessageAsError' :: (String -> String) -> Lua a
+throwTopMessageAsError' modifier = do
+ msg <- Lua.tostring' Lua.stackTop
+ Lua.pop 2 -- remove error and error string pushed by tostring'
+ Lua.throwException (modifier (UTF8.toString msg))
+
+-- | Mark the context of a Lua computation for better error reporting.
+defineHowTo :: String -> Lua a -> Lua a
+defineHowTo ctx = Lua.withExceptionMessage (("Could not " <> ctx <> ": ") <>)
+
+-- | Like @'Lua.pcall'@, but uses a predefined error handler which adds a
+-- traceback on error.
+pcallWithTraceback :: NumArgs -> NumResults -> Lua Status
+pcallWithTraceback nargs nresults = do
+ let traceback' :: Lua NumResults
+ traceback' = do
+ l <- Lua.state
+ msg <- Lua.tostring' (Lua.nthFromBottom 1)
+ Lua.traceback l (Just (UTF8.toString msg)) 2
+ return 1
+ tracebackIdx <- Lua.absindex (Lua.nthFromTop (Lua.fromNumArgs nargs + 1))
+ Lua.pushHaskellFunction traceback'
+ Lua.insert tracebackIdx
+ result <- Lua.pcall nargs nresults (Just tracebackIdx)
+ Lua.remove tracebackIdx
+ return result
+
+-- | Like @'Lua.call'@, but adds a traceback to the error message (if any).
+callWithTraceback :: NumArgs -> NumResults -> Lua ()
+callWithTraceback nargs nresults = do
+ result <- pcallWithTraceback nargs nresults
+ when (result /= Lua.OK) Lua.throwTopMessage
+
+-- | Run the given string as a Lua program, while also adding a traceback to the
+-- error message if an error occurs.
+dofileWithTraceback :: FilePath -> Lua Status
+dofileWithTraceback fp = do
+ loadRes <- Lua.loadfile fp
+ case loadRes of
+ Lua.OK -> pcallWithTraceback 0 Lua.multret
+ _ -> return loadRes