@@ -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,\n A\n B "
197+ "Hello,\n A 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
381390end
0 commit comments