mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-16 01:43:35 +00:00
This fix uses the approach discussed on m-dev 2020 nov 16/17 for fixing github issue #72, whose core problem is a need for information flow back to a the caller from a callee when the callee fills in the argument of a function symbol whose representation is a direct_arg tag. In most cases when the callee fills in the value of an argument, the caller can see it because the argument is in a word on the heap, but when the function symbol uses a direct_arg tag, that is not the case. compiler/direct_arg_in_out.m: A new module that implements the transformation proposed on m-dev. It creates a fresh clone variable every time an argument of a direct_arg tag function symbol is (or may be) updated. This can happen several times if a type has more than one function symbol with a direct_arg tag. Since the affected variable can be bound to only one function symbol at the start, its argument can be filled in only once, but the compiler cannot know in advance what function symbol the variable contains, and therefore which of the possibly several fill-in sites (which fill in the arguments of different function symbols) executed in sequence will actually update the variable. The transformation ensures that once a variable is cloned, it is never referred to again. It also ensures that in a branched control structure (if-then-else, disjunction or switch), all branches will use the *same* variable to represent the latest version of each cloned variable at the end, so that following code has a consistent view regardless of through which branch execution has reached it. There are three situations that the transformation cannot and does not handle. 1. Situations in which the mode of an argument is either an inst variable, or an abstract inst. In either case, the pass cannot know whether it should apply its transformation to the argument. 2. Situations where a procedure that has such an argument is exported to C code as a function. In that case, the C signature of the function we would generate would be different from what the user would normally expect. We could modify the documentation of the export pragma, but I don't think there much point due to lack of demand. (The problem cannot arise when targeting any language other than C, because we use direct_arg tags only with the low level data representation, which we only use for C.) 3. Situations where a procedure that has such an argument is defined by foreign_proc. Again, dealing with the problem would require nontrivial changes to the documented interface between code in foreign_procs and the surrounding Mercury code, and I see no demand for code that could benefit from that. In these cases, this module generates error messages. compiler/transform_hlds.m: Include the new module in the transform_hlds package. Delete unnecessary module qualification on some existing inclusions. Put some existing inclusions into a more meaningful order. compiler/notes/compiler_design.html: Document the new pass. Fix some nearby prose. compiler/lambda.m: compiler/simplify_proc.m: Use a predicate exported by direct_arg_in_out.m to test, for each procedure, whether the procedure has any argument positions that are subject to the problem that direct_arg_in_out.m addresses. simplify_proc.m does this for all procedures it processes; lambda.m does this for all the procedures it creates from lambda expressions. Give a predicate in simplify_proc.m a better name. Sort a list of predicate names. compiler/hlds_module.m: Add a field to the module_info that simplify_proc.m and lambda.m can use to tell direct_arg_in_out.m what work (if any) it needs to do. compiler/mercury_compile_middle_passes.m: Invoke direct_arg_in_out.m if the new field in the HLDS indicates that it has some work to do. (In the vast majority of compiler invocations, it won't have any.) compiler/hlds_pred.m: The new code in direct_arg_in_out.m creates a clone of each procedure affected by the problem, before deleting the originals (to make sure that no references to the unfixed versions of now-fixed procedures remain.) Make it possible to create exact clones of both predicates and procedures by adding two pairs of predicates, {pred,proc}_prepare_to_clone and {pred,proc}_create. Add the direct_arg_in_out transformation as a possible source of transformed predicates. library/private_builtin.m: Add a new builtin operation, partial_inst_copy, that the new module generates calls to. configure.ac: Require the installed compiler to recognize partial_inst_copy as a no_type_info builtin. compiler/builtin_ops.m: Recognize the new builtin. (This was committed before the rest; the diff to private_builtin.m can be done only once the change to builtin_ops.m is part of the installed compiler.) compiler/options.m: Add a way to test whether the builtin_ops.m in the installed compiler recognizes the new builtin. compiler/dead_proc_elim.m: Do not delete the new primitive before direct_arg_in_out.m has had a chance to generate calls to it. Add an XXX. compiler/error_util.m: Recognize the new module as a source of error messages. compiler/pred_table.m: Add a pair of utility predicates to be used when looking up builtin predicates, for which the compiler writer knows that there should be exactly one match. These are used in direct_arg_in_out.m. compiler/simplify_goal_call.m: Replace some existing code with calls to the new predicates in pred_table.m. compiler/hlds_goal.m: Add modes to rename_vars_in_goal_expr that express the fact that when an atomic goal_expr has some variables renamed inside it, it does not suddenly become some *other* kind of goal_expr. New code in direct_arg_in_out.m relies on this. compiler/hlds_out_goal.m: When the HLDS we are dumping out is malformed because it contains calls to predicates that have been deleted, the compiler used to abort at such calls. (I ran into this while debugging direct_arg_in_out.m.) Fix this. When such calls are encountered, we now print out as much information we can about the call, and prefix the call with an unmistakable prefix to draw attention to the problem. compiler/inst_util.m: Fix a bug that prevented direct_arg_in_out.m from even being invoked on some test code for it. The bug was in code that we use to unify a headvar's initial inst with its final inst. When the initial inst was a non-ground bound_inst such as the ones used in tests/hard_coded/gh72.m, and the final inst was simply "ground", this code quite properly returned a bound_inst (which, unlike ground, can show the exact set of function symbols that the headvar could be bound to). The problem was that it reused the original bound_inst's test results, including the one that said the final inst is NOT ground, which of course is wrong for any inst unified with ground. Fix two instances of this bug. compiler/modes.m: Make some of the code I had to traverse to find the bug in inst_util.m easier to read and understand. Replace some uses of booleans with bespoke enum types. Change the argument lists of some predicates to put related arguments next to each other. Give some variables more descriptive names. compiler/layout_out.m: Conform to the change in hlds_pred.m. compiler/var_locn.m: Fix a code generation bug. When filling-in the value of the argument of a function symbol represented by a direct_arg tag, the code we generated for it worked only if the direct_arg tag used 0 as its ptag value. In the test cases we initially used for github issue 72, that was the case, but the new tests/hard_coded/gh72.m has direct_tag args that use other ptag values as well. Document the reason why the updated code works. compiler/term_constr_initial.m: Add the new primitive predicate added to private_builtin.m, partial_inst_copy, to a table of builtins that do not take type_infos, even though their signatures contain type variables. Fix a bunch of old bugs: most other such primitives were not listed either. mdbcomp/program_representation.m: Add partial_inst_copy to the master list of builtins that do not take type_infos even though their signatures contain type variables. (Done by an earlier commit.) Document the fact that any updates here require updates to term_constr_initial.m. library/multi_map.m: We have long had multi_map.add and multi_map.set as synonyms, but we only had multi_map.reverse_set. Add multi_map.reverse_add as a synonym for it. Define the "set" versions in terms of the "add" versions, instead of vice versa. NEWS: Document the new predicates in multi_map.m. tests/hard_coded/gh72a.m: Fix typo. tests/hard_coded/gh72.{m,exp}: A new, much more comprehensive test case than gh72a.m. This one tries to tickle github issue 72 in as many forms of code as I can think of. tests/invalid/gh72_errors.{m,err_exp}: A test case for testing the generation of error messages for two out of the three kinds of situations that direct_arg_in_out.m cannot handle. (Proposals for how to test the third category welcome.) tests/hard_coded/Mmakefile: tests/invalid/Mmakefile: Enable the two new test cases, as well as two old ones, gh72[ab].m, that previously we didn't pass. tests/invalid/Mercury.option: Do not compile gh72_error.m with --errorcheck-only, since its errors are reported by a pass that --errorcheck-only does not invoke.
2620 lines
98 KiB
Mathematica
2620 lines
98 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 1997-2012 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: error_util.m.
|
|
% Main author: zs.
|
|
%
|
|
% This module contains code that can be helpful in the formatting of
|
|
% error messages.
|
|
%
|
|
% Given a context, a starting indentation level and a list of words,
|
|
% print an error message that looks like this:
|
|
%
|
|
% module.m:10: first line of error message blah blah blah
|
|
% module.m:10: second line of error message blah blah blah
|
|
% module.m:10: third line of error message blah blah blah
|
|
%
|
|
% The words will be packed into lines as tightly as possible,
|
|
% with spaces between each pair of words, subject to the constraints
|
|
% that every line starts with a context, followed by Indent+1 spaces
|
|
% on the first line and Indent+3 spaces on later lines, and that every
|
|
% line contains at most <n> characters (unless a long single word
|
|
% forces the line over this limit) where --max-error-line-width <n>.
|
|
%
|
|
% The caller supplies the list of words to be printed in the form
|
|
% of a list of error message components. Each component may specify
|
|
% a string to printed exactly as it is, or it may specify a string
|
|
% containing a list of words, which may be broken at white space.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module parse_tree.error_util.
|
|
:- interface.
|
|
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module bool.
|
|
:- import_module io.
|
|
:- import_module list.
|
|
:- import_module maybe.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Every distinct problem should generate a single error specification.
|
|
% This specification should state
|
|
%
|
|
% - the severity of the problem (so that we can update the exit status
|
|
% of the compiler accordingly),
|
|
% - which phase of the compiler found the problem (since later phases
|
|
% may wish to suppress some problem reports if some specific earlier phases
|
|
% found problems, e.g. when a missing clause could be caused
|
|
% by a syntax error), and
|
|
% - a specification of what to print.
|
|
%
|
|
% In most cases, the "what to print" will be a single message for a single
|
|
% context. However, we may want to print messages for several contexts.
|
|
% For example, when reporting a duplicate declaration, we want to report
|
|
% this fact in the duplicate declaration's context, while printing another
|
|
% message giving the original declaration's context.
|
|
%
|
|
% simplest_spec(Id, Severity, Phase, Context, Pieces) is a shorthand for
|
|
% (and equivalent in every respect to) error_spec(Id, Severity, Phase,
|
|
% [simple_msg(Context, always(Pieces)])]).
|
|
%
|
|
% conditional_spec(Id, Option, MatchValue, Severity, Phase, Msgs) is intended
|
|
% to represent the error specification given by its last three fields
|
|
% *iff* Option has the value MatchValue. If Option is *not* MatchValue,
|
|
% it asks for nothing to be printed, and for the exit status to be left alone.
|
|
%
|
|
% The id field, which is present in all three alternatives, is totally
|
|
% ignored when printing error_specs. Its job is something completely different:
|
|
% helping developers track down where in the source code each error_spec
|
|
% was constructed. Without the id fields, if developers wants to know this,
|
|
% e.g. because they do not want the message printed, or because there is
|
|
% a problem with its wording, they have to grep for some words in the message.
|
|
% However, grepping for a single word will usually get many false hits,
|
|
% while grepping for two or more consecutive words in the message may miss
|
|
% the code generating the message, because in that code, some of those
|
|
% consecutive words are on different lines. On the other hand, if every
|
|
% place that constructs an error_spec, of any of these three varieties,
|
|
% fills in the id field with $pred, then finding the right place is easy:
|
|
% just specify the developer-only option --print-error-spec-id, and
|
|
% the identity of the predicate or function that generated each error_spec
|
|
% will be output just after the messages in that error_spec. Even if the
|
|
% predicate or function that this identifies has several pieces of code
|
|
% that construct specs, the scope in which you have to search for it
|
|
% will be easily manageable.
|
|
|
|
:- type error_spec
|
|
---> error_spec(
|
|
error_id :: string,
|
|
error_severity :: error_severity,
|
|
error_phase :: error_phase,
|
|
error_msgs :: list(error_msg)
|
|
)
|
|
; simplest_spec(
|
|
simp_id :: string,
|
|
simp_spec_severity :: error_severity,
|
|
simp_spec_phase :: error_phase,
|
|
simp_spec_context :: prog_context,
|
|
simp_spec_pieces :: list(format_component)
|
|
)
|
|
; conditional_spec(
|
|
cond_id :: string,
|
|
cond_spec_option :: option,
|
|
cond_spec_value :: bool,
|
|
|
|
cond_spec_severity :: error_severity,
|
|
cond_spec_phase :: error_phase,
|
|
cond_spec_msgs :: list(error_msg)
|
|
).
|
|
|
|
:- pred extract_spec_msgs(globals::in, error_spec::in,
|
|
list(error_msg)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% An error_spec that is *intended* to contain a warning,
|
|
%
|
|
:- type warning_spec == error_spec.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Many operations in the compiler may either succeed or fail.
|
|
% When they succeed, they return some result(s); when they don't,
|
|
% they return one or more errors.
|
|
|
|
:- type maybe_error_specs(T)
|
|
---> ok_no_spec(T)
|
|
; error_specs(error_spec, list(error_spec)).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type error_severity
|
|
---> severity_error
|
|
% Always set the exit status to indicate an error.
|
|
|
|
; severity_warning
|
|
% Only set the exit status to indicate an error if --halt-at-warn
|
|
% is enabled.
|
|
|
|
; severity_informational
|
|
% Don't set the exit status to indicate an error.
|
|
|
|
; severity_conditional(
|
|
% If the given boolean option has the given value, then the actual
|
|
% severity is given by the third argument; if it has the other
|
|
% value, then the actual severity is given by the fourth argument.
|
|
% If the fourth argument is `no', then the error_spec shouldn't
|
|
% actually print anything if cond_option doesn't have the value
|
|
% in cond_option_value.
|
|
|
|
cond_option :: option,
|
|
cond_option_value :: bool,
|
|
cond_if_match :: error_severity,
|
|
cond_if_no_match :: maybe(error_severity)
|
|
).
|
|
|
|
:- type actual_severity
|
|
---> actual_severity_error
|
|
; actual_severity_warning
|
|
; actual_severity_informational.
|
|
|
|
:- type mode_report_control
|
|
---> report_in_any_mode
|
|
; report_only_if_in_all_modes.
|
|
|
|
:- type error_phase
|
|
---> phase_options
|
|
; phase_read_files
|
|
; phase_module_name
|
|
; phase_term_to_parse_tree
|
|
; phase_type_repn
|
|
; phase_parse_tree_to_hlds
|
|
; phase_expand_types
|
|
; phase_type_check
|
|
; phase_inst_check
|
|
; phase_polymorphism
|
|
; phase_mode_check(mode_report_control)
|
|
; phase_purity_check
|
|
; phase_detism_check
|
|
; phase_fact_table_check
|
|
; phase_oisu_check
|
|
; phase_simplify(mode_report_control)
|
|
; phase_direct_arg_in_out
|
|
; phase_style
|
|
; phase_dead_code
|
|
; phase_termination_analysis
|
|
; phase_accumulator_intro
|
|
; phase_auto_parallelism
|
|
; phase_interface_gen
|
|
; phase_code_gen.
|
|
|
|
% An error message may have several components that may be printed under
|
|
% different circumstances. Some components are always printed; some are
|
|
% printed only if specific options have specific values. When an error
|
|
% specification is printed, we concatenate the list of all the
|
|
% format_components that should be printed. If this yields the empty list,
|
|
% we print nothing. Otherwise, we give them all to write_error_pieces to print
|
|
% out.
|
|
%
|
|
% When we print an error message in a list of error messages, we normally
|
|
% treat the first line of the first message differently than the rest:
|
|
% we separate it from the context by one space, whereas following lines
|
|
% are separated by three spaces. You can request that the first line of
|
|
% a message be treated as it were the first by setting the error_treat_as_first
|
|
% field to "treat_as_first". You can also request that the pieces in a message
|
|
% be given extra indentation by setting the error_extra_indent field
|
|
% to a nonzero value.
|
|
%
|
|
% The term simple_msg(Context, Components) is a shorthand for (and equivalent
|
|
% in every respect to) the term error_msg(yes(Context), do_not_treat_as_first,
|
|
% 0, Components).
|
|
%
|
|
% The term simplest_msg(Context, Pieces) is a shorthand for (and equivalent
|
|
% in every respect to) the term simple_msg(Context, [always(Pieces)]).
|
|
|
|
:- type maybe_treat_as_first
|
|
---> treat_as_first
|
|
; do_not_treat_as_first.
|
|
|
|
:- type error_msg
|
|
---> simplest_msg(
|
|
simplest_context :: prog_context,
|
|
simple_pieces :: list(format_component)
|
|
)
|
|
; simple_msg(
|
|
simple_context :: prog_context,
|
|
simple_components :: list(error_msg_component)
|
|
)
|
|
; error_msg(
|
|
error_context :: maybe(prog_context),
|
|
error_treat_as_first :: maybe_treat_as_first,
|
|
error_extra_indent :: int,
|
|
error_components :: list(error_msg_component)
|
|
).
|
|
|
|
:- type verbose_always_or_once
|
|
---> verbose_always
|
|
; verbose_once.
|
|
% Message components marked as verbose_once should be printed
|
|
% just once.
|
|
|
|
:- type error_msg_component
|
|
---> always(list(format_component))
|
|
% Print these components under all circumstances.
|
|
|
|
; option_is_set(option, bool, list(error_msg_component))
|
|
% Print the embedded components only if the specified boolean
|
|
% option has the specified value.
|
|
|
|
; verbose_only(verbose_always_or_once, list(format_component))
|
|
% Print these components only if --verbose-errors is specified.
|
|
% If it is not specified, set the flag that triggers the printing
|
|
% of the message reminding the user about --verbose-errors.
|
|
% In addition, if the first field is verbose_once, then disable
|
|
% all but the first printing of the message even if
|
|
% --verbose-errors is specified.
|
|
|
|
; verbose_and_nonverbose(list(format_component),
|
|
list(format_component))
|
|
% If --verbose-errors is specified, print the first set of
|
|
% components. If it is not specified, print the second set,
|
|
% and set the flag that triggers the printing of the message
|
|
% reminding the user about --verbose-errors. The verbose part
|
|
% is implicitly verbose_always.
|
|
|
|
; some [T] ( print_anything(T) => print_anything(T) ).
|
|
% This alternative allows the caller to specify an arbitrary thing
|
|
% to be printed at any point in the sequence. Since things printed
|
|
% this way aren't formatted as error messages should be (context
|
|
% at start etc), this capability is intended only for messages
|
|
% that help debug the compiler itself.
|
|
|
|
:- typeclass print_anything(T) where [
|
|
pred print_anything(T::in, io::di, io::uo) is det
|
|
].
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type maybe_add_quotes
|
|
---> do_not_add_quotes
|
|
; add_quotes.
|
|
|
|
% type_to_pieces(MaybeAddQuotes, TVarSet, InstVarSet, ExternalTypeParams,
|
|
% Type) = Pieces:
|
|
%
|
|
% Format Type for printing as part of an error message. Use TVarSet
|
|
% as the source of the names of any type variables in the type, and
|
|
% InstVarSet as the source of the names of any inst variables in any
|
|
% higher order types. Put an existential quantifier in front of any type
|
|
% that contains any of the type variables in ExternalTypeParams.
|
|
%
|
|
:- func type_to_pieces(maybe_add_quotes, tvarset, inst_varset, list(tvar),
|
|
mer_type) = list(format_component).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Return the worst of two actual severities.
|
|
%
|
|
:- func worst_severity(actual_severity, actual_severity)
|
|
= actual_severity.
|
|
|
|
% Compute the actual severity of a message with the given severity
|
|
% (if it actually prints anything).
|
|
%
|
|
:- func actual_error_severity(globals, error_severity)
|
|
= maybe(actual_severity).
|
|
|
|
% Compute the actual severity of an error_spec
|
|
% (if it actually prints anything).
|
|
%
|
|
:- func actual_spec_severity(globals, error_spec) = maybe(actual_severity).
|
|
|
|
% Compute the worst actual severity (if any) occurring in a list of
|
|
% error_specs.
|
|
%
|
|
:- func worst_severity_in_specs(globals, list(error_spec))
|
|
= maybe(actual_severity).
|
|
|
|
% Return `yes' if the given list contains error_specs whose actual severity
|
|
% is actual_severity_error.
|
|
%
|
|
:- func contains_errors(globals, list(error_spec)) = bool.
|
|
|
|
% Return `yes' if the given list contains error_specs whose actual severity
|
|
% is actual_severity_error or actual_severity_warning.
|
|
%
|
|
:- func contains_errors_and_or_warnings(globals, list(error_spec)) = bool.
|
|
|
|
% If --halt-at-warn is not set, then return `yes' if the given list
|
|
% contains error_specs whose actual severity is actual_severity_error.
|
|
%
|
|
% If --halt-at-warn is set, then return `yes' if the given list
|
|
% contains error_specs whose actual severity is either
|
|
% actual_severity_error or actual_severity_warning.
|
|
%
|
|
:- func contains_errors_or_warnings_treated_as_errors(globals,
|
|
list(error_spec)) = bool.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred sort_error_msgs(list(error_msg)::in, list(error_msg)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Delete all the given error_specs, which are supposed to have been
|
|
% gathered during the process that generates the contents of an interface
|
|
% file, if two conditions are both satisfied:
|
|
%
|
|
% - print_errors_warnings_when_generating_interface is NOT set, and
|
|
% - the exit status has NOT been set to indicate an error.
|
|
%
|
|
:- pred filter_interface_generation_specs(globals::in,
|
|
list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% The error_spec_accumulator type can be used to accumulate errors for
|
|
% multiple modes of a predicate. accumulate_error_specs_for_proc will
|
|
% eliminate warnings that should only be reported if they occur in every mode,
|
|
% but don't occur in every mode.
|
|
|
|
:- type error_spec_accumulator.
|
|
|
|
:- func init_error_spec_accumulator = error_spec_accumulator.
|
|
|
|
:- pred accumulate_error_specs_for_proc(list(error_spec)::in,
|
|
error_spec_accumulator::in, error_spec_accumulator::out) is det.
|
|
|
|
:- func error_spec_accumulator_to_list(error_spec_accumulator) =
|
|
list(error_spec).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred pre_hlds_maybe_write_out_errors(bool::in, globals::in,
|
|
list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det.
|
|
:- pred pre_hlds_maybe_write_out_errors(io.text_output_stream::in,
|
|
bool::in, globals::in,
|
|
list(error_spec)::in, list(error_spec)::out, io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% write_error_spec_ignore(Globals, Spec, !IO):
|
|
% write_error_spec_ignore(Stream, Globals, Spec, !IO):
|
|
% write_error_specs_ignore(Globals, Specs !IO):
|
|
% write_error_specs_ignore(Stream, Globals, Specs !IO):
|
|
% write_error_spec(Globals, Spec, !NumWarnings, !NumErrors, !IO):
|
|
% write_error_spec(Stream, Globals, Spec, !NumWarnings, !NumErrors, !IO):
|
|
% write_error_specs(Globals, Specs !NumWarnings, !NumErrors, !IO):
|
|
% write_error_specs(Stream, Globals, Specs !NumWarnings, !NumErrors, !IO):
|
|
%
|
|
% Write out the error message(s) specified by Spec or Specs, minus the
|
|
% parts whose conditions are false. In the non-ignore versions,
|
|
% increment !NumWarnings by the number of printed warnings and
|
|
% !NumErrors by the number of printed errors.
|
|
%
|
|
% Set the exit status to 1 if we found any errors, or if we found any
|
|
% warnings and --halt-at-warn is set. If some error specs have verbose
|
|
% components but they aren't being printed out, set the flag for reminding
|
|
% the user about --verbose-errors.
|
|
%
|
|
% Look up option values in the supplied Globals.
|
|
%
|
|
% If an error spec contains only conditional messages and those conditions
|
|
% are all false, then nothing will be printed out and the exit status
|
|
% will not be changed. This will happen even if the severity means
|
|
% that something should have been printed out.
|
|
%
|
|
% Calls to the ignore versions could be considered to implicitly have
|
|
% an XXX next to them, for not incrementing the num_errors field
|
|
% in the module_info if the (ignored) number of errors printed
|
|
% is nonzero. However, we have got along fine for a long time ignoring
|
|
% the number of warnings and errors at most calls to write out specs,
|
|
% since we do set the exit status to a nonzero value when we print an
|
|
% error, and the main use of the num_errors field is to do likewise.
|
|
%
|
|
:- pred write_error_spec_ignore(globals::in, error_spec::in,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_spec_ignore(io.text_output_stream::in, globals::in,
|
|
error_spec::in, io::di, io::uo) is det.
|
|
:- pred write_error_specs_ignore(globals::in, list(error_spec)::in,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_specs_ignore(io.text_output_stream::in, globals::in,
|
|
list(error_spec)::in, io::di, io::uo) is det.
|
|
|
|
:- pred write_error_spec(globals::in,
|
|
error_spec::in, int::in, int::out, int::in, int::out,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_spec(io.text_output_stream::in, globals::in,
|
|
error_spec::in, int::in, int::out, int::in, int::out,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_specs(globals::in,
|
|
list(error_spec)::in, int::in, int::out, int::in, int::out,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_specs(io.text_output_stream::in, globals::in,
|
|
list(error_spec)::in, int::in, int::out, int::in, int::out,
|
|
io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type format_component
|
|
---> invis_order_default_start(int)
|
|
% Prints nothing. If the compiler generates two different specs
|
|
% for the same context that we intend to appear in a specific
|
|
% order, even though it may not be the order that sorting those
|
|
% specs would normally give, we can add one of these to the
|
|
% start of each error_spec, with the order of the numbers
|
|
% inside these invis orders controlling the final order
|
|
% of the error_specs.
|
|
%
|
|
% This component sorts before other components that do not
|
|
% specify such an ordinal number. The invis_order_default_end
|
|
% component sorts after them. By choosing to use one or the other,
|
|
% users of this type can control sorting with respect to
|
|
% error messages generated in places in the code they do not
|
|
% control.
|
|
|
|
; fixed(string)
|
|
% This string should appear in the output in one piece, as it is.
|
|
|
|
; quote(string)
|
|
% Surround the string with `' quotes, then treat as fixed.
|
|
|
|
; int_fixed(int)
|
|
% Convert the integer to a string, then treat as fixed.
|
|
|
|
; nth_fixed(int)
|
|
% Convert the integer to a string, such as "first", "second",
|
|
% "third", ... 49th, and then treat as fixed.
|
|
|
|
; lower_case_next_if_not_first
|
|
% If this is the first component, ignore it. If this is not
|
|
% the first component, lower case the initial letter of the
|
|
% next component. There is no effect if the next component
|
|
% does not exist or does not start with an upper case letter.
|
|
|
|
; prefix(string)
|
|
% This string should appear in the output in one piece, as it is,
|
|
% inserted directly before the next format_component, without
|
|
% any intervening space.
|
|
|
|
; suffix(string)
|
|
% This string should appear in the output in one piece, as it is,
|
|
% appended directly after the previous format_component, without
|
|
% any intervening space.
|
|
|
|
; words(string)
|
|
% This string contains words separated by white space. The words
|
|
% should appear in the output in the given order, but the white
|
|
% space may be rearranged and line breaks may be inserted.
|
|
|
|
; words_quote(string)
|
|
% Surround the string with `' quotes, then treat as words.
|
|
|
|
; qual_sym_name(sym_name)
|
|
; unqual_sym_name(sym_name)
|
|
% The output should contain the string form of the sym_name,
|
|
% surrounded by `' quotes.
|
|
|
|
; qual_sym_name_arity(sym_name_arity)
|
|
; unqual_sym_name_arity(sym_name_arity)
|
|
% The output should contain the string form of the sym_name,
|
|
% surrounded by `' quotes, followed by '/' and the arity.
|
|
|
|
; qual_pf_sym_name_orig_arity(pf_sym_name_arity)
|
|
; unqual_pf_sym_name_orig_arity(pf_sym_name_arity)
|
|
% The output should contain the string form of the sym_name,
|
|
% surrounded by `' quotes, followed by '/' and the arity, but
|
|
% - precede them with either "predicate" or "function", and
|
|
% - for functions, use their *original* arity, which does not
|
|
% count the function result.
|
|
|
|
; qual_type_ctor(type_ctor)
|
|
; unqual_type_ctor(type_ctor)
|
|
% The output should contain the string form of the type_ctor,
|
|
% surrounded by `' quotes, followed by '/' and the arity.
|
|
|
|
; qual_class_id(class_id)
|
|
; unqual_class_id(class_id)
|
|
% The output should contain the string form of the class_id,
|
|
% surrounded by `' quotes, followed by '/' and the arity.
|
|
|
|
; qual_cons_id_and_maybe_arity(cons_id)
|
|
; unqual_cons_id_and_maybe_arity(cons_id)
|
|
% If the cons_id is a cons_id for a builtin type, strip the
|
|
% builtin qualifier (or all qualifier) from it, and output
|
|
% the result. If the cons_id is for a du type, output its name
|
|
% in quotes, followed by '/' and its arity.
|
|
|
|
; qual_top_ctor_of_type(mer_type)
|
|
% The top level type constructor of the given type,
|
|
% which must have one (i.e. must not be a variable).
|
|
|
|
; p_or_f(pred_or_func)
|
|
% Output the string "predicate" or "function" as appropriate.
|
|
|
|
; decl(string)
|
|
% Prefix the string with ":- ", surround it with single quotes,
|
|
% and then treat as fixed.
|
|
|
|
; pragma_decl(string)
|
|
% As above, but prefix the string with ":- pragma ".
|
|
|
|
; nl
|
|
% Insert a line break if there has been text output since
|
|
% the last line break.
|
|
|
|
; nl_indent_delta(int)
|
|
% Act as nl, but also add the given integer (which should be a
|
|
% small positive or negative integer) to the current indent level.
|
|
|
|
; blank_line
|
|
% Create a blank line.
|
|
|
|
; invis_order_default_end(int).
|
|
% See the documentation of invis_order_default_start above.
|
|
|
|
% Wrap words() around a string.
|
|
%
|
|
:- func string_to_words_piece(string) = format_component.
|
|
|
|
% Convert a list of strings into a list of format_components
|
|
% separated by commas, with the last two elements separated by `and'.
|
|
%
|
|
:- func list_to_pieces(list(string)) = list(format_component).
|
|
|
|
% Convert a list of strings into a list of format_components
|
|
% separated by commas. Even the last pair of strings will be
|
|
% separated by commas.
|
|
%
|
|
:- func strict_list_to_pieces(list(string)) = list(format_component).
|
|
|
|
% As list_to_pieces, but surround each string by `' quotes.
|
|
%
|
|
:- func list_to_quoted_pieces(list(string)) = list(format_component).
|
|
|
|
% As above, but with the last two elements separated by `or'.
|
|
%
|
|
:- func list_to_quoted_pieces_or(list(string)) = list(format_component).
|
|
|
|
% Convert a list of lists of format_components into a list of
|
|
% format_components separated by commas, with the last two elements
|
|
% separated by the first argument as a word.
|
|
%
|
|
:- func component_lists_to_pieces(string, list(list(format_component))) =
|
|
list(format_component).
|
|
|
|
% Convert a list of lists of format_components into a list of
|
|
% format_components separated by commas. Even the last pair of lists
|
|
% will be separated by commas.
|
|
%
|
|
:- func strict_component_lists_to_pieces(list(list(format_component))) =
|
|
list(format_component).
|
|
|
|
% Convert a list of format_components into a list of format_components
|
|
% separated by commas, with the last two elements separated
|
|
% by the first argument as a word.
|
|
%
|
|
:- func component_list_to_pieces(string, list(format_component)) =
|
|
list(format_component).
|
|
|
|
% Convert a list of format_components into a list of format_components
|
|
% separated by commas. Even the last pair of list elements will be
|
|
% separated by commas.
|
|
%
|
|
:- func strict_component_list_to_pieces(list(format_component)) =
|
|
list(format_component).
|
|
|
|
% component_list_to_line_pieces(Lines, Final):
|
|
%
|
|
% Convert Lines, a list of lines (each given by a list of format_components
|
|
% *without* a final nl) into a condensed list of format_components
|
|
% in which adjacent lines are separated by commas and newlines.
|
|
% What goes between the last line and the newline ending is not
|
|
% a comma but the value of Final.
|
|
%
|
|
:- func component_list_to_line_pieces(list(list(format_component)),
|
|
list(format_component)) = list(format_component).
|
|
|
|
% choose_number(List, Singular, Plural) = Form
|
|
%
|
|
% Choose between a singular version and a plural version of something,
|
|
% based on the length of a list. Chooses the plural if the list is empty.
|
|
%
|
|
:- func choose_number(list(T), U, U) = U.
|
|
|
|
% is_or_are(List) throws an exception if the list is empty, returns "is"
|
|
% if the list is a singleton, and otherwise returns "are".
|
|
%
|
|
:- func is_or_are(list(T)) = string.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% XXX The predicates below should not be called in new code. New code should
|
|
% create error specifications, and then call write_error_spec to print them.
|
|
|
|
% Display the given error message, without a context and with standard
|
|
% indentation.
|
|
%
|
|
:- pred write_error_pieces_plain(globals::in, list(format_component)::in,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_pieces_plain(io.text_output_stream::in, globals::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
|
|
% write_error_plain_with_progname(ProgName, Msg):
|
|
%
|
|
% Display Msg as the error string, with ProgName as a context
|
|
% and with standard indentation.
|
|
%
|
|
:- pred write_error_plain_with_progname(string::in, string::in,
|
|
io::di, io::uo) is det.
|
|
:- pred write_error_plain_with_progname(io.text_output_stream::in,
|
|
string::in, string::in, io::di, io::uo) is det.
|
|
|
|
% write_error_pieces(Globals, Context, Indent, Components):
|
|
%
|
|
% Display `Components' as the error message, with `Context' as a context
|
|
% and indent by `Indent'.
|
|
%
|
|
:- pred write_error_pieces(globals::in, prog_context::in, int::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
:- pred write_error_pieces(io.text_output_stream::in, globals::in,
|
|
prog_context::in, int::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
|
|
:- pred write_error_pieces_maybe_with_context(globals::in,
|
|
maybe(prog_context)::in, int::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
:- pred write_error_pieces_maybe_with_context(io.text_output_stream::in,
|
|
globals::in, maybe(prog_context)::in, int::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
|
|
:- func error_pieces_to_string(list(format_component)) = string.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- func describe_sym_name(sym_name) = string.
|
|
|
|
:- func describe_sym_name_arity(sym_name_arity) = string.
|
|
|
|
% Put `' quotes around the given string.
|
|
%
|
|
:- func add_quotes(string) = string.
|
|
|
|
% Report a warning, and set the exit status to error if the
|
|
% --halt-at-warn option is set.
|
|
%
|
|
:- pred report_warning(globals::in,
|
|
prog_context::in, int::in, list(format_component)::in,
|
|
io::di, io::uo) is det.
|
|
:- pred report_warning(io.text_output_stream::in, globals::in,
|
|
prog_context::in, int::in, list(format_component)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Report why the file is not able to be opened to stderr_stream,
|
|
% and set the exit status to 1.
|
|
%
|
|
:- pred unable_to_open_file(string::in, io.error::in, io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module libs.compiler_util.
|
|
:- import_module parse_tree.parse_tree_out_info.
|
|
:- import_module parse_tree.parse_tree_out_term.
|
|
:- import_module parse_tree.parse_tree_to_term.
|
|
:- import_module parse_tree.prog_out.
|
|
:- import_module parse_tree.prog_type.
|
|
:- import_module parse_tree.prog_util.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module char.
|
|
:- import_module cord.
|
|
:- import_module int.
|
|
:- import_module map.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
:- import_module term.
|
|
:- import_module varset.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
extract_spec_msgs(Globals, Spec, Msgs) :-
|
|
(
|
|
Spec = error_spec(_Id, _Severity, _Phase, Msgs)
|
|
;
|
|
Spec = simplest_spec(_Id, _Severity, _Phase, Context, Pieces),
|
|
Msgs = [simplest_msg(Context, Pieces)]
|
|
;
|
|
Spec = conditional_spec(_Id, Option, MatchValue, _Severity, _Phase,
|
|
Msgs0),
|
|
globals.lookup_bool_option(Globals, Option, Value),
|
|
( if Value = MatchValue then
|
|
Msgs = Msgs0
|
|
else
|
|
Msgs = []
|
|
)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
type_to_pieces(MaybeAddQuotes, TVarSet, InstVarSet, ExternalTypeParams,
|
|
Type0) = Pieces :-
|
|
strip_builtin_qualifiers_from_type(Type0, Type),
|
|
(
|
|
MaybeAddQuotes = do_not_add_quotes,
|
|
StartQuotePieces = [],
|
|
EndQuotePieces = []
|
|
;
|
|
MaybeAddQuotes = add_quotes,
|
|
StartQuotePieces = [error_util.prefix("`")],
|
|
EndQuotePieces = [error_util.suffix("'")]
|
|
),
|
|
% For most types, we convert the type back to a term, and print that.
|
|
% This is done by the final else part of this if-then-else.
|
|
% This is ok for most types, which tend to be small.
|
|
( if
|
|
% We handle higher order types differently, because when the
|
|
% actual and expected types differ in e.g. arity, having each
|
|
% argument type on its own line helps readability a *lot*.
|
|
Type = higher_order_type(PorF, ArgTypes, HOInstInfo, Purity,
|
|
_LambdaEvalMethod)
|
|
then
|
|
( Purity = purity_pure, PurityPieces = []
|
|
; Purity = purity_semipure, PurityPieces = [words("semipure")]
|
|
; Purity = purity_impure, PurityPieces = [words("impure")]
|
|
),
|
|
( PorF = pf_predicate, PorFPieces = [words("pred")]
|
|
; PorF = pf_function, PorFPieces = [words("func")]
|
|
),
|
|
(
|
|
HOInstInfo = none_or_default_func,
|
|
ArgPieces = list.map(
|
|
type_to_pieces(do_not_add_quotes, TVarSet, InstVarSet,
|
|
ExternalTypeParams),
|
|
ArgTypes),
|
|
FuncResultPrefixPieces = [],
|
|
FuncResultSuffixPieces = [],
|
|
DetismPieces = [],
|
|
PorFMismatchPieces = [],
|
|
ArityMismatchPieces = []
|
|
;
|
|
HOInstInfo = higher_order(PredInstInfo),
|
|
PorFStr = pred_or_func_to_full_str(PorF),
|
|
PredInstInfo = pred_inst_info(HOPorF, ArgModes, _ArgRegs, Detism),
|
|
( if PorF = HOPorF then
|
|
PorFMismatchPieces = []
|
|
else
|
|
HOPorFStr = pred_or_func_to_full_str(HOPorF),
|
|
PorFMismatchPieces = [nl,
|
|
words("The type says this is a"),
|
|
words(PorFStr), error_util.suffix(","),
|
|
words("but its mode says it is a"),
|
|
words(HOPorFStr), error_util.suffix(".")]
|
|
),
|
|
list.length(ArgTypes, NumArgTypes),
|
|
list.length(ArgModes, NumArgModes),
|
|
( if NumArgTypes = NumArgModes then
|
|
assoc_list.from_corresponding_lists(ArgTypes, ArgModes,
|
|
ArgTypesModes),
|
|
% If this higher order type is a function, then the type::mode
|
|
% for the function result must be wrapped in parentheses.
|
|
FuncResultPrefixPieces = [error_util.prefix("(")],
|
|
FuncResultSuffixPieces = [error_util.suffix(")")],
|
|
ArgPieces = list.map(
|
|
type_and_mode_to_pieces(TVarSet, InstVarSet,
|
|
ExternalTypeParams),
|
|
ArgTypesModes),
|
|
ArityMismatchPieces = []
|
|
else
|
|
ArgPieces = list.map(
|
|
type_to_pieces(do_not_add_quotes, TVarSet, InstVarSet,
|
|
ExternalTypeParams),
|
|
ArgTypes),
|
|
FuncResultPrefixPieces = [],
|
|
FuncResultSuffixPieces = [],
|
|
ArityMismatchPieces = [nl,
|
|
words("The type says this"), words(PorFStr),
|
|
words("has"), int_fixed(NumArgTypes), words("arguments,"),
|
|
words("but its mode says it has"),
|
|
int_fixed(NumArgModes), error_util.suffix(".")]
|
|
),
|
|
DetismPieces = [words("is"), words(determinism_to_string(Detism))]
|
|
),
|
|
(
|
|
PorF = pf_predicate,
|
|
(
|
|
ArgPieces = [],
|
|
ArgBlockPieces = []
|
|
;
|
|
ArgPieces = [_ | _],
|
|
ArgBlockPieces =
|
|
[error_util.suffix("("), nl_indent_delta(1)] ++
|
|
component_list_to_line_pieces(ArgPieces, []) ++
|
|
[nl_indent_delta(-1), fixed(")")]
|
|
)
|
|
;
|
|
PorF = pf_function,
|
|
(
|
|
ArgPieces = [],
|
|
unexpected($pred, "function has no arguments")
|
|
;
|
|
ArgPieces = [ReturnValuePieces],
|
|
ArgBlockPieces = [fixed("=")] ++
|
|
FuncResultPrefixPieces ++ ReturnValuePieces ++
|
|
FuncResultSuffixPieces
|
|
;
|
|
ArgPieces = [_, _ | _],
|
|
list.det_split_last(ArgPieces, FuncArgPieces,
|
|
ReturnValuePieces),
|
|
ArgBlockPieces = [suffix("("), nl_indent_delta(1)] ++
|
|
component_list_to_line_pieces(FuncArgPieces, []) ++
|
|
[nl_indent_delta(-1), fixed(") =")] ++
|
|
FuncResultPrefixPieces ++ ReturnValuePieces ++
|
|
FuncResultSuffixPieces
|
|
)
|
|
),
|
|
Pieces = StartQuotePieces ++
|
|
PurityPieces ++ PorFPieces ++ ArgBlockPieces ++ DetismPieces ++
|
|
EndQuotePieces ++
|
|
PorFMismatchPieces ++ ArityMismatchPieces
|
|
else
|
|
unparse_type(Type, Term0),
|
|
list.map(term.coerce_var, ExternalTypeParams, ExistQVars),
|
|
maybe_add_existential_quantifier(ExistQVars, Term0, Term),
|
|
varset.coerce(TVarSet, VarSet),
|
|
TermPiece =
|
|
words(mercury_term_to_string(VarSet, print_name_only, Term)),
|
|
Pieces = StartQuotePieces ++ [TermPiece] ++ EndQuotePieces
|
|
).
|
|
|
|
:- func type_and_mode_to_pieces(tvarset, inst_varset, list(tvar),
|
|
pair(mer_type, mer_mode)) = list(format_component).
|
|
|
|
type_and_mode_to_pieces(TVarSet, InstVarSet, ExternalTypeParams,
|
|
Type - Mode) = Pieces :-
|
|
TypePieces = type_to_pieces(do_not_add_quotes, TVarSet, InstVarSet,
|
|
ExternalTypeParams, Type),
|
|
ModeTerm0 = mode_to_term(output_mercury, Mode),
|
|
term.coerce(ModeTerm0, ModeTerm),
|
|
ModePiece =
|
|
words(mercury_term_to_string(InstVarSet, print_name_only, ModeTerm)),
|
|
Pieces = TypePieces ++ [fixed("::"), ModePiece].
|
|
|
|
% Check if any of the variables in the term are existentially quantified
|
|
% (occur in the first argument), and if so, add the appropriate
|
|
% quantification to the term. Otherwise, return the term unchanged.
|
|
%
|
|
:- pred maybe_add_existential_quantifier(list(var)::in, term::in, term::out)
|
|
is det.
|
|
|
|
maybe_add_existential_quantifier(ExternalTypeParams, !Term) :-
|
|
term.vars(!.Term, Vars),
|
|
ExistQVars = set.to_sorted_list(set.intersect(
|
|
set.list_to_set(ExternalTypeParams), set.list_to_set(Vars))),
|
|
(
|
|
ExistQVars = []
|
|
;
|
|
ExistQVars = [_ | _],
|
|
QTerm = make_list_term(ExistQVars),
|
|
!:Term = term.functor(term.atom("some"), [QTerm, !.Term],
|
|
term.context_init)
|
|
).
|
|
|
|
:- func make_list_term(list(var)) = term.
|
|
|
|
make_list_term([]) =
|
|
term.functor(term.atom("[]"), [], term.context_init).
|
|
make_list_term([Var | Vars]) =
|
|
term.functor(term.atom("[|]"),
|
|
[term.variable(Var, context_init), make_list_term(Vars)],
|
|
term.context_init).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
worst_severity(actual_severity_error, actual_severity_error) =
|
|
actual_severity_error.
|
|
worst_severity(actual_severity_error, actual_severity_warning) =
|
|
actual_severity_error.
|
|
worst_severity(actual_severity_error, actual_severity_informational) =
|
|
actual_severity_error.
|
|
worst_severity(actual_severity_warning, actual_severity_error) =
|
|
actual_severity_error.
|
|
worst_severity(actual_severity_warning, actual_severity_warning) =
|
|
actual_severity_warning.
|
|
worst_severity(actual_severity_warning, actual_severity_informational) =
|
|
actual_severity_warning.
|
|
worst_severity(actual_severity_informational, actual_severity_error) =
|
|
actual_severity_error.
|
|
worst_severity(actual_severity_informational, actual_severity_warning) =
|
|
actual_severity_warning.
|
|
worst_severity(actual_severity_informational, actual_severity_informational) =
|
|
actual_severity_informational.
|
|
|
|
actual_error_severity(Globals, Severity) = MaybeActual :-
|
|
(
|
|
Severity = severity_error,
|
|
MaybeActual = yes(actual_severity_error)
|
|
;
|
|
Severity = severity_warning,
|
|
MaybeActual = yes(actual_severity_warning)
|
|
;
|
|
Severity = severity_informational,
|
|
MaybeActual = yes(actual_severity_informational)
|
|
;
|
|
Severity = severity_conditional(Option, MatchValue,
|
|
Match, MaybeNoMatch),
|
|
globals.lookup_bool_option(Globals, Option, Value),
|
|
( if Value = MatchValue then
|
|
MaybeActual = actual_error_severity(Globals, Match)
|
|
else
|
|
(
|
|
MaybeNoMatch = no,
|
|
MaybeActual = no
|
|
;
|
|
MaybeNoMatch = yes(NoMatch),
|
|
MaybeActual = actual_error_severity(Globals, NoMatch)
|
|
)
|
|
)
|
|
).
|
|
|
|
actual_spec_severity(Globals, Spec) = MaybeSeverity :-
|
|
(
|
|
( Spec = error_spec(_, Severity, _, _)
|
|
; Spec = simplest_spec(_, Severity, _, _, _)
|
|
),
|
|
MaybeSeverity = actual_error_severity(Globals, Severity)
|
|
;
|
|
Spec = conditional_spec(_, Option, MatchValue, Severity, _, _),
|
|
globals.lookup_bool_option(Globals, Option, OptionValue),
|
|
( if OptionValue = MatchValue then
|
|
MaybeSeverity = actual_error_severity(Globals, Severity)
|
|
else
|
|
MaybeSeverity = no
|
|
)
|
|
).
|
|
|
|
worst_severity_in_specs(Globals, Specs) = MaybeWorst :-
|
|
worst_severity_in_specs_2(Globals, Specs, no, MaybeWorst).
|
|
|
|
:- pred worst_severity_in_specs_2(globals::in, list(error_spec)::in,
|
|
maybe(actual_severity)::in, maybe(actual_severity)::out) is det.
|
|
|
|
worst_severity_in_specs_2(_Globals, [], !MaybeWorst).
|
|
worst_severity_in_specs_2(Globals, [Spec | Specs], !MaybeWorst) :-
|
|
MaybeThis = actual_spec_severity(Globals, Spec),
|
|
(
|
|
!.MaybeWorst = no,
|
|
!:MaybeWorst = MaybeThis
|
|
;
|
|
!.MaybeWorst = yes(Worst),
|
|
(
|
|
MaybeThis = no
|
|
;
|
|
MaybeThis = yes(This),
|
|
!:MaybeWorst = yes(worst_severity(Worst, This))
|
|
)
|
|
),
|
|
worst_severity_in_specs_2(Globals, Specs, !MaybeWorst).
|
|
|
|
contains_errors(Globals, Specs) = Errors :-
|
|
MaybeWorstActual = worst_severity_in_specs(Globals, Specs),
|
|
(
|
|
MaybeWorstActual = no,
|
|
Errors = no
|
|
;
|
|
MaybeWorstActual = yes(WorstActual),
|
|
(
|
|
WorstActual = actual_severity_error,
|
|
Errors = yes
|
|
;
|
|
( WorstActual = actual_severity_warning
|
|
; WorstActual = actual_severity_informational
|
|
),
|
|
Errors = no
|
|
)
|
|
).
|
|
|
|
contains_errors_and_or_warnings(Globals, Specs) = ErrorsOrWarnings :-
|
|
MaybeWorstActual = worst_severity_in_specs(Globals, Specs),
|
|
(
|
|
MaybeWorstActual = no,
|
|
ErrorsOrWarnings = no
|
|
;
|
|
MaybeWorstActual = yes(WorstActual),
|
|
(
|
|
( WorstActual = actual_severity_error
|
|
; WorstActual = actual_severity_warning
|
|
),
|
|
ErrorsOrWarnings = yes
|
|
;
|
|
WorstActual = actual_severity_informational,
|
|
ErrorsOrWarnings = no
|
|
)
|
|
).
|
|
|
|
contains_errors_or_warnings_treated_as_errors(Globals, Specs) = Halt :-
|
|
MaybeWorstActual = worst_severity_in_specs(Globals, Specs),
|
|
(
|
|
MaybeWorstActual = no,
|
|
Halt = no
|
|
;
|
|
MaybeWorstActual = yes(WorstActual),
|
|
(
|
|
WorstActual = actual_severity_error,
|
|
Halt = yes
|
|
;
|
|
WorstActual = actual_severity_warning,
|
|
globals.lookup_bool_option(Globals, halt_at_warn, HaltAtWarn),
|
|
(
|
|
HaltAtWarn = yes,
|
|
Halt = yes
|
|
;
|
|
HaltAtWarn = no,
|
|
Halt = no
|
|
)
|
|
;
|
|
WorstActual = actual_severity_informational,
|
|
Halt = no
|
|
)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred sort_error_specs(globals::in,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
sort_error_specs(Globals, !Specs) :-
|
|
% The purpose of remove_conditionals_in_spec is to remove differences
|
|
% between error_specs that exist only in the structure of the error_specs
|
|
% themselves, as opposed to the text that we output for them.
|
|
%
|
|
% For example, the parser can generate two error specs for a bad module
|
|
% name that differ in two things.
|
|
%
|
|
% - The first difference is that one has "severity_error", while the other
|
|
% has "severity_conditional(warn_wrong_module_name, yes, severity_error,
|
|
% no)". However, since warn_wrong_module_name is yes by default,
|
|
% this difference has no effect.
|
|
%
|
|
% - The second difference is that some error_msg_components consist of
|
|
% "always(...)" in one, and "option_is_set(warn_wrong_module_name, yes,
|
|
% always(...))" in the other. But if warn_wrong_module_name is yes,
|
|
% this difference has no effect either.
|
|
%
|
|
% (The parser should no longer generate duplicate error messages
|
|
% for bad module names, but we still keep this workaround in place,
|
|
% since the cost of doing so is trivial.)
|
|
%
|
|
list.filter_map(remove_conditionals_in_spec(Globals), !Specs),
|
|
list.sort_and_remove_dups(compare_error_specs(Globals), !Specs).
|
|
|
|
:- pred remove_conditionals_in_spec(globals::in,
|
|
error_spec::in, error_spec::out) is semidet.
|
|
|
|
remove_conditionals_in_spec(Globals, Spec0, Spec) :-
|
|
require_det (
|
|
(
|
|
Spec0 = error_spec(Id, Severity0, Phase, Msgs0),
|
|
MaybeActualSeverity = actual_error_severity(Globals, Severity0),
|
|
list.filter_map(remove_conditionals_in_msg(Globals), Msgs0, Msgs)
|
|
;
|
|
Spec0 = simplest_spec(Id, Severity0, Phase, Context0, Pieces0),
|
|
MaybeActualSeverity = actual_error_severity(Globals, Severity0),
|
|
Msgs = [simplest_msg(Context0, Pieces0)]
|
|
;
|
|
Spec0 = conditional_spec(Id, Option, MatchValue,
|
|
Severity0, Phase, Msgs0),
|
|
globals.lookup_bool_option(Globals, Option, OptionValue),
|
|
( if OptionValue = MatchValue then
|
|
MaybeActualSeverity =
|
|
actual_error_severity(Globals, Severity0),
|
|
Msgs = Msgs0
|
|
else
|
|
MaybeActualSeverity = no,
|
|
Msgs = []
|
|
)
|
|
)
|
|
),
|
|
( if
|
|
MaybeActualSeverity = yes(ActualSeverity),
|
|
Msgs = [_ | _]
|
|
then
|
|
require_det (
|
|
(
|
|
ActualSeverity = actual_severity_error,
|
|
Severity = severity_error
|
|
;
|
|
ActualSeverity = actual_severity_warning,
|
|
Severity = severity_warning
|
|
;
|
|
ActualSeverity = actual_severity_informational,
|
|
Severity = severity_informational
|
|
),
|
|
Spec = error_spec(Id, Severity, Phase, Msgs)
|
|
)
|
|
else
|
|
% Spec0 would result in nothing being printed.
|
|
fail
|
|
).
|
|
|
|
:- pred remove_conditionals_in_msg(globals::in,
|
|
error_msg::in, error_msg::out) is semidet.
|
|
|
|
remove_conditionals_in_msg(Globals, Msg0, Msg) :-
|
|
require_det (
|
|
(
|
|
Msg0 = simplest_msg(Context, Pieces0),
|
|
Components0 = [always(Pieces0)],
|
|
MaybeContext = yes(Context),
|
|
TreatAsFirst = do_not_treat_as_first,
|
|
ExtraIndent = 0
|
|
;
|
|
Msg0 = simple_msg(Context, Components0),
|
|
MaybeContext = yes(Context),
|
|
TreatAsFirst = do_not_treat_as_first,
|
|
ExtraIndent = 0
|
|
;
|
|
Msg0 = error_msg(MaybeContext, TreatAsFirst, ExtraIndent,
|
|
Components0)
|
|
),
|
|
list.foldl(remove_conditionals_in_msg_component(Globals),
|
|
Components0, cord.init, ComponentCord),
|
|
Components = cord.list(ComponentCord),
|
|
Msg = error_msg(MaybeContext, TreatAsFirst, ExtraIndent, Components)
|
|
),
|
|
% Don't include the Msg if Components is empty.
|
|
Components = [_ | _].
|
|
|
|
:- pred remove_conditionals_in_msg_component(globals::in,
|
|
error_msg_component::in,
|
|
cord(error_msg_component)::in, cord(error_msg_component)::out) is det.
|
|
|
|
remove_conditionals_in_msg_component(Globals, Component, !ComponentCord) :-
|
|
(
|
|
Component = option_is_set(Option, MatchValue, EmbeddedComponents),
|
|
% We could recurse down into EmbeddedComponents, but we currently
|
|
% have any places in the compiler that can generate two error messages
|
|
% that differ only in nested option settings, so there would be
|
|
% no point.
|
|
globals.lookup_bool_option(Globals, Option, OptionValue),
|
|
( if OptionValue = MatchValue then
|
|
!:ComponentCord =
|
|
!.ComponentCord ++ cord.from_list(EmbeddedComponents)
|
|
else
|
|
true
|
|
)
|
|
;
|
|
% We don't want to eliminate the verbose only part of a message
|
|
% even if verbose_errors isn't set. We want to keep them around
|
|
% until we print the component, so that we can record the presence
|
|
% of such verbose components, and generate a reminder of their
|
|
% existence at the end of the compilation.
|
|
%
|
|
% Besides, the compiler can't (yet) generate two error_msg_components
|
|
% that differ only in the presence of a verbose error.
|
|
( Component = always(_)
|
|
; Component = verbose_only(_, _)
|
|
; Component = verbose_and_nonverbose(_, _)
|
|
; Component = print_anything(_)
|
|
),
|
|
!:ComponentCord = cord.snoc(!.ComponentCord, Component)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred compare_error_specs(globals::in, error_spec::in, error_spec::in,
|
|
comparison_result::out) is det.
|
|
|
|
compare_error_specs(Globals, SpecA, SpecB, Result) :-
|
|
extract_spec_msgs(Globals, SpecA, MsgsA),
|
|
extract_spec_msgs(Globals, SpecB, MsgsB),
|
|
compare_error_msg_lists(MsgsA, MsgsB, MsgsResult),
|
|
(
|
|
MsgsResult = (=),
|
|
compare(Result, SpecA, SpecB)
|
|
;
|
|
( MsgsResult = (>)
|
|
; MsgsResult = (<)
|
|
),
|
|
Result = MsgsResult
|
|
).
|
|
|
|
:- pred compare_error_msg_lists(list(error_msg)::in, list(error_msg)::in,
|
|
comparison_result::out) is det.
|
|
|
|
compare_error_msg_lists(MsgsA, MsgsB, Result) :-
|
|
(
|
|
MsgsA = [],
|
|
MsgsB = [],
|
|
Result = (=)
|
|
;
|
|
MsgsA = [],
|
|
MsgsB = [_ | _],
|
|
Result = (<)
|
|
;
|
|
MsgsA = [_ | _],
|
|
MsgsB = [],
|
|
Result = (>)
|
|
;
|
|
MsgsA = [HeadMsgA | TailMsgsA],
|
|
MsgsB = [HeadMsgB | TailMsgsB],
|
|
compare_error_msgs(HeadMsgA, HeadMsgB, HeadResult),
|
|
(
|
|
HeadResult = (=),
|
|
compare_error_msg_lists(TailMsgsA, TailMsgsB, Result)
|
|
;
|
|
( HeadResult = (>)
|
|
; HeadResult = (<)
|
|
),
|
|
Result = HeadResult
|
|
)
|
|
).
|
|
|
|
sort_error_msgs(Msgs0, Msgs) :-
|
|
list.sort_and_remove_dups(compare_error_msgs, Msgs0, Msgs).
|
|
|
|
:- pred compare_error_msgs(error_msg::in, error_msg::in,
|
|
comparison_result::out) is det.
|
|
|
|
compare_error_msgs(MsgA, MsgB, Result) :-
|
|
MaybeContextA = project_msg_context(MsgA),
|
|
MaybeContextB = project_msg_context(MsgB),
|
|
compare(ContextResult, MaybeContextA, MaybeContextB),
|
|
(
|
|
ContextResult = (=),
|
|
ComponentsA = project_msg_components(MsgA),
|
|
ComponentsB = project_msg_components(MsgB),
|
|
compare(ComponentsResult, ComponentsA, ComponentsB),
|
|
(
|
|
ComponentsResult = (=),
|
|
compare(Result, MsgA, MsgB)
|
|
;
|
|
( ComponentsResult = (>)
|
|
; ComponentsResult = (<)
|
|
),
|
|
Result = ComponentsResult
|
|
)
|
|
;
|
|
( ContextResult = (>)
|
|
; ContextResult = (<)
|
|
),
|
|
Result = ContextResult
|
|
).
|
|
|
|
:- func project_msg_context(error_msg) = maybe(prog_context).
|
|
|
|
project_msg_context(Msg) = MaybeContext :-
|
|
(
|
|
Msg = simplest_msg(Context, _),
|
|
MaybeContext = yes(Context)
|
|
;
|
|
Msg = simple_msg(Context, _),
|
|
MaybeContext = yes(Context)
|
|
;
|
|
Msg = error_msg(yes(Context), _, _, _),
|
|
MaybeContext = yes(Context)
|
|
;
|
|
Msg = error_msg(no, _, _, __),
|
|
MaybeContext = no
|
|
).
|
|
|
|
:- func project_msg_components(error_msg) = list(error_msg_component).
|
|
|
|
project_msg_components(Msg) = Components :-
|
|
(
|
|
Msg = simplest_msg(_, Pieces),
|
|
Components = [always(Pieces)]
|
|
;
|
|
Msg = simple_msg(_, Components)
|
|
;
|
|
Msg = error_msg(_, _, _, Components)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
filter_interface_generation_specs(Globals, Specs, SpecsToPrint, !IO) :-
|
|
globals.lookup_bool_option(Globals,
|
|
print_errors_warnings_when_generating_interface, PrintErrors),
|
|
(
|
|
PrintErrors = yes,
|
|
SpecsToPrint = Specs
|
|
;
|
|
PrintErrors = no,
|
|
io.get_exit_status(Status, !IO),
|
|
( if Status = 0 then
|
|
SpecsToPrint = []
|
|
else
|
|
SpecsToPrint = Specs
|
|
)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type error_spec_accumulator == maybe(pair(set(error_spec))).
|
|
|
|
init_error_spec_accumulator = no.
|
|
|
|
accumulate_error_specs_for_proc(ProcSpecs, !MaybeSpecs) :-
|
|
list.filter(
|
|
( pred(Spec::in) is semidet :-
|
|
Phase = project_spec_phase(Spec),
|
|
ModeReportControl = get_maybe_mode_report_control(Phase),
|
|
ModeReportControl = yes(report_only_if_in_all_modes)
|
|
), ProcSpecs, ProcAllModeSpecs, ProcAnyModeSpecs),
|
|
ProcAnyModeSpecSet = set.list_to_set(ProcAnyModeSpecs),
|
|
ProcAllModeSpecSet = set.list_to_set(ProcAllModeSpecs),
|
|
(
|
|
!.MaybeSpecs = yes(AnyModeSpecSet0 - AllModeSpecSet0),
|
|
set.union(AnyModeSpecSet0, ProcAnyModeSpecSet, AnyModeSpecSet),
|
|
set.intersect(AllModeSpecSet0, ProcAllModeSpecSet, AllModeSpecSet),
|
|
!:MaybeSpecs = yes(AnyModeSpecSet - AllModeSpecSet)
|
|
;
|
|
!.MaybeSpecs = no,
|
|
!:MaybeSpecs = yes(ProcAnyModeSpecSet - ProcAllModeSpecSet)
|
|
).
|
|
|
|
:- func project_spec_phase(error_spec) = error_phase.
|
|
|
|
project_spec_phase(Spec) = Phase :-
|
|
(
|
|
Spec = error_spec(_, _, Phase, _)
|
|
;
|
|
Spec = simplest_spec(_, _, Phase, _, _)
|
|
;
|
|
Spec = conditional_spec(_, _, _, _, Phase, _)
|
|
).
|
|
|
|
error_spec_accumulator_to_list(no) = [].
|
|
error_spec_accumulator_to_list(yes(AnyModeSpecSet - AllModeSpecSet)) =
|
|
set.to_sorted_list(set.union(AnyModeSpecSet, AllModeSpecSet)).
|
|
|
|
:- func get_maybe_mode_report_control(error_phase) =
|
|
maybe(mode_report_control).
|
|
|
|
get_maybe_mode_report_control(phase_options) = no.
|
|
get_maybe_mode_report_control(phase_read_files) = no.
|
|
get_maybe_mode_report_control(phase_module_name) = no.
|
|
get_maybe_mode_report_control(phase_term_to_parse_tree) = no.
|
|
get_maybe_mode_report_control(phase_type_repn) = no.
|
|
get_maybe_mode_report_control(phase_parse_tree_to_hlds) = no.
|
|
get_maybe_mode_report_control(phase_expand_types) = no.
|
|
get_maybe_mode_report_control(phase_type_check) = no.
|
|
get_maybe_mode_report_control(phase_inst_check) = no.
|
|
get_maybe_mode_report_control(phase_polymorphism) = no.
|
|
get_maybe_mode_report_control(phase_mode_check(Control)) = yes(Control).
|
|
get_maybe_mode_report_control(phase_purity_check) = no.
|
|
get_maybe_mode_report_control(phase_detism_check) = no.
|
|
get_maybe_mode_report_control(phase_fact_table_check) = no.
|
|
get_maybe_mode_report_control(phase_oisu_check) = no.
|
|
get_maybe_mode_report_control(phase_simplify(Control)) = yes(Control).
|
|
get_maybe_mode_report_control(phase_direct_arg_in_out) = no.
|
|
get_maybe_mode_report_control(phase_style) = no.
|
|
get_maybe_mode_report_control(phase_dead_code) = no.
|
|
get_maybe_mode_report_control(phase_termination_analysis) = no.
|
|
get_maybe_mode_report_control(phase_accumulator_intro) = no.
|
|
get_maybe_mode_report_control(phase_auto_parallelism) = no.
|
|
get_maybe_mode_report_control(phase_interface_gen) = no.
|
|
get_maybe_mode_report_control(phase_code_gen) = no.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
pre_hlds_maybe_write_out_errors(Verbose, Globals, !Specs, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
pre_hlds_maybe_write_out_errors(Stream, Verbose, Globals, !Specs, !IO).
|
|
|
|
pre_hlds_maybe_write_out_errors(Stream, Verbose, Globals, !Specs, !IO) :-
|
|
% maybe_write_out_errors in hlds_error_util.m is a HLDS version
|
|
% of this predicate. The documentation is in that file.
|
|
(
|
|
Verbose = no
|
|
;
|
|
Verbose = yes,
|
|
write_error_specs_ignore(Stream, Globals, !.Specs, !IO),
|
|
!:Specs = []
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
write_error_spec_ignore(Globals, Spec, !IO) :-
|
|
write_error_spec(Globals, Spec, 0, _, 0, _, !IO).
|
|
|
|
write_error_spec_ignore(Stream, Globals, Spec, !IO) :-
|
|
write_error_spec(Stream, Globals, Spec, 0, _, 0, _, !IO).
|
|
|
|
write_error_specs_ignore(Globals, Specs, !IO) :-
|
|
write_error_specs(Globals, Specs, 0, _, 0, _, !IO).
|
|
|
|
write_error_specs_ignore(Stream, Globals, Specs, !IO) :-
|
|
write_error_specs(Stream, Globals, Specs, 0, _, 0, _, !IO).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% We keep a record of the set of already-printed verbose_once components
|
|
% only during the invocation of a single call to write_error_specs, or
|
|
% its singular version write_error_spec.
|
|
%
|
|
% We could possibly keep this set in a mutable, but there is no need for that.
|
|
% All error messages are generated in only one place, which means that
|
|
% they are generated in one pass. For pretty much all our passes,
|
|
% all the error messages generated by the pass are printed by a single
|
|
% call to write_error_specs. This means that while in theory, it is possible
|
|
% for verbose_once message to be printed by each of several invocations
|
|
% of write_error_specs, in practice it won't happen.
|
|
|
|
write_error_spec(Globals, Spec, !NumWarnings, !NumErrors, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
write_error_spec(Stream, Globals, Spec, !NumWarnings, !NumErrors, !IO).
|
|
|
|
write_error_spec(Stream, Globals, Spec, !NumWarnings, !NumErrors, !IO) :-
|
|
do_write_error_spec(Stream, Globals, Spec, !NumWarnings, !NumErrors,
|
|
set.init, _, !IO).
|
|
|
|
%---------------------%
|
|
|
|
write_error_specs(Globals, Specs0, !NumWarnings, !NumErrors, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
write_error_specs(Stream, Globals, Specs0, !NumWarnings, !NumErrors, !IO).
|
|
|
|
write_error_specs(Stream, Globals, Specs0, !NumWarnings, !NumErrors, !IO) :-
|
|
sort_error_specs(Globals, Specs0, Specs),
|
|
list.foldl4(do_write_error_spec(Stream, Globals), Specs,
|
|
!NumWarnings, !NumErrors, set.init, _, !IO).
|
|
|
|
%---------------------%
|
|
|
|
:- pred do_write_error_spec(io.text_output_stream::in, globals::in,
|
|
error_spec::in, int::in, int::out, int::in, int::out,
|
|
already_printed_verbose::in, already_printed_verbose::out,
|
|
io::di, io::uo) is det.
|
|
|
|
do_write_error_spec(Stream, Globals, Spec, !NumWarnings, !NumErrors,
|
|
!AlreadyPrintedVerbose, !IO) :-
|
|
(
|
|
Spec = error_spec(Id, Severity, _Phase, Msgs1),
|
|
MaybeActual = actual_error_severity(Globals, Severity)
|
|
;
|
|
Spec = simplest_spec(Id, Severity, _Phase, Context, Pieces),
|
|
MaybeActual = actual_error_severity(Globals, Severity),
|
|
Msgs1 = [simplest_msg(Context, Pieces)]
|
|
;
|
|
Spec = conditional_spec(Id, Option, MatchValue,
|
|
Severity, _Phase, Msgs0),
|
|
globals.lookup_bool_option(Globals, Option, Value),
|
|
( if Value = MatchValue then
|
|
MaybeActual = actual_error_severity(Globals, Severity),
|
|
Msgs1 = Msgs0
|
|
else
|
|
MaybeActual = no,
|
|
Msgs1 = []
|
|
)
|
|
),
|
|
globals.lookup_bool_option(Globals, print_error_spec_id, PrintId),
|
|
(
|
|
PrintId = no,
|
|
Msgs = Msgs1
|
|
;
|
|
PrintId = yes,
|
|
(
|
|
Msgs1 = [],
|
|
% Don't add a pred id message to an empty list of messages,
|
|
% since there is nothing to identify.
|
|
Msgs = Msgs1
|
|
;
|
|
Msgs1 = [HeadMsg | _],
|
|
(
|
|
( HeadMsg = simplest_msg(HeadContext, _Pieces)
|
|
; HeadMsg = simple_msg(HeadContext, _)
|
|
),
|
|
MaybeHeadContext = yes(HeadContext)
|
|
;
|
|
HeadMsg = error_msg(MaybeHeadContext, _, _, _)
|
|
),
|
|
IdMsg = error_msg(MaybeHeadContext, do_not_treat_as_first, 0,
|
|
[always([words("error_spec id:"), fixed(Id), nl])]),
|
|
Msgs = Msgs1 ++ [IdMsg]
|
|
)
|
|
),
|
|
do_write_error_msgs(Stream, Msgs, Globals, treat_as_first,
|
|
have_not_printed_anything, PrintedSome, !AlreadyPrintedVerbose, !IO),
|
|
(
|
|
PrintedSome = have_not_printed_anything
|
|
% XXX The following assertion is commented out because the compiler
|
|
% can generate error specs that consist only of conditional error
|
|
% messages whose conditions can all be false (in which case nothing
|
|
% will be printed). Such specs will cause the assertion to fail if
|
|
% they have a severity that means something *should* have been
|
|
% printed out. Error specs like this are generated by --debug-modes.
|
|
% expect(unify(MaybeActual, no), $pred, "MaybeActual isn't no")
|
|
;
|
|
PrintedSome = printed_something,
|
|
(
|
|
MaybeActual = yes(Actual),
|
|
(
|
|
Actual = actual_severity_error,
|
|
!:NumErrors = !.NumErrors + 1,
|
|
io.set_exit_status(1, !IO)
|
|
;
|
|
Actual = actual_severity_warning,
|
|
!:NumWarnings = !.NumWarnings + 1,
|
|
record_warning(Globals, !IO)
|
|
;
|
|
Actual = actual_severity_informational
|
|
)
|
|
;
|
|
MaybeActual = no,
|
|
unexpected($pred, "printed_something but MaybeActual = no")
|
|
)
|
|
).
|
|
|
|
:- type maybe_printed_something
|
|
---> printed_something
|
|
; have_not_printed_anything.
|
|
|
|
:- type maybe_lower_next_initial
|
|
---> lower_next_initial
|
|
; do_not_lower_next_initial.
|
|
|
|
:- type already_printed_verbose == set(list(format_component)).
|
|
|
|
:- pred do_write_error_msgs(io.text_output_stream::in,
|
|
list(error_msg)::in, globals::in, maybe_treat_as_first::in,
|
|
maybe_printed_something::in, maybe_printed_something::out,
|
|
already_printed_verbose::in, already_printed_verbose::out,
|
|
io::di, io::uo) is det.
|
|
|
|
do_write_error_msgs(_Stream, [], _Globals, _First, !PrintedSome,
|
|
!AlreadyPrintedVerbose, !IO).
|
|
do_write_error_msgs(Stream, [Msg | Msgs], Globals, !.First, !PrintedSome,
|
|
!AlreadyPrintedVerbose, !IO) :-
|
|
(
|
|
Msg = simplest_msg(SimpleContext, Pieces),
|
|
Components = [always(Pieces)],
|
|
MaybeContext = yes(SimpleContext),
|
|
TreatAsFirst = do_not_treat_as_first,
|
|
ExtraIndentLevel = 0
|
|
;
|
|
Msg = simple_msg(SimpleContext, Components),
|
|
MaybeContext = yes(SimpleContext),
|
|
TreatAsFirst = do_not_treat_as_first,
|
|
ExtraIndentLevel = 0
|
|
;
|
|
Msg = error_msg(MaybeContext, TreatAsFirst, ExtraIndentLevel,
|
|
Components)
|
|
),
|
|
(
|
|
TreatAsFirst = treat_as_first,
|
|
!:First = treat_as_first
|
|
;
|
|
TreatAsFirst = do_not_treat_as_first
|
|
% Leave !:First as it is, even if it is treat_as_first.
|
|
),
|
|
Indent = ExtraIndentLevel * indent_increment,
|
|
write_msg_components(Stream, Components, MaybeContext, Indent, Globals,
|
|
!First, !PrintedSome, !AlreadyPrintedVerbose, !IO),
|
|
do_write_error_msgs(Stream, Msgs, Globals, !.First, !PrintedSome,
|
|
!AlreadyPrintedVerbose, !IO).
|
|
|
|
:- pred write_msg_components(io.text_output_stream::in,
|
|
list(error_msg_component)::in, maybe(prog_context)::in,
|
|
int::in, globals::in, maybe_treat_as_first::in, maybe_treat_as_first::out,
|
|
maybe_printed_something::in, maybe_printed_something::out,
|
|
already_printed_verbose::in, already_printed_verbose::out,
|
|
io::di, io::uo) is det.
|
|
|
|
write_msg_components(_Stream, [], _, _, _, !First, !PrintedSome,
|
|
!AlreadyPrintedVerbose, !IO).
|
|
write_msg_components(Stream, [Component | Components], MaybeContext, Indent,
|
|
Globals, !First, !PrintedSome, !AlreadyPrintedVerbose, !IO) :-
|
|
(
|
|
Component = always(ComponentPieces),
|
|
do_write_error_pieces(Stream, !.First, MaybeContext, Indent, Globals,
|
|
ComponentPieces, !IO),
|
|
!:First = do_not_treat_as_first,
|
|
!:PrintedSome = printed_something
|
|
;
|
|
Component = option_is_set(Option, MatchValue, EmbeddedComponents),
|
|
globals.lookup_bool_option(Globals, Option, OptionValue),
|
|
( if OptionValue = MatchValue then
|
|
write_msg_components(Stream, EmbeddedComponents, MaybeContext,
|
|
Indent, Globals, !First, !PrintedSome,
|
|
!AlreadyPrintedVerbose, !IO)
|
|
else
|
|
true
|
|
)
|
|
;
|
|
Component = verbose_only(AlwaysOrOnce, ComponentPieces),
|
|
globals.lookup_bool_option(Globals, verbose_errors, VerboseErrors),
|
|
(
|
|
VerboseErrors = yes,
|
|
(
|
|
AlwaysOrOnce = verbose_always,
|
|
do_write_error_pieces(Stream, !.First, MaybeContext,
|
|
Indent, Globals, ComponentPieces, !IO),
|
|
!:First = do_not_treat_as_first,
|
|
!:PrintedSome = printed_something
|
|
;
|
|
AlwaysOrOnce = verbose_once,
|
|
( if
|
|
set.contains(!.AlreadyPrintedVerbose, ComponentPieces)
|
|
then
|
|
true
|
|
else
|
|
do_write_error_pieces(Stream, !.First, MaybeContext,
|
|
Indent, Globals, ComponentPieces, !IO),
|
|
!:First = do_not_treat_as_first,
|
|
!:PrintedSome = printed_something,
|
|
set.insert(ComponentPieces, !AlreadyPrintedVerbose)
|
|
)
|
|
)
|
|
;
|
|
VerboseErrors = no,
|
|
globals.io_set_extra_error_info(some_extra_error_info, !IO)
|
|
)
|
|
;
|
|
Component = verbose_and_nonverbose(VerbosePieces, NonVerbosePieces),
|
|
globals.lookup_bool_option(Globals, verbose_errors, VerboseErrors),
|
|
(
|
|
VerboseErrors = yes,
|
|
do_write_error_pieces(Stream, !.First, MaybeContext,
|
|
Indent, Globals, VerbosePieces, !IO)
|
|
;
|
|
VerboseErrors = no,
|
|
do_write_error_pieces(Stream, !.First, MaybeContext,
|
|
Indent, Globals, NonVerbosePieces, !IO),
|
|
globals.io_set_extra_error_info(some_extra_error_info, !IO)
|
|
),
|
|
!:First = do_not_treat_as_first,
|
|
!:PrintedSome = printed_something
|
|
;
|
|
Component = print_anything(Anything),
|
|
print_anything(Anything, !IO),
|
|
!:First = do_not_treat_as_first,
|
|
!:PrintedSome = printed_something
|
|
),
|
|
write_msg_components(Stream, Components, MaybeContext, Indent, Globals,
|
|
!First, !PrintedSome, !AlreadyPrintedVerbose, !IO).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type maybe_first_in_msg
|
|
---> first_in_msg
|
|
; not_first_in_msg.
|
|
|
|
string_to_words_piece(Str) = words(Str).
|
|
|
|
list_to_pieces([]) = [].
|
|
list_to_pieces([Elem]) = [words(Elem)].
|
|
list_to_pieces([Elem1, Elem2]) = [fixed(Elem1), words("and"), fixed(Elem2)].
|
|
list_to_pieces([Elem1, Elem2, Elem3 | Elems]) =
|
|
[fixed(Elem1 ++ ",") | list_to_pieces([Elem2, Elem3 | Elems])].
|
|
|
|
strict_list_to_pieces([]) = [].
|
|
strict_list_to_pieces([Elem]) = [words(Elem)].
|
|
strict_list_to_pieces([Elem1, Elem2 | Elems]) =
|
|
[fixed(Elem1 ++ ",") | strict_list_to_pieces([Elem2 | Elems])].
|
|
|
|
list_to_quoted_pieces([]) = [].
|
|
list_to_quoted_pieces([Elem]) = [quote(Elem)].
|
|
list_to_quoted_pieces([Elem1, Elem2]) =
|
|
[quote(Elem1), words("and"), quote(Elem2)].
|
|
list_to_quoted_pieces([Elem1, Elem2, Elem3 | Elems]) =
|
|
[quote(Elem1), suffix(",") |
|
|
list_to_quoted_pieces([Elem2, Elem3 | Elems])].
|
|
|
|
list_to_quoted_pieces_or([]) = [].
|
|
list_to_quoted_pieces_or([Elem]) = [quote(Elem)].
|
|
list_to_quoted_pieces_or([Elem1, Elem2]) =
|
|
[quote(Elem1), words("or"), quote(Elem2)].
|
|
list_to_quoted_pieces_or([Elem1, Elem2, Elem3 | Elems]) =
|
|
[quote(Elem1), suffix(",") |
|
|
list_to_quoted_pieces_or([Elem2, Elem3 | Elems])].
|
|
|
|
component_lists_to_pieces(_, []) = [].
|
|
component_lists_to_pieces(_, [Comps]) = Comps.
|
|
component_lists_to_pieces(LastSep, [Comps1, Comps2]) =
|
|
Comps1 ++ [words(LastSep)] ++ Comps2.
|
|
component_lists_to_pieces(LastSep, [Comps1, Comps2, Comps3 | Comps]) =
|
|
Comps1 ++ [suffix(",")]
|
|
++ component_lists_to_pieces(LastSep, [Comps2, Comps3 | Comps]).
|
|
|
|
strict_component_lists_to_pieces([]) = [].
|
|
strict_component_lists_to_pieces([Comps]) = Comps.
|
|
strict_component_lists_to_pieces([Comps1, Comps2 | Comps]) =
|
|
Comps1 ++ [suffix(",")]
|
|
++ strict_component_lists_to_pieces([Comps2 | Comps]).
|
|
|
|
component_list_to_pieces(_, []) = [].
|
|
component_list_to_pieces(_, [Comp]) = [Comp].
|
|
component_list_to_pieces(LastSep, [Comp1, Comp2]) =
|
|
[Comp1, words(LastSep), Comp2].
|
|
component_list_to_pieces(LastSep, [Comp1, Comp2, Comp3 | Comps]) =
|
|
[Comp1, suffix(",")]
|
|
++ component_list_to_pieces(LastSep, [Comp2, Comp3 | Comps]).
|
|
|
|
strict_component_list_to_pieces([]) = [].
|
|
strict_component_list_to_pieces([Comp]) = [Comp].
|
|
strict_component_list_to_pieces([Comp1, Comp2 | Comps]) =
|
|
[Comp1, suffix(",")]
|
|
++ strict_component_list_to_pieces([Comp2 | Comps]).
|
|
|
|
component_list_to_line_pieces([], _) = [].
|
|
component_list_to_line_pieces([Comps], Final) = Comps ++ Final ++ [nl].
|
|
component_list_to_line_pieces([Comps1, Comps2 | CompLists], Final) =
|
|
Comps1 ++ [suffix(","), nl]
|
|
++ component_list_to_line_pieces([Comps2 | CompLists], Final).
|
|
|
|
choose_number([], _Singular, Plural) = Plural.
|
|
choose_number([_], Singular, _Plural) = Singular.
|
|
choose_number([_, _ | _], _Singular, Plural) = Plural.
|
|
|
|
is_or_are([]) = "" :-
|
|
unexpected($pred, "[]").
|
|
is_or_are([_]) = "is".
|
|
is_or_are([_, _ | _]) = "are".
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
write_error_pieces_plain(Globals, Components, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
write_error_pieces_plain(Stream, Globals, Components, !IO).
|
|
|
|
write_error_pieces_plain(Stream, Globals, Components, !IO) :-
|
|
do_write_error_pieces(Stream, treat_as_first, no, 0,
|
|
Globals, Components, !IO).
|
|
|
|
%---------------------%
|
|
|
|
write_error_plain_with_progname(ProgName, Msg, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
write_error_plain_with_progname(Stream, ProgName, Msg, !IO).
|
|
|
|
write_error_plain_with_progname(Stream, ProgName, Msg, !IO) :-
|
|
MaxWidth = 79,
|
|
LinesInMsg = string.split_at_char('\n', Msg),
|
|
convert_lines_in_msg_to_pieces(LinesInMsg, LinesInMsgPieces),
|
|
Components = [fixed(ProgName ++ ":") | LinesInMsgPieces],
|
|
do_write_error_pieces_params(Stream, treat_as_first, no, 0,
|
|
yes(MaxWidth), map.init, Components, !IO).
|
|
|
|
:- pred convert_lines_in_msg_to_pieces(list(string)::in,
|
|
list(format_component)::out) is det.
|
|
|
|
convert_lines_in_msg_to_pieces([], []).
|
|
convert_lines_in_msg_to_pieces([Line | Lines], Pieces) :-
|
|
convert_lines_in_msg_to_pieces(Lines, TailPieces),
|
|
Pieces = [words(Line), nl | TailPieces].
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
write_error_pieces(Globals, Context, Indent, Components, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
write_error_pieces(Stream, Globals, Context, Indent, Components, !IO).
|
|
|
|
write_error_pieces(Stream, Globals, Context, Indent, Components, !IO) :-
|
|
do_write_error_pieces(Stream, treat_as_first, yes(Context), Indent,
|
|
Globals, Components, !IO).
|
|
|
|
%---------------------%
|
|
|
|
write_error_pieces_maybe_with_context(Globals, MaybeContext,
|
|
Indent, Components, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
write_error_pieces_maybe_with_context(Stream, Globals, MaybeContext,
|
|
Indent, Components, !IO).
|
|
|
|
write_error_pieces_maybe_with_context(Stream, Globals, MaybeContext, Indent,
|
|
Components, !IO) :-
|
|
do_write_error_pieces(Stream, treat_as_first, MaybeContext, Indent,
|
|
Globals, Components, !IO).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred do_write_error_pieces(io.text_output_stream::in,
|
|
maybe_treat_as_first::in, maybe(prog_context)::in, int::in, globals::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
|
|
do_write_error_pieces(Stream, TreatAsFirst, MaybeContext, FixedIndent,
|
|
Globals, Components, !IO) :-
|
|
globals.lookup_maybe_int_option(Globals, max_error_line_width,
|
|
MaybeMaxWidth),
|
|
globals.get_limit_error_contexts_map(Globals, LimitErrorContextsMap),
|
|
do_write_error_pieces_params(Stream, TreatAsFirst, MaybeContext,
|
|
FixedIndent, MaybeMaxWidth, LimitErrorContextsMap, Components, !IO).
|
|
|
|
:- pred do_write_error_pieces_params(io.text_output_stream::in,
|
|
maybe_treat_as_first::in, maybe(prog_context)::in,
|
|
int::in, maybe(int)::in, limit_error_contexts_map::in,
|
|
list(format_component)::in, io::di, io::uo) is det.
|
|
|
|
do_write_error_pieces_params(Stream, TreatAsFirst, MaybeContext, FixedIndent,
|
|
MaybeMaxWidth, LimitErrorContextsMap, Components, !IO) :-
|
|
% The fixed characters at the start of the line are:
|
|
% filename
|
|
% :
|
|
% line number (min 3 chars)
|
|
% :
|
|
% space
|
|
% indent
|
|
(
|
|
MaybeContext = yes(Context),
|
|
term.context_file(Context, FileName),
|
|
term.context_line(Context, LineNumber),
|
|
( if
|
|
(
|
|
map.search(LimitErrorContextsMap, FileName, LineNumberRanges),
|
|
line_number_is_in_a_range(LineNumberRanges, LineNumber) = no
|
|
;
|
|
% The entry for the empty filename applies to all files.
|
|
map.search(LimitErrorContextsMap, "", LineNumberRanges),
|
|
line_number_is_in_a_range(LineNumberRanges, LineNumber) = no
|
|
)
|
|
then
|
|
io_set_some_errors_were_context_limited(
|
|
some_errors_were_context_limited, !IO),
|
|
MaybeContextLength = no
|
|
else
|
|
string.count_codepoints(FileName, FileNameLength),
|
|
string.int_to_string(LineNumber, LineNumberStr),
|
|
string.count_codepoints(LineNumberStr, LineNumberStrLength0),
|
|
( if LineNumberStrLength0 < 3 then
|
|
LineNumberStrLength = 3
|
|
else
|
|
LineNumberStrLength = LineNumberStrLength0
|
|
),
|
|
MaybeContextLength =
|
|
yes(FileNameLength + 1 + LineNumberStrLength + 2)
|
|
)
|
|
;
|
|
MaybeContext = no,
|
|
MaybeContextLength = yes(0)
|
|
),
|
|
(
|
|
MaybeContextLength = no
|
|
% Suppress the printing of the error pieces.
|
|
;
|
|
MaybeContextLength = yes(ContextLength),
|
|
(
|
|
Components = []
|
|
% There are no error pieces to print. Don't print the context
|
|
% at the start of a line followed by nothing.
|
|
%
|
|
% This can happen if e.g. the original error_msg_component was
|
|
% verbose_and_nonverbose(SomePieces, []), and this compiler
|
|
% invocation is not printing verbose errors.
|
|
;
|
|
Components = [_ | _],
|
|
convert_components_to_paragraphs(Components, Paragraphs),
|
|
FirstIndent = (if TreatAsFirst = treat_as_first then 0 else 1),
|
|
(
|
|
MaybeMaxWidth = yes(MaxWidth),
|
|
Remain = MaxWidth - (ContextLength + FixedIndent),
|
|
MaybeRemain = yes(Remain)
|
|
;
|
|
MaybeMaxWidth = no,
|
|
MaybeRemain = no
|
|
),
|
|
divide_paragraphs_into_lines(TreatAsFirst, FirstIndent, Paragraphs,
|
|
MaybeRemain, Lines),
|
|
write_msg_lines(Stream, Lines, MaybeContext, FixedIndent, !IO)
|
|
)
|
|
).
|
|
|
|
:- func line_number_is_in_a_range(list(line_number_range), int) = bool.
|
|
|
|
line_number_is_in_a_range([], _) = no.
|
|
line_number_is_in_a_range([Range | Ranges], LineNumber) = IsInARange :-
|
|
Range = line_number_range(MaybeMin, MaybeMax),
|
|
( if
|
|
(
|
|
MaybeMin = no
|
|
;
|
|
MaybeMin = yes(Min),
|
|
Min =< LineNumber
|
|
),
|
|
(
|
|
MaybeMax = no
|
|
;
|
|
MaybeMax = yes(Max),
|
|
LineNumber =< Max
|
|
)
|
|
then
|
|
IsInARange = yes
|
|
else
|
|
IsInARange = line_number_is_in_a_range(Ranges, LineNumber)
|
|
).
|
|
|
|
:- func indent_increment = int.
|
|
|
|
indent_increment = 2.
|
|
|
|
:- pred write_msg_lines(io.text_output_stream::in, list(error_line)::in,
|
|
maybe(prog_context)::in, int::in, io::di, io::uo) is det.
|
|
|
|
write_msg_lines(_Stream, [], _, _, !IO).
|
|
write_msg_lines(Stream, [Line | Lines], MaybeContext, FixedIndent, !IO) :-
|
|
(
|
|
MaybeContext = yes(Context),
|
|
prog_out.write_context(Stream, Context, !IO)
|
|
;
|
|
MaybeContext = no
|
|
),
|
|
Line = error_line(LineIndent, LineWords),
|
|
Indent = FixedIndent + LineIndent * indent_increment,
|
|
string.pad_left("", ' ', Indent, IndentStr),
|
|
io.write_string(Stream, IndentStr, !IO),
|
|
error_util.write_msg_line(Stream, LineWords, !IO),
|
|
write_msg_lines(Stream, Lines, MaybeContext, FixedIndent, !IO).
|
|
|
|
:- pred write_msg_line(io.text_output_stream::in, list(string)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
write_msg_line(Stream, [], !IO) :-
|
|
io.write_char(Stream, '\n', !IO).
|
|
write_msg_line(Stream, [Word | Words], !IO) :-
|
|
io.write_string(Stream, Word, !IO),
|
|
write_msg_line_rest(Stream, Words, !IO),
|
|
io.write_char(Stream, '\n', !IO).
|
|
|
|
:- pred write_msg_line_rest(io.text_output_stream::in, list(string)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
write_msg_line_rest(_Stream, [], !IO).
|
|
write_msg_line_rest(Stream, [Word | Words], !IO) :-
|
|
io.write_char(Stream, ' ', !IO),
|
|
io.write_string(Stream, Word, !IO),
|
|
write_msg_line_rest(Stream, Words, !IO).
|
|
|
|
error_pieces_to_string(Components) =
|
|
error_pieces_to_string_2(first_in_msg, Components).
|
|
|
|
:- func error_pieces_to_string_2(maybe_first_in_msg, list(format_component))
|
|
= string.
|
|
|
|
error_pieces_to_string_2(_, []) = "".
|
|
error_pieces_to_string_2(FirstInMsg, [Component | Components]) = Str :-
|
|
TailStr = error_pieces_to_string_2(not_first_in_msg, Components),
|
|
(
|
|
Component = words(Words),
|
|
Str = join_string_and_tail(Words, Components, TailStr)
|
|
;
|
|
Component = words_quote(Words),
|
|
Str = join_string_and_tail(add_quotes(Words), Components, TailStr)
|
|
;
|
|
Component = fixed(Word),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
Component = quote(Word),
|
|
Str = join_string_and_tail(add_quotes(Word), Components, TailStr)
|
|
;
|
|
Component = int_fixed(Int),
|
|
Str = join_string_and_tail(int_to_string(Int), Components, TailStr)
|
|
;
|
|
Component = nth_fixed(Int),
|
|
Str = join_string_and_tail(nth_fixed_str(Int), Components, TailStr)
|
|
;
|
|
Component = lower_case_next_if_not_first,
|
|
(
|
|
FirstInMsg = first_in_msg,
|
|
Str = TailStr
|
|
;
|
|
FirstInMsg = not_first_in_msg,
|
|
Str = uncapitalize_first(TailStr)
|
|
)
|
|
;
|
|
Component = prefix(Prefix),
|
|
Str = Prefix ++ TailStr
|
|
;
|
|
Component = suffix(Suffix),
|
|
Str = join_string_and_tail(Suffix, Components, TailStr)
|
|
;
|
|
(
|
|
Component = qual_sym_name(SymName)
|
|
;
|
|
Component = unqual_sym_name(SymName0),
|
|
SymName = unqualified(unqualify_name(SymName0))
|
|
),
|
|
Word = sym_name_to_word(SymName),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
(
|
|
Component = qual_sym_name_arity(SymNameAndArity)
|
|
;
|
|
Component = unqual_sym_name_arity(SymNameAndArity0),
|
|
SymNameAndArity0 = sym_name_arity(SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0)),
|
|
SymNameAndArity = sym_name_arity(SymName, Arity)
|
|
),
|
|
Word = sym_name_arity_to_word(SymNameAndArity),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
(
|
|
Component = qual_pf_sym_name_orig_arity(PFSymNameArity)
|
|
;
|
|
Component = unqual_pf_sym_name_orig_arity(PFSymNameArity0),
|
|
PFSymNameArity0 = pf_sym_name_arity(PF, SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0)),
|
|
PFSymNameArity = pf_sym_name_arity(PF, SymName, Arity)
|
|
),
|
|
Word = pf_sym_name_orig_arity_to_string(PFSymNameArity),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
(
|
|
Component = qual_cons_id_and_maybe_arity(ConsId0),
|
|
strip_builtin_qualifier_from_cons_id(ConsId0, ConsId)
|
|
;
|
|
Component = unqual_cons_id_and_maybe_arity(ConsId0),
|
|
strip_module_qualifier_from_cons_id(ConsId0, ConsId)
|
|
),
|
|
Word = maybe_quoted_cons_id_and_arity_to_string(ConsId),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
(
|
|
Component = qual_type_ctor(TypeCtor),
|
|
TypeCtor = type_ctor(SymName, Arity)
|
|
;
|
|
Component = unqual_type_ctor(TypeCtor),
|
|
TypeCtor = type_ctor(SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0))
|
|
;
|
|
Component = qual_class_id(ClassId),
|
|
ClassId = class_id(SymName, Arity)
|
|
;
|
|
Component = unqual_class_id(ClassId),
|
|
ClassId = class_id(SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0))
|
|
),
|
|
SymNameAndArity = sym_name_arity(SymName, Arity),
|
|
Word = sym_name_arity_to_word(SymNameAndArity),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
Component = qual_top_ctor_of_type(Type),
|
|
type_to_ctor_det(Type, TypeCtor),
|
|
TypeCtor = type_ctor(TypeCtorSymName, TypeCtorArity),
|
|
SymNameArity = sym_name_arity(TypeCtorSymName, TypeCtorArity),
|
|
Word = sym_name_arity_to_word(SymNameArity),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
Component = p_or_f(PredOrFunc),
|
|
Word = pred_or_func_to_string(PredOrFunc),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
Component = decl(Decl),
|
|
Word = add_quotes(":- " ++ Decl),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
Component = pragma_decl(PragmaName),
|
|
Word = add_quotes(":- pragma " ++ PragmaName),
|
|
Str = join_string_and_tail(Word, Components, TailStr)
|
|
;
|
|
Component = nl,
|
|
Str = "\n" ++ TailStr
|
|
;
|
|
Component = nl_indent_delta(_),
|
|
% There is nothing we can do about the indent delta.
|
|
Str = "\n" ++ TailStr
|
|
;
|
|
Component = blank_line,
|
|
Str = "\n\n" ++ TailStr
|
|
;
|
|
( Component = invis_order_default_start(_)
|
|
; Component = invis_order_default_end(_)
|
|
),
|
|
Str = TailStr
|
|
).
|
|
|
|
:- func nth_fixed_str(int) = string.
|
|
|
|
nth_fixed_str(N) = Str :-
|
|
( if N = 1 then
|
|
Str = "first"
|
|
else if N = 2 then
|
|
Str = "second"
|
|
else if N = 3 then
|
|
Str = "third"
|
|
else if N = 4 then
|
|
Str = "fourth"
|
|
else if N = 5 then
|
|
Str = "fifth"
|
|
else if N = 6 then
|
|
Str = "sixth"
|
|
else if N = 7 then
|
|
Str = "seventh"
|
|
else if N = 8 then
|
|
Str = "eighth"
|
|
else if N = 9 then
|
|
Str = "ninth"
|
|
else if N = 10 then
|
|
Str = "tenth"
|
|
else
|
|
% We want to print 12th and 13th, not 12nd and 13rd,
|
|
% but 42nd and 43rd instead of 42th and 43th.
|
|
NStr = int_to_string(N),
|
|
LastDigit = N mod 10,
|
|
( if N > 20, LastDigit = 2 then
|
|
Str = NStr ++ "nd"
|
|
else if N > 20, LastDigit = 3 then
|
|
Str = NStr ++ "rd"
|
|
else
|
|
Str = NStr ++ "th"
|
|
)
|
|
).
|
|
|
|
:- func join_string_and_tail(string, list(format_component), string) = string.
|
|
|
|
join_string_and_tail(Word, Components, TailStr) = Str :-
|
|
( if TailStr = "" then
|
|
Str = Word
|
|
else if Components = [suffix(_) | _] then
|
|
Str = Word ++ TailStr
|
|
else
|
|
Str = Word ++ " " ++ TailStr
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type paragraph
|
|
---> paragraph(
|
|
list(string), % The list of words to print in the paragraph.
|
|
% It should not be empty.
|
|
int, % The number of blank lines to print after
|
|
% the paragraph.
|
|
int % The indent delta to apply for the next
|
|
% paragraph.
|
|
).
|
|
|
|
:- pred convert_components_to_paragraphs(list(format_component)::in,
|
|
list(paragraph)::out) is det.
|
|
|
|
convert_components_to_paragraphs(Components, Paras) :-
|
|
convert_components_to_paragraphs_acc(first_in_msg, Components,
|
|
[], cord.empty, ParasCord),
|
|
Paras = cord.list(ParasCord).
|
|
|
|
:- type word
|
|
---> plain_word(string)
|
|
; prefix_word(string)
|
|
; suffix_word(string)
|
|
; lower_next_word.
|
|
|
|
:- pred convert_components_to_paragraphs_acc(maybe_first_in_msg::in,
|
|
list(format_component)::in, list(word)::in,
|
|
cord(paragraph)::in, cord(paragraph)::out) is det.
|
|
|
|
convert_components_to_paragraphs_acc(_, [], RevWords0, !Paras) :-
|
|
Strings = rev_words_to_strings(RevWords0),
|
|
!:Paras = snoc(!.Paras, paragraph(Strings, 0, 0)).
|
|
convert_components_to_paragraphs_acc(FirstInMsg, [Component | Components],
|
|
RevWords0, !Paras) :-
|
|
(
|
|
Component = words(WordsStr),
|
|
break_into_words(WordsStr, RevWords0, RevWords1)
|
|
;
|
|
Component = words_quote(WordsStr),
|
|
break_into_words(add_quotes(WordsStr), RevWords0, RevWords1)
|
|
;
|
|
Component = fixed(Word),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
Component = quote(Word),
|
|
RevWords1 = [plain_word(add_quotes(Word)) | RevWords0]
|
|
;
|
|
Component = int_fixed(Int),
|
|
RevWords1 = [plain_word(int_to_string(Int)) | RevWords0]
|
|
;
|
|
Component = nth_fixed(Int),
|
|
RevWords1 = [plain_word(nth_fixed_str(Int)) | RevWords0]
|
|
;
|
|
Component = lower_case_next_if_not_first,
|
|
(
|
|
FirstInMsg = first_in_msg,
|
|
RevWords1 = RevWords0
|
|
;
|
|
FirstInMsg = not_first_in_msg,
|
|
RevWords1 = [lower_next_word | RevWords0]
|
|
)
|
|
;
|
|
Component = prefix(Word),
|
|
RevWords1 = [prefix_word(Word) | RevWords0]
|
|
;
|
|
Component = suffix(Word),
|
|
RevWords1 = [suffix_word(Word) | RevWords0]
|
|
;
|
|
(
|
|
Component = qual_sym_name(SymName)
|
|
;
|
|
Component = unqual_sym_name(SymName0),
|
|
SymName = unqualified(unqualify_name(SymName0))
|
|
),
|
|
RevWords1 = [plain_word(sym_name_to_word(SymName)) | RevWords0]
|
|
;
|
|
(
|
|
Component = qual_sym_name_arity(SymNameAndArity)
|
|
;
|
|
Component = unqual_sym_name_arity(SymNameAndArity0),
|
|
SymNameAndArity0 = sym_name_arity(SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0)),
|
|
SymNameAndArity = sym_name_arity(SymName, Arity)
|
|
),
|
|
Word = sym_name_arity_to_word(SymNameAndArity),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
(
|
|
Component = qual_pf_sym_name_orig_arity(PFSymNameArity)
|
|
;
|
|
Component = unqual_pf_sym_name_orig_arity(PFSymNameArity0),
|
|
PFSymNameArity0 = pf_sym_name_arity(PF, SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0)),
|
|
PFSymNameArity = pf_sym_name_arity(PF, SymName, Arity)
|
|
),
|
|
WordsStr = pf_sym_name_orig_arity_to_string(PFSymNameArity),
|
|
break_into_words(WordsStr, RevWords0, RevWords1)
|
|
;
|
|
(
|
|
Component = qual_cons_id_and_maybe_arity(ConsId0),
|
|
strip_builtin_qualifier_from_cons_id(ConsId0, ConsId)
|
|
;
|
|
Component = unqual_cons_id_and_maybe_arity(ConsId0),
|
|
strip_module_qualifier_from_cons_id(ConsId0, ConsId)
|
|
),
|
|
Word = maybe_quoted_cons_id_and_arity_to_string(ConsId),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
(
|
|
Component = qual_type_ctor(TypeCtor),
|
|
TypeCtor = type_ctor(SymName, Arity)
|
|
;
|
|
Component = unqual_type_ctor(TypeCtor),
|
|
TypeCtor = type_ctor(SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0))
|
|
;
|
|
Component = qual_class_id(ClassId),
|
|
ClassId = class_id(SymName, Arity)
|
|
;
|
|
Component = unqual_class_id(ClassId),
|
|
ClassId = class_id(SymName0, Arity),
|
|
SymName = unqualified(unqualify_name(SymName0))
|
|
),
|
|
SymNameAndArity = sym_name_arity(SymName, Arity),
|
|
Word = sym_name_arity_to_word(SymNameAndArity),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
Component = qual_top_ctor_of_type(Type),
|
|
type_to_ctor_det(Type, TypeCtor),
|
|
TypeCtor = type_ctor(TypeCtorName, TypeCtorArity),
|
|
SymNameArity = sym_name_arity(TypeCtorName, TypeCtorArity),
|
|
NewWord = plain_word(sym_name_arity_to_word(SymNameArity)),
|
|
RevWords1 = [NewWord | RevWords0]
|
|
;
|
|
Component = p_or_f(PredOrFunc),
|
|
Word = pred_or_func_to_string(PredOrFunc),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
Component = decl(DeclName),
|
|
Word = add_quotes(":- " ++ DeclName),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
Component = pragma_decl(PragmaName),
|
|
Word = add_quotes(":- pragma " ++ PragmaName),
|
|
RevWords1 = [plain_word(Word) | RevWords0]
|
|
;
|
|
Component = nl,
|
|
Strings = rev_words_to_strings(RevWords0),
|
|
!:Paras = snoc(!.Paras, paragraph(Strings, 0, 0)),
|
|
RevWords1 = []
|
|
;
|
|
Component = nl_indent_delta(IndentDelta),
|
|
Strings = rev_words_to_strings(RevWords0),
|
|
!:Paras = snoc(!.Paras, paragraph(Strings, 0, IndentDelta)),
|
|
RevWords1 = []
|
|
;
|
|
Component = blank_line,
|
|
Strings = rev_words_to_strings(RevWords0),
|
|
!:Paras = snoc(!.Paras, paragraph(Strings, 1, 0)),
|
|
RevWords1 = []
|
|
;
|
|
( Component = invis_order_default_start(_)
|
|
; Component = invis_order_default_end(_)
|
|
),
|
|
RevWords1 = RevWords0
|
|
),
|
|
convert_components_to_paragraphs_acc(not_first_in_msg, Components,
|
|
RevWords1, !Paras).
|
|
|
|
:- type plain_or_prefix
|
|
---> plain(string)
|
|
; prefix(string)
|
|
; lower_next.
|
|
|
|
:- func rev_words_to_strings(list(word)) = list(string).
|
|
|
|
rev_words_to_strings(RevWords) = Strings :-
|
|
PorPs = list.reverse(rev_words_to_rev_plain_or_prefix(RevWords)),
|
|
Strings = join_prefixes(PorPs).
|
|
|
|
:- func rev_words_to_rev_plain_or_prefix(list(word)) = list(plain_or_prefix).
|
|
|
|
rev_words_to_rev_plain_or_prefix([]) = [].
|
|
rev_words_to_rev_plain_or_prefix([Word | Words]) = PorPs :-
|
|
(
|
|
Word = plain_word(String),
|
|
PorPs = [plain(String) | rev_words_to_rev_plain_or_prefix(Words)]
|
|
;
|
|
Word = lower_next_word,
|
|
PorPs = [lower_next | rev_words_to_rev_plain_or_prefix(Words)]
|
|
;
|
|
Word = prefix_word(Prefix),
|
|
PorPs = [prefix(Prefix) | rev_words_to_rev_plain_or_prefix(Words)]
|
|
;
|
|
Word = suffix_word(Suffix),
|
|
(
|
|
Words = [],
|
|
PorPs = [plain(Suffix)]
|
|
;
|
|
Words = [plain_word(String) | Tail],
|
|
PorPs = [plain(String ++ Suffix)
|
|
| rev_words_to_rev_plain_or_prefix(Tail)]
|
|
;
|
|
Words = [lower_next_word | Tail],
|
|
% Convert the lower_next_word/suffix combination into just the
|
|
% suffix after lowercasing the suffix (which will probably have
|
|
% no effect, since the initial character of a suffix is usually
|
|
% not a letter).
|
|
NewWords = [suffix_word(uncapitalize_first(Suffix)) | Tail],
|
|
PorPs = rev_words_to_rev_plain_or_prefix(NewWords)
|
|
;
|
|
Words = [prefix_word(Prefix) | Tail],
|
|
% Convert the prefix/suffix combination into a plain word.
|
|
% We could convert it into a prefix, but since prefix/suffix
|
|
% combinations shouldn't come up at all, what we do here probably
|
|
% doesn't matter.
|
|
PorPs = [plain(Prefix ++ Suffix)
|
|
| rev_words_to_rev_plain_or_prefix(Tail)]
|
|
;
|
|
Words = [suffix_word(MoreSuffix) | Tail],
|
|
PorPs = rev_words_to_rev_plain_or_prefix(
|
|
[suffix_word(MoreSuffix ++ Suffix) | Tail])
|
|
)
|
|
).
|
|
|
|
:- func join_prefixes(list(plain_or_prefix)) = list(string).
|
|
|
|
join_prefixes([]) = [].
|
|
join_prefixes([Head | Tail]) = Strings :-
|
|
TailStrings = join_prefixes(Tail),
|
|
(
|
|
Head = plain(String),
|
|
Strings = [String | TailStrings]
|
|
;
|
|
Head = prefix(Prefix),
|
|
(
|
|
TailStrings = [First | Later],
|
|
Strings = [Prefix ++ First | Later]
|
|
;
|
|
TailStrings = [],
|
|
Strings = [Prefix | TailStrings]
|
|
)
|
|
;
|
|
Head = lower_next,
|
|
(
|
|
TailStrings = [],
|
|
Strings = TailStrings
|
|
;
|
|
TailStrings = [FirstTailString | LaterTailStrings],
|
|
Strings = [uncapitalize_first(FirstTailString) | LaterTailStrings]
|
|
)
|
|
).
|
|
|
|
:- func sym_name_to_word(sym_name) = string.
|
|
|
|
sym_name_to_word(SymName) =
|
|
add_quotes(sym_name_to_string(SymName)).
|
|
|
|
:- func sym_name_arity_to_word(sym_name_arity) = string.
|
|
|
|
sym_name_arity_to_word(sym_name_arity(SymName, Arity)) =
|
|
add_quotes(sym_name_to_string(SymName)) ++ "/" ++ int_to_string(Arity).
|
|
|
|
:- pred break_into_words(string::in, list(word)::in, list(word)::out) is det.
|
|
|
|
break_into_words(String, Words0, Words) :-
|
|
break_into_words_from(String, 0, Words0, Words).
|
|
|
|
:- pred break_into_words_from(string::in, int::in, list(word)::in,
|
|
list(word)::out) is det.
|
|
|
|
break_into_words_from(String, Cur, Words0, Words) :-
|
|
( if find_word_start(String, Cur, Start) then
|
|
find_word_end(String, Start, End),
|
|
string.between(String, Start, End, WordStr),
|
|
break_into_words_from(String, End, [plain_word(WordStr) | Words0],
|
|
Words)
|
|
else
|
|
Words = Words0
|
|
).
|
|
|
|
:- pred find_word_start(string::in, int::in, int::out) is semidet.
|
|
|
|
find_word_start(String, Cur, WordStart) :-
|
|
string.unsafe_index_next(String, Cur, Next, Char),
|
|
( if char.is_whitespace(Char) then
|
|
find_word_start(String, Next, WordStart)
|
|
else
|
|
WordStart = Cur
|
|
).
|
|
|
|
:- pred find_word_end(string::in, int::in, int::out) is det.
|
|
|
|
find_word_end(String, Cur, WordEnd) :-
|
|
( if string.unsafe_index_next(String, Cur, Next, Char) then
|
|
( if char.is_whitespace(Char) then
|
|
WordEnd = Cur
|
|
else
|
|
find_word_end(String, Next, WordEnd)
|
|
)
|
|
else
|
|
WordEnd = Cur
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type error_line
|
|
---> error_line(
|
|
int, % Indent level; multiply by indent_increment
|
|
% to get number of spaces of indentation.
|
|
list(string) % The words on the line.
|
|
).
|
|
|
|
% Groups the words in the given paragraphs into lines. The first line
|
|
% can have up to Max characters on it; the later lines (if any) up
|
|
% to Max-2 characters.
|
|
%
|
|
% If MaybeMax is `no', handle it as if Max were infinity (i.e. put
|
|
% everything in each paragraph on one line).
|
|
%
|
|
% The given list of paragraphs should be nonempty, since we always return
|
|
% at least one line.
|
|
%
|
|
:- pred divide_paragraphs_into_lines(maybe_treat_as_first::in, int::in,
|
|
list(paragraph)::in, maybe(int)::in, list(error_line)::out) is det.
|
|
|
|
divide_paragraphs_into_lines(TreatAsFirst, CurIndent, Paras, MaybeMax,
|
|
Lines) :-
|
|
(
|
|
Paras = [],
|
|
Lines = []
|
|
;
|
|
Paras = [FirstPara | LaterParas],
|
|
FirstPara = paragraph(FirstParaWords, NumBlankLines, FirstIndentDelta),
|
|
(
|
|
TreatAsFirst = treat_as_first,
|
|
RestIndent = CurIndent + 1
|
|
;
|
|
TreatAsFirst = do_not_treat_as_first,
|
|
RestIndent = CurIndent
|
|
),
|
|
NextIndent = RestIndent + FirstIndentDelta,
|
|
|
|
BlankLine = error_line(CurIndent, []),
|
|
list.duplicate(NumBlankLines, BlankLine, FirstParaBlankLines),
|
|
(
|
|
FirstParaWords = [],
|
|
NextTreatAsFirst = TreatAsFirst,
|
|
FirstParaLines = []
|
|
;
|
|
FirstParaWords = [FirstWord | LaterWords],
|
|
NextTreatAsFirst = do_not_treat_as_first,
|
|
(
|
|
MaybeMax = yes(Max),
|
|
get_line_of_words(FirstWord, LaterWords, CurIndent, Max,
|
|
LineWords, RestWords),
|
|
CurLine = error_line(CurIndent, LineWords),
|
|
|
|
group_nonfirst_line_words(RestWords, RestIndent, Max,
|
|
FirstParaRestLines),
|
|
FirstParaLines = [CurLine | FirstParaRestLines]
|
|
;
|
|
MaybeMax = no,
|
|
FirstParaLines = [error_line(CurIndent, FirstParaWords)]
|
|
)
|
|
),
|
|
divide_paragraphs_into_lines(NextTreatAsFirst, NextIndent, LaterParas,
|
|
MaybeMax, LaterParaLines),
|
|
Lines = FirstParaLines ++ FirstParaBlankLines ++ LaterParaLines
|
|
).
|
|
|
|
:- pred group_nonfirst_line_words(list(string)::in, int::in, int::in,
|
|
list(error_line)::out) is det.
|
|
|
|
group_nonfirst_line_words(Words, Indent, Max, Lines) :-
|
|
(
|
|
Words = [],
|
|
Lines = []
|
|
;
|
|
Words = [FirstWord | LaterWords],
|
|
get_line_of_words(FirstWord, LaterWords, Indent, Max,
|
|
LineWords, RestWords),
|
|
Line = error_line(Indent, LineWords),
|
|
group_nonfirst_line_words(RestWords, Indent, Max, RestLines),
|
|
Lines = [Line | RestLines]
|
|
).
|
|
|
|
:- pred get_line_of_words(string::in, list(string)::in, int::in, int::in,
|
|
list(string)::out, list(string)::out) is det.
|
|
|
|
get_line_of_words(FirstWord, LaterWords, Indent, Max, Line, RestWords) :-
|
|
string.count_codepoints(FirstWord, FirstWordLen),
|
|
Avail = Max - Indent * indent_increment,
|
|
get_later_words(LaterWords, FirstWordLen, Avail, [FirstWord],
|
|
Line, RestWords).
|
|
|
|
:- pred get_later_words(list(string)::in, int::in, int::in,
|
|
list(string)::in, list(string)::out, list(string)::out) is det.
|
|
|
|
get_later_words([], _, _, Line, Line, []).
|
|
get_later_words([Word | Words], OldLen, Avail, Line0, Line, RestWords) :-
|
|
string.count_codepoints(Word, WordLen),
|
|
NewLen = OldLen + 1 + WordLen,
|
|
( if NewLen =< Avail then
|
|
list.append(Line0, [Word], Line1),
|
|
get_later_words(Words, NewLen, Avail, Line1, Line, RestWords)
|
|
else
|
|
Line = Line0,
|
|
RestWords = [Word | Words]
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
describe_sym_name(SymName) =
|
|
string.append_list(["`", sym_name_to_string(SymName), "'"]).
|
|
|
|
describe_sym_name_arity(sym_name_arity(SymName, Arity)) =
|
|
string.append_list(["`", sym_name_to_string(SymName), "/",
|
|
string.int_to_string(Arity), "'"]).
|
|
|
|
add_quotes(Str) = "`" ++ Str ++ "'".
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
report_warning(Globals, Context, Indent, Components, !IO) :-
|
|
io.output_stream(Stream, !IO),
|
|
report_warning(Stream, Globals, Context, Indent, Components, !IO).
|
|
|
|
report_warning(Stream, Globals, Context, Indent, Components, !IO) :-
|
|
record_warning(Globals, !IO),
|
|
write_error_pieces(Stream, Globals, Context, Indent, Components, !IO).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
unable_to_open_file(FileName, IOErr, !IO) :-
|
|
io.stderr_stream(StdErr, !IO),
|
|
io.write_string(StdErr, "Unable to open file: '", !IO),
|
|
io.write_string(StdErr, FileName, !IO),
|
|
io.write_string(StdErr, "' because\n", !IO),
|
|
io.write_string(StdErr, io.error_message(IOErr), !IO),
|
|
io.nl(StdErr, !IO),
|
|
|
|
io.set_exit_status(1, !IO).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module parse_tree.error_util.
|
|
%---------------------------------------------------------------------------%
|