PreviousUpNext

15.4.830  src/lib/prettyprint/big/src/core-prettyprinter-box-formatting-policies-g.pkg

## core-prettyprinter-box-formatting-policies-g.pkg
#
# Formatting text is an art dominated by esthetics and
# formatting code is doubly do;  it is rare for any two
# people to agree on how it should be done.
#
# Consequently we try to isolate the policy decisions
# of formatting from the mechanisms needed to implement
# them, and allow client coders to provide their own
# policies.
#
# This file contains the canned policies we provide.

# Compiled by:
#     src/lib/prettyprint/big/prettyprinter.lib

stipulate
    package fil =  file__premicrothread;                                        # file__premicrothread                                                  is from   src/lib/std/src/posix/file--premicrothread.pkg
    package l2s =  list_to_string;                                              # list_to_string                                                        is from   src/lib/src/list-to-string.pkg
herein

    # This generic is invoked (only) from
    #
    #     src/lib/prettyprint/big/src/core-prettyprinter-g.pkg
    #
    generic package   core_prettyprinter_box_formatting_policies_g   (  # 
        #             ===============================================
        #                                                                       # "tt" == "traitful text"
        package typ:    Core_Prettyprinter_Types;                               # Core_Prettyprinter_Types      is from   src/lib/prettyprint/big/src/core-prettyprinter-types.api
                                                                                # core_prettyprinter_types_g    is from   src/lib/prettyprint/big/src/core-prettyprinter-types-g.pkg
        package dbg:                                                            # core_prettyprinter_debug_g is from   src/lib/prettyprint/big/src/core-prettyprinter-debug-g.pkg
                        api {
                                left_margin_is_to_string:       typ::Left_Margin_Is -> String;
                                phase1_token_to_string:         typ::Phase1_Token -> String;
                                phase1_tokens_to_string:        List(typ::Phase1_Token) -> String;

                                prettyprint_prettyprinter: (fil::Output_Stream, typ::Prettyprinter) -> Void;
                            };
        too_long: Int;
    )
    {
        debug_prints = FALSE;                                                           # When debugging I usually just replace  debug_prints  by  *log::debugging  throughout this file.
                                                                                        # Usually you'll want to do the same in  src/lib/prettyprint/big/src/core-prettyprinter-g.pkg
                                                                                        # Note that with debug_prints==FALSE, 'if debug_prints ... fi;' will optimize to no code produced courtesy of dead-code removal.
        Break_Policy
          #
          = NONE                                                                        # All on one line -- break never rendered as newline.
          | ALL                                                                         # One line each -- every break rendered as newline.
          | ALL_OR_NONE                                                                 # NONE if it fits, else ALL.
          | AS_NEEDED                                                                   # Normal wordwrap:  break rendered as newline only when necessary.
          ;

        fun nblanks n
            =
            string::implode (map (\\ _ = ' ') (1 .. n));


        fun count_breaks []                    =>  0;
            count_breaks (typ::BREAK _ ! rest) =>  1  +  count_breaks rest;
            count_breaks (           _ ! rest) =>  0  +  count_breaks rest;
        end;
        
        fun tablen (column, { tab_to, tabstops_are_every })                                                                     # We are at (zero-based) 'column' and tabstops are set
            =                                                                                                                   # every 'tabstop_are_every' chars. What is the minimum number
            if (tabstops_are_every <= 0                                                                                         # of blanks to print to make  (column % tabstop) == tab_to?
            or  tab_to             <  0
            or  tab_to             >= tabstops_are_every)
                0;
            else
                ((tab_to + tabstops_are_every) - (column % tabstops_are_every)) % tabstops_are_every;                           # Phrased to avoid negative numbers because the behavior of '%' is not well-defined for them.
            fi;


        fun breaklen (column', { blanks, tab_to, tabstops_are_every })                  # Compute length of break.
            =
            {   column = column' + blanks;
                column = column  + tablen (column, { tab_to, tabstops_are_every });
                column - column';
            };



        # Given the list of tokens in a BOX,
        # wrap either all BREAKs or none of them.
        #
        # Any embedded boxes have already been
        # wrap_box()'d, so they have valid values
        # for actual_width and is_multiline.
        #
        fun wrap_box_contents_all_or_none
            #
            wrap_policy                                 # Which BREAKs should we wrap?  One of ALL, NONE, ALL_OR_NONE.
            {
              target_width,                             # Target width for this box.
              box_contents => tokens                    # List of tokens in this box.
            }
            =
            {
                                                                                                                                if debug_prints
                                                                                                                                        printf "wrap_box_contents_all_or_none: target_width d=%d, %d tokens: %s    -- wrap_box_contents_all_or_none/TOP in prettyprinter-g.pkg\n"
                                                                                                                                                target_width (list::length tokens) (dbg::phase1_tokens_to_string tokens);
                                                                                                                                fi;
                box_is_multiline    =  REF FALSE;
                actual_width        =  REF 0;
                column              =  0;               # Current column, relative to box left margin
                #
                wrap_them                               # Should we wrap all BREAKs, or none or them?
                    =
                    case wrap_policy
                        #
                        ALL  =>  TRUE;
                        NONE =>  FALSE;

                        ALL_OR_NONE
                            =>
                            {   unwrapped_length
                                    =
                                    total_length (tokens, 0)
                                    where
                                        fun total_length' (tokens, column)
                                            =
                                            {
                                                                                                                                if debug_prints
                                                                                                                                printf "total_length: column d=%d tokens==%s\n" column (dbg::phase1_tokens_to_string tokens);
                                                                                                                                fi;
                                                total_length (tokens, column);
                                            }   

                                        also
                                        fun total_length ([], column) => column;
                                            #
                                            total_length (((typ::LIT    s) ! rest), column) =>   total_length' (rest, column +  string::length_in_bytes s              );
                                            total_length (((typ::ENDLIT s) ! rest), column) =>   total_length' (rest, column +  string::length_in_bytes s              );
                                            total_length (((typ::TAB    t) ! rest), column) =>   total_length' (rest, column +  breaklen (0, t)        );
                                            total_length (((typ::BREAK  b) ! rest), column) =>   total_length' (rest, column +  breaklen(column, b.ifnotwrap));

                                            total_length ([ typ::BOX box ], column)
                                                =>
                                                {
                                                                                                                                if debug_prints
                                                                                                                                printf "[box] case: column=%d *box.is_multiline=%B *box.actual_width=%d left_margin_is=%s\n" column *box.is_multiline *box.actual_width (dbg::left_margin_is_to_string box.left_margin_is);
                                                                                                                                fi;
                                                    case box.left_margin_is
                                                        #
                                                        typ::CURSOR_RELATIVE _ =>   column + *box.actual_width;         # Multiline is potentially OK when it is last in line and a cbox.

                                                        typ::BOX_RELATIVE    _ =>   if *box.is_multiline     too_long;
                                                                                    else                     column + *box.actual_width;
                                                                                    fi;

                                                    esac;
                                                };

                                            total_length (((typ::BOX box) ! rest), column)
                                                =>
                                                if *box.is_multiline   too_long;
                                                else                   total_length' (rest, column + *box.actual_width);
                                                fi;

                                            total_length ((_ ! rest), column)
                                                =>
                                                total_length' (rest, column);
                                        end;
                                    end;

                                                                                                                                if debug_prints
                                                                                                                                if (unwrapped_length  >  target_width)
                                                                                                                                        printf "wrap_box_contents_all_or_none: unwrapped_length %d > target_width %d, %d tokens: %s: WE WILL WRAP YOU!    -- wrap_box_contents_all_or_none/TOP in prettyprinter-g.pkg\n"
                                                                                                                                                unwrapped_length target_width (list::length tokens) (dbg::phase1_tokens_to_string tokens);
                                                                                                                                fi;
                                                                                                                                fi;
                                unwrapped_length  >  target_width;
                            };

                        AS_NEEDED => raise exception DIE "wrap_box_contents_all_or_none: wrap_policy == AS_NEEDED!?";
                    esac;


                fun per_token ([],  column) =>   column;
                    #
                    per_token (tokens as (token ! rest),  column)
                        =>
                        case token
                            #
                            typ::BREAK b
                                =>
#                               if (column <= breaklen (0, b.ifwrap)  and  count_breaks tokens == 1)
                                    #
#                                   per_token  (rest, column + breaklen (column, b.ifnotwrap));                 # Wrapping won't move us leftward anyhow, so don't even consider it.

#                               elif wrap_them
                                if wrap_them
                                    #
                                                                                                                                if debug_prints
                                                                                                                                printf "WRAPPING at column %d indent after wrap d=%d -- wrap_box_contents_all_or_none in prettyprinter-g.pkg\n" column (breaklen (0, b.ifwrap));
                                                                                                                                fi;
                                    if (*actual_width < column)   actual_width := column;   fi;
                                    b.wrap := TRUE;
                                    box_is_multiline := TRUE;
                                    per_token  (rest, breaklen (0, b.ifwrap)    );
                                else
                                    per_token  (rest, column + breaklen(column, b.ifnotwrap));
                                fi;

                            typ::NEWLINE        =>  {   if (*actual_width < column)   actual_width := column;   fi;
                                                        box_is_multiline := TRUE;
                                                        per_token  (rest, 0);
                                                    };

                            typ::BLANKS i       =>   per_token  (rest, column + i                   );
                            typ::LIT    s       =>   per_token  (rest, column + string::length_in_bytes s    );
                            typ::ENDLIT s       =>   per_token  (rest, column + string::length_in_bytes s    );
                            typ::TAB    t       =>   per_token  (rest, column + breaklen(column, t) );
                            typ::PUSH_TT _      =>   per_token  (rest, column                       );
                            typ::POP_TT         =>   per_token  (rest, column                       );
                            typ::CONTROL _      =>   per_token  (rest, column                       );
                            typ::INDENT _       =>   per_token  (rest, column                       );                          # We don't track left margin in this pass.

                            typ::BOX box        =>  {   if(*actual_width <  column + *box.actual_width)
                                                            actual_width := column + *box.actual_width;
                                                        fi;
                                                        if *box.is_multiline
                                                            box_is_multiline :=  TRUE;
                                                            per_token( rest, 0 );
                                                        else
                                                            per_token (rest, column + *box.actual_width);
                                                        fi;
                                                    };
                        esac;
                end;                                                    # fun per_token

                column =   per_token (tokens, column);

                if (*actual_width < column)   actual_width := column;   fi;

                { actual_box_width =>  *actual_width,
                  is_multiline     =>  *box_is_multiline
                };
            };                                                          # fun wrap_box_contents_all_or_none


        # Here we implement a conventional word-wrap
        # style algorithm where we wrap a line at
        # a BREAK iff it is our last chance to avoid
        # exceeding our assigned box width.

        fun wrap_box_contents_as_needed  { target_width, box_contents => tokens }
            =
            {   column =   per_token (tokens, /*column=*/0);
                #
                if (*actual_width < column)   actual_width := column;   fi;

                { actual_box_width =>  *actual_width,
                  is_multiline     =>  *box_is_multiline
                };
            }
            where
                                                                                                                                if debug_prints
                                                                                                                                printf "target_width d=%d, %d tokens    -- wrap_box_contents_as_needed in prettyprinter-g.pkg\n" target_width (list::length tokens);
                                                                                                                                fi;
                box_is_multiline = REF FALSE;
                actual_width     = REF 0;

                fun forced_followon_fits_in (tokens, space_left_on_line)                                # Decide if tokens from here to next break/newline will fit in remaining space on line.
                    =
                    fits_in'  (tokens, 0)
                    where 
                        fun fits_in' (tokens, column)                                                   # Early-out check: Stop iterating as soon as we know we don't fit.
                            =
                            {
                                                                                                                                if debug_prints
                                                                                                                                printf "forced_followon_fits_in (column=%d, %d tokens: %s\n" column (list::length tokens) (dbg::phase1_tokens_to_string tokens);
                                                                                                                                fi;
                                if (column > space_left_on_line)   FALSE;
                                else                               fits_in (tokens, column);
                                fi;
                            }

                        also
                        fun fits_in  ([], column) =>   (column <= space_left_on_line);
                            #
                            fits_in  ([ typ::BOX { left_margin_is => typ::CURSOR_RELATIVE _, is_multiline => REF TRUE, actual_width => REF width, ... } ],  column)
                                =>
                                (column + width) <= space_left_on_line;                                                         # Multiline box is ok when CURSOR_RELATIVE and last in line.

                            fits_in  (token ! rest,  column)
                                =>
                                case token
                                    #
                                    typ::BLANKS i                       =>      fits_in'  (rest, column + i                );
                                    typ::LIT    s                       =>      fits_in'  (rest, column + string::length_in_bytes s );
                                    typ::ENDLIT s                       =>      fits_in'  (rest, column + string::length_in_bytes s );
                                    typ::TAB    t                       =>      fits_in'  (rest, column + breaklen(column, t));
                                    typ::PUSH_TT _                      =>      fits_in'  (rest, column                    );
                                    typ::POP_TT                         =>      fits_in'  (rest, column                    );
                                    typ::CONTROL _                      =>      fits_in'  (rest, column                    );
                                    typ::INDENT _                       =>      fits_in'  (rest, column                    );
                                   (typ::NEWLINE | typ::BREAK _ )       =>      column <= space_left_on_line;

                                    typ::BOX box =>  if *box.is_multiline       FALSE;                                                  # Multiline boxes by definition don't fit on one line. :-)
                                                     else                       fits_in'  (rest,  column + *box.actual_width);          # Monoline box, so first line == whole box.
                                                     fi;
                                esac;
                        end;                    # fun fits_in
                end;                            # fun forced_followon_fits_in


                fun per_token  ([], column) =>   column;                                                                                # Scan the tokens in a LINE setting BREAKs to wrap as appropriate.
                    #
                    per_token  (token ! rest,   column)
                        =>
                        {
                                                                                                                                        if debug_prints
                                                                                                                                                printf "per_token/TOP column d=%d   %d tokens = %s)   -- wrap_box_contents_as_needed() in prettyprinter-g.pkg\n"
                                                                                                                                                        column (list::length (token ! rest)) (dbg::phase1_tokens_to_string (token ! rest));
                                                                                                                                        fi;
                            case token
                                typ::PUSH_TT _          =>   per_token (rest,   column                    );
                                typ::POP_TT             =>   per_token (rest,   column                    );
                                typ::CONTROL _          =>   per_token (rest,   column                    );
                                typ::INDENT _           =>   per_token (rest,   column                    );                            # We don't track left margin in this pass.
                                typ::BLANKS i           =>   per_token (rest,   column + i                );
                                typ::LIT    s           =>   per_token (rest,   column + string::length_in_bytes s );
                                typ::ENDLIT s           =>   per_token (rest,   column + string::length_in_bytes s );
                                typ::TAB    t           =>   per_token (rest,   column + breaklen(column, t));                          # This isn't quite right because 'column' is box-relative but tabstops should be absolute.
                                                                                                                                        # We format boxes innermost first, so at present there's no way to know our absolute column at this point.
                                                                                                                                        # We mostly keep boxes tab-aligned, which should hide this problem most of the time.
                                typ::NEWLINE    =>  {   if (*actual_width < column)   actual_width := column;   fi;
                                                        box_is_multiline :=  TRUE;
                                                        per_token( rest, /*column=*/ 0 );
                                                    };

                                typ::BREAK b    =>  {   space_left_on_line =  target_width - (column  +  breaklen (column, b.ifnotwrap));
                                                    #                                                                                                   
                                                        if (column <= breaklen (0, b.ifwrap))
                                                            #
                                                            per_token (rest,   column + breaklen (column, b.ifnotwrap));                # Wrapping won't move us left anyhow, so no point in considering it.

                                                        elif (forced_followon_fits_in (rest, space_left_on_line))                       # Is next BREAK or NEWLINE beyond right margin of box?
                                                            #
                                                            per_token (rest,   column + breaklen (column, b.ifnotwrap));                # No, treat BREAK as blanks.
                                                        else
                                                                                                                                        if debug_prints
                                                                                                                                            printf "WRAPPING at column %d indent-after-wrap d=%d   target_width %d space_left_on_line %d -- wrap_box_contents_as_needed in prettyprinter-g.pkg\n"
                                                                                                                                                column (breaklen (0, b.ifwrap)) target_width space_left_on_line;
                                                                                                                                        fi;
                                                            if (*actual_width < column)   actual_width := column;   fi;                 # Yes, treat BREAK as newline.
                                                            b.wrap := TRUE;
                                                            box_is_multiline :=  TRUE;
                                                            per_token (rest, /*column=*/ breaklen (0, b.ifwrap));
                                                        fi;
                                                    };

                                typ::BOX box    =>  {   if(*actual_width <  column + *box.actual_width)
                                                            actual_width := column + *box.actual_width;
                                                        fi;
                                                        if *box.is_multiline
                                                            box_is_multiline :=  TRUE;
                                                            per_token( rest, 0 );                                                       # Return to left margin after each multiline box.  This does the best job of decoupling
                                                        else                                                                            # events inside and outside a box, making for simple predictable behavior.
                                                            per_token (rest, column + *box.actual_width);
                                                        fi;
                                                    };
                            esac;
                        };
                end;                    # fun per_token
            end;                        # fun wrap_box_contents_as_needed

    };
end;

## COPYRIGHT (c) 2005 John Reppy (http://www.cs.uchicago.edu/~jhr)
## All rights reserved.
## Subsequent changes by Jeff Prothero Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext