From d126c26dd5f9eaed17462a3f1836b0edaf79e11c Mon Sep 17 00:00:00 2001
From: Albert Krewinkel <albert@zeitkraut.de>
Date: Sat, 13 Oct 2018 14:57:20 +0200
Subject: Lua filter internals: push Shared.Element as userdata

Hierarchical Elements were pushed to Lua as plain tables. This is
simple, but has the disadvantage that marshaling is eager: all child
elements will be marshaled as part of the object. Using a Lua userdata
object instead allows lazy access to fields, causing content marshaling
just (but also each time) when a field is accessed. Filters which do not
traverse the full element contents tree become faster as a result.
---
 src/Text/Pandoc/Lua/StackInstances.hs | 43 +++++++++++++++++++----------------
 1 file changed, 24 insertions(+), 19 deletions(-)

(limited to 'src/Text/Pandoc/Lua')

diff --git a/src/Text/Pandoc/Lua/StackInstances.hs b/src/Text/Pandoc/Lua/StackInstances.hs
index 8538a14ee..25e81bb64 100644
--- a/src/Text/Pandoc/Lua/StackInstances.hs
+++ b/src/Text/Pandoc/Lua/StackInstances.hs
@@ -36,9 +36,10 @@ module Text.Pandoc.Lua.StackInstances () where
 
 import Prelude
 import Control.Applicative ((<|>))
-import Control.Monad (when)
 import Data.Data (showConstr, toConstr)
 import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
+import Foreign.Lua.Userdata ( ensureUserdataMetatable, pushAnyWithMetatable
+                            , metatableName)
 import Text.Pandoc.Definition
 import Text.Pandoc.Extensions (Extensions)
 import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor)
@@ -308,24 +309,28 @@ instance Peekable LuaListAttributes where
 --
 instance Pushable Element where
   push (Blk blk) = Lua.push blk
-  push (Sec lvl num attr label contents) = do
-    Lua.newtable
-    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
-        pushSecMetaTable :: Lua ()
-        pushSecMetaTable = do
-          inexistant <- Lua.newmetatable "PandocElementSec"
-          when inexistant $ do
-            LuaUtil.addField "t" "Sec"
-            Lua.push "__index"
-            Lua.pushvalue (-2)
-            Lua.rawset (-3)
+  push sec = pushAnyWithMetatable pushElementMetatable sec
+   where
+    pushElementMetatable = ensureUserdataMetatable (metatableName sec) $
+                           LuaUtil.addFunction "__index" indexElement
+
+instance Peekable Element where
+  peek idx = Lua.ltype idx >>= \case
+    Lua.TypeUserdata -> Lua.peekAny idx
+    _                -> Blk <$> Lua.peek idx
+
+indexElement :: Element -> String -> Lua Lua.NumResults
+indexElement = \case
+  (Blk _) -> const (1 <$ Lua.pushnil) -- this shouldn't happen
+  (Sec lvl num attr label contents) -> fmap (return 1) . \case
+    "level"     -> Lua.push lvl
+    "numbering" -> Lua.push num
+    "attr"      -> Lua.push (LuaAttr attr)
+    "label"     -> Lua.push label
+    "contents"  -> Lua.push contents
+    "tag"       -> Lua.push "Sec"
+    "t"         -> Lua.push "Sec"
+    _           -> Lua.pushnil
 
 
 --
-- 
cgit v1.2.3