%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 1998-2000, 2003-2006 The University of Melbourne. % This file may only be copied under the terms of the GNU Library General % Public License - see the file COPYING.LIB in the Mercury distribution. %-----------------------------------------------------------------------------% % % File: help.m. % Author: zs. % Stability: low. % % This file provides a basic help system that stores information in help nodes % which are organized as a tree structure of arbitrary depth. % % The help system consists of a list of help list entries. Each entry % has a name, an index (an integer that determines its position in the list), % and a help node. Each node contains text that should shed some light % on the topic named by the node's entry. Each node also has an associated % list of child entries; this list may of course be empty. % %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% :- module mdb.help. :- interface. :- import_module io. :- import_module list. :- import_module type_desc. :- type system. :- type path == list(string). :- type help_res ---> help_ok ; help_error(string). %-----------------------------------------------------------------------------% % Initialize an empty help system. % :- pred init(system::out) is det. % Add a node to the given help system, at the given path, and with % the given name and index. If successful, return help_ok and the % updated help system; if not, return an error message and the % original help system. % :- pred add_help_node(system::in, path::in, int::in, string::in, string::in, help_res::out, system::out) is det. % Print the top-level help nodes. This should give an overview % of the main topics for which help is available. % :- pred help(system::in, io.output_stream::in, io::di, io::uo) is det. % Print the help node at the given path. If there is none, % print the top-level nodes. % :- pred path(system::in, path::in, io.output_stream::in, help_res::out, io::di, io::uo) is det. % Print all help nodes with the given name. If there are none, % print the top-level nodes. % :- pred name(system::in, string::in, io.output_stream::in, io::di, io::uo) is det. %-----------------------------------------------------------------------------% % Return the type_info for the type system, for use by C code. % :- pred help_system_type(type_desc::out) is det. % Help interpret a help_res for C code. % :- pred result_is_error(help_res::in, string::out) is semidet. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% :- implementation. :- import_module int. :- import_module require. :- import_module string. :- type system == list(entry). :- type node ---> node( text, list(entry) ). :- type text == string. % Should be one or more complete lines. :- type entry ---> entry( int, % This integer determines the position % of the node in the node list. A node % list is always sorted on this field. string, % The name of the node, which should % be one word or phrase. It must be % unique within the node list, but % need not be unique globally. node ). %-----------------------------------------------------------------------------% :- pragma foreign_export("C", init(out), "ML_HELP_init"). :- pragma foreign_export("C", add_help_node(in, in, in, in, in, out, out), "ML_HELP_add_help_node"). :- pragma foreign_export("C", help(in, in, di, uo), "ML_HELP_help"). :- pragma foreign_export("C", path(in, in, in, out, di, uo), "ML_HELP_path"). :- pragma foreign_export("C", name(in, in, in, di, uo), "ML_HELP_name"). :- pragma foreign_export("C", help_system_type(out), "ML_HELP_help_system_type"). :- pragma foreign_export("C", result_is_error(in, out), "ML_HELP_result_is_error"). %-----------------------------------------------------------------------------% init([]). add_help_node(Sys0, Path, Index, Name, Text, Res, Sys) :- Node = node(Text, []), add_node(Sys0, Path, Index, Name, Node, Res, Sys). :- pred add_node(system::in, path::in, int::in, string::in, node::in, help_res::out, system::out) is det. add_node(Nodes0, [Step | Steps], Index, Name, NewNode, Res, Nodes) :- ( one_path_step(Nodes0, Step, Entry0) -> Entry0 = entry(EntryIndex, EntryName, EntryNode0), EntryNode0 = node(Text, SubNodes0), add_node(SubNodes0, Steps, Index, Name, NewNode, Res, SubNodes), EntryNode = node(Text, SubNodes), Entry = entry(EntryIndex, EntryName, EntryNode), replace_entry(Nodes0, Entry, Nodes) ; string.append("invalid path component ", Step, Msg), Res = help_error(Msg), Nodes = Nodes0 ). add_node(Nodes0, [], Index, Name, Node, Res, Nodes) :- ( list.member(Entry1, Nodes0), Entry1 = entry(Index, _, _) -> Res = help_error("entry with given index already exists"), Nodes = Nodes0 ; list.member(Entry1, Nodes0), Entry1 = entry(_, Name, _) -> Res = help_error("entry with given name already exists"), Nodes = Nodes0 ; Res = help_ok, insert_into_entry_list(Nodes0, Index, Name, Node, Nodes) ). :- pred insert_into_entry_list(list(entry)::in, int::in, string::in, node::in, list(entry)::out) is det. insert_into_entry_list([], Index, Name, Node, [Entry]) :- Entry = entry(Index, Name, Node). insert_into_entry_list([Head | Tail], Index, Name, Node, List) :- Head = entry(HeadIndex, _, _), ( HeadIndex < Index -> insert_into_entry_list(Tail, Index, Name, Node, NewTail), List = [Head | NewTail] ; Entry = entry(Index, Name, Node), List = [Entry, Head | Tail] ). %-----------------------------------------------------------------------------% help(Sys, Stream, !IO) :- print_entry_list(Sys, Stream, !IO). name(Sys, Name, Stream, !IO) :- search_entry_list(Sys, Name, 0, Count, Stream, !IO), ( Count = 0 -> io.write_string("There is no such help topic.\n", !IO), help(Sys, Stream, !IO) ; true ). :- pred search_entry_list(list(entry)::in, string::in, int::in, int::out, io.output_stream::in, io::di, io::uo) is det. search_entry_list([], _, !C, _, !IO). search_entry_list([Entry | Tail], Name, !C, Stream, !IO) :- Entry = entry(_, EntryName, Node), ( Name = EntryName -> % We print this node, but don't search its children. print_node(Node, Stream, !IO), !:C = !.C + 1 ; search_node(Node, Name, !C, Stream, !IO), search_entry_list(Tail, Name, !C, Stream, !IO) ). :- pred search_node(node::in, string::in, int::in, int::out, io.output_stream::in, io::di, io::uo) is det. search_node(node(_, SubNodes), Name, !C, Stream, !IO) :- search_entry_list(SubNodes, Name, !C, Stream, !IO). path(Entries, Path, Stream, Result, !IO) :- ( Path = [Step | Tail], ( one_path_step(Entries, Step, Entry) -> Entry = entry(_, _, EntryNode), ( Tail = [], EntryNode = node(Text, _), io.write_string(Stream, Text, !IO), Result = help_ok ; Tail = [_ | _], EntryNode = node(_, SubEntries), path(SubEntries, Tail, Stream, Result, !IO) ) ; Msg = "error at path component """ ++ Step ++ """", Result = help_error(Msg) ) ; Path = [], Result = help_error("the path does not go that deep") ). %-----------------------------------------------------------------------------% :- pred print_entry_list(list(entry)::in, io.output_stream::in, io::di, io::uo) is det. print_entry_list([], _, !IO). print_entry_list([entry(_, _, Node) | Nodes], Stream, !IO) :- print_node(Node, Stream, !IO), print_entry_list(Nodes, Stream, !IO). :- pred print_node(node::in, io.output_stream::in, io::di, io::uo) is det. print_node(node(Text, _Nodes), Stream, !IO) :- io.write_string(Stream, Text, !IO). % XXX print_entry_list(Nodes, Stream, !IO). %-----------------------------------------------------------------------------% :- pred one_path_step(list(entry)::in, string::in, entry::out) is semidet. one_path_step([Head | Tail], Name, Entry) :- Head = entry(_, HeadName, _), ( HeadName = Name -> Entry = Head ; one_path_step(Tail, Name, Entry) ). :- pred replace_entry(list(entry)::in, entry::in, list(entry)::out) is det. replace_entry([], _, _) :- error("replace_entry: entry to be replaced not found"). replace_entry([Head | Tail], Entry, List) :- Head = entry(HeadIndex, _, _), Entry = entry(EntryIndex, _, _), ( HeadIndex = EntryIndex -> List = [Entry | Tail] ; replace_entry(Tail, Entry, NewTail), List = [Head | NewTail] ). %-----------------------------------------------------------------------------% help_system_type(Type) :- init(HelpInit), Type = type_of(HelpInit). result_is_error(help_error(Msg), Msg). %-----------------------------------------------------------------------------%