@@ -47,6 +47,95 @@ local function remove_vault(doc)
4747 end
4848end
4949
50+ --- Create a deep copy of a table.
51+ local function copy_table (tbl , depth )
52+ if type (tbl ) == ' table' then
53+ if tbl .clone then
54+ return tbl :clone ()
55+ end
56+
57+ local copy = {}
58+ -- Iterate 'raw' pairs, i.e., without using metamethods
59+ for key , value in next , tbl , nil do
60+ if depth == ' shallow' then
61+ copy [key ] = value
62+ else
63+ copy [copy_table (key )] = copy_table (value )
64+ end
65+ end
66+ return setmetatable (copy , getmetatable (tbl ))
67+ elseif type (tbl ) == ' userdata' then
68+ return tbl :clone ()
69+ else -- number, string, boolean, etc
70+ return tbl
71+ end
72+ end
73+
74+ --- Checks if two tables are equal
75+ function equals (o1 , o2 )
76+ if o1 == o2 then
77+ return true
78+ end
79+ local o1type = type (o1 )
80+ local o2type = type (o2 )
81+ if o1type ~= o2type or o1type ~= ' table' then
82+ return false
83+ end
84+
85+ local keys = {}
86+
87+ for key1 , value1 in pairs (o1 ) do
88+ local value2 = o2 [key1 ]
89+ if value2 == nil or equals (value1 , value2 ) == false then
90+ return false
91+ end
92+ keys [key1 ] = true
93+ end
94+
95+ for key2 , _ in pairs (o2 ) do
96+ if not keys [key2 ] then return false end
97+ end
98+ return true
99+ end
100+
101+ --- Checks if a filter follows the "nondestructive" property.
102+ -- The nondestructive property is fulfilled if filter functions returns
103+ -- an explicit object, or if it returns `nil` while leaving the passed
104+ -- in object unmodified.
105+ --
106+ -- An error is raised if the property is violated.
107+ --
108+ -- Only filters with this property can use jog safely, without
109+ -- unintended consequences.
110+ local function check_nondestructive_property (namedfilter )
111+ for name , fn in pairs (namedfilter .filter ) do
112+ if type (fn ) == ' function' then
113+ local copy = function (x )
114+ return type (x ) == ' table' and copy_table (x ) or x :clone ()
115+ end
116+ namedfilter .filter [name ] = function (obj , context )
117+ local orig = copy (obj )
118+ local result , descend = fn (obj , context )
119+ if result == nil then
120+ if not equals (obj , orig ) then
121+ warn (
122+ " \n Function '" .. name .. " ' in filter '" .. namedfilter .name ..
123+ " ' returned `nil`, but modified the input."
124+ )
125+ end
126+ -- elseif result.t == obj.t and not rawequal(result, obj) then
127+ -- warn(
128+ -- "\nFunction '" .. name .. "' in filter '" .. namedfilter.name ..
129+ -- "' returned a new object instead of passing the original one through."
130+ -- )
131+ end
132+ return result , descend
133+ end
134+ end
135+ end
136+ return namedfilter
137+ end
138+
50139local function run_emulated_filter_chain (doc , filters , afterFilterPass , profiling )
51140 init_trace (doc )
52141 local compare_jog_and_walk = os.getenv ' QUARTO_JOG_CHECK'
@@ -82,17 +171,9 @@ local function run_emulated_filter_chain(doc, filters, afterFilterPass, profilin
82171 _quarto .ast ._current_doc = doc
83172
84173 if compare_jog_and_walk and not v .force_pandoc_walk then
85- local expected = run_emulated_filter (doc :clone (), v .filter , true )
86- doc = run_emulated_filter (doc :clone (), v .filter , false )
87- if doc == expected then
88- io.stderr :write (" [ OK ] " .. v .name .. ' \n ' )
89- else
90- io.stderr :write (" [FAIL] " .. v .name .. ' \n ' )
91- doc = expected
92- end
93- else
94- doc = run_emulated_filter (doc , v .filter , v .force_pandoc_walk )
174+ v = check_nondestructive_property (v )
95175 end
176+ doc = run_emulated_filter (doc , v .filter , v .force_pandoc_walk )
96177
97178 ensure_vault (doc )
98179
0 commit comments