/* -*- LPC -*- */
/*
* $Locker: $
* $Id: autodoc_file.c,v 1.11 2001/11/13 21:32:51 pinkfish Exp $
* $Log: autodoc_file.c,v $
* Revision 1.11 2001/11/13 21:32:51 pinkfish
* Fix up a runtime.
*
* Revision 1.10 1999/06/23 01:42:09 pinkfish
* Stop the autodoc handler getting too deep recusion errors.
*
* Revision 1.9 1999/06/22 02:44:24 pinkfish
* Fix up an error that was causing too deep recursions.
*
* Revision 1.8 1999/06/22 01:33:26 pinkfish
* Fix up the errors in the way it was noticing changed files.
*
* Revision 1.7 1999/05/05 00:33:28 pinkfish
* Fix up the autodoc to use ignore in class files.
*
* Revision 1.6 1999/03/22 23:08:07 pinkfish
* Fixes to make it work properly with classes.
*
* Revision 1.5 1999/02/10 09:15:29 pinkfish
* Fix up to use classes.
*
* Revision 1.4 1998/05/13 16:25:34 pinkfish
* Make it check to see if the file has been deleted.
*
* Revision 1.3 1998/02/20 05:39:24 pinkfish
* Again fixing up the includ stuff.
* A bit neater this time around...
*
* Revision 1.2 1998/02/16 15:04:04 pinkfish
* Stopped it from ripping comments in from include files and using them for the main file.
*
* Revision 1.1 1998/01/06 05:04:22 ceres
* Initial revision
*
*/
/**
* The automatic document generator. It takes source files from various
* directories and creates help files from the comments embeded in the
* code.
*
* @see /obj/handlers/autodoc/autodoc_handler
* @author Pinkfish
* @started Fri Oct 24 16:03:57 EDT 1997
*/
#define MASTER_OB "/secure/master.c"
#define EOF -1
#define SAVE_DIR "/save/autodoc/"
nosave mapping private_functions;
mapping public_functions;
mapping protected_functions;
mapping inherits;
mapping main_docs;
mapping define_docs;
mapping includes;
mapping class_docs;
string file_name;
int last_changed;
int num_failed_tries;
/* temporary... */
nosave string current_comment;
nosave string current_file;
nosave int current_position;
nosave int changed;
/*
* We will only handle simple defines. Function ones we will ignore
*/
nosave mapping defines;
nosave mixed *exclude_methods;
private void setup();
private mapping parse_comment(string stuff);
private void do_parse_file(function func);
private int query_file_position();
private void handle_inherit(mixed *bits);
/**
* The start constructor of the file. This sets up all the basic variables
* and all that rubbish.
*/
void create() {
seteuid(getuid());
setup();
} /* create() */
private void setup() {
changed = 0;
main_docs = 0;
file_name = "";
private_functions = ([ ]);
public_functions = ([ ]);
protected_functions = ([ ]);
inherits = ([ ]);
defines = ([ ]);
define_docs = ([ ]);
includes = ([ ]);
class_docs = ([ ]);
current_comment = 0;
current_file = "";
current_position = 0;
last_changed = 0;
exclude_methods = ({ "setup", "create", "init", "dest_me", "reset" });
} /* setup() */
private int query_file_position() {
return current_position;
} /* query_file_position() */
private int lookahead_character(int num) {
if (current_position + num - 1 < strlen(current_file)) {
return current_file[current_position + num - 1];
}
return EOF;
} /* lookahead_character() */
private int next_character() {
if (current_position < strlen(current_file)) {
return current_file[current_position++];
}
return EOF;
} /* next_character() */
private int pop_character(int num) {
current_position += num;
} /* pop_character() */
/*
* Throw away everything to the end of the line.
*/
private void skip_to_end_of_line() {
int ch;
do {
ch = next_character();
} while (ch != '\r' && ch != '\n' && ch != EOF);
} /* skip_to_end_of_line() */
/*
* Throw away all the characters until the end of the comment.
*/
private string skip_to_end_of_comment() {
string data;
int ch;
int ok;
/*
* This will pull all the stuff out of a comment and stick them into
* a nice string. Wheeee!
*/
data = "";
do {
ok = 1;
ch = next_character();
if (ch == '*' && lookahead_character(1) == '/') {
/* End of comment... */
ok = 0;
pop_character(1);
} else if (ch == '\r' || ch == '\n') {
data += "\n";
if (lookahead_character(1) == ' ') {
pop_character(1);
}
if (lookahead_character(1) == '*' && lookahead_character(2) != '/') {
pop_character(1);
if (lookahead_character(1) == ' ') {
pop_character(1);
}
}
} else if (ch == EOF) {
ok = 0;
} else if (ch == '\\' && (lookahead_character(1) == '/' ||
lookahead_character(1) == '*' ||
lookahead_character(1) == '\\')) {
/* Build up our comment stuff... */
} else {
data += sprintf("%c", ch);
}
} while (ok);
return data;
} /* skip_to_end_of_comment() */
/*
* Skips all the spaces and comments that are in our way.
*/
private void skip_spaces_and_comments() {
int ok;
do {
switch (lookahead_character(1)) {
case ' ' :
case '\t' :
case '\n' :
case '\r' :
ok = 1;
/* Move our index up one... */
pop_character(1);
break;
case '/' :
/* Could be a comment... */
if (lookahead_character(2) == '/') {
ok = 1;
skip_to_end_of_line();
} else if (lookahead_character(2) == '*') {
ok = 1;
if (lookahead_character(3) != '*' ||
lookahead_character(4) == '*') {
/* Make sure it is not a code comment... */
pop_character(2);
skip_to_end_of_comment();
current_comment = 0;
} else {
pop_character(3);
if (lookahead_character(1) == ' ') {
pop_character(1);
}
if (!main_docs) {
main_docs = parse_comment(skip_to_end_of_comment());
} else {
current_comment = skip_to_end_of_comment();
}
}
} else {
ok = 0;
}
break;
default :
ok = 0;
break;
}
} while (ok);
} /* skip_spaces_and_comments() */
/*
* Expands the defines...
*/
private string expand_token(string token) {
if (defines[token]) {
return defines[token];
}
return 0;
} /* expand_token() */
/*
* Gets the next token...
*/
private string get_word() {
string data;
int ok;
int ch;
skip_spaces_and_comments();
ok = 1;
data = "";
ch = lookahead_character(1);
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch == '_')) {
do {
ch = lookahead_character(1);
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
(ch == '_')) {
ch = next_character();
data += sprintf("%c", ch);
} else {
ok = 0;
}
} while (ok);
if (expand_token(data)) {
current_file = expand_token(data) + current_file[current_position..];
current_position = 0;
return get_word();
}
} else if ((ch >= '0' && ch <= '9') ||
(ch == '-')) {
if (ch == '-') {
data += sprintf("%c", next_character());
}
/* Number, only search for number bits... */
do {
ch = lookahead_character(1);
if ((ch >= '0' && ch <= '9') ||
(ch >= '.')) {
ch = next_character();
data += sprintf("%c", ch);
} else {
ok = 0;
}
} while (ok);
} else if (ch == '\"' || ch == '\'') {
int end_ch;
end_ch = ch;
ch = next_character();
data += sprintf("%c", ch);
do {
ch = next_character();
if (ch == end_ch) {
ok = 0;
data += sprintf("%c", ch);
} else if (ch == '\\') {
/* Skip the next character... */
ch = next_character();
data += sprintf("\\%c", ch);
} else if (ch == EOF) {
ok = 0;
} else {
data += sprintf("%c", ch);
}
} while (ok);
} else if (ch == '(') {
if (lookahead_character(2) == '{' ||
lookahead_character(2) == '[') {
return sprintf("%c%c", next_character(), next_character());
}
return sprintf("%c", next_character());
} else if (ch == '}' || ch == ']') {
if (lookahead_character(2) == ')') {
return sprintf("%c%c", next_character(), next_character());
}
return sprintf("%c", next_character());
} else if (ch == ';' ||
ch == ')' ||
ch == '=' ||
ch == '{' ||
ch == '}') {
/* open brace, semi colon, close brace... is a special thingy... */
return sprintf("%c", next_character());
} else if (ch == EOF) {
return "";
} else {
/* All non-space, non alphanumeric... Dump together... */
do {
ch = lookahead_character(1);
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
(ch == '_') ||
(ch == ' ') ||
(ch == '\t') ||
(ch == '\n') ||
(ch == '(') ||
(ch == ')') ||
(ch == EOF) ||
(ch == ';') ||
(ch == '=') ||
(ch == '{') ||
(ch == '}') ||
(ch == '\'') ||
(ch == '\"') ||
(ch == '\r')) {
ok = 0;
} else {
ch = next_character();
data += sprintf("%c", ch);
}
} while (ok);
}
return data;
} /* get_word() */
/*
* Attempts to get a complete statement...
*/
private mixed *get_statement(string start) {
mixed *bits;
string curr;
int depth;
string temp_comment;
int last_pos;
int in_class;
/* If it starts with the class keyword, then is must be a class... */
/* We rip until a semi colon or an open brace... */
/*
* If we find a semi colon... Then it is a predef or an inherit
* statement...
*/
bits = ({ start });
do {
last_pos = query_file_position();
curr = get_word();
bits += ({ curr });
if (last_pos != query_file_position()) {
/* Make sure it is doing something... */
reset_eval_cost();
}
} while (curr != ";" && curr != "{" && curr != "");
if (curr == "{") {
if (member_array("class", bits) != -1 &&
member_array("(", bits) == -1) {
/*
* We have a class, now we need to parse this in a useful way.
* Sadly this cannot fit into the normal way of parsing. It is an
* exception to one of those rules :)
*/
in_class = 1;
}
/* Grab the rest... but ignore it... */
/* Keep the current comment though... */
temp_comment = current_comment;
depth = 1;
do {
last_pos = query_file_position();
curr = get_word();
if (curr == "{") {
depth++;
} else if (curr == "}") {
depth--;
} else if (curr == "") {
/* End of file... */
depth = 0;
}
if (last_pos != query_file_position()) {
/* Make sure it is doing something... */
reset_eval_cost();
}
if (in_class) {
bits += ({ curr });
}
} while (depth > 0);
current_comment = temp_comment;
}
return bits;
} /* get_statement() */
/*
* This parses the comment into the appropriate bits...
*/
private mapping parse_comment(string stuff) {
string *bits;
int i;
mapping frog;
string name;
int j;
int rabbit;
if (!stuff) {
return ([ ]);
}
if (stuff[0] == '@') {
stuff = "\n" + stuff;
} else {
stuff = "\n@main " + stuff;
}
bits = explode(stuff, "\n@");
frog = ([ ]);
for (i = 0; i< sizeof(bits); i++) {
j = strsrch(bits[i], " ");
rabbit = strsrch(bits[i], "\n");
if (j == -1 || (rabbit != -1 && rabbit < j)) {
j = rabbit;
}
if (j > 0) {
name = bits[i][0..j - 1];
stuff = bits[i][j+1..];
if (!frog[name]) {
frog[name] = ({ stuff });
} else {
frog[name] += ({ stuff });
}
}
}
return frog;
} /* parse_comment() */
/*
* Handles a class...
*/
private void handle_class(mixed *bits) {
string name;
int i;
string *types;
mapping comm;
name = bits[1];
types = ({ });
i = member_array("{", bits);
if (i != -1) {
/* Ok, figure out all the elements and types. */
bits = bits[i + 1..];
while (sizeof(bits)) {
i = member_array(";", bits);
if (i != -1) {
types += ({ ({ bits[i - 1], bits[0..i - 2] }) });
bits = bits[i + 1..];
} else {
bits = ({ });
}
}
comm = parse_comment(current_comment);
if (!comm["ignore"]) {
class_docs[name] = ({ 0, types,
parse_comment(current_comment) });
}
}
} /* handle_class() */
/*
* Handles an inherit... Sticks all the needed stuff in like the
* thing being inherited and the state of it and stuff.
*/
private void handle_inherit(mixed *bits) {
int pos;
string name;
pos = member_array("inherit", bits);
if (pos >= 0) {
/* Need to strip off the last thingy which should be a semi colon. */
name = implode(map(bits[pos+1.. sizeof(bits)-2],
function(string str) {
if (str[0] == '\"') {
sscanf(str, "\"%s\"", str);
return str;
}
return "";
} ), "");
inherits[name] = bits[0..pos-1];
}
} /* handle_inherit() */
/*
* We have found a function definition... Create the information
* we need from it.
*/
private void handle_function_definition(mixed *bits) {
int pos;
int end_pos;
int new_pos;
string name;
string *type;
mixed *args;
mapping comm;
pos = member_array("(", bits);
if (pos > 0) {
name = bits[pos-1];
if (member_array(name, exclude_methods) == -1) {
type = bits[0..pos-2];
if (sizeof(type) == 0) {
type = ({ "int" });
}
end_pos = member_array(")", bits, pos);
args = ({ });
if (end_pos > pos + 1) {
/* Whoo, there are some arguments... */
pos++;
while (member_array(",", bits, pos) != -1) {
new_pos = member_array(",", bits, pos);
args += ({ bits[pos..new_pos-2], bits[new_pos-1] });
pos = new_pos + 1;
}
args += ({ bits[pos..end_pos -2], bits[end_pos-1] });
}
comm = parse_comment(current_comment);
if (!comm["ignore"]) {
if (member_array("private", type) != -1) {
type -= ({ "private" });
private_functions[name] = ({ type, args,
comm });
} else if (member_array("protected", type) != -1) {
type -= ({ "protected" });
protected_functions[name] = ({ type, args,
comm });
} else {
type -= ({ "public" });
public_functions[name] = ({ type, args,
comm });
}
}
}
current_comment = 0;
}
} /* handle_function_definition() */
/*
* Gets the rest of the line. Mostly used by the hash stuff...
*/
private string get_rest_of_line() {
string value;
int ch;
int last_pos;
value = "";
/* Skip the spaces at the start... */
ch = lookahead_character(1);
while (ch == ' ' || ch == '\t') {
pop_character(1);
ch = lookahead_character(1);
}
/* Get the whole definition */
do {
last_pos = query_file_position();
ch = next_character();
if (ch == '\\') {
/* Skip one! This is escaped... */
ch = next_character();
if (ch == '\r' && lookahead_character(1) == '\n') {
/* Handle MSDOS sillyness */
ch = next_character();
}
ch = ' ';
}
if (last_pos != query_file_position()) {
reset_eval_cost();
}
value += sprintf("%c", ch);
} while (ch != '\n' && ch != '\r' && ch != EOF);
return value;
} /* get_rest_of_line() */
/*
* Handles the hairy #define's and #includes.
*/
private void handle_hash() {
int i;
string token;
string name;
string value;
string *bits;
string stuff;
string inc_name;
string curr_comm;
mapping comm;
token = get_word();
/* Not really sure what to do about these right now... */
switch (token) {
case "define" :
case "defin" :
/* Ok, with a define... we... get a name and a substitution value. */
curr_comm = current_comment;
value = get_rest_of_line();
if (sscanf(value, "%s %s", name, value) == 2) {
defines[name] = value;
if (token == "define") {
comm = parse_comment(curr_comm);
if (!comm["ignore"]) {
define_docs[name] = comm;
}
current_comment = 0;
}
}
break;
case "include" :
/*
* Eeeek! This will be evil... At the moment assume they don't
* do arithmetic in the #include line...
*/
value = get_rest_of_line();
if (value[0] == '\"') {
/* This means a local inherit... */
bits = explode(file_name, "/");
sscanf(value, "\"%s\"", name);
stuff = read_file(implode(bits[0..<2], "/") +
"/" + name);
if (stuff) {
inc_name = "/" + implode(bits[0..<2], "/") +
"/" + name;
}
} else if (value[0] == '<') {
/* This means it could be a global inherit... */
sscanf(value, "<%s>", name);
}
if (name[0] == '/') {
stuff = read_file(name);
if (stuff) {
inc_name = name;
}
}
bits = MASTER_OB->define_include_dirs();
while (!stuff && i < sizeof(bits)) {
stuff = read_file(sprintf(bits[i], name));
if (stuff) {
/* Remove all the autodoc comments from the include file... */
stuff = replace_string(stuff, "/**", "/* ");
/* Ignore included classes. */
stuff = replace_string(stuff, "class ", "clas ");
stuff = replace_string(stuff, "#define ", "#defin ");
inc_name = sprintf(bits[i], name);
}
i++;
}
if (inc_name) {
/* Zap double '//'s */
inc_name = replace(inc_name, "//", "/");
if (inc_name[0] != '/') {
inc_name = "/" + inc_name;
}
}
if (stuff && !includes[inc_name]) {
/* We found it! */
/* Stick the include bit in where it is needed... */
current_file = stuff + current_file[current_position..];
current_position = 0;
includes[inc_name] = unguarded( (: stat($(inc_name)) :) )[1];
}
break;
default :
/* Pragma's and other silly hash things */
skip_to_end_of_line();
break;
}
} /* handle_hash() */
/*
* Gets the next token... This is state dependant, we need to know
* what state we are in to figure out what sort of token we are probably
* looking for...
*/
private void next_statement() {
string token;
string *bits;
token = get_word();
if (token[0] == '#') {
/* A hash directive... */
return handle_hash();
} else if (token == ";") {
return ;
} else if (token != "") {
bits = get_statement(token);
if (member_array("inherit", bits) != -1) {
/* An inherit statement. */
return handle_inherit(bits);
} else if (bits[0] == "class" &&
member_array("(", bits) == -1) {
return handle_class(bits);
} else if (bits[sizeof(bits) - 1] == "{" &&
member_array("=", bits) == -1) {
return handle_function_definition(bits);
} else {
/* It was a predef or a variable declaration... */
return ;
}
}
} /* next_statement() */
/**
* Loads up the currently set file name from the archives.
*/
void load_file() {
unguarded((: restore_object(SAVE_DIR + replace_string(file_name, "/", ".")) :));
if (!includes) {
includes = ([ ]);
}
if (!class_docs) {
class_docs = ([ ]);
}
} /* load_file() */
/**
* Saves the current file name to the archives.
*/
void save_file() {
unguarded((: save_object(SAVE_DIR + replace_string(file_name, "/", ".")) :));
} /* save_file() */
/**
* Parses the input file figuring out all the documentation bits of it.
*
* @param name the name of the file to parse
* @param func the function to call when the parsing is finished
* @param only_load a flag telling us to only load the information
*/
void parse_file(string name, function func, int only_load) {
int curr_change;
int my_change;
string my_name;
int reload;
setup();
file_name = name;
load_file();
if (!only_load) {
if (sizeof(unguarded( (: stat($(name)) :) )) > 1) {
curr_change = unguarded( (: stat($(name)) :) )[1];
my_name = file_name(this_object());
sscanf(my_name, "%s#%*s", my_name);
my_name += ".c";
my_change = unguarded( (: stat($(my_name)) :) )[1];
reload = curr_change > last_changed;
if (my_change > last_changed &&
my_change > curr_change) {
curr_change = my_change;
reload = 1;
}
if (!reload) {
/* Check to see if the include files have changed. */
foreach (my_name, my_change in includes) {
if ( unguarded( (: stat($(my_name)) :) )[1] != my_change) {
reload = 1;
break;
}
}
}
if (reload) {
setup();
num_failed_tries = 0;
file_name = name;
changed = 1;
last_changed = curr_change;
current_file = read_file(name);
current_position = 0;
if (catch(do_parse_file(func))) {
evaluate(func);
}
} else {
if (num_failed_tries) {
num_failed_tries = 0;
save_file();
}
call_out( (: evaluate($1) :), 2, func);
}
} else {
num_failed_tries++;
save_file();
call_out( (: evaluate($1) :), 2, func);
}
} else {
call_out( (: evaluate($1) :), 2, func);
}
} /* parse_file() */
private void do_parse_file(function func) {
int num;
if (unguarded( (: stat(file_name) :))[1] > last_changed) {
/* Darn, changed while we were reading it. */
return parse_file(file_name, func, 0);
}
num = 0;
while (lookahead_character(1) != EOF && num < 2) {
next_statement();
num++;
}
if (lookahead_character(1) == EOF) {
save_file();
call_out((: evaluate($1) :), 2, func);
} else {
call_out((: do_parse_file($1) :), 2, func);
}
} /* do_parse_file() */
/**
* Returns the inherits mapping of the system. This returns a mapping of the
* form ([ inherit_name : ({ flags }) ]). Where the name of the inherit is
* something like "/std/object" and the flags are things you can apply to
* an inherit, like "protected" or "private". If there are no flags then
* the flags will be an empty array.
*
* @return a mapping of things inherited by this file
*/
mapping query_inherits() { return inherits; }
/**
* The mapping of private functions.
* The mapping is of the form ([ func_name : ({ type, args, docs }) ]).
* The type bit is an array of the type name, ie: ({ "int" }) or
* ({ "mixed", "*" }). The args bit looks like ({ "name", type }),
* where the type is the same as in the previous array. The docs is
* a mapping of the form ([ "tag" : ({ data }) ]), where each reference
* to a tag creates a new element in the data array.
*
* @return a mapping containing the private functions
*/
mapping query_private_functions() { return private_functions; }
/**
* The mapping of public functions.
* The mapping is of the form ([ func_name : ({ type, args, docs }) ]).
* The type bit is an array of the type name, ie: ({ "int" }) or
* ({ "mixed", "*" }). The args bit looks like ({ "name", type }),
* where the type is the same as in the previous array. The docs is
* a mapping of the form ([ "tag" : ({ data }) ]), where each reference
* to a tag creates a new element in the data array.
*
* @return a mapping containing the public functions
*/
mapping query_public_functions() { return public_functions; }
/**
* The mapping of protected functions.
* The mapping is of the form ([ func_name : ({ type, args, docs }) ]).
* The type bit is an array of the type name, ie: ({ "int" }) or
* ({ "mixed", "*" }). The args bit looks like ({ "name", type }),
* where the type is the same as in the previous array. The docs is
* a mapping of the form ([ "tag" : ({ data }) ]), where each reference
* to a tag creates a new element in the data array.
*
* @return a mapping containing the protected functions
*/
mapping query_protected_functions() { return protected_functions; }
/**
* Returns the main docs for the class. The mapping is of
* the form ([ "tag" : ({ data }) ]), where each reference
* to a tag creates a new element in the data array.
*
* @return a mapping containing the main docs for the file
*/
mapping query_main_docs() {
if (main_docs) {
return main_docs;
}
return ([ ]);
} /* query_main_docs() */
/**
* The file name being processed.
*
* @return the name of the file being processed
*/
string query_file_name() { return file_name; }
/**
* The defines which were setup in the class. This is the mapping of the
* defines which were processed. The format of the mapping is
* ([ "name" : "value" ]), where the name is the name of the define and
* the value is what to replace it with.
*
* @return the mapping of defines
*/
mapping query_defines() { return defines; }
/**
* Did the file change? Checks to see if the file changed since it
* was last read.
*
* @return 1 if it changed, 0 if it has not changed
*/
int query_changed() { return changed; }
/**
* This method returns the number of times the file was attempted to
* be read and it was discovered not to exist at all.
* @return the number of times it was unable to be read
*/
int query_num_failed_tries() { return num_failed_tries; }
/**
* The files included by this one.
*
* @return an array of included files
*/
string *query_includes() { return keys(includes); }
/**
* The documentation for the defines. This is mostly used by the include
* file documentation system.
*
* @return the mapping of define names to documentation
*/
mapping query_define_docs() { return define_docs; }
/**
* The documentation for the classes.
* @return the mapping of the class names to documentation
*/
mapping query_class_docs() { return class_docs; }
/**
* Allow this object to be destructed nicely...
*/
void dest_me() {
destruct(this_object());
} /* dest_me() */