parent root object help_node var root name 'help_node var help_node title 0 var help_node brief 0 var help_node text 0 var help_node footnotes 0 var help_node references 0 var help_node upnode 0 var help_node subnodes 0 var help_node menu 0 method init_help_node if (caller() != $root) throw(~perm, "Caller is not root."); title = "untitled"; brief = ""; text = []; footnotes = []; references = #[]; upnode = $help_root; subnodes = []; menu = 0; $help_root.add_subnode(); . method uninit_help_node var i; if (caller() != $root) throw(~perm, "Caller is not root."); for i in (subnodes) (| i.set_upnode(.upnode()) |); (| upnode.del_subnode() |); . eval .initialize(); .set_fertile(1); .set_title("Generic"); . method title return title; . method set_title arg new_title; if (!.is_owned_by(sender())) throw(~perm, "Sender is not an owner."); if (type(new_title) != 'string) throw(~perm, "New title is not a string."); title = uppercase(new_title); upnode.invalidate_menu(); . method invalidate_menu menu = 0; . method upnode return upnode; . method subnodes return subnodes; . method set_upnode arg node; if (!node.is_owned_by(sender())) throw(~perm, "Sender isn't an owner."); (> node.add_subnode() <); (| upnode.del_subnode() |); upnode = node; . method add_subnode if (caller() != definer()) throw(~perm, "Invalid access to private method."); subnodes = setadd(subnodes, sender()); .invalidate_menu(); . method del_subnode if (caller() != definer()) throw(~perm, "Invalid access to private method."); subnodes = setremove(subnodes, sender()); .invalidate_menu(); . method path var cur, list; list = []; cur = this(); while (cur != $help_root) { cur = cur.upnode(); list = [cur, @list]; } return list; . method brief return brief; . method set_brief arg string; if (!.is_owned_by(sender())) throw( ~perm, "Sender isn't an owner."); brief = string; . method text var ret, sub; ret = .path(); // The returned text is the path from root to node, the node's name and brief description, and its text. ret = ["Help path: " + (ret ? $list.to_string( $list.map(ret, 'title), " ") | "<none>"), .title() + " [" + .brief() + "]", "----------------", @text]; if (!.subnodes()) return ret; // If there are subnodes, list them one per line with their brief descriptions ret = [@ret, ""]; for sub in (.subnodes()) ret = [@ret, "** " + pad(sub.name() + ":", 13) + sub.brief()]; return ret; . method footnote arg n; return (> footnotes[n] <); . method match_menu arg name; var sub, l; if (menu == 0) { // menu is invalid. Build it from scratch menu = references; for sub in (.subnodes()) menu = dict_add(menu, sub.name(), sub); } catch ~keynf { return menu[name]; } l = strlen(name); for sub in (menu) if (name == (|substr(sub[1], 1, l)|)) return sub[2]; throw(~keynf, "There is no subnode called " + toliteral(name) + "."); . method parse_line arg line, fns, refs; var sub, i, j, name, obj; // Shift parsed prefixes of `line' into `sub'. sub = ""; while (1) { // Look for a reference begin-marker: `(:' or `[:' i = "(:" in line; j = "[:" in line; if (!i && !j) break; if (!j || (i && i < j)) { // `(:' came first. sub = sub + substr(line, 1, i + 1); line = substr(line, i + 2); // Match the trigger name. i = ":" in line; if (!i) throw(~parse, "Expected `:' in " + toliteral(line) + "."); name = substr(line, 1, i-1); line = substr(line,i+1); // Match the referenced node. i = ":)" in line; if (!i) throw(~parse, "Expected `:)' in " + toliteral(line) + "."); obj = substr(line, 1, i - 1); catch any { obj = sender().match_help_path(obj); } with handler { if (obj[1] != "$") rethrow(error()); obj = todbref(substr(obj,2)); } line = substr(line, i + 2); sub = sub + name + ":)"; // write down the new reference refs = dict_add(refs, name, obj); } else { // '[:' came first. sub = sub + substr(line, 1, j + 1); line = substr(line, j + 2); i = ":]" in line; if (!i) throw(~parse, "Expected `:]' in " + toliteral(line) + "."); fns = [@fns, substr(line, 1, i - 1)]; sub = sub + tostr(listlen(fns)) + ":]"; line = substr(line, i+1); } } return [sub + line, fns, refs]; . method set_text arg input; var text, tmp, line; if (!.is_owned_by(sender())) throw(~perm, "Sender isn't an owner."); text = []; tmp = ["", [], #[]]; for line in (input) { tmp = replace(tmp, 1, line); tmp = (> .parse_line(@tmp) <); // Add the newly parsed line. text = [@text, tmp[1]]; } // If all went well, store the new info and invalidate the menu. text = text; footnotes = tmp[2]; references = tmp[3]; .invalidate_menu(); . method create_subnode arg name, b, t; var sub; if (!.is_owned_by(sender())) throw(~perm, "Sender isn't an owner."); sub = definer().spawn( name); sub.add_owner($help_root); sub.add_owner(sender()); catch any { sub.set_brief(b); sub.set_text(t); sub.set_upnode(this()); } with handler { sub.destroy(); rethrow(error()); } return sub; .