Files
mercury/compiler/source_file_map.m
Zoltan Somogyi 31f9d5034e Move a predicate next to its only call site.
compiler/source_file_map.m:
    Move a predicate here from find_module.m. Give it a more meaningful name.

    Replace a bool with a more appropriate type.

compiler/find_module.m:
    Delete the predicate moved to source_file_map.m.

compiler/generate_mmakefile_fragments.m:
compiler/read_modules.m:
    Conform to the changes above.
2024-10-23 22:00:01 +11:00

396 lines
15 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2002-2009, 2011 The University of Melbourne.
% Copyright (C) 2014-2015, 2019-2021, 2023-2024 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: source_file_map.m.
% Author: stayl.
%
% Maintain a mapping from module name to source file name.
%
% The reason why this module is in the parse_tree package is that discovering
% what module is stored in a file requires reading the ":- module" declaration
% in that file.
%
%---------------------------------------------------------------------------%
:- module parse_tree.source_file_map.
:- interface.
:- import_module libs.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.maybe_error.
:- import_module io.
:- import_module list.
:- import_module maybe.
%---------------------------------------------------------------------------%
%
% Part 1: file name operations that do not depend on Mercury.modules files.
%
% Return the default fully qualified source file name.
%
:- func default_source_file_name(module_name) = file_name.
%---------------------------------------------------------------------------%
%
% Part 2: constructing Mercury.modules files.
%
% write_source_file_map(Globals, ProgressStream, FileNames, !IO):
%
% Given a list of file names, produce the Mercury.modules file.
%
:- pred write_source_file_map(io.text_output_stream::in,
globals::in, list(string)::in, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%
% Part 3: testing for the presence of a Mercury.modules file.
%
% Return `found' if there is a valid Mercury.modules file.
%
:- pred have_source_file_map(maybe_found::out, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%
% Part 4: lookups that use the info in a Mercury.modules file, if there is one.
%
% lookup_module_source_file(ModuleName, MaybeFileName, !IO):
%
% Return `yes(FileName)' if FileName is the source file for ModuleName,
% getting it from Mercury.modules if that file exists *and* has an entry
% for ModuleName, and otherwise by computing it using the rule for
% a module's default file name. Return `no' if no source file is available
% for ModuleName because the default file name for ModuleName is
% mapped to another module.
%
:- pred lookup_module_source_file(module_name::in, maybe(file_name)::out,
io::di, io::uo) is det.
% lookup_source_file_module(FileName, MaybeModuleName, !IO):
%
% Return `yes(ModuleName)' if FileName is the source file for ModuleName,
% either through the source file map, or by default. Return `no' if no
% module name is available for FileName because the default module name
% for FileName is stored in another file.
%
:- pred lookup_source_file_module(file_name::in, maybe(module_name)::out,
io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.file_names.
:- import_module parse_tree.parse_module. % for peek_at_file
:- import_module parse_tree.parse_tree_out_sym_name.
:- import_module parse_tree.write_error_spec.
:- import_module bimap.
:- import_module dir.
:- import_module int.
:- import_module string.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Part 1.
%
default_source_file_name(ModuleName) = sym_name_to_string(ModuleName) ++ ".m".
% If the file name ends in ".m", return the module name whose
% default file name that would be.
%
:- pred default_module_name_for_file(file_name::in, module_name::out)
is semidet.
default_module_name_for_file(FileName, DefaultModuleName) :-
string.remove_suffix(FileName, ".m", FileNameBeforeDotM),
file_name_to_module_name(FileNameBeforeDotM, DefaultModuleName).
%---------------------------------------------------------------------------%
%
% Part 2.
%
write_source_file_map(ProgressStream, Globals, FileNames, !IO) :-
ModulesFileName = modules_file_name,
io.open_output(ModulesFileName, MapFileResult, !IO),
(
MapFileResult = ok(MapFileStream),
list.foldl2(
write_source_file_map_line(ProgressStream, MapFileStream, Globals),
FileNames, bimap.init, _, !IO),
io.close_output(MapFileStream, !IO)
;
MapFileResult = error(Error),
io.stderr_stream(StdErr, !IO),
io.format(StdErr,
"mercury_compile: error opening `%s' for output: %s",
[s(ModulesFileName), s(io.error_message(Error))], !IO),
io.set_exit_status(1, !IO)
).
:- pred write_source_file_map_line(io.text_output_stream::in,
io.text_output_stream::in, globals::in, file_name::in,
bimap(module_name, file_name)::in, bimap(module_name, file_name)::out,
io::di, io::uo) is det.
write_source_file_map_line(ProgressStream, MapFileStream, Globals,
FileName, SeenModules0, SeenModules, !IO) :-
find_name_of_module_in_file(FileName, MaybeModuleName, !IO),
(
MaybeModuleName = ok1(ModuleName),
( if
bimap.search(SeenModules0, ModuleName, PrevFileName),
PrevFileName \= FileName
then
io.format(ProgressStream,
"mercury_compile: " ++
"module `%s' defined in multiple files: %s, %s\n.",
[s(sym_name_to_string(ModuleName)),
s(PrevFileName), s(FileName)], !IO),
io.set_exit_status(1, !IO),
SeenModules = SeenModules0
else
bimap.set(ModuleName, FileName, SeenModules0, SeenModules)
),
( if string.remove_suffix(FileName, ".m", PartialFileName0) then
PartialFileName = PartialFileName0
else
PartialFileName = FileName
),
file_name_to_module_name(dir.det_basename(PartialFileName),
DefaultModuleName),
( if
% Only include a module in the mapping if the name doesn't match
% the default.
dir.dirname(PartialFileName) = dir.this_directory : string,
ModuleName = DefaultModuleName
then
true
else
io.format(MapFileStream, "%s\t%s\n",
[s(escaped_sym_name_to_string(ModuleName)), s(FileName)], !IO)
)
;
MaybeModuleName = error1(Specs),
write_error_specs(ProgressStream, Globals, Specs, !IO),
SeenModules = SeenModules0
).
% find_name_of_module_in_file(FileName, MaybeModuleName, !IO):
%
% Read the first item from the given file to find the module name.
%
:- pred find_name_of_module_in_file(file_name::in, maybe1(module_name)::out,
io::di, io::uo) is det.
find_name_of_module_in_file(FileName, MaybeModuleName, !IO) :-
io.open_input(FileName, OpenRes, !IO),
(
OpenRes = ok(FileStream),
( if string.remove_suffix(FileName, ".m", PartialFileName0) then
PartialFileName = PartialFileName0
else
PartialFileName = FileName
),
( if dir.basename(PartialFileName, BaseName0) then
BaseName = BaseName0
else
BaseName = ""
),
file_name_to_module_name(BaseName, DefaultModuleName),
peek_at_file(FileStream, FileName, DefaultModuleName,
MaybeModuleName, !IO),
io.close_input(FileStream, !IO)
;
OpenRes = error(Error),
ErrorMsg = io.error_message(Error),
io.progname_base("mercury_compile", Progname, !IO),
Pieces = [fixed(Progname), suffix(":"), words("error opening"),
quote(FileName), suffix(":"), words(ErrorMsg), suffix("."), nl],
Spec = no_ctxt_spec($pred, severity_error, phase_read_files, Pieces),
MaybeModuleName = error1([Spec])
).
%---------------------------------------------------------------------------%
%
% Part 3.
%
have_source_file_map(HaveMap, !IO) :-
get_source_file_map(SourceFileMap, !IO),
( if bimap.is_empty(SourceFileMap) then
HaveMap = not_found
else
HaveMap = found
).
%---------------------------------------------------------------------------%
%
% Part 4.
%
lookup_module_source_file(ModuleName, MaybeFileName, !IO) :-
get_source_file_map(SourceFileMap, !IO),
( if bimap.search(SourceFileMap, ModuleName, FileName) then
MaybeFileName = yes(FileName)
else
DefaultFileName = default_source_file_name(ModuleName),
( if bimap.reverse_search(SourceFileMap, _, DefaultFileName) then
MaybeFileName = no
else
MaybeFileName = yes(DefaultFileName)
)
).
lookup_source_file_module(FileName, MaybeModuleName, !IO) :-
get_source_file_map(SourceFileMap, !IO),
( if bimap.reverse_search(SourceFileMap, ModuleName, FileName) then
MaybeModuleName = yes(ModuleName)
else
( if default_module_name_for_file(FileName, DefaultModuleName) then
( if bimap.search(SourceFileMap, DefaultModuleName, _) then
MaybeModuleName = no
else
MaybeModuleName = yes(DefaultModuleName)
)
else
MaybeModuleName = no
)
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Code common to parts 1, 2 and 3.
%
% Bidirectional map between module names and file names.
%
:- type source_file_map == bimap(module_name, string).
%---------------------%
:- mutable(maybe_source_file_map, maybe(source_file_map), no, ground,
[untrailed, attach_to_io_state]).
%---------------------%
:- func modules_file_name = string.
modules_file_name = "Mercury.modules".
%---------------------%
% Read the Mercury.modules file (if it exists, and if we have not
% read and parsed it before) to find and return the mapping
% from module names to file names, and vice versa.
%
:- pred get_source_file_map(source_file_map::out, io::di, io::uo) is det.
get_source_file_map(SourceFileMap, !IO) :-
get_maybe_source_file_map(MaybeSourceFileMap0, !IO),
(
MaybeSourceFileMap0 = yes(SourceFileMap0),
SourceFileMap = SourceFileMap0
;
MaybeSourceFileMap0 = no,
ModulesFileName = modules_file_name,
io.read_named_file_as_lines(ModulesFileName, ReadResult, !IO),
(
ReadResult = ok(FileLines),
bimap.init(SourceFileMap0),
parse_source_file_map(FileLines, ModulesFileName, 1, ErrorMsg,
SourceFileMap0, SourceFileMap1),
( if ErrorMsg = "" then
SourceFileMap = SourceFileMap1
else
% If the file does exist but is malformed, then
% we *should* generate an error, but pretending that
% the file exists and is empty preserves old behavior.
bimap.init(SourceFileMap)
)
;
ReadResult = error(_),
% If the file doesn't exist, then the mapping is empty.
% XXX ReadResult can be error/1 even when the file *does* exist.
% For example, the open could fail due to a permission problem.
bimap.init(SourceFileMap)
),
% Set the mutable even in the presence of failures. If one attempt
% at reading Mercury.modules has failed, that is no point in trying
% again. In the usual case, every later attempt would fail the same
% way, wasting time, which is bad. However, due to changes by other
% processes, a later attempt could succeed. This would be worse,
% because the module name / file name lookups done before and after
% that later success would have almost certainly reported different
% results, and those inconsistencies could result in some very weird
% errors.
set_maybe_source_file_map(yes(SourceFileMap), !IO)
).
:- pred parse_source_file_map(list(string)::in, string::in, int::in,
string::out, source_file_map::in, source_file_map::out) is det.
parse_source_file_map(Lines, ModulesFileName, CurLineNumber, ErrorMsg,
!SourceFileMap) :-
(
Lines = [HeadLine | TailLines],
( if string.sub_string_search(HeadLine, "\t", TabIndex) then
string.length(HeadLine, LineLength),
string.unsafe_between(HeadLine, 0, TabIndex, ModuleNameStr),
string.unsafe_between(HeadLine, TabIndex + 1, LineLength,
FileName),
ModuleName = string_to_sym_name(ModuleNameStr),
% XXX A module cannot be contained in two files, which means that
% ModuleName should be a unique key in the forward map.
% However, with nested modules, a single file may contain
% more than one module, so FileName may *not* be a unique key
% in the backward map.
% XXX However, if Mercury.modules contains more than one line
% with the same filename, then this code has a bug, because
% the call sequence
%
% bimap.set("module_a", "filename", !SourceFileMap)
% bimap.set("module_a.sub1", "filename", !SourceFileMap)
% bimap.set("module_a.sub2", "filename", !SourceFileMap)
%
% will leave only one key that maps to the value "filename",
% which will be the last one added ("module_a.sub2" in this case).
%
% XXX We should call bimap.det_insert here to abort in such
% situations, but I (zs) am not sure that output generated by
% write_source_file_map is guaranteed to be a bijection.
bimap.set(ModuleName, FileName, !SourceFileMap),
parse_source_file_map(TailLines, ModulesFileName,
CurLineNumber + 1, ErrorMsg, !SourceFileMap)
else
string.format("line %d of %s is missing a tab character",
[i(CurLineNumber), s(ModulesFileName)], ErrorMsg)
)
;
Lines = [],
ErrorMsg = ""
).
%---------------------------------------------------------------------------%
:- end_module parse_tree.source_file_map.
%---------------------------------------------------------------------------%