Files
mercury/library/pretty_printer.m
2025-09-23 17:05:06 +10:00

2033 lines
71 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 expandtab ft=mercury
%---------------------------------------------------------------------------%
% Copyright (C) 2007, 2009-2011 The University of Melbourne
% Copyright (C) 2014-2016, 2018, 2020, 2022-2025 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: pretty_printer.m.
% Main author: rafe
% Stability: high.
%
% This module defines a doc type for formatting and a pretty printer for
% displaying docs.
%
% The doc type includes data constructors for outputting strings, newlines,
% forming groups, indented blocks, and arbitrary values.
%
% The key feature of the algorithm is this: newlines in a group are ignored if
% the group can fit on the remainder of the current line. (The algorithm is
% similar to those of Oppen and Wadler, although it uses neither coroutines or
% laziness.)
%
% When a newline is printed, indentation is also output according to the
% current indentation level.
%
% The pretty printer includes special support for formatting Mercury style
% terms in a way that respects Mercury's rules for operator precedence and
% bracketing.
%
% The pretty printer takes a parameter specifying a collection of user-defined
% formatting functions for handling certain types rather than using the
% default built-in mechanism. This allows one to, say, format maps as
% sequences of (key -> value) pairs rather than exposing the underlying
% 234-tree structure.
%
% The amount of output produced is controlled via limit parameters.
% Three kinds of limits are supported: the output line width, the maximum
% number of lines to be output, and a limit on the depth for formatting
% arbitrary terms. Output is replaced with ellipsis ("...") when a limit
% has been exceeded.
%
%---------------------------------------------------------------------------%
:- module pretty_printer.
:- interface.
:- import_module array.
:- import_module char.
:- import_module deconstruct.
:- import_module io.
:- import_module list.
:- import_module one_or_more.
:- import_module stream.
:- import_module string.
:- import_module string.builder.
:- import_module tree234.
:- import_module type_desc.
:- import_module univ.
:- import_module version_array.
%---------------------------------------------------------------------------%
:- type doc
---> str(string)
% Output a literal string. This string should not contain newlines,
% hard tabs, or other formatting characters other than spaces;
% if it does, the resulting output will almost certainly look
% strange.
; nl
% Output a newline, followed by indentation, if and only if
% - the enclosing group does not fit on the current line, and
% - starting a new line adds more space.
; hard_nl
% Always outputs a newline, followed by indentation.
; docs(list(doc))
% An embedded sequence of docs.
; format_univ(univ)
% Use a specialised formatter on the given value if
% is available for its type. Otherwise, use the generic formatter.
; format_list(list(univ), doc)
% Pretty print a list of items using the given doc as a separator
% between each pair of items.
; format_term(string, list(univ))
% Pretty print a term with zero or more arguments. If the term
% corresponds to a Mercury operator, it will be printed with
% appropriate fixity and, if necessary, in parentheses. The term
% name will be quoted and escaped if necessary.
; format_susp((func) = doc)
% The argument is a suspended computation that, if evaluated,
% will produce a doc to print. The evaluation must materialize
% at least one part of this doc, but other parts may remain
% in the form of other suspensions. This will produce a final
% doc in a lazy fashion, if needed. The *point* of producing the
% doc lazily is that when the formatting limit is reached,
% then the prettyprinter will just output "...", and will do so
% *without* evaluating any remaining suspensions. This is useful
% for formatting large structures without using more resources
% than required. Note that expanding a suspended computation
% reduces the formatting limit by one.
; pp_internal(pp_internal).
% pp_internal docs are used in the implementation, and cannot be
% exploited by user code.
:- type docs == list(doc).
% This type is private to the implementation and cannot be exploited
% by user code.
%
:- type pp_internal.
%---------------------------------------------------------------------------%
%
% Functions for constructing docs.
%
% indent(IndentString, Docs):
%
% Append IndentString to the current indentation while printing Docs.
% Indentation is printed after each newline that is output.
%
:- func indent(string, list(doc)) = doc.
% indent(Docs) = indent(" ", Docs).
%
% A convenient abbreviation.
%
:- func indent(list(doc)) = doc.
% group(Docs):
%
% If Docs can be output on the remainder of the current line by ignoring
% any nls in Docs, then do so. Otherwise nls in Docs are printed
% (followed by any indentation). The formatting test is applied recursively
% for any subgroups in Docs.
%
:- func group(list(doc)) = doc.
% format(X) = format_univ(univ(X)):
%
% A convenient abbreviation.
%
:- func format(T) = doc.
% format_arg(Doc) has the effect of formatting any term in Doc as though
% it were an argument in a Mercury term, by enclosing it in parentheses
% if necessary.
%
:- func format_arg(doc) = doc.
%---------------------------------------------------------------------------%
%
% Functions for converting docs to strings and writing them out to streams.
%
% write_doc_formatted(X, !IO):
% write_doc_formatted(FileStream, X, !IO):
%
% Convert X to a doc using the format function, and then
% call write_doc on the result.
%
:- pred write_doc_formatted(T::in, io::di, io::uo) is det.
:- pred write_doc_formatted(io.text_output_stream::in, T::in,
io::di, io::uo) is det.
% write_doc(Doc, !IO):
% write_doc(FileStream, Doc, !IO):
%
% Format Doc to io.stdout_stream or FileStream respectively using put_doc,
% with include_details_cc, the default formatter_map, and the default
% pp_params.
%
:- pred write_doc(doc::in, io::di, io::uo) is det.
:- pred write_doc(io.text_output_stream::in, doc::in, io::di, io::uo) is det.
% put_doc(Stream, Canonicalize, FMap, Params, Doc, !State):
%
% Format Doc to Stream. Format format_univ(_) docs using specialised
% formatters Formatters, and using Params as the pretty printer parameters.
% The Canonicalize argument controls how put_doc deconstructs values
% of noncanonical types (see the documentation of the noncanon_handling
% type for details).
%
:- pred put_doc(Stream, noncanon_handling, formatter_map, pp_params, doc,
State, State) <= stream.writer(Stream, string, State).
:- mode put_doc(in, in(canonicalize), in, in, in, di, uo) is det.
:- mode put_doc(in, in(include_details_cc), in, in, in, di, uo) is cc_multi.
:- pragma type_spec_constrained_preds(
[stream.writer(Stream, string, State)],
apply_to_superclasses,
[subst([Stream => io.text_output_stream, State = io.state]),
subst([Stream => string.builder.handle, State = string.builder.state])]).
%---------------------------------------------------------------------------%
%
% Mechanisms for controlling *how* docs are converted to strings.
%
% The type of generic formatting functions.
% The first argument is the univ of the value to be formatted.
% The type of this value will have the form "TC(AT1, AT2, ..., ATn)",
% where TC is a type constructor, and ATi are its argument types.
% The second argument of the function will consist of the list of
% type descriptors describing AT1, AT2, ... ATn.
%
% These arguments are intended to be used as shown by this example
% function, which can be the entry for the type constructor tree234(K, V):
%
% fmt_tree234(Univ, ArgDescs) =
% ( if
% ArgDescs = [ArgDescA, ArgDescB],
% has_type(_ArgA : K, ArgDescA),
% has_type(_ArgB : V, ArgDescB),
% Value = univ_value(Univ),
% dynamic_cast(Value, Tree : tree234(K, V))
% then
% pretty_printer.tree234_to_doc(Tree)
% else
% str("internal error: expected a tree234, did not get it")
% ).
%
% Since the tree234 type constructor has arity two, the caller will pass
% two type descriptors to fmt_tree234, which will describe the actual types
% of the keys and values in *this* tree. The two calls to has_type
% (which is defined in the type_desc module of the Mercury standard
% library) tell the compiler that the type variables K and V in *this*
% function should stand for the ground types described by ArgDescA
% and ArgDescB respectively.
%
% After the call to univ_value picks the value out of Univ, the call to
% dynamic_cast (which is defined in the builtin module of the Mercury
% standard library) checks whether the type of Value is tree234(K, V),
% and if it is, (which it should be, since the predicates and functions
% of this module would not have called fmt_tree234 otherwise), will return
% Value as Tree. Note that the difference between Value and Tree is that
%
% - the compiler does not know the type of Value statically, since that
% information comes from Univ, which is available only at runtime, but
%
% - the compiler *does* know the type of Tree statically, due to the type
% annotation on it. This type, tree234(K, V), does contain type
% variables, but its principal type constructor is known, and that is
% enough for the code in the then-part of the if-then-else to do its job.
%
% Note that the code in the else-part should not matter. If that code
% is ever executed, that would mean that a predicate or function of
% this module has called fmt_tree234 with inappropriate data.
%
:- type formatter == (func(univ, list(type_desc)) = doc).
% A formatter_map maps type constructors to formatters.
%
% If the principal (outermost) type constructor of a value's type
% has an entry in the formatter_map given to one of the prettyprinting
% predicates or functions below, then that predicate or function will use
% the corresponding formatter to format that value.
%
:- type formatter_map.
% Formatter maps identify type constructors by
%
% - the name of the module that defines the type constructor,
% - the type constructor's name, and
% - the type constructor's arity.
%
% The three fields contain this info in this order.
%
:- type formatter_map_entry
---> formatter_map_entry(string, string, int).
% ModuleName.TypeName/TypeArity.
% Construct a new formatter_map.
%
:- func new_formatter_map = formatter_map.
% set_formatter(ModuleName, TypeName, TypeArity, Formatter, !FMap):
%
% Update !FMap to use Formatter to format values whose type is
% ModuleName.TypeName/TypeArity.
%
:- pred set_formatter(string::in, string::in, int::in, formatter::in,
formatter_map::in, formatter_map::out) is det.
:- func get_formatter_map_entry_types(formatter_map) =
list(formatter_map_entry).
%---------------------%
% The func_symbol_limit type controls *how many* of the function symbols
% stored in the term inside a format_univ, format_list, or format_term doc
% the write_doc family of functions should include in the resulting string.
%
% A limit of linear(N) formats the first N functors before truncating
% output to "...".
%
% A limit of triangular(N) formats a term t(X1, ..., Xn) by applying
% the following limits:
%
% - triangular(N - 1) when formatting X1,
% - triangular(N - 2) when formatting X2,
% - ..., and
% - triangular(N - n) when formatting Xn.
%
% The cost of formatting the term t(X1, ..., Xn) as a whole is just one,
% so a sequence of terms T1, T2, ... is formatted with limits
% triangular(N), triangular(N - 1), ... respectively. When the limit
% is exhausted, terms are output as just "...".
%
:- type func_symbol_limit
---> linear(int)
; triangular(int).
% The pp_params type contains the parameters of the prettyprinting process:
%
% - the width of each line,
% - the maximum number of lines to print, and
% - the controls for how many function symbols to print.
%
:- type pp_params
---> pp_params(
pp_line_width :: int,
pp_max_lines :: int,
pp_limit :: func_symbol_limit
).
%---------------------%
% A user-configurable default set of type-specific formatters and
% formatting parameters is always attached to the I/O state.
% The write_doc predicate (in both its arities) uses these settings.
%
% The get_default_formatter_map predicate reads the default formatter_map
% from the current I/O state, while set_default_formatter_map writes
% the specified formatter_map to the I/O state to become the new default.
%
% The initial value of the default formatter_map provides the means
% to prettyprint the most commonly used types in the Mercury standard
% library, such as arrays, chars, floats, ints, maps, strings, etc.
%
% The default formatter_map may also be updated by users' modules
% (e.g. in initialisation goals).
%
:- pred get_default_formatter_map(formatter_map::out, io::di, io::uo) is det.
:- pred set_default_formatter_map(formatter_map::in, io::di, io::uo) is det.
% set_default_formatter(ModuleName, TypeName, TypeArity, Formatter, !IO):
%
% Update the default formatter in the I/O state to use Formatter
% to print values of the type ModuleName.TypeName/TypeArity.
%
:- pred set_default_formatter(string::in, string::in, int::in, formatter::in,
io::di, io::uo) is det.
% Alongside the default formatter_map, the I/O state also always stores
% a default set of pretty-printing parameters (pp_params) for use by
% the write_doc predicate (in both its arities).
%
% The get_default_params predicate reads the default parameters
% from the current I/O state, while set_default_params writes the specified
% parameters to the I/O state to become the new default.
%
% The initial default parameters are pp_params(78, 100, triangular(100)).
%
:- pred get_default_params(pp_params::out, io::di, io::uo) is det.
:- pred set_default_params(pp_params::in, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
% Convert a char to a doc.
%
:- func char_to_doc(char) = doc.
% Convert a string to a doc.
%
:- func string_to_doc(string) = doc.
% Convert a float to a doc.
%
:- func float_to_doc(float) = doc.
% Convert an int to a doc.
%
:- func int_to_doc(int) = doc.
:- func int8_to_doc(int8) = doc.
:- func int16_to_doc(int16) = doc.
:- func int32_to_doc(int32) = doc.
:- func int64_to_doc(int64) = doc.
% Convert a uint to a doc.
%
:- func uint_to_doc(uint) = doc.
:- func uint8_to_doc(uint8) = doc.
:- func uint16_to_doc(uint16) = doc.
:- func uint32_to_doc(uint32) = doc.
:- func uint64_to_doc(uint64) = doc.
% Convert a list to a doc.
%
:- func list_to_doc(list(T)) = doc.
% Convert a nonempty list to a doc.
%
:- func one_or_more_to_doc(one_or_more(T)) = doc.
% Convert a 2-3-4 tree to a doc.
%
:- func tree234_to_doc(tree234(K, V)) = doc.
% Convert an array to a doc.
%
:- func array_to_doc(array(T)) = doc.
% Convert a version array to a doc.
%
:- func version_array_to_doc(version_array(T)) = doc.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module bool.
:- import_module fat_sparse_bitset.
:- import_module fatter_sparse_bitset.
:- import_module int.
:- import_module map.
:- import_module ops.
:- import_module require.
:- import_module sparse_bitset.
:- import_module term_io.
:- import_module tree_bitset.
:- import_module uint.
%---------------------------------------------------------------------------%
% Consider {add,remove}_indent as (), and {inc,dec}_std_indent as [].
% The indent-related operations must occur as balanced pairs, meaning
% that matching sequences such as (([])), ([][]) and [([])] are allowed,
% but non-matching sequences such as ([)] are not.
%
% Since the definition of the pp_internal type is private to this module,
% code outside this module cannot violate this requirement; only code
% inside this module can. If it does, that is a bug.
:- type pp_internal
---> open_group
% Mark the start of a group.
; close_group
% Mark the end of a group.
; add_indent(string)
% Extend the current indent stack with the given string.
; remove_indent
% Restore indentation to before the last add_indent/1.
% Calling the two operations above {inc,dec}_user_indent
% would be more consistent with the two operations below,
% but the hard_coded/test_pretty_printer test case references
% these names, even though they are supposed to be private.
; inc_std_indent
% Add a standard indentation level.
; dec_std_indent
% Remove a standard indentation level.
; set_op_priority(ops.priority)
% Set the current priority for printing operator terms with the
% correct parenthesisation.
; set_limit(func_symbol_limit).
% Set the truncation limit.
% Maps module names (first map), type names (second map) and type arities
% (third map) to the formatter to be used when printing values of the type
% ModuleName.TypeName/TypeArity.
%
:- type formatter_map == map(string, map(string, map(int, formatter))).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
indent(Indent, Docs) =
docs([
pp_internal(add_indent(Indent)),
docs(Docs),
pp_internal(remove_indent)
]).
indent(Docs) =
docs([
pp_internal(inc_std_indent),
docs(Docs),
pp_internal(dec_std_indent)
]).
group(Docs) =
docs([pp_internal(open_group), docs(Docs), pp_internal(close_group)]).
%---------------------------------------------------------------------------%
format(X) = format_univ(univ(X)).
format_arg(Doc) =
docs([
pp_internal(set_arg_priority),
Doc
]).
:- func set_arg_priority = pp_internal.
:- pragma inline(func(set_arg_priority/0)).
set_arg_priority = set_op_priority(ops.mercury_op_table_arg_priority).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- pragma foreign_export("C", write_doc_formatted(in, di, uo),
"ML_write_doc_formatted").
:- pragma foreign_export("C", write_doc_formatted(in, in, di, uo),
"ML_write_doc_formatted_to_stream").
write_doc_formatted(X, !IO) :-
Doc = format(X),
write_doc(io.stdout_stream, Doc, !IO).
write_doc_formatted(Stream, X, !IO) :-
Doc = format(X),
write_doc(Stream, Doc, !IO).
write_doc(Doc, !IO) :-
write_doc(io.stdout_stream, Doc, !IO).
write_doc(Stream, Doc, !IO) :-
get_default_formatter_map(Formatters, !IO),
get_default_params(Params, !IO),
promise_equivalent_solutions [!:IO] (
put_doc(Stream, include_details_cc, Formatters, Params, Doc, !IO)
).
%---------------------------------------------------------------------------%
put_doc(Stream, Canonicalize, FMap, Params, Doc, !IO) :-
Pri = ops.mercury_op_table_loosest_op_priority,
Params = pp_params(LineWidth, MaxLines, Limit),
RemainingWidth = LineWidth,
Indents = indent_empty,
do_put_docs(Stream, Canonicalize, FMap, LineWidth, [Doc],
RemainingWidth, _, Indents, _, MaxLines, _, Limit, _, Pri, _, !IO).
%---------------------------------------------------------------------------%
% do_put_docs(FMap, LineWidth, Docs, !RemainingWidth, !Indents,
% !RemainingLines, !Limit, !Pri, !IO):
%
% Format Docs to fit on LineWidth chars per line,
% - tracking !RemainingWidth chars left on the current line,
% - indenting by !Indents after newlines,
% - truncating output after !RemainingLines,
% - expanding terms to at most !Limit depth before truncating,
% - tracking current operator priority !Pri.
% Assumes that Docs is the output of expand.
%
:- pred do_put_docs(Stream, noncanon_handling, formatter_map, int,
list(doc), int, int, indent_stack, indent_stack, int, int,
func_symbol_limit, func_symbol_limit,
ops.priority, ops.priority, State, State)
<= stream.writer(Stream, string, State).
:- mode do_put_docs(in, in(canonicalize), in, in, in,
in, out, in, out, in, out, in, out, in, out, di, uo) is det.
:- mode do_put_docs(in, in(include_details_cc), in, in, in,
in, out, in, out, in, out, in, out, in, out, di, uo) is cc_multi.
do_put_docs(_Stream, _Canonicalize, _FMap, _LineWidth, [],
!RemainingWidth, !Indents, !RemainingLines, !Limit, !Pri, !IO).
do_put_docs(Stream, Canonicalize, FMap, LineWidth, [HeadDoc0 | TailDocs0],
!RemainingWidth, !Indents, !RemainingLines, !Limit, !Pri, !IO) :-
( if !.RemainingLines =< 0 then
stream.put(Stream, "...", !IO)
else
(
% Output strings directly.
HeadDoc0 = str(String),
stream.put(Stream, String, !IO),
StrWidth = string.count_code_points(String),
!:RemainingWidth = !.RemainingWidth - StrWidth,
Docs = TailDocs0
;
HeadDoc0 = nl,
IndentWidth = count_indent_code_points(!.Indents),
( if !.RemainingWidth < LineWidth - IndentWidth then
format_nl(Stream, LineWidth, !.Indents, !:RemainingWidth,
!RemainingLines, !IO)
else
true
),
Docs = TailDocs0
;
HeadDoc0 = hard_nl,
format_nl(Stream, LineWidth, !.Indents, !:RemainingWidth,
!RemainingLines, !IO),
Docs = TailDocs0
;
HeadDoc0 = docs(HeadDocs0),
Docs = list.(HeadDocs0 ++ TailDocs0)
;
(
HeadDoc0 = format_univ(Univ),
expand_format_univ(Canonicalize, FMap, Univ, TailDocs0, Docs,
!Limit, !.Pri)
;
HeadDoc0 = format_list(Univs, Sep),
expand_format_list(!.Limit, Univs, Sep, TailDocs0, Docs)
;
HeadDoc0 = format_term(Name, Univs),
expand_format_term(Name, Univs, TailDocs0, Docs, !Limit, !.Pri)
;
HeadDoc0 = format_susp(Susp),
expand_format_susp(Susp, TailDocs0, Docs, !Limit)
)
;
HeadDoc0 = pp_internal(Internal),
(
% Open groups: if the current group (and what follows,
% up to the next nl) fits on the remainder of the current line,
% then print it that way; otherwise we have to recognise
% the nls in the group.
Internal = open_group,
OpenGroups1 = 1,
CurrentRemainingWidth = !.RemainingWidth,
expand_docs_to_line_end(Canonicalize, FMap, TailDocs0, Docs1,
OpenGroups1, !Limit, !Pri,
CurrentRemainingWidth, RemainingWidthAfterGroup),
( if RemainingWidthAfterGroup >= 0 then
output_current_group(Stream, LineWidth, !.Indents,
Docs1, Docs, OpenGroups1,
!RemainingWidth, !RemainingLines, !IO)
else
Docs = Docs1
)
;
(
Internal = close_group
;
Internal = add_indent(IndentStr),
increment_user_indent(IndentStr, !Indents)
;
Internal = remove_indent,
decrement_user_indent(!Indents)
;
Internal = inc_std_indent,
increment_std_indent(!Indents)
;
Internal = dec_std_indent,
decrement_std_indent(!Indents)
;
Internal = set_limit(Limit),
!:Limit = Limit
;
Internal = set_op_priority(Pri),
!:Pri = Pri
),
Docs = TailDocs0
)
),
do_put_docs(Stream, Canonicalize, FMap, LineWidth, Docs,
!RemainingWidth, !Indents, !RemainingLines, !Limit, !Pri, !IO)
).
%---------------------%
:- type open_groups == int.
:- pred output_current_group(Stream::in, int::in, indent_stack::in,
list(doc)::in, list(doc)::out, open_groups::in, int::in, int::out,
int::in, int::out, State::di, State::uo) is det
<= stream.writer(Stream, string, State).
output_current_group(_Stream, _LineWidth, _Indents, [], [],
_OpenGroups, !RemainingWidth, !RemainingLines, !IO).
output_current_group(Stream, LineWidth, Indents, [HeadDoc0 | TailDocs0], Docs,
!.OpenGroups, !RemainingWidth, !RemainingLines, !IO) :-
(
HeadDoc0 = str(String),
stream.put(Stream, String, !IO),
StrWidth = string.count_code_points(String),
!:RemainingWidth = !.RemainingWidth - StrWidth,
output_current_group(Stream, LineWidth, Indents, TailDocs0, Docs,
!.OpenGroups, !RemainingWidth, !RemainingLines, !IO)
;
HeadDoc0 = hard_nl,
format_nl(Stream, LineWidth, Indents, !:RemainingWidth,
!RemainingLines, !IO),
( if !.RemainingLines =< 0 then
Docs = TailDocs0
else
output_current_group(Stream, LineWidth, Indents, TailDocs0, Docs,
!.OpenGroups, !RemainingWidth, !RemainingLines, !IO)
)
;
HeadDoc0 = pp_internal(Internal),
(
Internal = open_group,
!:OpenGroups = !.OpenGroups + 1,
output_current_group(Stream, LineWidth, Indents, TailDocs0, Docs,
!.OpenGroups, !RemainingWidth, !RemainingLines, !IO)
;
Internal = close_group,
( if !.OpenGroups = 1 then
Docs = TailDocs0
else
!:OpenGroups = !.OpenGroups - 1,
output_current_group(Stream, LineWidth, Indents,
TailDocs0, Docs, !.OpenGroups,
!RemainingWidth, !RemainingLines, !IO)
)
;
( Internal = add_indent(_)
; Internal = remove_indent
; Internal = inc_std_indent
; Internal = dec_std_indent
; Internal = set_op_priority(_)
; Internal = set_limit(_)
),
output_current_group(Stream, LineWidth, Indents, TailDocs0, Docs,
!.OpenGroups, !RemainingWidth, !RemainingLines, !IO)
)
;
( HeadDoc0 = nl
; HeadDoc0 = docs(_)
; HeadDoc0 = format_univ(_)
; HeadDoc0 = format_list(_, _)
; HeadDoc0 = format_term(_, _)
; HeadDoc0 = format_susp(_)
),
output_current_group(Stream, LineWidth, Indents, TailDocs0, Docs,
!.OpenGroups, !RemainingWidth, !RemainingLines, !IO)
).
%---------------------%
% expand_docs_to_line_end(Canonicalize, Docs0, Docs,
% !.OpenGroups, !Limit, !Pri, !RemainingWidth)
%
% expands out any doc(_), pp_univ(_), format_list(_, _), and pp_term(_)
% constructors in Docs0 into Docs, until either
%
% - Docs0 has been completely expanded, or
% - a nl is encountered, or
% - the remaining space on the current line has been accounted for.
%
% !.OpenGroups is used to track nested groups.
% !Limit tracks the limits after accounting for expansion.
% !Pri tracks the operator priority after accounting for expansion.
% !RemainingWidth tracks the remaining line width after accounting for
% expansion.
%
:- pred expand_docs_to_line_end(noncanon_handling, formatter_map,
list(doc), list(doc), open_groups, func_symbol_limit, func_symbol_limit,
ops.priority, ops.priority, int, int).
:- mode expand_docs_to_line_end(in(canonicalize), in,
in, out, in, in, out, in, out, in, out) is det.
:- mode expand_docs_to_line_end(in(include_details_cc), in,
in, out, in, in, out, in, out, in, out) is cc_multi.
expand_docs_to_line_end(_, _, [], [], _OpenGroups,
!Limit, !Pri, !RemainingWidth).
expand_docs_to_line_end(Canonicalize, FMap,
Docs0 @ [HeadDoc0 | TailDocs0], Docs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth) :-
( if
(
!.OpenGroups =< 0, ( HeadDoc0 = nl ; HeadDoc0 = hard_nl )
% We have found the first nl after the close of the current
% open group.
;
!.RemainingWidth < 0
% We have run out of space on this line: the current open
% group will not fit.
)
then
Docs = Docs0
else
(
HeadDoc0 = str(String),
StrWidth = string.count_code_points(String),
!:RemainingWidth = !.RemainingWidth - StrWidth,
expand_docs_to_line_end(Canonicalize, FMap, TailDocs0, TailDocs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth),
Docs = [HeadDoc0 | TailDocs]
;
( HeadDoc0 = nl
; HeadDoc0 = hard_nl
),
( if !.OpenGroups =< 0 then
Docs = Docs0
else
expand_docs_to_line_end(Canonicalize, FMap,
TailDocs0, TailDocs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth),
Docs = [HeadDoc0 | TailDocs]
)
;
HeadDoc0 = docs(HeadDocs0),
Docs1 = list.(HeadDocs0 ++ TailDocs0),
expand_docs_to_line_end(Canonicalize, FMap, Docs1, Docs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth)
;
(
HeadDoc0 = format_univ(HeadUniv),
expand_format_univ(Canonicalize, FMap, HeadUniv,
TailDocs0, Docs1, !Limit, !.Pri)
;
HeadDoc0 = format_list(HeadUnivs, Sep),
expand_format_list(!.Limit, HeadUnivs, Sep, TailDocs0, Docs1)
;
HeadDoc0 = format_term(Name, HeadUnivs),
expand_format_term(Name, HeadUnivs, TailDocs0, Docs1, !Limit,
!.Pri)
;
HeadDoc0 = format_susp(HeadSusp),
expand_format_susp(HeadSusp, TailDocs0, Docs1, !Limit)
),
expand_docs_to_line_end(Canonicalize, FMap, Docs1, Docs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth)
;
HeadDoc0 = pp_internal(Internal),
(
(
Internal = add_indent(_)
;
Internal = remove_indent
;
Internal = inc_std_indent
;
Internal = dec_std_indent
;
Internal = open_group,
% XXX This is probably a bug, because if !.OpenGroups = 0,
% then it will *stay* at 0 even *after* this opening
% of a group.
!:OpenGroups = !.OpenGroups +
( if !.OpenGroups > 0 then 1 else 0 )
;
Internal = close_group,
!:OpenGroups = !.OpenGroups -
( if !.OpenGroups > 0 then 1 else 0 )
),
expand_docs_to_line_end(Canonicalize, FMap,
TailDocs0, TailDocs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth),
Docs = [HeadDoc0 | TailDocs]
;
(
Internal = set_limit(Limit),
!:Limit = Limit
;
Internal = set_op_priority(Pri),
!:Pri = Pri
),
expand_docs_to_line_end(Canonicalize, FMap, TailDocs0, Docs,
!.OpenGroups, !Limit, !Pri, !RemainingWidth)
)
)
).
%---------------------%
% Output a newline followed by indentation.
%
:- pred format_nl(Stream::in, int::in, indent_stack::in, int::out,
int::in, int::out, State::di, State::uo) is det
<= stream.writer(Stream, string, State).
format_nl(Stream, LineWidth, Indents, RemainingWidth, !RemainingLines, !IO) :-
stream.put(Stream, "\n", !IO),
RemainingWidth = LineWidth - count_indent_code_points(Indents),
output_indent_stack(Stream, Indents, !IO),
!:RemainingLines = !.RemainingLines - 1.
:- pred output_indent_stack(Stream::in, indent_stack::in,
State::di, State::uo) is det <= stream.writer(Stream, string, State).
output_indent_stack(Stream, IndentStack, !IO) :-
(
IndentStack = indent_empty
;
IndentStack = indent_user(PrevStack, IndentStr, _NumCPs),
output_indent_stack(Stream, PrevStack, !IO),
stream.put(Stream, IndentStr, !IO)
;
IndentStack = indent_std(PrevStack, IndentLevels, _NumCPs),
output_indent_stack(Stream, PrevStack, !IO),
output_std_indent_levels(Stream, IndentLevels, !IO)
).
:- pred output_std_indent_levels(Stream::in, int::in,
State::di, State::uo) is det <= stream.writer(Stream, string, State).
output_std_indent_levels(Stream, NumLevels, !IO) :-
% We try to amortize the overhead of stream.put over as large a part
% of the overall indentation as we can.
( if NumLevels >= 30 then
std_indent_30(IndentStr),
stream.put(Stream, IndentStr, !IO),
output_std_indent_levels(Stream, NumLevels - 30, !IO)
else if NumLevels > 0 then
( if std_indent(NumLevels, IndentStr) then
stream.put(Stream, IndentStr, !IO)
else
unexpected($pred, "std_indent failed")
)
else
true
).
:- pred std_indent(int::in, string::out) is semidet.
:- pred std_indent_30(string::out) is det.
std_indent(1, " ").
std_indent(2, " ").
std_indent(3, " ").
std_indent(4, " ").
std_indent(5, " ").
std_indent(6, " ").
std_indent(7, " ").
std_indent(8, " ").
std_indent(9, " ").
std_indent(10, " ").
std_indent(11, " ").
std_indent(12, " ").
std_indent(13, " ").
std_indent(14, " ").
std_indent(15, " ").
std_indent(16, " ").
std_indent(17, " ").
std_indent(18, " ").
std_indent(19, " ").
std_indent(20, " ").
std_indent(21, " ").
std_indent(22, " ").
std_indent(23, " ").
std_indent(24, " ").
std_indent(25, " ").
std_indent(26, " ").
std_indent(27, " ").
std_indent(28, " ").
std_indent(29, " ").
std_indent_30( " ").
%---------------------%
% Expand a univ into docs using the first pretty-printer in the given list
% that succeeds, otherwise use the generic pretty- printer. If the
% pretty-printer limit has been exhausted, then generate only "...".
%
:- pred expand_format_univ(noncanon_handling, formatter_map, univ,
list(doc), list(doc), func_symbol_limit, func_symbol_limit, ops.priority).
:- mode expand_format_univ(in(canonicalize), in, in,
in, out, in, out, in) is det.
:- mode expand_format_univ(in(include_details_cc), in, in,
in, out, in, out, in) is cc_multi.
expand_format_univ(Canonicalize, FMap, Univ, TailDocs, Docs,
!Limit, CurrentPri) :-
( if func_limit_reached(!.Limit) then
Docs = [ellipsis | TailDocs]
else
Value = univ_value(Univ),
( if
type_ctor_and_args(type_of(Value), TypeCtorDesc, ArgTypeDescs),
ModuleName = type_ctor_module_name(TypeCtorDesc),
map.search(FMap, ModuleName, FMapTypeArity),
TypeName = type_ctor_name(TypeCtorDesc),
map.search(FMapTypeArity, TypeName, FMapArity),
list.length(ArgTypeDescs, Arity),
map.search(FMapArity, Arity, Formatter)
then
decrement_func_limit(!Limit),
Doc0 = Formatter(Univ, ArgTypeDescs),
set_func_limit_in_doc(!.Limit, Doc0, TailDocs, Docs)
else
deconstruct(Value, Canonicalize, Name, _Arity, Args),
expand_format_term(Name, Args, TailDocs, Docs, !Limit, CurrentPri)
)
).
%---------------------%
% Expand a list of univs into docs using the given separator.
%
:- pred expand_format_list(func_symbol_limit::in, list(univ)::in,
doc::in, list(doc)::in, list(doc)::out) is det.
expand_format_list(_Limit, [], _Sep, TailDocs, Docs) :-
Docs = TailDocs.
expand_format_list(Limit, [HeadUniv | TailUnivs], Sep, TailDocs, Docs) :-
( if func_limit_reached(Limit) then
Docs = [ellipsis | TailDocs]
else
(
TailUnivs = [],
Docs = [
pp_internal(set_arg_priority),
pp_internal(open_group),
nl,
format_univ(HeadUniv),
pp_internal(close_group)
| TailDocs
]
;
TailUnivs = [_ | _],
Docs = [
pp_internal(set_arg_priority),
pp_internal(open_group),
nl,
format_univ(HeadUniv),
Sep,
pp_internal(close_group),
format_list(TailUnivs, Sep)
| TailDocs
]
)
).
% Expand a name and list of univs into docs corresponding to Mercury
% term syntax.
%
:- pred expand_format_term(string::in, list(univ)::in,
list(doc)::in, list(doc)::out,
func_symbol_limit::in, func_symbol_limit::out, ops.priority::in) is det.
expand_format_term(Name, Args, TailDocs, Docs, !Limit, CurrentPri) :-
( if Args = [] then
HeadDoc0 = str(term_io.quoted_atom(Name)),
decrement_func_limit(!Limit),
set_func_limit_in_doc(!.Limit, HeadDoc0, TailDocs, Docs)
else if func_limit_reached(!.Limit) then
HeadDoc0 = ellipsis,
% There *should* be no point im decrementing !Limit even further.
decrement_func_limit(!Limit),
set_func_limit_in_doc(!.Limit, HeadDoc0, TailDocs, Docs)
else if expand_format_op(Name, Args, CurrentPri, OpDocs) then
decrement_func_limit(!Limit),
set_func_limit_in_docs(!.Limit, OpDocs, TailDocs, Docs)
else
( if Name = "{}" then
HeadDocs0 = [
str("{"),
pp_internal(inc_std_indent),
format_list(Args, str(", ")),
pp_internal(dec_std_indent),
str("}")
]
else
HeadDocs0 = [
pp_internal(open_group),
nl,
str(term_io.quoted_atom(Name)),
str("("),
pp_internal(inc_std_indent),
format_list(Args, str(", ")),
pp_internal(dec_std_indent),
str(")"),
pp_internal(close_group)
]
),
decrement_func_limit(!Limit),
set_func_limit_in_docs(!.Limit, HeadDocs0, TailDocs, Docs)
).
% Expand a function symbol name and list of univs representing its
% arguments into docs corresponding to Mercury operator syntax.
%
:- pred expand_format_op(string::in, list(univ)::in, ops.priority::in,
list(doc)::out) is semidet.
expand_format_op(Op, Args, EnclosingPriority, Docs) :-
% XXX With one exception, all the set_op_priority pp_internals are
% created here. They are intended to set the priority of one argument,
% which in this case is equivalent to setting the priority from
% the occurrence of the set_op_priority until either the next
% set_op_priority, or until the next close_group. The fact that
% the predicates above do NOT reset the priority when they get to
% a close_group seems to me to be a bug -zs.
%
% The one exception is the format_arg function, which appears
% to have the same bug, but in an even worse form, since it does not
% wrap an open_group/close_group pair around the argument.
(
Args = [ArgA],
ops.mercury_op_table_search_op_infos(Op, OpInfos),
( if OpInfos ^ oi_prefix = pre(Pri, GtOrGeA) then
OpPriority = Pri,
PriorityArgA = min_priority_for_arg(OpPriority, GtOrGeA),
Docs0 = [
pp_internal(open_group),
str(Op),
pp_internal(set_op_priority(PriorityArgA)),
format_univ(ArgA),
pp_internal(close_group)
]
else if OpInfos ^ oi_postfix = post(Pri, GtOrGeA) then
OpPriority = Pri,
PriorityArgA = min_priority_for_arg(OpPriority, GtOrGeA),
Docs0 = [
pp_internal(open_group),
pp_internal(set_op_priority(PriorityArgA)),
format_univ(ArgA),
str(Op),
pp_internal(close_group)
]
else
fail
)
;
Args = [ArgA, ArgB],
ops.mercury_op_table_search_op_infos(Op, OpInfos),
( if
OpInfos ^ oi_infix = in(Pri, GtOrGeA, GtOrGeB)
then
OpPriority = Pri,
PriorityArgA = min_priority_for_arg(OpPriority, GtOrGeA),
PriorityArgB = min_priority_for_arg(OpPriority, GtOrGeB),
Docs0 = [
pp_internal(open_group),
pp_internal(set_op_priority(PriorityArgA)),
format_univ(ArgA),
( if Op = "." then
str(Op)
else
docs([str(" "), str(Op), str(" ")])
),
pp_internal(inc_std_indent),
nl,
pp_internal(set_op_priority(PriorityArgB)),
format_univ(ArgB),
pp_internal(dec_std_indent),
pp_internal(close_group)
]
else if
OpInfos ^ oi_binary_prefix = bin_pre(Pri, GtOrGeA, GtOrGeB)
then
OpPriority = Pri,
PriorityArgA = min_priority_for_arg(OpPriority, GtOrGeA),
PriorityArgB = min_priority_for_arg(OpPriority, GtOrGeB),
Docs0 = [
pp_internal(open_group),
str(Op),
str(" "),
pp_internal(set_op_priority(PriorityArgA)),
format_univ(ArgA),
str(" "),
pp_internal(inc_std_indent),
pp_internal(set_op_priority(PriorityArgB)),
format_univ(ArgB),
pp_internal(dec_std_indent),
pp_internal(close_group)
]
else
fail
)
),
% Add parentheses around a doc if required by operator priority.
( if priority_lt(OpPriority, EnclosingPriority) then
Docs = [str("(") | Docs0] ++ [str(")")]
else
Docs = Docs0
).
%---------------------%
:- pred expand_format_susp(((func) = doc)::in, list(doc)::in, list(doc)::out,
func_symbol_limit::in, func_symbol_limit::out) is det.
expand_format_susp(Susp, TailDocs, Docs, !Limit) :-
( if func_limit_reached(!.Limit) then
Docs = [ellipsis | TailDocs]
else
decrement_func_limit(!Limit),
HeadDoc0 = apply(Susp),
set_func_limit_in_doc(!.Limit, HeadDoc0, TailDocs, Docs)
).
%---------------------%
:- func ellipsis = doc.
ellipsis = str("...").
%---------------------%
% Update the limits properly after processing a term.
%
:- pred set_func_limit_in_doc(func_symbol_limit::in,
doc::in, list(doc)::in, list(doc)::out) is det.
set_func_limit_in_doc(linear(_), HeadDoc, TailDocs, [HeadDoc | TailDocs]).
set_func_limit_in_doc(Limit @ triangular(_), HeadDoc0, TailDocs, Docs) :-
Docs = [HeadDoc0, pp_internal(set_limit(Limit)) | TailDocs].
% Update the limits properly after processing a term.
%
:- pred set_func_limit_in_docs(func_symbol_limit::in,
list(doc)::in, list(doc)::in, list(doc)::out) is det.
set_func_limit_in_docs(linear(_), HeadDocs, TailDocs, HeadDocs ++ TailDocs).
set_func_limit_in_docs(Limit @ triangular(_), HeadDocs0, TailDocs, Docs) :-
Docs = HeadDocs0 ++ [pp_internal(set_limit(Limit)) | TailDocs].
% Succeeds if the pretty-printer state limits have been used up.
%
:- pred func_limit_reached(func_symbol_limit::in) is semidet.
func_limit_reached(linear(N)) :-
N =< 0.
func_limit_reached(triangular(N)) :-
N =< 0.
% Reduce the pretty-printer limit by one.
%
:- pred decrement_func_limit(func_symbol_limit::in, func_symbol_limit::out)
is det.
decrement_func_limit(linear(N), linear(N - 1)).
decrement_func_limit(triangular(N), triangular(N - 1)).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- type indent_stack
---> indent_empty
% No indent at all. Number of code points is zero.
; indent_user(
% The indentation after which this indentation is added.
user_prevstack :: indent_stack,
% Indentation added by a non-standard indent() function.
user_indent_string :: string,
% The total number of code points in user_prevstack and
% user_indent_string. Must be the sum of
% count_indent_code_points(user_prevstack) and
% string.count_code_points(user_indent_string).
user_total_code_points :: int
)
; indent_std(
% The indentation after which this indentation is added.
std_prevstack :: indent_stack,
% The number of extra standard indent levels added
% after std_prevstack. Each indent level consists of two
% spaces.
std_extra_indent_levels :: int,
% The total number of code points in user_prevstack and
% user_extra_indent. Must be the sum of
% count_indent_code_points(std_prevstack) and
% 2 * std_extra_indent_levels.
std_total_code_points :: int
).
:- func count_indent_code_points(indent_stack) = int.
count_indent_code_points(indent_empty) = 0.
count_indent_code_points(indent_user(_PrevStack, _Str, NumCPs)) = NumCPs.
count_indent_code_points(indent_std(_PrevStack, _NL, NumCPs)) = NumCPs.
:- pred increment_user_indent(string::in, indent_stack::in, indent_stack::out)
is det.
increment_user_indent(IndentStr, IndentStack0, IndentStack) :-
NumCPs0 = count_indent_code_points(IndentStack0),
NumCPs = NumCPs0 + string.count_code_points(IndentStr),
IndentStack = indent_user(IndentStack0, IndentStr, NumCPs).
:- pred decrement_user_indent(indent_stack::in, indent_stack::out) is det.
decrement_user_indent(IndentStack0, IndentStack) :-
(
IndentStack0 = indent_user(IndentStack, _, _)
;
( IndentStack0 = indent_empty
; IndentStack0 = indent_std(_, _, _)
),
unexpected($pred, "last indent is not user indent")
).
:- pred increment_std_indent(indent_stack::in, indent_stack::out) is det.
increment_std_indent(IndentStack0, IndentStack) :-
(
IndentStack0 = indent_user(_, _, NumCPs0),
NumCPs = NumCPs0 + 2,
IndentStack = indent_std(IndentStack0, 1, NumCPs)
;
IndentStack0 = indent_empty,
NumCPs = 2,
IndentStack = indent_std(IndentStack0, 1, NumCPs)
;
IndentStack0 = indent_std(PrevStack, NumLevels0, NumCPs0),
NumLevels = NumLevels0 + 1,
NumCPs = NumCPs0 + 2,
IndentStack = indent_std(PrevStack, NumLevels, NumCPs)
).
:- pred decrement_std_indent(indent_stack::in, indent_stack::out) is det.
decrement_std_indent(IndentStack0, IndentStack) :-
(
IndentStack0 = indent_std(PrevStack, NumLevels0, NumCPs0),
NumLevels = NumLevels0 - 1,
( if NumLevels > 0 then
NumCPs = NumCPs0 - 2,
IndentStack = indent_std(PrevStack, NumLevels, NumCPs)
else
IndentStack = PrevStack
)
;
( IndentStack0 = indent_empty
; IndentStack0 = indent_user(_, _, _)
),
unexpected($pred, "last indent is not std indent")
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
new_formatter_map = map.init.
set_formatter(ModuleName, TypeName, Arity, Formatter, !FMap) :-
( if map.search(!.FMap, ModuleName, FMapTypeArity0) then
( if map.search(FMapTypeArity0, TypeName, FMapArity0) then
map.det_update(Arity, Formatter, FMapArity0, FMapArity),
map.det_update(TypeName, FMapArity, FMapTypeArity0, FMapTypeArity)
else
FMapArity = map.singleton(Arity, Formatter),
map.det_insert(TypeName, FMapArity, FMapTypeArity0, FMapTypeArity)
),
map.det_update(ModuleName, FMapTypeArity, !FMap)
else
FMapArity = map.singleton(Arity, Formatter),
FMapTypeArity = map.singleton(TypeName, FMapArity),
map.det_insert(ModuleName, FMapTypeArity, !FMap)
).
%---------------------%
get_formatter_map_entry_types(FMap) = Entries :-
% To allocate as few cons cells as possible, build Entries from the back.
map.foldr(get_fmap_entries_module, FMap, [], Entries).
:- pred get_fmap_entries_module(string::in,
map(string, map(int, formatter))::in,
list(formatter_map_entry)::in, list(formatter_map_entry)::out) is det.
get_fmap_entries_module(ModuleName, TypeNameArityMap, !Entries) :-
map.foldr(get_fmap_entries_type(ModuleName), TypeNameArityMap, !Entries).
:- pred get_fmap_entries_type(string::in, string::in, map(int, formatter)::in,
list(formatter_map_entry)::in, list(formatter_map_entry)::out) is det.
get_fmap_entries_type(ModuleName, TypeName, ArityMap, !Entries) :-
map.foldr(get_fmap_entries_arity(ModuleName, TypeName),
ArityMap, !Entries).
:- pred get_fmap_entries_arity(string::in, string::in, int::in, formatter::in,
list(formatter_map_entry)::in, list(formatter_map_entry)::out) is det.
get_fmap_entries_arity(ModuleName, TypeName, Arity, _Formatter, !Entries) :-
Entry = formatter_map_entry(ModuleName, TypeName, Arity),
!:Entries = [Entry | !.Entries].
%---------------------%
get_default_formatter_map(FMap, !IO) :-
pretty_printer_is_initialised(Okay, !IO),
(
Okay = no,
FMap = initial_formatter_map,
set_default_formatter_map(FMap, !IO)
;
Okay = yes,
unsafe_get_default_formatter_map(FMap, !IO)
).
% set_default_formatter_map is implemented using only foreign_procs,
% with no Mercury code.
set_default_formatter(ModuleName, TypeName, TypeArity, Formatter, !IO) :-
get_default_formatter_map(FMap0, !IO),
set_formatter(ModuleName, TypeName, TypeArity, Formatter, FMap0, FMap),
set_default_formatter_map(FMap, !IO).
%---------------------%
% Because there is no guaranteed order of module initialisation, we need
% to ensure that we do the right thing if other modules try to update the
% default formatter_map before this module has been initialised.
%
% All of this machinery is needed to avoid a race condition between
% initialise directives and initialisation of mutables.
%
:- pragma foreign_decl("C",
"
extern MR_Bool ML_pretty_printer_is_initialised;
extern MR_Word ML_pretty_printer_default_formatter_map;
").
:- pragma foreign_code("C",
"
MR_Bool ML_pretty_printer_is_initialised = MR_NO;
MR_Word ML_pretty_printer_default_formatter_map = 0;
").
:- pragma foreign_code("C#",
"
static mr_bool.Bool_0 isInitialised = mr_bool.NO;
static tree234.Tree234_2 defaultFormatterMap = null;
").
:- pragma foreign_code("Java",
"
static bool.Bool_0 isInitialised = bool.NO;
static tree234.Tree234_2<String,
tree234.Tree234_2<String,
tree234.Tree234_2<Integer, /* closure */ java.lang.Object[]>>>
defaultFormatterMap = null;
").
%---------------------%
:- pred pretty_printer_is_initialised(bool::out, io::di, io::uo) is det.
:- pragma foreign_proc("C",
pretty_printer_is_initialised(Okay::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe],
"
Okay = ML_pretty_printer_is_initialised;
").
:- pragma foreign_proc("C#",
pretty_printer_is_initialised(Okay::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe, may_not_duplicate],
"
Okay = pretty_printer.isInitialised;
").
:- pragma foreign_proc("Java",
pretty_printer_is_initialised(Okay::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe, may_not_duplicate],
"
Okay = pretty_printer.isInitialised;
").
%---------------------%
% This predicate must not be called unless pretty_printer_is_initialised ==
% MR_TRUE, which occurs when set_default_formatter_map has been called at
% least once.
%
:- pred unsafe_get_default_formatter_map(formatter_map::out, io::di, io::uo)
is det.
:- pragma foreign_proc("C",
unsafe_get_default_formatter_map(FMap::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe],
"
FMap = ML_pretty_printer_default_formatter_map;
").
:- pragma foreign_proc("C#",
unsafe_get_default_formatter_map(FMap::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe, may_not_duplicate],
"
FMap = pretty_printer.defaultFormatterMap;
").
:- pragma foreign_proc("Java",
unsafe_get_default_formatter_map(FMap::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe, may_not_duplicate],
"
FMap = pretty_printer.defaultFormatterMap;
").
%---------------------%
:- pragma foreign_proc("C",
set_default_formatter_map(FMap::in, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury],
"
ML_pretty_printer_default_formatter_map = FMap;
ML_pretty_printer_is_initialised = MR_TRUE;
").
:- pragma foreign_proc("C#",
set_default_formatter_map(FMap::in, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, may_not_duplicate],
"
pretty_printer.isInitialised = mr_bool.YES;
pretty_printer.defaultFormatterMap = FMap;
").
:- pragma foreign_proc("Java",
set_default_formatter_map(FMap::in, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, may_not_duplicate],
"
pretty_printer.isInitialised = bool.YES;
pretty_printer.defaultFormatterMap = FMap;
").
%---------------------------------------------------------------------------%
% Construct the initial default formatter map. This function
% should be extended as more specialised formatters are added
% to the standard library modules.
%
:- func initial_formatter_map = formatter_map.
initial_formatter_map = !:Formatters :-
!:Formatters = new_formatter_map,
set_formatter("builtin", "character", 0, fmt_char, !Formatters),
set_formatter("builtin", "float", 0, fmt_float, !Formatters),
set_formatter("builtin", "int", 0, fmt_int, !Formatters),
set_formatter("builtin", "int8", 0, fmt_int8, !Formatters),
set_formatter("builtin", "int16", 0, fmt_int16, !Formatters),
set_formatter("builtin", "int32", 0, fmt_int32, !Formatters),
set_formatter("builtin", "int64", 0, fmt_int64, !Formatters),
set_formatter("builtin", "uint", 0, fmt_uint, !Formatters),
set_formatter("builtin", "uint8", 0, fmt_uint8, !Formatters),
set_formatter("builtin", "uint16", 0, fmt_uint16, !Formatters),
set_formatter("builtin", "uint32", 0, fmt_uint32, !Formatters),
set_formatter("builtin", "uint64", 0, fmt_uint64, !Formatters),
set_formatter("builtin", "string", 0, fmt_string, !Formatters),
set_formatter("array", "array", 1, fmt_array, !Formatters),
set_formatter("list", "list", 1, fmt_list, !Formatters),
set_formatter("one_or_more", "one_or_more",
1, fmt_one_or_more, !Formatters),
set_formatter("tree234", "tree234", 2, fmt_tree234, !Formatters),
set_formatter("version_array", "version_array",
1, fmt_version_array, !Formatters),
set_formatter("sparse_bitset", "sparse_bitset",
1, fmt_sparse_bitset, !Formatters),
set_formatter("fat_sparse_bitset", "fat_sparse_bitset",
1, fmt_fat_sparse_bitset, !Formatters),
set_formatter("fatter_sparse_bitset", "fatter_sparse_bitset",
1, fmt_fatter_sparse_bitset, !Formatters),
set_formatter("tree_bitset", "tree_bitset",
1, fmt_tree_bitset, !Formatters).
%---------------------%
:- func fmt_char(univ, list(type_desc)) = doc.
fmt_char(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.char_to_doc(X)
else
str("?char?")
).
:- func fmt_float(univ, list(type_desc)) = doc.
fmt_float(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.float_to_doc(X)
else
str("?float?")
).
:- func fmt_int(univ, list(type_desc)) = doc.
fmt_int(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.int_to_doc(X)
else
str("?int?")
).
:- func fmt_int8(univ, list(type_desc)) = doc.
fmt_int8(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.int8_to_doc(X)
else
str("?int8?")
).
:- func fmt_int16(univ, list(type_desc)) = doc.
fmt_int16(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.int16_to_doc(X)
else
str("?int16?")
).
:- func fmt_int32(univ, list(type_desc)) = doc.
fmt_int32(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.int32_to_doc(X)
else
str("?int32?")
).
:- func fmt_int64(univ, list(type_desc)) = doc.
fmt_int64(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.int64_to_doc(X)
else
str("?int64?")
).
:- func fmt_uint(univ, list(type_desc)) = doc.
fmt_uint(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.uint_to_doc(X)
else
str("?uint?")
).
:- func fmt_uint8(univ, list(type_desc)) = doc.
fmt_uint8(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.uint8_to_doc(X)
else
str("?uint8?")
).
:- func fmt_uint16(univ, list(type_desc)) = doc.
fmt_uint16(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.uint16_to_doc(X)
else
str("?uint16?")
).
:- func fmt_uint32(univ, list(type_desc)) = doc.
fmt_uint32(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.uint32_to_doc(X)
else
str("?uint32?")
).
:- func fmt_uint64(univ, list(type_desc)) = doc.
fmt_uint64(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.uint64_to_doc(X)
else
str("?uint64?")
).
:- func fmt_string(univ, list(type_desc)) = doc.
fmt_string(Univ, _ArgDescs) =
( if Univ = univ(X) then
pretty_printer.string_to_doc(X)
else
str("?string?")
).
:- func fmt_array(univ, list(type_desc)) = doc.
fmt_array(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : array(T))
then
pretty_printer.array_to_doc(X)
else
str("?array?")
).
:- func fmt_list(univ, list(type_desc)) = doc.
fmt_list(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : list(T))
then
pretty_printer.list_to_doc(X)
else
str("?list?")
).
:- func fmt_one_or_more(univ, list(type_desc)) = doc.
fmt_one_or_more(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : one_or_more(T))
then
pretty_printer.one_or_more_to_doc(X)
else
str("?one_or_more?")
).
:- func fmt_tree234(univ, list(type_desc)) = doc.
fmt_tree234(Univ, ArgDescs) =
( if
ArgDescs = [ArgDescA, ArgDescB],
has_type(_ArgA : K, ArgDescA),
has_type(_ArgB : V, ArgDescB),
Value = univ_value(Univ),
dynamic_cast(Value, X : tree234(K, V))
then
pretty_printer.tree234_to_doc(X)
else
str("?tree234?")
).
:- func fmt_version_array(univ, list(type_desc)) = doc.
fmt_version_array(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : version_array(T))
then
pretty_printer.version_array_to_doc(X)
else
str("?version_array?")
).
:- func fmt_sparse_bitset(univ, list(type_desc)) = doc.
fmt_sparse_bitset(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : sparse_bitset(uint))
then
pretty_printer.sparse_bitset_to_doc(X)
else
str("?sparse_bitset?")
).
:- func fmt_fat_sparse_bitset(univ, list(type_desc)) = doc.
fmt_fat_sparse_bitset(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : fat_sparse_bitset(uint))
then
pretty_printer.fat_sparse_bitset_to_doc(X)
else
str("?fat_sparse_bitset?")
).
:- func fmt_fatter_sparse_bitset(univ, list(type_desc)) = doc.
fmt_fatter_sparse_bitset(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : fatter_sparse_bitset(uint))
then
pretty_printer.fatter_sparse_bitset_to_doc(X)
else
str("?sparse_bitset?")
).
:- func fmt_tree_bitset(univ, list(type_desc)) = doc.
fmt_tree_bitset(Univ, ArgDescs) =
( if
ArgDescs = [ArgDesc],
has_type(_Arg : T, ArgDesc),
Value = univ_value(Univ),
dynamic_cast(Value, X : tree_bitset(uint))
then
pretty_printer.tree_bitset_to_doc(X)
else
str("?tree_bitset?")
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
% This is where we keep the display parameters (line width etc.).
% The formatter map is handled separately, because it *has* to be
% initialised immediately, i.e. before any other module's initialisation
% directive can update the default formatter map.
%
:- mutable(io_pp_params, pp_params, pp_params(78, 100, triangular(100)),
ground, [attach_to_io_state, untrailed]).
get_default_params(Params, !IO) :-
get_io_pp_params(Params, !IO).
set_default_params(Params, !IO) :-
set_io_pp_params(Params, !IO).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
char_to_doc(C) = str(term_io.quoted_char_to_string(C)).
string_to_doc(S) = str(term_io.quoted_string(S)).
float_to_doc(F) = str(string.float_to_string(F)).
int_to_doc(I) = str(string.int_to_string(I)).
int8_to_doc(I) = str(string.int8_to_string(I)).
int16_to_doc(I) = str(string.int16_to_string(I)).
int32_to_doc(I) = str(string.int32_to_string(I)).
int64_to_doc(I) = str(string.int64_to_string(I)).
uint_to_doc(U) = str(string.uint_to_string(U)).
uint8_to_doc(U) = str(string.uint8_to_string(U)).
uint16_to_doc(U) = str(string.uint16_to_string(U)).
uint32_to_doc(U) = str(string.uint32_to_string(U)).
uint64_to_doc(U) = str(string.uint64_to_string(U)).
%---------------------------------------------------------------------------%
% XXX The to_doc functions of the compound library types used to put
% different amounts of indentation at the outermost level.
%
% - list_to_doc added one space as indent
% - one_or_more_to_doc added one space as indent
% - tree234_to_doc added one standard indent (two spaces)
% - array_to_doc added one standard indent (two spaces)
% - version_array_to_doc added one standard indent (two spaces)
%
% We now leave any indentation around the doc returned by all these X_to_doc
% functions to their caller.
%---------------------------------------------------------------------------%
list_to_doc(Xs) = docs([str("["), list_to_doc_loop(Xs), str("]")]).
:- func list_to_doc_loop(list(T)) = doc.
list_to_doc_loop([]) = str("").
list_to_doc_loop([X | Xs]) = Doc :-
(
Xs = [],
Doc = format_arg(format(X))
;
Xs = [_ | _],
Doc = docs([
format_arg(format(X)),
group([str(", "), nl]),
format_susp((func) = list_to_doc_loop(Xs))
])
).
%---------------------------------------------------------------------------%
one_or_more_to_doc(one_or_more(H, T)) =
docs([
str("one_or_more("),
format_arg(format(H)),
group([str(", "), nl]),
str("["),
format_susp((func) = list_to_doc_loop(T)),
str("])")
]).
%---------------------------------------------------------------------------%
% With this type definition, including the use of the -> operator
% as the function symbol, the default pretty_printer formatting
% for key_value_pair will generate the output we want.
%
:- type key_value_pair(K, V)
---> (K -> V).
tree234_to_doc(T) =
docs([
str("map(["),
tree234_elements_to_doc(tree234_to_lazy_list(T, tll_nil)),
str("])")
]).
:- func tree234_elements_to_doc(tree234_lazy_list(K, V)) = doc.
tree234_elements_to_doc(tll_nil) = str("").
tree234_elements_to_doc(tll_lazy_cons(K, V, Susp)) = Doc :-
LL = apply(Susp),
(
LL = tll_nil,
Doc = group([nl, format_arg(format((K -> V)))])
;
LL = tll_lazy_cons(_, _, _),
Doc = docs([
group([nl, format_arg(format((K -> V))), str(", ")]),
format_susp((func) = tree234_elements_to_doc(LL))
])
).
%---------------------------------------------------------------------------%
array_to_doc(A) =
docs([str("array(["), array_to_doc_loop(A, 0), str("])")]).
:- func array_to_doc_loop(array(T), int) = doc.
array_to_doc_loop(A, I) = Doc :-
( if I > array.max(A) then
Doc = str("")
else
array.unsafe_lookup(A, I, Elem),
Doc = docs([
format_arg(format(Elem)),
( if I = array.max(A) then
str("")
else
group([str(", "), nl])
),
format_susp((func) = array_to_doc_loop(A, I + 1))
])
).
%---------------------------------------------------------------------------%
version_array_to_doc(A) =
docs([
str("version_array(["),
version_array_to_doc_loop(A, 0),
str("])")
]).
:- func version_array_to_doc_loop(version_array(T), int) = doc.
version_array_to_doc_loop(VA, I) = Doc :-
( if I > version_array.max(VA) then
Doc = str("")
else
version_array.lookup(VA, I, Elem),
Doc = docs([
format_arg(format(Elem)),
( if I = version_array.max(VA) then
str("")
else
group([str(", "), nl])
),
format_susp((func) = version_array_to_doc_loop(VA, I + 1))
])
).
%---------------------------------------------------------------------------%
%
% Convert a sparse_bitset, in any one of its forms, to a doc.
%
% Unlike all the other X_to_doc functions above, these are not exported.
% The reason for that is that as far as I (zs) know, the current RTTI
% system does not support taking a value of type sparse_bitset(T)
% and converting each element of that set back to a value of type T.
% That would requiring invoking the from_uint method of T's instance
% of the uenum type class, and we do not have the data structures
% we would need to find that method.
%
% The best we can do is to print the set elements as uints. (Our callers,
% fmt_sparse_bitset and its other versions do the required cast from e.g.
% sparse_bitset(T) to sparse_bitset(uint) for us.) This is not as good
% as printing the values of type T encoded by the uints, but it is *much*
% more understandable than e.g. a list of bitset_elems.
%
% Note that formatting a list of uints yields a string containing numbers
% *without* the "u" suffix, because string.uint_to_string does not add one.
%
:- func sparse_bitset_to_doc(sparse_bitset(uint)) = doc.
sparse_bitset_to_doc(Set) = Doc :-
sparse_bitset.to_sorted_list(Set, SortedList),
Doc = docs([
str("sparse_bitset("),
format(SortedList),
str(")")
]).
:- func fat_sparse_bitset_to_doc(fat_sparse_bitset(uint)) = doc.
fat_sparse_bitset_to_doc(Set) = Doc :-
fat_sparse_bitset.to_sorted_list(Set, SortedList),
Doc = docs([
str("fat_sparse_bitset("),
format(SortedList),
str(")")
]).
:- func fatter_sparse_bitset_to_doc(fatter_sparse_bitset(uint)) = doc.
fatter_sparse_bitset_to_doc(Set) = Doc :-
fatter_sparse_bitset.to_sorted_list(Set, SortedList),
Doc = docs([
str("fatter_sparse_bitset("),
format(SortedList),
str(")")
]).
:- func tree_bitset_to_doc(tree_bitset(uint)) = doc.
tree_bitset_to_doc(Set) = Doc :-
tree_bitset.to_sorted_list(Set, SortedList),
Doc = docs([
str("tree_bitset("),
format(SortedList),
str(")")
]).
%---------------------------------------------------------------------------%
:- end_module pretty_printer.
%---------------------------------------------------------------------------%