@@ -47,8 +47,97 @@ local function remove_vault(doc)
4747 end
4848end
4949
50+ --- Create a deep copy of a table.
51+ local function copy_table (tbl , depth , seen )
52+ local tp = type (tbl )
53+ if tp == ' table' then
54+ local copy = {}
55+ -- Iterate 'raw' pairs, i.e., without using metamethods
56+ for key , value in next , tbl , nil do
57+ if depth == ' shallow' then
58+ copy [key ] = value
59+ else
60+ copy [copy_table (key )] = copy_table (value )
61+ end
62+ end
63+ return setmetatable (copy , getmetatable (tbl ))
64+ elseif tp == ' userdata' then
65+ return tbl :clone ()
66+ else -- number, string, boolean, etc
67+ return tbl
68+ end
69+ end
70+
71+ --- Checks if two tables are equal
72+ function equals (o1 , o2 )
73+ if o1 == o2 then
74+ return true
75+ end
76+ local o1type = type (o1 )
77+ local o2type = type (o2 )
78+ if o1type ~= o2type or o1type ~= ' table' then
79+ return false
80+ end
81+
82+ local keys = {}
83+
84+ for key1 , value1 in pairs (o1 ) do
85+ local value2 = o2 [key1 ]
86+ if value2 == nil or equals (value1 , value2 ) == false then
87+ return false
88+ end
89+ keys [key1 ] = true
90+ end
91+
92+ for key2 in pairs (o2 ) do
93+ if not keys [key2 ] then return false end
94+ end
95+ return true
96+ end
97+
98+ --- Checks if a filter follows the "nondestructive" property.
99+ -- The nondestructive property is fulfilled if filter functions returns
100+ -- an explicit object, or if it returns `nil` while leaving the passed
101+ -- in object unmodified.
102+ --
103+ -- An error is raised if the property is violated.
104+ --
105+ -- Only filters with this property can use jog safely, without
106+ -- unintended consequences.
107+ local function check_nondestructive_property (namedfilter )
108+ for name , fn in pairs (namedfilter .filter ) do
109+ if type (fn ) == ' function' then
110+ local copy = function (x )
111+ local tp = type (x )
112+ return tp ~= ' table' and x :clone () or
113+ (pandoc .utils .type (x ) == ' Meta' and pandoc .Meta (x ) or copy_table (x ))
114+ end
115+ namedfilter .filter [name ] = function (obj , context )
116+ local orig = copy (obj )
117+ local result , descend = fn (obj , context )
118+ if result == nil then
119+ if type (obj ) ~= ' table' and not equals (obj , orig ) then
120+ warn (
121+ " \n Function '" .. name .. " ' in filter '" .. namedfilter .name ..
122+ " ' returned `nil`, but modified the input."
123+ )
124+ end
125+ -- elseif result.t == obj.t and not rawequal(result, obj) then
126+ -- warn(
127+ -- "\nFunction '" .. name .. "' in filter '" .. namedfilter.name ..
128+ -- "' returned a new object instead of passing the original one through."
129+ -- )
130+ end
131+ return result , descend
132+ end
133+ end
134+ end
135+ return namedfilter
136+ end
137+
50138local function run_emulated_filter_chain (doc , filters , afterFilterPass , profiling )
51139 init_trace (doc )
140+ local compare_jog_and_walk = os.getenv ' QUARTO_JOG_CHECK'
52141 for i , v in ipairs (filters ) do
53142 local function callback ()
54143 if v .flags then
@@ -79,7 +168,17 @@ local function run_emulated_filter_chain(doc, filters, afterFilterPass, profilin
79168 print (pandoc .write (doc , " native" ))
80169 else
81170 _quarto .ast ._current_doc = doc
82- doc = run_emulated_filter (doc , v .filter )
171+
172+ if compare_jog_and_walk and not v .force_pandoc_walk then
173+ v = check_nondestructive_property (v )
174+ end
175+ doc = run_emulated_filter (doc , v .filter , v .force_pandoc_walk )
176+
177+ if compare_jog_and_walk and not v .force_pandoc_walk then
178+ -- Types of meta values are only check on assignment.
179+ doc .meta = doc .meta
180+ end
181+
83182 ensure_vault (doc )
84183
85184 add_trace (doc , v .name )
@@ -204,4 +303,4 @@ function run_as_extended_ast(specTable)
204303 end
205304
206305 return pandocFilterList
207- end
306+ end
0 commit comments