From c0d8b0abcb99adf5f7d2ddac3ad343d48da94910 Mon Sep 17 00:00:00 2001
From: Albert Krewinkel <albert@zeitkraut.de>
Date: Mon, 19 Nov 2018 21:36:02 +0100
Subject: [PATCH] Lua filters: test AST object equality via Haskell

Equality of Lua objects representing pandoc AST elements is tested by
unmarshalling the objects and comparing the result in Haskell. A new
function `equals` which performs this test has been added to the
`pandoc.utils` module.

Closes: #5092
---
 data/pandoc.lua                     |  5 ++-
 doc/lua-filters.md                  | 48 ++++++++++++++++++++++++++++-
 src/Text/Pandoc/Lua/Module/Utils.hs | 18 +++++++++--
 3 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/data/pandoc.lua b/data/pandoc.lua
index e69f8ac9c..9289c177f 100644
--- a/data/pandoc.lua
+++ b/data/pandoc.lua
@@ -25,6 +25,7 @@ THIS SOFTWARE.
 local M = {}
 
 local List = require 'pandoc.List'
+local utils = require 'pandoc.utils'
 
 ------------------------------------------------------------------------
 -- Accessor objects
@@ -119,6 +120,7 @@ local function create_accessor_behavior (tag, accessors)
     'function (x, v) x.c%s = v end',
     accessors
   )
+  behavior.__eq = utils.equals
   behavior.__index = function(t, k)
     if getmetatable(t).getters[k] then
       return getmetatable(t).getters[k](t)
@@ -873,6 +875,7 @@ function M.Attr:new (identifier, classes, attributes)
   return {identifier, classes, attributes}
 end
 M.Attr.behavior._field_names = {identifier = 1, classes = 2, attributes = 3}
+M.Attr.behavior.__eq = utils.equals
 M.Attr.behavior.__index = function(t, k)
   return rawget(t, getmetatable(t)._field_names[k]) or
     getmetatable(t)[k]
@@ -931,6 +934,7 @@ function M.ListAttributes:new (start, style, delimiter)
   return {start, style, delimiter}
 end
 M.ListAttributes.behavior._field_names = {start = 1, style = 2, delimiter = 3}
+M.ListAttributes.behavior.__eq = utils.equals
 M.ListAttributes.behavior.__index = function (t, k)
   return rawget(t, getmetatable(t)._field_names[k]) or
     getmetatable(t)[k]
@@ -1033,7 +1037,6 @@ M.UpperAlpha = "UpperAlpha"
 
 ------------------------------------------------------------------------
 -- Functions which have moved to different modules
-local utils = require 'pandoc.utils'
 M.sha1 = utils.sha1
 
 return M
diff --git a/doc/lua-filters.md b/doc/lua-filters.md
index 57eb4e79c..b0d1884e8 100644
--- a/doc/lua-filters.md
+++ b/doc/lua-filters.md
@@ -171,7 +171,7 @@ variables.
 :   The name used to involve the filter. This value can be used
     to find files relative to the script file. This variable is
     also set in custom writers.
-    
+
 `PANDOC_STATE`
 :   The state shared by all readers and writers. It is used by
     pandoc to collect and pass information. The value of this
@@ -641,6 +641,9 @@ to create these objects.
 
 Pandoc document
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 `blocks`
 :   document content ([List] of [Block]s)
 
@@ -653,10 +656,16 @@ Pandoc document
 Meta information on a document; string-indexed collection of
 [MetaValue]s.
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 ## MetaValue {#type-ref-MetaValue}
 
 Document meta information items.
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 ### MetaBlocks {#type-ref-MetaBlocks}
 
 A list of blocks usable as meta value ([List] of [Block]s)
@@ -707,6 +716,9 @@ Plain Lua string value (string)
 
 ## Block {#type-ref-Block}
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 ### BlockQuote {#type-ref-BlockQuote}
 
 A block quote element
@@ -926,6 +938,9 @@ centered).
 
 ## Inline {#type-ref-Inline}
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 ### Cite {#type-ref-Cite}
 Citation
 
@@ -1166,6 +1181,9 @@ Superscripted text
 
 A set of element attributes
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 `identifier`
 :   element identifier (string)
 
@@ -1184,6 +1202,9 @@ indices to the list table.
 
 Single citation entry
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 `id`
 :   citation identifier, e.g., a bibtex key (string)
 
@@ -1206,6 +1227,9 @@ Single citation entry
 ### ListAttributes {#type-ref-ListAttributes}
 List attributes
 
+Object equality is determined via
+[`pandoc.utils.equals`](#utils-equals).
+
 `start`
 :   number of the first list item (integer)
 
@@ -2202,6 +2226,28 @@ functions.
         --   pandoc.Emph{ pandoc.Str 'Paragraph2' }
         -- }
 
+[`equals (element1, element2)`]{#utils-equals}
+
+:   Test equality of AST elements. Elements in Lua are considered
+    equal if and only if the objects obtained by unmarshaling are
+    equal.
+
+    Parameters:
+
+    `element1`, `element2`:
+    :   Objects to be compared. Acceptable input types are
+        [Pandoc](#type-ref-pandoc), [Meta](#type-ref-meta),
+        [MetaValue](#type-ref-MetaValue),
+        [Block](#type-ref-Block), [Inline](#type-ref-Inline),
+        [Attr](#type-ref-Attr),
+        [ListAttributes](#type-ref-ListAttributes), and
+        [Citation](#type-ref-Citation).
+
+    Returns:
+
+    -   Whether the two objects represent the same element
+        (boolean)
+
 [`hierarchicalize (blocks)`]{#utils-hierarchicalize}
 
 :   Convert list of blocks into an hierarchical list. An
diff --git a/src/Text/Pandoc/Lua/Module/Utils.hs b/src/Text/Pandoc/Lua/Module/Utils.hs
index 01762aebf..a75a2934b 100644
--- a/src/Text/Pandoc/Lua/Module/Utils.hs
+++ b/src/Text/Pandoc/Lua/Module/Utils.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE NoImplicitPrelude #-}
 {-
 Copyright © 2017-2018 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
@@ -15,7 +16,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 NoImplicitPrelude #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Utils
    Copyright   : Copyright © 2017-2018 Albert Krewinkel
@@ -36,7 +36,8 @@ import Data.Char (toLower)
 import Data.Default (def)
 import Foreign.Lua (Peekable, Lua, NumResults)
 import Text.Pandoc.Class (runIO, setUserDataDir)
-import Text.Pandoc.Definition (Pandoc, Meta, MetaValue (..), Block, Inline)
+import Text.Pandoc.Definition ( Pandoc, Meta, MetaValue (..), Block, Inline
+                              , Citation, Attr, ListAttributes)
 import Text.Pandoc.Lua.StackInstances ()
 import Text.Pandoc.Lua.Util (addFunction)
 
@@ -52,6 +53,7 @@ pushModule :: Maybe FilePath -> Lua NumResults
 pushModule mbDatadir = do
   Lua.newtable
   addFunction "blocks_to_inlines" blocksToInlines
+  addFunction "equals" equals
   addFunction "hierarchicalize" hierarchicalize
   addFunction "normalize_date" normalizeDate
   addFunction "run_json_filter" (runJSONFilter mbDatadir)
@@ -112,7 +114,9 @@ stringify el = return $ case el of
   InlineElement i  -> Shared.stringify i
   BlockElement b   -> Shared.stringify b
   MetaElement m    -> Shared.stringify m
+  CitationElement c  -> Shared.stringify c
   MetaValueElement m -> stringifyMetaValue m
+  _                  -> ""
 
 stringifyMetaValue :: MetaValue -> String
 stringifyMetaValue mv = case mv of
@@ -120,19 +124,27 @@ stringifyMetaValue mv = case mv of
   MetaString s -> s
   _            -> Shared.stringify mv
 
+equals :: AstElement -> AstElement -> Lua Bool
+equals e1 e2 = return (e1 == e2)
+
 data AstElement
   = PandocElement Pandoc
   | MetaElement Meta
   | BlockElement Block
   | InlineElement Inline
   | MetaValueElement MetaValue
-  deriving (Show)
+  | AttrElement Attr
+  | ListAttributesElement ListAttributes
+  | CitationElement Citation
+  deriving (Eq, Show)
 
 instance Peekable AstElement where
   peek idx  = do
     res <- Lua.try $  (PandocElement <$> Lua.peek idx)
                   <|> (InlineElement <$> Lua.peek idx)
                   <|> (BlockElement <$> Lua.peek idx)
+                  <|> (AttrElement <$> Lua.peek idx)
+                  <|> (ListAttributesElement <$> Lua.peek idx)
                   <|> (MetaElement <$> Lua.peek idx)
                   <|> (MetaValueElement <$> Lua.peek idx)
     case res of