diff --git a/markdown.dtx b/markdown.dtx
index 9858ff35c..6df1b9144 100644
--- a/markdown.dtx
+++ b/markdown.dtx
@@ -15715,6 +15715,23 @@ function M.reader.new(writer, options)
 % \par
 % \begin{markdown}
 %
+% Define \luamdef{iterlines} as a function that iterates over the lines of
+% the input string `s`, transforms them using an input function `f`, and
+% reassembles them into a new string, which it returns.
+%
+% \end{markdown}
+%  \begin{macrocode}
+  local function iterlines(s, f)
+    rope = {}
+    for line in lpeg.match((parsers.line / f)^1, s) do
+      table.insert(rope, line)
+    end
+    return util.rope_to_string(rope)
+  end
+%    \end{macrocode}
+% \par
+% \begin{markdown}
+%
 % Define \luamdef{expandtabs} either as an identity function, when the
 % \Opt{preserveTabs} Lua inrerface option is enabled, or to a function that
 % expands tabs into spaces otherwise.
@@ -15727,7 +15744,7 @@ function M.reader.new(writer, options)
   else
     expandtabs = function(s)
                    if s:find("\t") then
-                     return s:gsub("[^\n]*", util.expand_tabs_in_line)
+                     return iterlines(s, util.expand_tabs_in_line)
                    else
                      return s
                    end
@@ -15737,23 +15754,6 @@ function M.reader.new(writer, options)
 % \par
 % \begin{markdown}
 %
-% Define \luamdef{iterlines} as a function that iterates over the lines of
-% the input string `s`, transforms them using an input function `f`, and
-% reassembles them into a new string, which it returns.
-%
-% \end{markdown}
-%  \begin{macrocode}
-  local function iterlines(s, f)
-    rope = {}
-    for line in lpeg.match((parsers.line / f)^1, s) do
-      table.insert(rope, line)
-    end
-    return util.rope_to_string(rope)
-  end
-%    \end{macrocode}
-% \par
-% \begin{markdown}
-%
 % The \luamdef{larsers} (as in ``local \luam{parsers}'') hash table stores
 % \acro{peg} patterns that depend on the received `options`, which impedes
 % their reuse between different \luam{reader} objects.