Skip to content

Commit 2dc1e41

Browse files
author
José Valim
committed
Pretty printer now prints grouped elements in the same line, closes #1383
This implementation slightly changes the semantic of Lindig's algorithm to allow elements that belong to the same group to be printed together in the same line, even if they do not fit the line fully. This was achieved by changing `:break` to mean a possible break and `:flat` to force a flat structure. Then deciding if a break works as a newline is just a matter of checking if we have enough space until the next break that is not inside a group (which is still flat).
1 parent 63cfe89 commit 2dc1e41

File tree

3 files changed

+52
-43
lines changed

3 files changed

+52
-43
lines changed

lib/elixir/lib/inspect/algebra.ex

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,19 @@ defmodule Inspect.Algebra do
4343
4444
The original Haskell implementation of the algorithm by [Wadler][1]
4545
relies on lazy evaluation to unfold document groups on two alternatives:
46-
`:flat` (breaks as spaces) and `:broken` (breaks as newlines).
46+
`:flat` (breaks as spaces) and `:break` (breaks as newlines).
4747
Implementing the same logic in a strict language such as Elixir leads
48-
to an exponential growth of possible documents, unless document
49-
groups are encoded explictly as `:flat` or `:broken`. Those groups are
50-
then reduced to a simple document, where the layout is already decided,
51-
per [Lindig][0].
48+
to an exponential growth of possible documents, unless document groups
49+
are encoded explictly as `:flat` or `:break`. Those groups are then reduced
50+
to a simple document, where the layout is already decided, per [Lindig][0].
51+
52+
This implementation slightly changes the semantic of Lindig's algorithm
53+
to allow elements that belong to the same group to be printed together
54+
in the same line, even if they do not fit the line fully. This was achieved
55+
by changing `:break` to mean a possible break and `:flat` to force a flat
56+
structure. Then deciding if a break works as a newline is just a matter
57+
of checking if we have enough space until the next break that is not
58+
inside a group (which is still flat).
5259
5360
Custom pretty printers can be implemented using the documents returned
5461
by this module and by providing their own rendering functions.
@@ -187,7 +194,7 @@ defmodule Inspect.Algebra do
187194
iex> Inspect.Algebra.pretty(doc, 80)
188195
"Hello, A B"
189196
iex> Inspect.Algebra.pretty(doc, 6)
190-
"Hello,\nA\nB"
197+
"Hello,\nA B"
191198
192199
"""
193200
@spec group(doc) :: :doc_group_t
@@ -309,45 +316,33 @@ defmodule Inspect.Algebra do
309316
defp decrement(:infinity), do: :infinity
310317
defp decrement(counter), do: counter - 1
311318

312-
# Rendering and internal helpers
313-
314-
# Records representing __simple__ documents, already on a fixed layout
315-
# Those are generalized by `sdoc` type.
316-
@type sdoc :: :s_nil | :s_text_t | :s_line_t
317-
defrecordp :s_text, [str: "", sdoc: :s_nil]
318-
defrecordp :s_line, [indent: 1, sdoc: :s_nil] # newline + spaces
319-
320-
@doc """
321-
Renders a simple document into a binary
322-
"""
323-
@spec render(sdoc) :: binary
324-
def render(sdoc) do
325-
iolist_to_binary do_render sdoc
326-
end
327-
328-
@spec do_render(sdoc) :: [binary]
329-
defp do_render(:s_nil), do: [""]
330-
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
331-
defp do_render(s_line(indent: i, sdoc: d)) do
332-
prefix = repeat " ", i
333-
[@newline | [prefix | do_render d]]
334-
end
335-
336319
@doc """
337320
The pretty printing function.
338-
Takes the maximum width and a document to print as its arguments and returns the string
339-
representation of the best layout for the document to fit in the given width.
321+
322+
Takes the maximum width and a document to print as its arguments
323+
and returns the string representation of the best layout for the
324+
document to fit in the given width.
340325
"""
341326
@spec pretty(doc, non_neg_integer) :: binary
342327
def pretty(d, w) do
343-
sdoc = format w, 0, [{0, :flat, doc_group(doc: d)}]
328+
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
344329
render(sdoc)
345330
end
346331

332+
defp default_mode(:infinity), do: :flat
333+
defp default_mode(_), do: :break
334+
335+
# Rendering and internal helpers
336+
337+
# Records representing __simple__ documents, already on a fixed layout
338+
# Those are generalized by `sdoc` type.
339+
@type sdoc :: :s_nil | :s_text_t | :s_line_t
340+
defrecordp :s_text, [str: "", sdoc: :s_nil]
341+
defrecordp :s_line, [indent: 1, sdoc: :s_nil] # newline + spaces
342+
347343
# Record representing the document mode to be rendered: __flat__ or __broken__
348344
@type mode :: :flat | :break
349345

350-
# The fits? and format functions have to deal explicitly with the document modes
351346
@doc false
352347
@spec fits?(integer, [{ integer, mode, doc }]) :: boolean
353348
def fits?(:infinity, _), do: true # no pretty printing
@@ -363,19 +358,33 @@ defmodule Inspect.Algebra do
363358

364359
@doc false
365360
@spec format(integer, integer, [{ integer, mode, doc }]) :: atom | tuple
366-
def format(:infinity, k, [{i, _, doc_group(doc: x)} | t]), do: format(:infinity, k, [{i, :flat, x} | t]) # no pretty printing
367361
def format(_, _, []), do: :s_nil
368362
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
369363
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
370364
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
371365
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
366+
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
372367
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
373-
def format(w, _, [{i, :break, doc_break(str: _)} | t]), do: s_line(indent: i, sdoc: format(w, i, t))
374-
def format(w, k, [{i, _, doc_group(doc: x)} | t]) do
375-
if fits? (w - k), [{i, :flat, x} | t] do
376-
format w, k, [{i, :flat, x} | t]
368+
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
369+
k = k + byte_size(s)
370+
if fits?(w - k, t) do
371+
s_text(str: s, sdoc: format(w, k, t))
377372
else
378-
format w, k, [{i, :break, x} | t]
373+
s_line(indent: i, sdoc: format(w, i, t))
379374
end
380375
end
376+
377+
@doc false
378+
@spec render(sdoc) :: binary
379+
def render(sdoc) do
380+
iolist_to_binary do_render sdoc
381+
end
382+
383+
@spec do_render(sdoc) :: [binary]
384+
defp do_render(:s_nil), do: [""]
385+
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
386+
defp do_render(s_line(indent: i, sdoc: d)) do
387+
prefix = repeat " ", i
388+
[@newline | [prefix | do_render d]]
389+
end
381390
end

lib/elixir/lib/io.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ defmodule IO do
154154
def inspect(device, item, opts) when is_list(opts) do
155155
opts = Keyword.put_new(opts, :pretty, true)
156156
opts = case :io.columns(device) do
157-
{ :ok, width } -> Keyword.put_new(opts, :width, min(width, 80))
157+
{ :ok, width } -> Keyword.put_new(opts, :width, width)
158158
{ :error, _ } -> opts
159159
end
160160
puts device, Kernel.inspect(item, opts)

lib/elixir/test/elixir/inspect/algebra_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,13 @@ defmodule Inspect.AlgebraTest do
110110

111111
# Consistence of corresponding sdoc
112112
assert factor(glue("a", "b"), 1) ==
113-
{ :s_text, "a", { :s_line, 0, { :s_text, "b", :s_nil }}}
113+
{ :s_text, "a", { :s_text, " ", { :s_text, "b", :s_nil }}}
114114

115115
assert factor(glue("a", "b"), 9) ==
116116
{ :s_text, "a", { :s_text, " ", { :s_text, "b", :s_nil }}}
117117

118118
# Consistent formatting
119-
assert pretty(helloabcd, 5) == "hello\na\nb\ncd"
119+
assert pretty(helloabcd, 5) == "hello\na b\ncd"
120120
assert pretty(helloabcd, 80) == "hello a b cd"
121121
end
122122
end

0 commit comments

Comments
 (0)