%-----------------------------------------------------------------------------e % vim: ft=mercury ts=4 sw=4 et %-----------------------------------------------------------------------------e % Copyright (C) 1993-2012 The University of Melbourne. % Copyright (C) 2016-2026 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: parse_module.m. % Main author of original version: fjh. % Main author of current version: zs. % % This module defines the top level predicates for parsing Mercury programs. % % The main predicates of this module, actually_read_module_{src,int,opt}, % read in and parse the contents of source files, interface files and % optimization files respectively. % % The original design of this module represented all three kinds of files % as simple lists of items. The design of the items themselves was made % considerably more complex than is necessary for the compiler, because % one objective was to preserve as much information about the source code % as possible, to allow tools such as prettyprinters to reconstruct the % source code as closely as possible. (For example, this is why each item % has a sequence number.) The only information that was designed to be lost % was the stripping of comments, whitespace and redundant parenthesization, % the standardization of the spellings of the names of operators (such as % rewriting "\+" to "not"), and the expansion of DCG clauses. % % We have not found a need for such prettyprinters in more than twenty years, % so the original concern for minimizing the differences between the source % code and the AST, which was Fergus's argument for keeping the AST a simple % item list, has been shown to be irrelevant. % % We therefore now use a representation for the parse tree that is more suited % to the needs of the compiler itself, even though it is more complex % that a simple list of items. Part of this complexity is that source files, % interface files and optimization files have different parse trees, % and we accordingly have different predicates for creating those parse trees. % The main predicates for reading these three kinds of files are % % - parse_src_file, % - parse_int_file, and % - parse_opt_file. % % Each of these kinds of files has its grammar describing the structure % its items should follow. The predicates parsing parts of those structures % each have a comment before them specifying what part of the relevant file % kind they are designed to parse. The grammar is simplest for opt files, and % next simplest for int files, so the order of those predicates in this module % is parse_opt_file, parse_int_file, and then parse_src_file. % % Our parsing process has four stages instead of the usual two. % % The usual stages are: % lexical analysis: chars -> tokens % parsing: tokens -> structured parse tree % % Our stages are: % lexical analysis: chars -> tokens % parsing stage 1: tokens -> terms % parsing stage 2: terms -> items and markers % parsing stage 3: items and markers -> structured parse tree % % An item represents a clause or a declaration. A marker represents % a declaration that affects the structure of the parse tree, usually by % controlling what section or what (sub)module the following items belong to. % % We never materialize the intermediate representations (a token list, % a term list or an item/marker list) all at once. Instead, we read in the % contents of the relevant file one term at a time, and convert that % to an item or marker before reading in the next term. % % Some predicates, when they parse a term, discover that it represents an % item or marker that it is not their job to handle. In such cases, they % return it to their caller, for the next step in the caller to process. % %---------------------------------------------------------------------------% :- module parse_tree.parse_module. :- 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 parse_tree.parse_error. :- import_module parse_tree.prog_data. :- import_module parse_tree.prog_parse_tree. :- import_module io. :- import_module list. :- import_module maybe. %---------------------------------------------------------------------------% % peek_at_file(FileStream, SourceFileName, MaybeDefaultModuleName, % MaybeModuleName, !IO): % % "mmc -f", which creates the Mercury.modules file that all later % compiler invocations will use to map modules to the name of the file % containing them, uses this predicate to peek into a file to try to read % its first Mercury item. % % It this attempt succeeds and finds a ":- module" declaration giving % the name of the module as ModuleName, then return ok1(ModuleName). % If any part of this process fails, return error1(Spec) where Specs % describes the problem. % % NOTE At the moment (2025 jun 3), we have only one caller, in % source_file_map.m, which always passes "no" as MaybeDefaultModuleName. % We take the MaybeDefaultModuleName argument just in case we get % another caller. % :- pred peek_at_file(io.text_input_stream::in, file_name::in, maybe(module_name)::in, maybe1(module_name)::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% % parse_intN_file(Globals, FileName, FileStr, FileStrLen, % DefaultModuleName, DefaultExpectationContexts, % MaybeParseTreeIntN, Errors): % % Given FileName's its contents (FileStr, whose length is FileStrLen code % units), try to parse those contents as a .intN file. % :- pred parse_int0_file(globals::in, file_name::in, string::in, int::in, module_name::in, list(prog_context)::in, maybe(parse_tree_int0)::out, read_module_errors::out) is det. :- pred parse_int1_file(globals::in, file_name::in, string::in, int::in, module_name::in, list(prog_context)::in, maybe(parse_tree_int1)::out, read_module_errors::out) is det. :- pred parse_int2_file(globals::in, file_name::in, string::in, int::in, module_name::in, list(prog_context)::in, maybe(parse_tree_int2)::out, read_module_errors::out) is det. :- pred parse_int3_file(globals::in, file_name::in, string::in, int::in, module_name::in, list(prog_context)::in, maybe(parse_tree_int3)::out, read_module_errors::out) is det. % parse_{plain,trans}_opt_file(FileName, FileStr, FileStrLen, % DefaultModuleName, MaybeParseTree{Plain,Trans}Opt, Errors): % % Given FileName's its contents (FileStr, whose length is FileStrLen code % units), try to parse those contents as a .*opt file. % :- pred parse_plain_opt_file(file_name::in, string::in, int::in, module_name::in, maybe(parse_tree_plain_opt)::out, read_module_errors::out) is det. :- pred parse_trans_opt_file(file_name::in, string::in, int::in, module_name::in, maybe(parse_tree_trans_opt)::out, read_module_errors::out) is det. % parse_src_file(FileName, FileString, FileStringLen, % DefaultModuleName, DefaultExpectationContexts, % MaybeParseTreeSrc, Errors): % % Given FileName's contents (FileStr, whose length is FileStrLen code % units), try to parse those contents as a Mercury source file. % :- pred parse_src_file(file_name::in, string::in, int::in, module_name::in, list(prog_context)::in, maybe(parse_tree_src)::out, read_module_errors::out) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module libs.options. :- import_module parse_tree.convert_import_use. :- import_module parse_tree.convert_parse_tree. :- import_module parse_tree.error_spec. :- import_module parse_tree.file_kind. :- import_module parse_tree.parse_item. :- import_module parse_tree.parse_types. :- import_module parse_tree.prog_item. :- import_module recompilation. :- import_module bool. :- import_module cord. :- import_module counter. :- import_module int. :- import_module mercury_term_lexer. :- import_module mercury_term_parser. :- import_module one_or_more. :- import_module pair. :- import_module require. :- import_module string. :- import_module term. :- import_module term_context. :- import_module varset. %---------------------------------------------------------------------------% peek_at_file(FileStream, SourceFileName0, MaybeDefaultModuleName, MaybeModuleName, !IO) :- io.read_file_as_string_and_num_code_units(FileStream, MaybeResult, !IO), ( MaybeResult = ok2(FileString, FileStringLen), DefaultExpectationContexts = [], counter.init(1, SeqNumCounter0), LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), parse_first_module_decl(FileString, FileStringLen, MaybeDefaultModuleName, DefaultExpectationContexts, ModuleDeclPresent, may_change_source_file_name, SourceFileName0, _SourceFileName, SeqNumCounter0, _SeqNumCounter, LineContext0, _LineContext, LinePosn0, _LinePosn), ( ModuleDeclPresent = no_module_decl_present(_MaybeLookAhead, _Context, _NoModuleSpec), Pieces = [words("Error:")] ++ color_as_subject([quote(SourceFileName0)]) ++ color_as_incorrect([words("does not start with a"), decl("module"), words("declaration.")]) ++ [nl], Spec = no_ctxt_spec($pred, severity_error, phase_read_files, Pieces), MaybeModuleName = error1([Spec]) ; ModuleDeclPresent = wrong_module_decl_present(ModuleName, _ModuleNameContext, _WrongSpec), MaybeModuleName = ok1(ModuleName) ; ModuleDeclPresent = right_module_decl_present(ModuleName, _ModuleNameContext), MaybeModuleName = ok1(ModuleName) ) ; MaybeResult = error2(_PartialFileString, _FileStringLen, ErrorCode), io.error_message(ErrorCode, ErrorMsg0), ErrorMsg = "I/O error: " ++ ErrorMsg0, io_error_to_error_spec(phase_read_files, ErrorMsg, Spec, !IO), MaybeModuleName = error1([Spec]) ). %---------------------------------------------------------------------------% parse_int0_file(Globals, FileName, FileStr, FileStrLen, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt0, Errors) :- LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), parse_int_file(ifk_int0, FileName, FileStr, FileStrLen, LineContext0, LinePosn0, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt, Errors0), ( MaybeParseTreeInt = no, MaybeParseTreeInt0 = no, Errors = Errors0 ; MaybeParseTreeInt = yes(ParseTreeInt), check_convert_parse_tree_int_to_int0(ParseTreeInt, ParseTreeInt0, [], ConvertSpecs), MaybeParseTreeInt0 = yes(ParseTreeInt0), maybe_add_convert_specs(Globals, ConvertSpecs, Errors0, Errors) ). parse_int1_file(Globals, FileName, FileStr, FileStrLen, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt1, Errors) :- LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), parse_int_file(ifk_int1, FileName, FileStr, FileStrLen, LineContext0, LinePosn0, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt, Errors0), ( MaybeParseTreeInt = no, MaybeParseTreeInt1 = no, Errors = Errors0 ; MaybeParseTreeInt = yes(ParseTreeInt), check_convert_parse_tree_int_to_int1(ParseTreeInt, ParseTreeInt1, [], ConvertSpecs), MaybeParseTreeInt1 = yes(ParseTreeInt1), maybe_add_convert_specs(Globals, ConvertSpecs, Errors0, Errors) ). parse_int2_file(Globals, FileName, FileStr, FileStrLen, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt2, Errors) :- LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), parse_int_file(ifk_int2, FileName, FileStr, FileStrLen, LineContext0, LinePosn0, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt, Errors0), ( MaybeParseTreeInt = no, MaybeParseTreeInt2 = no, Errors = Errors0 ; MaybeParseTreeInt = yes(ParseTreeInt), check_convert_parse_tree_int_to_int2(ParseTreeInt, ParseTreeInt2, [], ConvertSpecs), MaybeParseTreeInt2 = yes(ParseTreeInt2), maybe_add_convert_specs(Globals, ConvertSpecs, Errors0, Errors) ). parse_int3_file(Globals, FileName, FileStr, FileStrLen, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt3, Errors) :- LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), parse_int_file(ifk_int3, FileName, FileStr, FileStrLen, LineContext0, LinePosn0, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt, Errors0), ( MaybeParseTreeInt = no, MaybeParseTreeInt3 = no, Errors = Errors0 ; MaybeParseTreeInt = yes(ParseTreeInt), check_convert_parse_tree_int_to_int3(ParseTreeInt, ParseTreeInt3, [], ConvertSpecs), MaybeParseTreeInt3 = yes(ParseTreeInt3), maybe_add_convert_specs(Globals, ConvertSpecs, Errors0, Errors) ). %---------------------% parse_plain_opt_file(FileName, FileStr, FileStrLen, DefaultModuleName, MaybeParseTreePlainOpt, Errors) :- LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), DefaultExpectationContexts = [], parse_opt_file(ofk_opt, FileName, FileStr, FileStrLen, LineContext0, LinePosn0, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeOpt, Errors0), ( MaybeParseTreeOpt = no, MaybeParseTreePlainOpt = no, Errors = Errors0 ; MaybeParseTreeOpt = yes(ParseTreeOpt), check_convert_parse_tree_opt_to_plain_opt(ParseTreeOpt, ParseTreePlainOpt, [], ConvertSpecs), MaybeParseTreePlainOpt = yes(ParseTreePlainOpt), add_any_nec_errors(ConvertSpecs, Errors0, Errors) ). parse_trans_opt_file(FileName, FileStr, FileStrLen, DefaultModuleName, MaybeParseTreeTransOpt, Errors) :- LineContext0 = line_context(1, 0), LinePosn0 = line_posn(0), DefaultExpectationContexts = [], parse_opt_file(ofk_trans_opt, FileName, FileStr, FileStrLen, LineContext0, LinePosn0, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeOpt, Errors0), ( MaybeParseTreeOpt = no, MaybeParseTreeTransOpt = no, Errors = Errors0 ; MaybeParseTreeOpt = yes(ParseTreeOpt), check_convert_parse_tree_opt_to_trans_opt(ParseTreeOpt, ParseTreeTransOpt, [], ConvertSpecs), MaybeParseTreeTransOpt = yes(ParseTreeTransOpt), add_any_nec_errors(ConvertSpecs, Errors0, Errors) ). %---------------------------------------------------------------------------% :- pred maybe_add_convert_specs(globals::in, list(error_spec)::in, read_module_errors::in, read_module_errors::out) is det. maybe_add_convert_specs(Globals, ConvertSpecs, !Errors) :- globals.lookup_bool_option(Globals, halt_at_invalid_interface, HaltAtInvalidInterface), ( HaltAtInvalidInterface = no ; HaltAtInvalidInterface = yes, add_any_nec_errors(ConvertSpecs, !Errors) ). %---------------------------------------------------------------------------% :- pred report_module_has_unexpected_name(file_name::in, module_name::in, list(prog_context)::in, module_name::in, maybe(term.context)::in, error_spec::out) is det. report_module_has_unexpected_name(FileName, ExpectedName, ExpectationContexts, ActualName, MaybeActualContext, Spec) :- ( if MaybeActualContext = yes(ActualContext), not term_context.is_dummy_context(ActualContext) then MaybeContext = MaybeActualContext else MaybeContext = maybe.no ), MainPieces = [words("Error: file"), quote(FileName), words("contains the wrong module."), words("Expected module")] ++ color_as_correct([qual_sym_name(ExpectedName), suffix(",")]) ++ [words("found module")] ++ color_as_incorrect([qual_sym_name(ActualName), suffix(".")]) ++ [nl], MainMsg = error_msg(MaybeContext, always_treat_as_first, 0u, [always(MainPieces)]), list.sort_and_remove_dups(ExpectationContexts, SortedExpectationContexts0), list.delete_all(SortedExpectationContexts0, dummy_context, SortedExpectationContexts), list.map(expectation_context_to_msg, SortedExpectationContexts, SubMsgs), % We only get invoked if the module *has* an expected name. % When invoked as "mmc -f", it doesn't have one. Spec = error_spec($pred, severity_error, phase_module_name, [MainMsg | SubMsgs]). :- pred expectation_context_to_msg(prog_context::in, error_msg::out) is det. expectation_context_to_msg(Context, SubMsg) :- SubPieces = [words("The expected name is specified here."), nl], SubMsg = msg(Context, SubPieces). %---------------------% :- type read_parse_tree(PT) == pred(file_name, string, int, line_context, line_posn, module_name, list(prog_context), maybe(PT), read_module_errors). :- inst read_parse_tree == (pred(in, in, in, in, in, in, in, out, out) is det). :- type make_dummy_parse_tree(PT) == pred(module_name, PT). :- inst make_dummy_parse_tree == (pred(in, out) is det). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % This part of the module parses optimization files. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % % opt: STARTHERE module_start, item* ENDHERE % %---------------------------------------------------------------------------% % Read an optimization file (.opt or .trans_opt) from standard input. % :- pred parse_opt_file(opt_file_kind::in, file_name::in, string::in, int::in, line_context::in, line_posn::in, module_name::in, list(prog_context)::in, maybe(parse_tree_opt)::out, read_module_errors::out) is det. parse_opt_file(OptFileKind, SourceFileName0, FileString, FileStringLen, !.LineContext, !.LinePosn, DefaultModuleName, DefaultExpectationContexts, MaybeParseTree, !:Errors) :- parse_module_header(FileString, FileStringLen, DefaultModuleName, DefaultExpectationContexts, SourceFileName0, MaybeModuleHeader, SeqNumCounter1, !LineContext, !LinePosn), ( MaybeModuleHeader = no_valid_module_header(!:Errors), MaybeParseTree = no ; MaybeModuleHeader = valid_module_header(ModuleName, ModuleNameContext), % XXX We should allow the update of SourceFileName. parse_item_sequence(FileString, FileStringLen, ModuleName, no_lookahead, FinalLookAhead, cord.init, InclsCord, cord.init, AvailsCord, cord.init, FIMsCord, cord.init, ItemsCord, SourceFileName0, SourceFileName, SeqNumCounter1, SeqNumCounter, init_read_module_errors, !:Errors, !LineContext, !LinePosn), check_for_unexpected_item_at_end(SourceFileName, FileString, FileStringLen, ModuleName, fk_opt(OptFileKind), FinalLookAhead, SeqNumCounter, !Errors, !.LineContext, !.LinePosn), expect(cord.is_empty(InclsCord), $pred, "Incls != []"), Avails = cord.list(AvailsCord), avail_imports_uses(Avails, Imports, Uses), expect(unify(Imports, []), $pred, "Imports != []"), FIMs = cord.list(FIMsCord), Items = cord.list(ItemsCord), ParseTree = parse_tree_opt(ModuleName, OptFileKind, ModuleNameContext, Uses, FIMs, Items), MaybeParseTree = yes(ParseTree) ). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % This part of the module parses interface files. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % % int: STARTHERE module_start, vns? section, section? ENDHERE % % section: interface_marker, item* % implementation_marker, item* % %---------------------------------------------------------------------------% % Read an interface file (.int0, .int3, .int2 or .int). % :- pred parse_int_file(int_file_kind::in, file_name::in, string::in, int::in, line_context::in, line_posn::in, module_name::in, list(prog_context)::in, maybe(parse_tree_int)::out, read_module_errors::out) is det. parse_int_file(IntFileKind, SourceFileName, FileString, FileStringLen, !.LineContext, !.LinePosn, DefaultModuleName, DefaultExpectationContexts, MaybeParseTreeInt, !:Errors) :- some [!SeqNumCounter, !LookAhead] ( parse_module_header(FileString, FileStringLen, DefaultModuleName, DefaultExpectationContexts, SourceFileName, MaybeModuleHeader, !:SeqNumCounter, !LineContext, !LinePosn), ( MaybeModuleHeader = no_valid_module_header(!:Errors), MaybeParseTreeInt = no ; MaybeModuleHeader = valid_module_header(ModuleName, ModuleNameContext), !:LookAhead = no_lookahead, parse_any_version_number_item(FileString, FileStringLen, ModuleName, SourceFileName, !LookAhead, VersionNumbersResult, !SeqNumCounter, !LineContext, !LinePosn), ( VersionNumbersResult = vnr_error(Spec, Error), !:Errors = init_read_module_errors, add_nonfatal_error(Error, [Spec], !Errors), MaybeParseTreeInt = no ; VersionNumbersResult = vnr_ok(MaybeVersionNumbers), parse_int_file_sections(FileString, FileStringLen, ModuleName, ModuleNameContext, IntFileKind, SourceFileName, MaybeVersionNumbers, !LookAhead, MaybeParseTreeInt, !SeqNumCounter, !:Errors, !LineContext, !LinePosn), check_for_unexpected_item_at_end(SourceFileName, FileString, FileStringLen, ModuleName, fk_int(IntFileKind), !.LookAhead, !.SeqNumCounter, !Errors, !.LineContext, !.LinePosn) ) ) ). :- type version_number_result ---> vnr_ok(maybe_version_numbers) ; vnr_error(error_spec, nonfatal_read_module_error). :- pred parse_any_version_number_item(string::in, int::in, module_name::in, file_name::in, maybe_lookahead::in, maybe_lookahead::out, version_number_result::out, counter::in, counter::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_any_version_number_item(FileString, FileStringLen, ModuleName, SourceFileName, InitLookAhead, FinalLookAhead, VersionNumberResult, !SeqNumCounter, !LineContext, !LinePosn) :- get_next_item_or_marker(SourceFileName, FileString, FileStringLen, InitLookAhead, ModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn), ( ReadIOMResult = read_iom_eof, % If we have found end-of-file, then we are done. FinalLookAhead = no_lookahead, VersionNumberResult = vnr_ok(no_version_numbers) ; ReadIOMResult = read_iom_parse_term_error(ItemSpec), % We used to continue looking for a version number in this situation, % but (a) given that version numbers are put into .intN files % by automatic processes, *any* error detected in them renders % the entire contents of the file suspect, and (b) when we are % expecting a section marker but find read_iom_parse_term_error, % we don't continue looking. FinalLookAhead = no_lookahead, VersionNumberResult = vnr_error(ItemSpec, rme_could_not_read_term) ; ReadIOMResult = read_iom_parse_item_errors(_, _, _), % Ignore the errors; our caller will compute them again, % and record them, if and when it parses FinalLookAhead again. FinalLookAhead = lookahead(ReadIOMResult), VersionNumberResult = vnr_ok(no_version_numbers) ; ReadIOMResult = read_iom_ok(_IOMVarSet, _IOMTerm, IOM), % If the term we have read in is a version number item, return it. % If the term is anything else, leave it in the input to be handled % later. ( IOM = iom_marker_version_numbers(VN), FinalLookAhead = no_lookahead, VersionNumberResult = vnr_ok(version_numbers(VN)) ; ( IOM = iom_marker_module_start(_, _, _) ; IOM = iom_marker_module_end(_, _, _) ; IOM = iom_marker_src_file(_) ; IOM = iom_marker_section(_, _, _) ; IOM = iom_marker_include(_) ; IOM = iom_marker_avail(_) ; IOM = iom_marker_fim(_) ; IOM = iom_item(_) ; IOM = iom_item_and_error_specs(_, _) % The reasoning for read_iom_parse_item_errors also applies % to iom_item_and_specs. ; IOM = iom_handled_no_error ; IOM = iom_handled_error(_) ), FinalLookAhead = lookahead(ReadIOMResult), VersionNumberResult = vnr_ok(no_version_numbers) ) ). %---------------------------------------------------------------------------% % % int: module_start, vns? STARTHERE int_section, int_section? ENDHERE % %---------------------------------------------------------------------------% % XXX ITEM_LIST We should return *either* a ParseTreeInt *or* some errors, % but *not* both. % :- pred parse_int_file_sections(string::in, int::in, module_name::in, prog_context::in, int_file_kind::in, file_name::in, maybe_version_numbers::in, maybe_lookahead::in, maybe_lookahead::out, maybe(parse_tree_int)::out, counter::in, counter::out, read_module_errors::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_int_file_sections(FileString, FileStringLen, ModuleName, ModuleNameContext, IntFileKind, SourceFileName, MaybeVersionNumbers, !LookAhead, MaybeParseTreeInt, !SeqNumCounter, !:Errors, !LineContext, !LinePosn) :- parse_int_file_section(FileString, FileStringLen, ModuleName, SourceFileName, "interface", !LookAhead, MaybeFirstRawItemBlock, !SeqNumCounter, init_read_module_errors, !:Errors, !LineContext, !LinePosn), ( MaybeFirstRawItemBlock = no, % Ignore MaybeVersionNumbers if the interface is empty, % since there are no items whose version numbers we may be % interested in. ParseTreeInt = parse_tree_int(ModuleName, IntFileKind, ModuleNameContext, no_version_numbers, [], [], [], [], [], [], [], []), MaybeParseTreeInt = yes(ParseTreeInt) ; MaybeFirstRawItemBlock = yes({FirstRawItemBlock, _}), FirstRawItemBlock = item_block(_, FirstSectionKind, FirstIncls, FirstAvails, FirstFIMs, FirstItems), ( FirstSectionKind = ms_interface, parse_int_file_section(FileString, FileStringLen, ModuleName, SourceFileName, "implementation", !LookAhead, MaybeSecondRawItemBlock, !SeqNumCounter, !Errors, !LineContext, !LinePosn), ( MaybeSecondRawItemBlock = no, ParseTreeInt = parse_tree_int(ModuleName, IntFileKind, ModuleNameContext, MaybeVersionNumbers, FirstIncls, [], FirstAvails, [], FirstFIMs, [], FirstItems, []), MaybeParseTreeInt = yes(ParseTreeInt) ; MaybeSecondRawItemBlock = yes({SecondRawItemBlock, SectionContext}), SecondRawItemBlock = item_block(_, SecondSectionKind, SecondIncls, SecondAvails, SecondFIMs, SecondItems), ( SecondSectionKind = ms_interface, Pieces = [words("Error: an interface file"), words("should not have two consecutive"), words("interface sections."), nl], Spec = spec($pred, severity_error, phase_t2pt, SectionContext, Pieces), add_nonfatal_error(rme_nec, [Spec], !Errors), MaybeParseTreeInt = no ; SecondSectionKind = ms_implementation, ParseTreeInt = parse_tree_int(ModuleName, IntFileKind, ModuleNameContext, MaybeVersionNumbers, FirstIncls, SecondIncls, FirstAvails, SecondAvails, FirstFIMs, SecondFIMs, FirstItems, SecondItems), MaybeParseTreeInt = yes(ParseTreeInt) ) ) ; FirstSectionKind = ms_implementation, % This should not happen, but I (zs) cannot come up % with a convincing argument for *why*. ParseTreeInt = parse_tree_int(ModuleName, IntFileKind, ModuleNameContext, MaybeVersionNumbers, [], FirstIncls, [], FirstAvails, [], FirstFIMs, [], FirstItems), MaybeParseTreeInt = yes(ParseTreeInt) ) ). %---------------------------------------------------------------------------% % % int_section: % STARTHERE interface_marker, item* ENDHERE % STARTHERE implementation_marker, item* ENDHERE % %---------------------------------------------------------------------------% :- pred parse_int_file_section(string::in, int::in, module_name::in, file_name::in, string::in, maybe_lookahead::in, maybe_lookahead::out, maybe({raw_item_block, prog_context})::out, counter::in, counter::out, read_module_errors::in, read_module_errors::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_int_file_section(FileString, FileStringLen, ModuleName, SourceFileName, ExpectedSectionKindStr, InitLookAhead, FinalLookAhead, MaybeRawItemBlock, !SeqNumCounter, !Errors, !LineContext, !LinePosn) :- get_next_item_or_marker(SourceFileName, FileString, FileStringLen, InitLookAhead, ModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn), ( ( ReadIOMResult = read_iom_eof ; ReadIOMResult = read_iom_parse_term_error(ItemSpec), add_nonfatal_error(rme_could_not_read_term, [ItemSpec], !Errors) ; ReadIOMResult = read_iom_parse_item_errors(_IOMVarSet, _IOMTerm, ItemSpecs), add_nonfatal_error(rme_could_not_parse_item, ItemSpecs, !Errors) ), MaybeRawItemBlock = no, FinalLookAhead = no_lookahead ; ReadIOMResult = read_iom_ok(_IOMVarSet, IOMTerm, IOM), ( IOM = iom_marker_section(SectionKind, SectionContext, _SectionSeqNum), parse_item_sequence(FileString, FileStringLen, ModuleName, no_lookahead, FinalLookAhead, cord.init, InclsCord, cord.init, AvailsCord, cord.init, FIMsCord, cord.init, ItemsCord, SourceFileName, _UpdatedSourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), RawItemBlock = item_block(ModuleName, SectionKind, cord.list(InclsCord), cord.list(AvailsCord), cord.list(FIMsCord), cord.list(ItemsCord)), MaybeRawItemBlock = yes({RawItemBlock, SectionContext}) ; ( IOM = iom_marker_src_file(_) ; IOM = iom_marker_module_start(_, _, _) ; IOM = iom_marker_module_end(_, _, _) ; IOM = iom_marker_version_numbers(_) ; IOM = iom_marker_include(_) ; IOM = iom_marker_avail(_) ; IOM = iom_marker_fim(_) ; IOM = iom_item(_) % The fact that IOM is not a section marker trumps any error_specs % in iom_item_and_error_specs, and in iom_handled_error. ; IOM = iom_item_and_error_specs(_, _) ; IOM = iom_handled_no_error ; IOM = iom_handled_error(_) ), Context = get_term_context(IOMTerm), IOMPieces = iom_desc_pieces(IOM), Pieces = [words("Error: expected the start of an")] ++ color_as_correct([words(ExpectedSectionKindStr), words("section,")]) ++ [words("got")] ++ color_as_incorrect(IOMPieces ++ [suffix(".")]) ++ [nl], Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces), add_nonfatal_error(rme_nec, [Spec], !Errors), FinalLookAhead = lookahead(ReadIOMResult), MaybeRawItemBlock = no ) ). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % This part of the module parses source files. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % % src: STARTHERE module_start, component*, (module_end | epsilon) ENDHERE % % component: (interface_marker | epsilon), item* % (implementation_marker | epsilon), item* % module_start, component*, (module_end | epsilon) % % The marker that starts a component may be missing if the previous components % were (one or more) nested submodules, and before those nested submodules, % there was an interface or implementation section. That previous section % may have started with an explicit section marker, or it may have been % preceded by other nested submodules, and identified as an interface or % implementation section by the preceding section, and so on. % % A submodule may have its final end_module marker missing if there is nothing % following it: no item, no marker, only the implicit EOF. % %---------------------------------------------------------------------------% parse_src_file(!.SourceFileName, FileString, FileStringLen, DefaultModuleName, DefaultExpectationContexts, MaybeParseTree, !:Errors) :- some [!LineContext, !LinePosn, !SeqNumCounter] ( !:LineContext = line_context(1, 0), !:LinePosn = line_posn(0), !:Errors = init_read_module_errors, counter.init(1, !:SeqNumCounter), % We handle the first module declaration specially. Read the % documentation on parse_first_module_decl for the reason. parse_first_module_decl(FileString, FileStringLen, yes(DefaultModuleName), DefaultExpectationContexts, ModuleDeclPresent, may_change_source_file_name, !SourceFileName, !SeqNumCounter, !LineContext, !LinePosn), ( ModuleDeclPresent = no_module_decl_present(InitLookAhead, InitLookAheadContext, NoModuleSpec), add_nonfatal_error(rme_no_module_decl_at_start, [NoModuleSpec], !Errors), ModuleName = DefaultModuleName, ModuleNameContext = InitLookAheadContext ; ModuleDeclPresent = wrong_module_decl_present(ModuleName, ModuleNameContext, WrongSpec), add_nonfatal_error(rme_unexpected_module_name, [WrongSpec], !Errors), InitLookAhead = no_lookahead ; ModuleDeclPresent = right_module_decl_present(ModuleName, ModuleNameContext), InitLookAhead = no_lookahead ), ContainingModules = [], MaybePrevSection = no, parse_src_file_components(FileString, FileStringLen, ModuleName, ContainingModules, MaybePrevSection, InitLookAhead, FinalLookAhead, cord.init, ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), check_for_unexpected_item_at_end(!.SourceFileName, FileString, FileStringLen, ModuleName, fk_src, FinalLookAhead, !.SeqNumCounter, !Errors, !.LineContext, !.LinePosn), ParseTree = parse_tree_src(ModuleName, ModuleNameContext, ModuleComponents), MaybeParseTree = yes(ParseTree) ). %---------------------------------------------------------------------------% % % src: module_start, STARTHERE component*, (module_end | epsilon) ENDHERE % % component: (interface_marker | epsilon), item* % (implementation_marker | epsilon), item* % module_start, STARTHERE component*, (module_end | epsilon) ENDHERE % %---------------------------------------------------------------------------% :- pred parse_src_file_components(string::in, int::in, module_name::in, list(module_name)::in, maybe(pair(module_section, prog_context))::in, maybe_lookahead::in, maybe_lookahead::out, cord(module_component)::in, cord(module_component)::out, file_name::in, file_name::out, counter::in, counter::out, read_module_errors::in, read_module_errors::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, MaybePrevSection, InitLookAhead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) :- get_next_item_or_marker(!.SourceFileName, FileString, FileStringLen, InitLookAhead, CurModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn), ( ReadIOMResult = read_iom_eof, % If we have found end-of-file, then we are done. FinalLookAhead = no_lookahead ; ReadIOMResult = read_iom_parse_term_error(ItemSpec), add_nonfatal_error(rme_could_not_read_term, [ItemSpec], !Errors), % Continue looking for a section marker. parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, MaybePrevSection, no_lookahead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; ReadIOMResult = read_iom_parse_item_errors(_IOMVarSet, IOMTerm, _Specs), % Generate an error for the missing section marker. % Leave the term in the lookahead, but otherwise handle the term % as it were an unexpected but perfectly parseable term, i.e. follow % the pattern of the iom_item case below. Context = get_term_context(IOMTerm), generate_missing_start_section_warning_src(CurModuleName, Context, !Errors), SectionKind = ms_implementation, SectionContext = dummy_context, ItemSeqInitLookAhead = lookahead(ReadIOMResult), parse_item_sequence(FileString, FileStringLen, CurModuleName, ItemSeqInitLookAhead, ItemSeqFinalLookAhead, cord.init, InclsCord, cord.init, AvailsCord, cord.init, FIMsCord, cord.init, ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), add_section_component(CurModuleName, SectionKind, SectionContext, InclsCord, AvailsCord, FIMsCord, ItemsCord, !ModuleComponents), % We have read in one component; recurse to read in other components. parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, yes(SectionKind - SectionContext), ItemSeqFinalLookAhead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; ReadIOMResult = read_iom_ok(_IOMVarSet, IOMTerm, IOM), ( IOM = iom_marker_src_file(!:SourceFileName), parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, MaybePrevSection, no_lookahead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; IOM = iom_marker_version_numbers(_), Pieces = [words("Error:")] ++ color_as_incorrect([words("unexpected version_numbers record"), words("in source file.")]) ++ [nl], Spec = spec($pred, severity_error, phase_read_files, get_term_context(IOMTerm), Pieces), add_nonfatal_error(rme_nec, [Spec], !Errors), parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, MaybePrevSection, no_lookahead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; IOM = iom_marker_module_start(RawStartModuleName, StartContext, _StartSeqNum), ( RawStartModuleName = unqualified(RawBaseName), StartModuleName = qualified(CurModuleName, RawBaseName) ; RawStartModuleName = qualified(RawModuleName, RawBaseName), ( if partial_sym_name_matches_full(RawModuleName, CurModuleName) then StartModuleName = qualified(CurModuleName, RawBaseName) else Pieces = [words("Error: module qualification of"), words("nested submodule")] ++ color_as_incorrect( [qual_sym_name(RawStartModuleName)]) ++ [words("does not match the then-current module,")] ++ color_as_correct([qual_sym_name(CurModuleName), suffix(".")]) ++ [nl], Spec = spec($pred, severity_error, phase_t2pt, StartContext, Pieces), add_nonfatal_error(rme_nec, [Spec], !Errors), % Recover partially by ignoring the bad module % qualification. The recovery is only partial because % an end_module marker that matches the incorrect module % name will get another error message about the % `:- end_module' not matching the `:- module' declaration, % which will be at least a bit misleading. StartModuleName = qualified(CurModuleName, RawBaseName) ) ), parse_src_file_submodule(FileString, FileStringLen, ContainingModules, MaybePrevSection, CurModuleName, StartModuleName, StartContext, no_lookahead, SubModuleFinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), % We have read in one component; recurse to read in others. parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, MaybePrevSection, SubModuleFinalLookAhead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; ( IOM = iom_marker_section(_, _, _) ; IOM = iom_marker_include(_) ; IOM = iom_marker_avail(_) ; IOM = iom_marker_fim(_) ; IOM = iom_item(_) ; IOM = iom_item_and_error_specs(_, _) ; IOM = iom_handled_no_error ; IOM = iom_handled_error(_) ), ( IOM = iom_marker_section(SectionKind, SectionContext, _SectionSeqNum), ItemSeqInitLookAhead = no_lookahead ; ( ( IOM = iom_marker_include(_) ; IOM = iom_marker_avail(_) ; IOM = iom_marker_fim(_) ; IOM = iom_item(_) ; IOM = iom_handled_no_error ) ; IOM = iom_handled_error(ItemSpecs), add_nonfatal_error(rme_nec, ItemSpecs, !Errors) ; IOM = iom_item_and_error_specs(_, ItemSpecs), add_nonfatal_error(rme_nec, ItemSpecs, !Errors) ), ( MaybePrevSection = yes(SectionKind - SectionContext) % When a nested module occurs in a section, the section % continues after the nested module without the need % for a new section declaration. ; MaybePrevSection = no, Context = get_term_context(IOMTerm), generate_missing_start_section_warning_src(CurModuleName, Context, !Errors), % The following code is duplicated in the case for % read_iom_parse_item_errors above. SectionKind = ms_implementation, SectionContext = dummy_context ), ItemSeqInitLookAhead = lookahead(ReadIOMResult) ), % The following code is duplicated in the case for % read_iom_parse_item_errors above. parse_item_sequence(FileString, FileStringLen, CurModuleName, ItemSeqInitLookAhead, ItemSeqFinalLookAhead, cord.init, InclsCord, cord.init, AvailsCord, cord.init, FIMsCord, cord.init, ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), add_section_component(CurModuleName, SectionKind, SectionContext, InclsCord, AvailsCord, FIMsCord, ItemsCord, !ModuleComponents), % We have read in one component; recurse to read in other % components. parse_src_file_components(FileString, FileStringLen, CurModuleName, ContainingModules, yes(SectionKind - SectionContext), ItemSeqFinalLookAhead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; IOM = iom_marker_module_end(EndedModuleName, EndContext, _EndSeqNum), handle_module_end_marker(CurModuleName, ContainingModules, ReadIOMResult, EndedModuleName, EndContext, FinalLookAhead, !Errors) ) ). :- pred add_section_component(module_name::in, module_section::in, prog_context::in, cord(item_include)::in, cord(item_avail)::in, cord(item_fim)::in, cord(item)::in, cord(module_component)::in, cord(module_component)::out) is det. add_section_component(ModuleName, SectionKind, SectionContext, InclsCord, AvailsCord, FIMsCord, ItemsCord, !ModuleComponents) :- ( if cord.is_empty(InclsCord), cord.is_empty(AvailsCord), cord.is_empty(FIMsCord), cord.is_empty(ItemsCord) then true else Component = mc_section(ModuleName, SectionKind, SectionContext, InclsCord, AvailsCord, FIMsCord, ItemsCord), cord.snoc(Component, !ModuleComponents) ). :- pred generate_missing_start_section_warning_src(module_name::in, prog_context::in, read_module_errors::in, read_module_errors::out) is det. generate_missing_start_section_warning_src(CurModuleName, Context, !Errors) :- Pieces = [invis_order_default_start(1, ""), words("Error: the contents of module"), qual_sym_name(CurModuleName), words("after the"), decl("module"), words("declaration")] ++ color_as_incorrect([words("should start with")]) ++ [words("either an")] ++ color_as_correct([decl("interface"), words("declaration")]) ++ [words("or an")] ++ color_as_correct([decl("implementation"), words("declaration.")]) ++ [nl, words("The following assumes that"), words("the missing declaration is an"), decl("implementation"), words("declaration."), nl], Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces), add_nonfatal_error(rme_no_section_decl_at_start, [Spec], !Errors). :- pred parse_src_file_submodule(string::in, int::in, list(module_name)::in, maybe(pair(module_section, prog_context))::in, module_name::in, module_name::in, prog_context::in, maybe_lookahead::in, maybe_lookahead::out, cord(module_component)::in, cord(module_component)::out, file_name::in, file_name::out, counter::in, counter::out, read_module_errors::in, read_module_errors::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_src_file_submodule(FileString, FileStringLen, ContainingModules, MaybePrevSection, ContainingModuleName, StartModuleName, StartContext, InitLookAhead, FinalLookAhead, !ModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) :- ( MaybePrevSection = yes(SectionKind - SectionContext) ; MaybePrevSection = no, NoSectionPieces = [words("Error: nested submodule"), qual_sym_name(StartModuleName)] ++ color_as_incorrect([words("should be preceded")]) ++ [words("by either an")] ++ color_as_correct([decl("interface"), words("declaration")]) ++ [words("or an")] ++ color_as_correct([decl("implementation"), words("declaration.")]) ++ [nl, words("The following assumes that"), words("the missing declaration is an"), decl("interface"), words("declaration."), nl], NoSectionSpec = spec($pred, severity_error, phase_t2pt, StartContext, NoSectionPieces), % XXX ITEM_LIST Should this be a situation-specific rme_X value? add_nonfatal_error(rme_no_section_decl_at_start, [NoSectionSpec], !Errors), SectionKind = ms_interface, SectionContext = dummy_context ), NestedContainingModules = [StartModuleName | ContainingModules], NestedMaybePrevSection = no, parse_src_file_components(FileString, FileStringLen, StartModuleName, NestedContainingModules, NestedMaybePrevSection, InitLookAhead, FinalLookAhead, cord.init, NestedModuleComponents, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), SubModuleParseTreeSrc = parse_tree_src(StartModuleName, StartContext, NestedModuleComponents), Component = mc_nested_submodule(ContainingModuleName, SectionKind, SectionContext, SubModuleParseTreeSrc), cord.snoc(Component, !ModuleComponents). :- pred handle_module_end_marker(module_name::in, list(module_name)::in, read_iom_result::in, module_name::in, prog_context::in, maybe_lookahead::out, read_module_errors::in, read_module_errors::out) is det. handle_module_end_marker(CurModuleName, ContainingModules, ReadIOMResult, EndedModuleName, EndContext, FinalLookAhead, !Errors) :- ( if CurModuleName = EndedModuleName then FinalLookAhead = no_lookahead else if partial_sym_name_matches_full(EndedModuleName, CurModuleName) then % XXX ITEM_LIST Should this be an error? Warning? FinalLookAhead = no_lookahead else if is_for_containing_module(EndedModuleName, ContainingModules) then Pieces = [words("Error:")] ++ color_as_incorrect([words("missing"), decl("end_module"), words("declaration")]) ++ [words("for")] ++ color_as_subject([qual_sym_name(CurModuleName), suffix(".")]) ++ [nl], Spec = spec($pred, severity_error, phase_t2pt, EndContext, Pieces), add_fatal_error(frme_bad_module_end, [Spec], !Errors), FinalLookAhead = lookahead(ReadIOMResult) else Pieces = [words("Error: this")] ++ color_as_subject([decl("end_module"), words("declaration for"), qual_sym_name(EndedModuleName)]) ++ color_as_incorrect([words("is not for the module"), words("at whose end it appears,")]) ++ [words("which is")] ++ color_as_correct([qual_sym_name(CurModuleName), suffix(".")]) ++ [nl], Spec = spec($pred, severity_error, phase_t2pt, EndContext, Pieces), add_fatal_error(frme_bad_module_end, [Spec], !Errors), % Eat the bad end_module declaration. FinalLookAhead = no_lookahead ). :- pred is_for_containing_module(module_name::in, list(module_name)::in) is semidet. is_for_containing_module(EndedModuleName, [ContainingModule | ContainingModules]) :- ( if partial_sym_name_matches_full(EndedModuleName, ContainingModule) then true else is_for_containing_module(EndedModuleName, ContainingModules) ). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% % This part of the module contains utility predicates. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- type maybe_module_header ---> no_valid_module_header( % A complete description of the errors involved. read_module_errors ) ; valid_module_header( % The name in the ":- module" decl, and its context. module_name, prog_context ). :- pred parse_module_header(string::in, int::in, module_name::in, list(prog_context)::in, file_name::in, maybe_module_header::out, counter::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_module_header(FileString, FileStringLen, DefaultModuleName, DefaultExpectationContexts, SourceFileName, MaybeModuleHeader, !:SeqNumCounter, !LineContext, !LinePosn) :- counter.init(1, !:SeqNumCounter), parse_first_module_decl(FileString, FileStringLen, yes(DefaultModuleName), DefaultExpectationContexts, ModuleDeclPresent, may_not_change_source_file_name, SourceFileName, _SourceFileName, !SeqNumCounter, !LineContext, !LinePosn), ( ModuleDeclPresent = no_module_decl_present(_MaybeLookAhead, _Context, NoModuleSpec), Errors0 = init_read_module_errors, add_nonfatal_error(rme_no_module_decl_at_start, [NoModuleSpec], Errors0, Errors), MaybeModuleHeader = no_valid_module_header(Errors) ; ModuleDeclPresent = wrong_module_decl_present(ModuleName, ModuleNameContext, WrongModuleSpec), NoModuleSpec = report_wrong_module_start(ModuleNameContext, DefaultModuleName, ModuleName), Errors0 = init_read_module_errors, add_nonfatal_error(rme_no_module_decl_at_start, [NoModuleSpec], Errors0, Errors1), add_nonfatal_error(rme_unexpected_module_name, [WrongModuleSpec], Errors1, Errors), MaybeModuleHeader = no_valid_module_header(Errors) ; ModuleDeclPresent = right_module_decl_present(ModuleName, ModuleNameContext), MaybeModuleHeader = valid_module_header(ModuleName, ModuleNameContext) ). :- type maybe_module_decl_present ---> no_module_decl_present( % Any lookahead left from the input. This lookahead is needed % ONLY to allow us to continue parsing .m files after % a missing initial ":- module" decl. maybe_lookahead, % The context of the term that is there instead of the module % declaration. Will contain term.context_init if there is % no such term. prog_context, % A message for the error. The error category is % always rme_no_module_decl_at_start. error_spec ) ; wrong_module_decl_present( % The name in the ":- module" decl, and its context. module_name, prog_context, % A message for the error, which may be conditional. % The category is always rme_unexpected_module_name. error_spec ) ; right_module_decl_present( % The name in the ":- module" decl, and its context. module_name, prog_context ). % Files written by users may contain source_file pragmas, which change % the parser's notion of the current filename. For these, callers should % therefore pass may_change_source_file_name. On the other hand, files % automatically generated by the compiler (.int* and .*opt files) % never contain source_file pragmas. For these, callers should pass % may_not_change_source_file_name, which calls for any occurrence of % a source_file pragma to be treated as the error it is. % :- type may_change_source_file_name ---> may_not_change_source_file_name ; may_change_source_file_name. % We used to have to jump through a few hoops when reading the first item, % to allow us to recover from a missing initial `:- module' declaration. % Now we don't bother trying to recover in such cases. The only special % treatment that the first declaration gets is that we check whether % the actual module name matches the expected (default) module name. % :- pred parse_first_module_decl(string::in, int::in, maybe(module_name)::in, list(prog_context)::in, maybe_module_decl_present::out, may_change_source_file_name::in, file_name::in, file_name::out, counter::in, counter::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_first_module_decl(FileString, FileStringLen, MaybeDefaultModuleName, DefaultExpectationContexts, ModuleDeclPresent, MayChangeSourceFileName, !SourceFileName, !SeqNumCounter, !LineContext, !LinePosn) :- mercury_term_parser.read_term_from_linestr(!.SourceFileName, FileString, FileStringLen, !LineContext, !LinePosn, FirstReadTerm), % We can pass a dummy module name to read_term_to_iom_result because % - there are only two kinds of item_or_markers that we do not return % for reprocessing, src file pragmas and ":- module" declarations, and % - these contain neither sym_names to be module qualified, nor % any error_specs that may contain the module name. DummyDefaultModuleName = unqualified(""), read_term_to_iom_result(DummyDefaultModuleName, !.SourceFileName, FirstReadTerm, MaybeFirstIOM, !SeqNumCounter), ( MaybeFirstIOM = read_iom_ok(_FirstVarSet, FirstTerm, FirstIOM), ( FirstIOM = iom_marker_src_file(!:SourceFileName), ( MayChangeSourceFileName = may_not_change_source_file_name, FirstLookAhead = lookahead(MaybeFirstIOM), FirstContext = get_term_context(FirstTerm), ModuleDeclPresent = no_module_decl_present(FirstLookAhead, FirstContext, report_missing_module_start(FirstContext)) ; MayChangeSourceFileName = may_change_source_file_name, % Apply and then skip `pragma source_file' decls, by calling % ourselves recursively with the new source file name. parse_first_module_decl(FileString, FileStringLen, MaybeDefaultModuleName, DefaultExpectationContexts, ModuleDeclPresent, MayChangeSourceFileName, !SourceFileName, !SeqNumCounter, !LineContext, !LinePosn) ) ; FirstIOM = iom_marker_module_start(StartModuleName, ModuleNameContext, _ModuleNameSeqNum), % The first term is a `:- module' decl, as expected. % Check whether it matches the expected module name, % if there is one (for "mmc -f", there isn't one). ( if MaybeDefaultModuleName = yes(DefaultModuleName), DefaultModuleName \= StartModuleName then report_module_has_unexpected_name(!.SourceFileName, DefaultModuleName, DefaultExpectationContexts, StartModuleName, yes(ModuleNameContext), NameSpec), ModuleDeclPresent = wrong_module_decl_present(StartModuleName, ModuleNameContext, NameSpec) else ModuleDeclPresent = right_module_decl_present(StartModuleName, ModuleNameContext) ) ; ( FirstIOM = iom_marker_module_end(_, _, _) ; FirstIOM = iom_marker_version_numbers(_) ; FirstIOM = iom_marker_section(_, _, _) ; FirstIOM = iom_marker_include(_) ; FirstIOM = iom_marker_avail(_) ; FirstIOM = iom_marker_fim(_) ; FirstIOM = iom_item(_) % Ignore the error_specs. ; FirstIOM = iom_item_and_error_specs(_, _) ; FirstIOM = iom_handled_no_error % Ignore the error_specs. ; FirstIOM = iom_handled_error(_) ), FirstLookAhead = lookahead(MaybeFirstIOM), FirstContext = get_term_context(FirstTerm), ModuleDeclPresent = no_module_decl_present(FirstLookAhead, get_term_context(FirstTerm), report_missing_module_start(FirstContext)) ) ; MaybeFirstIOM = read_iom_parse_item_errors(_, FirstTerm, _), LookAhead = lookahead(MaybeFirstIOM), FirstContext = get_term_context(FirstTerm), ModuleDeclPresent = no_module_decl_present(LookAhead, FirstContext, report_missing_module_start(FirstContext)) ; ( MaybeFirstIOM = read_iom_eof ; MaybeFirstIOM = read_iom_parse_term_error(_) ), FirstContext = term_context.context_init(!.SourceFileName, 1), ModuleDeclPresent = no_module_decl_present(no_lookahead, dummy_context, report_missing_module_start(FirstContext)) ). %---------------------------------------------------------------------------% % Read a sequence of items, until we find a marker that indicates % a change in section or module. If and when we find such a marker, % we stop reading, and return the term of that marker as the final % lookahead. % % We use the standard two level loop to avoid running out of stack % on long item sequences in grades that do not allow tail recursion. % :- pred parse_item_sequence(string::in, int::in, module_name::in, maybe_lookahead::in, maybe_lookahead::out, cord(item_include)::in, cord(item_include)::out, cord(item_avail)::in, cord(item_avail)::out, cord(item_fim)::in, cord(item_fim)::out, cord(item)::in, cord(item)::out, file_name::in, file_name::out, counter::in, counter::out, read_module_errors::in, read_module_errors::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_item_sequence(FileString, FileStringLen, ModuleName, InitLookAhead, FinalLookAhead, !InclsCord, !AvailsCord, !FIMsCord, !ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) :- get_next_item_or_marker(!.SourceFileName, FileString, FileStringLen, InitLookAhead, ModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn), parse_item_sequence_inner(FileString, FileStringLen, ModuleName, 1024, NumItemsLeft, ReadIOMResult, MidLookAhead, !InclsCord, !AvailsCord, !FIMsCord, !ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn), ( if NumItemsLeft = 0 then parse_item_sequence(FileString, FileStringLen, ModuleName, MidLookAhead, FinalLookAhead, !InclsCord, !AvailsCord, !FIMsCord, !ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) else FinalLookAhead = MidLookAhead ). :- pred parse_item_sequence_inner(string::in, int::in, module_name::in, int::in, int::out, read_iom_result::in, maybe_lookahead::out, cord(item_include)::in, cord(item_include)::out, cord(item_avail)::in, cord(item_avail)::out, cord(item_fim)::in, cord(item_fim)::out, cord(item)::in, cord(item)::out, file_name::in, file_name::out, counter::in, counter::out, read_module_errors::in, read_module_errors::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_item_sequence_inner(FileString, FileStringLen, ModuleName, !NumItemsLeft, ReadIOMResult, FinalLookAhead, !InclsCord, !AvailsCord, !FIMsCord, !ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) :- ( if !.NumItemsLeft =< 0 then FinalLookAhead = lookahead(ReadIOMResult) else ( ReadIOMResult = read_iom_eof, FinalLookAhead = no_lookahead ; ( ReadIOMResult = read_iom_parse_term_error(ItemSpec), add_nonfatal_error(rme_could_not_read_term, [ItemSpec], !Errors) ; ReadIOMResult = read_iom_parse_item_errors(_, _, ItemSpecs), add_nonfatal_error(rme_could_not_parse_item, ItemSpecs, !Errors) ), parse_next_item_or_marker(!.SourceFileName, FileString, FileStringLen, ModuleName, NextReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn), parse_item_sequence_inner(FileString, FileStringLen, ModuleName, !NumItemsLeft, NextReadIOMResult, FinalLookAhead, !InclsCord, !AvailsCord, !FIMsCord, !ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ; ReadIOMResult = read_iom_ok(_IOMVarSet, IOMTerm, IOM), !:NumItemsLeft = !.NumItemsLeft - 1, ( ( IOM = iom_marker_module_start(_, _, _) ; IOM = iom_marker_module_end(_, _, _) ; IOM = iom_marker_section(_, _, _) ), FinalLookAhead = lookahead(ReadIOMResult) ; ( IOM = iom_marker_src_file(!:SourceFileName) ; IOM = iom_marker_version_numbers(_), Pieces = [words("Error:")] ++ color_as_subject([words("version number records")]) ++ color_as_incorrect([words("should not appear"), words("anywhere except in automaticly"), words("generated interface files.")]) ++ [nl], Spec = spec($pred, severity_error, phase_read_files, get_term_context(IOMTerm), Pieces), add_nonfatal_error(rme_nec, [Spec], !Errors) ; IOM = iom_marker_include(Incls), Incls = one_or_more(HeadIncl, TailIncls), !:InclsCord = !.InclsCord ++ cord.from_list([HeadIncl | TailIncls]) ; IOM = iom_marker_avail(Avails), Avails = one_or_more(HeadAvail, TailAvails), !:AvailsCord = !.AvailsCord ++ cord.from_list([HeadAvail | TailAvails]) ; IOM = iom_marker_fim(FIM), cord.snoc(FIM, !FIMsCord) ; IOM = iom_item(Item), cord.snoc(Item, !ItemsCord) ; IOM = iom_item_and_error_specs(Item, ItemSpecs), cord.snoc(Item, !ItemsCord), add_nonfatal_error(rme_nec, ItemSpecs, !Errors) ; IOM = iom_handled_no_error ; IOM = iom_handled_error(HandledSpecs), add_nonfatal_error(rme_nec, HandledSpecs, !Errors) ), parse_next_item_or_marker(!.SourceFileName, FileString, FileStringLen, ModuleName, NextReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn), parse_item_sequence_inner(FileString, FileStringLen, ModuleName, !NumItemsLeft, NextReadIOMResult, FinalLookAhead, !InclsCord, !AvailsCord, !FIMsCord, !ItemsCord, !SourceFileName, !SeqNumCounter, !Errors, !LineContext, !LinePosn) ) ) ). %---------------------------------------------------------------------------% :- type maybe_lookahead ---> no_lookahead ; lookahead(read_iom_result). :- pred get_next_item_or_marker(file_name::in, string::in, int::in, maybe_lookahead::in, module_name::in, read_iom_result::out, counter::in, counter::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. get_next_item_or_marker(FileName, FileString, FileStringLen, InitLookAhead, ModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn) :- ( InitLookAhead = no_lookahead, parse_next_item_or_marker(FileName, FileString, FileStringLen, ModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn) ; InitLookAhead = lookahead(ReadIOMResult) ). :- pred parse_next_item_or_marker(file_name::in, string::in, int::in, module_name::in, read_iom_result::out, counter::in, counter::out, line_context::in, line_context::out, line_posn::in, line_posn::out) is det. parse_next_item_or_marker(FileName, FileString, FileStringLen, ModuleName, ReadIOMResult, !SeqNumCounter, !LineContext, !LinePosn) :- mercury_term_parser.read_term_from_linestr(FileName, FileString, FileStringLen, !LineContext, !LinePosn, ReadTermResult), read_term_to_iom_result(ModuleName, FileName, ReadTermResult, ReadIOMResult, !SeqNumCounter). %---------------------------------------------------------------------------% :- type read_iom_result ---> read_iom_eof % We have reached end-of-file. ; read_iom_parse_term_error(error_spec) % The call to mercury_term_parser.read_term_from_linestr % has failed, which means that what we found in the file string % is not a valid term. ; read_iom_parse_item_errors(varset, term, list(error_spec)) % We have successfully read a term from the file string, % but could not parse it as an item or marker. % The error category is implicitly rme_could_not_parse_item. ; read_iom_ok(varset, term, item_or_marker). % We have successfully read a term from the file string, % and parsed it as an item or marker. :- pred read_term_to_iom_result(module_name::in, string::in, read_term::in, read_iom_result::out, counter::in, counter::out) is det. read_term_to_iom_result(ModuleName, FileName, ReadTermResult, ReadIOMResult, !SeqNumCounter) :- % XXX ITEM_LIST Should add a prefix to eof, error, and term % in library/term_io.m? ( ReadTermResult = eof, ReadIOMResult = read_iom_eof ; ReadTermResult = error(ErrorMsg, LineNumber), Context = term_context.context_init(FileName, LineNumber), read_term_msg_to_pieces(ErrorMsg, Pieces), Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces), ReadIOMResult = read_iom_parse_term_error(Spec) ; ReadTermResult = term(VarSet, Term), counter.allocate(SeqNum, !SeqNumCounter), parse_item_or_marker(ModuleName, VarSet, Term, item_seq_num(SeqNum), MaybeItemOrMarker), ( MaybeItemOrMarker = ok1(ItemOrMarker), ReadIOMResult = read_iom_ok(VarSet, Term, ItemOrMarker) ; MaybeItemOrMarker = error1(Specs), ReadIOMResult = read_iom_parse_item_errors(VarSet, Term, Specs) ) ). :- pred read_term_msg_to_pieces(string::in, list(format_piece)::out) is det. read_term_msg_to_pieces(ErrorMsg, Pieces) :- % NOTE: I (zs) originally tried to add newlines between sentences % by breaking up ErrorMsg at periods, but that does not work, because % it also catches the periods that are part of the end-of-term token. % Splitting at \n characters is not subject to such false matches, % and is more flexible, in that mercury_term_parser.m can put them % where it wants them, not just at the end of every sentence. % (Though we probably will never want to use this capability.) % % It would be nice to add color to ErrorMsg, but that would require % % - either making the representation of lexer and parser errors % more complex than a simple string, % % - or looking for patterns such as "Syntax error at A: expected % B, C, or D", so that we could color A red and B, C and D green. % % Neither seems worthwhile. Lines = string.words_separator(unify('\n'), ErrorMsg), list.map(line_to_pieces, Lines, LinePieceLists), list.condense(LinePieceLists, Pieces). :- pred line_to_pieces(string::in, list(format_piece)::out) is det. line_to_pieces(Line, Pieces) :- Pieces = [words(Line), nl]. %---------------------------------------------------------------------------% :- func report_missing_module_start(prog_context) = error_spec. report_missing_module_start(FirstContext) = Spec :- Pieces = [invis_order_default_start(0, ""), words("Error:")] ++ color_as_incorrect([words("module must start with a"), decl("module"), words("declaration.")]) ++ [nl], Spec = spec($pred, severity_error, phase_t2pt, FirstContext, Pieces). :- func report_wrong_module_start(prog_context, module_name, module_name) = error_spec. report_wrong_module_start(FirstContext, Expected, Actual) = Spec :- Pieces = [words("Error: module starts with a"), decl("module"), words("declaration for the wrong module"), nl, words("Expected module")] ++ color_as_correct([qual_sym_name(Expected), suffix(",")]) ++ [words("got moduile")] ++ color_as_incorrect([qual_sym_name(Actual), suffix(".")]) ++ [nl], Spec = spec($pred, severity_error, phase_t2pt, FirstContext, Pieces). % The predicate that reads in source file handles all items and markers % it reads in, and stops only at an end_module declaration that matches % the name of the top level module in the file. If FileKind is % fk_src, we look for any items after this end_module marker. % % The predicates that read in interface and optimization files % handle only the items they expect in those files, since their contents % should be automatically generated by mmc itself, stopping (and returning % as lookahead) at items that don't fit the expected structure of those % files. If FileKind is fk_int or fk_opt, we look for any such unexpected % items. % :- pred check_for_unexpected_item_at_end(file_name::in, string::in, int::in, module_name::in, file_kind::in, maybe_lookahead::in, counter::in, read_module_errors::in, read_module_errors::out, line_context::in, line_posn::in) is det. check_for_unexpected_item_at_end(SourceFileName, FileString, FileStringLen, ModuleName, FileKind, FinalLookAhead, SeqNumCounter0, !Errors, LineContext, LinePosn) :- get_next_item_or_marker(SourceFileName, FileString, FileStringLen, FinalLookAhead, ModuleName, IOMResult, SeqNumCounter0, _SeqNumCounter, LineContext, _, LinePosn, _), ( IOMResult = read_iom_eof ; IOMResult = read_iom_parse_term_error(ItemSpec), add_nonfatal_error(rme_could_not_read_term, [ItemSpec], !Errors) ; IOMResult = read_iom_parse_item_errors(_VarSet, Term, ItemSpecs), add_nonfatal_error(rme_could_not_parse_item, ItemSpecs, !Errors), report_unexpected_term_at_end(FileKind, Term, !Errors) ; IOMResult = read_iom_ok(_IOMVarSet, IOMTerm, _IOM), report_unexpected_term_at_end(FileKind, IOMTerm, !Errors) ). :- pred report_unexpected_term_at_end(file_kind::in, term::in, read_module_errors::in, read_module_errors::out) is det. report_unexpected_term_at_end(FileKind, Term, !Errors) :- Context = get_term_context(Term), ( FileKind = fk_src, Error = rme_end_module_not_at_end_of_src, Pieces = [words("Error: there should be no code")] ++ color_as_incorrect([words("after the"), decl("end_module"), words("declaration.")]) ++ [nl] ; FileKind = fk_int(_IntFileKind), Error = rme_unexpected_term_in_int_or_opt, Pieces = [words("Error: unexpected item in interface file"), nl] ; FileKind = fk_opt(_OptFileKind), Error = rme_unexpected_term_in_int_or_opt, Pieces = [words("Error: unexpected item in optimization file"), nl] ), Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces), add_nonfatal_error(Error, [Spec], !Errors). %---------------------------------------------------------------------------% :- end_module parse_tree.parse_module. %---------------------------------------------------------------------------%