Lua filters: report traceback when an error occurs

A proper Lua traceback is added if either loading of a file or execution
of a filter function fails. This should be of help to authors of Lua
filters who need to debug their code.
This commit is contained in:
Albert Krewinkel 2018-10-01 16:10:46 +02:00
parent 1ffe47b9b9
commit 9abdbb2783
No known key found for this signature in database
GPG key ID: 388DC0B21F631124
9 changed files with 49 additions and 16 deletions

View file

@ -372,7 +372,7 @@ library
blaze-html >= 0.9 && < 0.10, blaze-html >= 0.9 && < 0.10,
blaze-markup >= 0.8 && < 0.9, blaze-markup >= 0.8 && < 0.9,
vector >= 0.10 && < 0.13, vector >= 0.10 && < 0.13,
hslua >= 1.0 && < 1.1, hslua >= 1.0.1 && < 1.1,
hslua-module-text >= 0.2 && < 0.3, hslua-module-text >= 0.2 && < 0.3,
binary >= 0.5 && < 0.10, binary >= 0.5 && < 0.10,
SHA >= 1.6 && < 1.7, SHA >= 1.6 && < 1.7,

View file

@ -39,6 +39,7 @@ import Text.Pandoc.Class (PandocIO)
import Text.Pandoc.Definition (Pandoc) import Text.Pandoc.Definition (Pandoc)
import Text.Pandoc.Lua.Filter (LuaFilter, walkMWithLuaFilter) import Text.Pandoc.Lua.Filter (LuaFilter, walkMWithLuaFilter)
import Text.Pandoc.Lua.Init (LuaException (..), runPandocLua, registerScriptPath) import Text.Pandoc.Lua.Init (LuaException (..), runPandocLua, registerScriptPath)
import Text.Pandoc.Lua.Util (dofileWithTraceback)
import Text.Pandoc.Options (ReaderOptions) import Text.Pandoc.Options (ReaderOptions)
import qualified Foreign.Lua as Lua import qualified Foreign.Lua as Lua
@ -58,7 +59,7 @@ runLuaFilter' ropts filterPath format pd = do
registerReaderOptions registerReaderOptions
registerScriptPath filterPath registerScriptPath filterPath
top <- Lua.gettop top <- Lua.gettop
stat <- Lua.dofile filterPath stat <- dofileWithTraceback filterPath
if stat /= Lua.OK if stat /= Lua.OK
then Lua.throwTopMessage then Lua.throwTopMessage
else do else do

View file

@ -52,6 +52,7 @@ import Text.Pandoc.Walk (walkM, Walkable)
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import qualified Foreign.Lua as Lua import qualified Foreign.Lua as Lua
import qualified Text.Pandoc.Lua.Util as LuaUtil
-- | Filter function stored in the registry -- | Filter function stored in the registry
newtype LuaFilterFunction = LuaFilterFunction Lua.Reference newtype LuaFilterFunction = LuaFilterFunction Lua.Reference
@ -118,11 +119,9 @@ tryFilter (LuaFilter fnMap) x =
-- element is left unchanged. -- element is left unchanged.
runFilterFunction :: Pushable a => LuaFilterFunction -> a -> Lua () runFilterFunction :: Pushable a => LuaFilterFunction -> a -> Lua ()
runFilterFunction lf x = do runFilterFunction lf x = do
let errorPrefix = "Error while running filter function:\n" pushFilterFunction lf
Lua.withExceptionMessage (errorPrefix <>) $ do Lua.push x
pushFilterFunction lf LuaUtil.callWithTraceback 1 1
Lua.push x
Lua.call 1 1
walkMWithLuaFilter :: LuaFilter -> Pandoc -> Lua Pandoc walkMWithLuaFilter :: LuaFilter -> Pandoc -> Lua Pandoc
walkMWithLuaFilter f = walkMWithLuaFilter f =

View file

@ -40,12 +40,14 @@ module Text.Pandoc.Lua.Util
, loadScriptFromDataDir , loadScriptFromDataDir
, defineHowTo , defineHowTo
, throwTopMessageAsError' , throwTopMessageAsError'
, callWithTraceback
, dofileWithTraceback
) where ) where
import Prelude import Prelude
import Control.Monad (unless, when) import Control.Monad (unless, when)
import Foreign.Lua ( Lua, NumArgs, Peekable, Pushable, StackIndex import Foreign.Lua ( Lua, NumArgs, NumResults, Peekable, Pushable, StackIndex
, ToHaskellFunction ) , Status, ToHaskellFunction )
import Text.Pandoc.Class (readDataFile, runIOorExplode, setUserDataDir) import Text.Pandoc.Class (readDataFile, runIOorExplode, setUserDataDir)
import qualified Foreign.Lua as Lua import qualified Foreign.Lua as Lua
@ -137,3 +139,35 @@ throwTopMessageAsError' modifier = do
-- | Mark the context of a Lua computation for better error reporting. -- | Mark the context of a Lua computation for better error reporting.
defineHowTo :: String -> Lua a -> Lua a defineHowTo :: String -> Lua a -> Lua a
defineHowTo ctx = Lua.withExceptionMessage (("Could not " <> ctx <> ": ") <>) 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

View file

@ -47,7 +47,7 @@ import Text.Pandoc.Error
import Text.Pandoc.Lua.Init (LuaException (LuaException), runPandocLua, import Text.Pandoc.Lua.Init (LuaException (LuaException), runPandocLua,
registerScriptPath) registerScriptPath)
import Text.Pandoc.Lua.StackInstances () import Text.Pandoc.Lua.StackInstances ()
import Text.Pandoc.Lua.Util (addField) import Text.Pandoc.Lua.Util (addField, dofileWithTraceback)
import Text.Pandoc.Options import Text.Pandoc.Options
import Text.Pandoc.Templates import Text.Pandoc.Templates
import qualified Text.Pandoc.UTF8 as UTF8 import qualified Text.Pandoc.UTF8 as UTF8
@ -111,7 +111,7 @@ writeCustom :: FilePath -> WriterOptions -> Pandoc -> PandocIO Text
writeCustom luaFile opts doc@(Pandoc meta _) = do writeCustom luaFile opts doc@(Pandoc meta _) = do
res <- runPandocLua $ do res <- runPandocLua $ do
registerScriptPath luaFile registerScriptPath luaFile
stat <- Lua.dofile luaFile stat <- dofileWithTraceback luaFile
-- check for error in lua script (later we'll change the return type -- check for error in lua script (later we'll change the return type
-- to handle this more gracefully): -- to handle this more gracefully):
when (stat /= Lua.OK) $ when (stat /= Lua.OK) $

View file

@ -19,13 +19,12 @@ extra-deps:
- test-framework-0.8.2.0 - test-framework-0.8.2.0
- pandoc-types-1.17.5.1 - pandoc-types-1.17.5.1
- cmark-gfm-0.1.3 - cmark-gfm-0.1.3
- hslua-module-text-0.1.2.1
- texmath-0.11.1 - texmath-0.11.1
- haddock-library-1.6.0 - haddock-library-1.6.0
- HsYAML-0.1.1.1 - HsYAML-0.1.1.1
- text-1.2.3.0 - text-1.2.3.0
- hs-bibutils-6.6.0.0 - hs-bibutils-6.6.0.0
- hslua-1.0.0 - hslua-1.0.1
- hslua-module-text-0.2.0 - hslua-module-text-0.2.0
ghc-options: ghc-options:
"$locals": -fhide-source-paths -XNoImplicitPrelude "$locals": -fhide-source-paths -XNoImplicitPrelude

View file

@ -20,7 +20,7 @@ extra-deps:
- HsYAML-0.1.1.1 - HsYAML-0.1.1.1
- hs-bibutils-6.6.0.0 - hs-bibutils-6.6.0.0
- yaml-0.9.0 - yaml-0.9.0
- hslua-1.0.0 - hslua-1.0.1
- hslua-module-text-0.2.0 - hslua-module-text-0.2.0
ghc-options: ghc-options:
"$locals": -fhide-source-paths -XNoImplicitPrelude "$locals": -fhide-source-paths -XNoImplicitPrelude

View file

@ -12,7 +12,7 @@ packages:
- '.' - '.'
extra-deps: extra-deps:
- pandoc-citeproc-0.14.4 - pandoc-citeproc-0.14.4
- hslua-1.0.0 - hslua-1.0.1
- hslua-module-text-0.2.0 - hslua-module-text-0.2.0
- ansi-terminal-0.8.0.2 - ansi-terminal-0.8.0.2
- cmark-gfm-0.1.3 - cmark-gfm-0.1.3

View file

@ -24,7 +24,7 @@ extra-deps:
- HsYAML-0.1.1.1 - HsYAML-0.1.1.1
- texmath-0.11.1 - texmath-0.11.1
- yaml-0.9.0 - yaml-0.9.0
- hslua-1.0.0 - hslua-1.0.1
- hslua-module-text-0.2.0 - hslua-module-text-0.2.0
ghc-options: ghc-options:
"$locals": -fhide-source-paths -XNoImplicitPrelude "$locals": -fhide-source-paths -XNoImplicitPrelude