/**
* The automatic document generator. It takes source files from various
* directories and creates help files from the comments embedded in the
* code.
*
* @see /secure/handlers/autodoc/autodoc_handler
* @author Pinkfish
* @started Fri Oct 24 16:03:57 EDT 1997
*/
#include <autodoc.h>
#define EOF -1
nosave mapping private_functions;
mapping public_functions,
protected_functions,
inherits,
main_docs,
define_docs,
includes,
class_docs;
string file_name;
int last_changed, num_failed_tries;
// Temporary.
nosave string current_comment, current_file;
nosave int current_position, changed;
// We will only handle simple defines. Function ones we will ignore.
nosave mapping defines;
private mapping parse_comment( string stuff );
private void do_parse_file( function func );
private int query_file_position();
private void handle_inherit( mixed bits );
/** @ignore yes */
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;
} /* setup() */
/** @ignore yes */
void create() {
seteuid(getuid());
setup();
} /* create() */
/** @ignore yes */
private int query_file_position() { return current_position; }
/** @ignore yes */
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() */
/** @ignore yes */
private int next_character() {
if( current_position < strlen(current_file) )
return current_file[current_position++];
return EOF;
} /* next_character() */
/** @ignore yes */
private int pop_character( int num ) { current_position += num; }
/**
* @ignore yes
* 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() */
/**
* @ignore yes
* Throw away all the characters until the end of the comment.
*/
private string skip_to_end_of_comment() {
string data;
int ch, 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() */
/**
* @ignore yes
* 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() */
/**
* @ignore yes
* Expands the defines...
*/
private string expand_token( string token ) { return defines[token]; }
/**
* @ignore yes
* 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;
}
if( str = string_to_define(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, end_pos, new_pos;
string name, *type;
mapping comm;
mixed args;
pos = member_array("(", bits );
if( pos > 0 ) {
name = bits[pos-1];
if( !AUTODOC_H->exclude_method(name) ) {
type = bits[0..pos-2];
if( !sizeof(type) )
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()->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 ) {
// We found it! Stick the include bit in where it is needed.
if( !includes[inc_name] ) {
current_file = stuff + current_file[current_position..];
current_position = 0;
}
// Update the time as well.
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();
// A hash directive.
if( token[0] == '#') {
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( AUTODOC_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( AUTODOC_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, reload;
string my_name, new_file;
setup();
file_name = name;
load_file();
if( !only_load ) {
if( sizeof( unguarded( (: stat($(name)) :) ) ) ) {
if( file_size(name) != -2 ) {
curr_change = unguarded( (: stat($(name)) :) )[1];
} else {
AUTODOC_H->remove_file(name);
if( name[<1] != '/')
name += "/";
foreach( new_file in unguarded( (: stat($(name)) :) ) ) {
if( file_size(name+new_file) != -2 &&
new_file[sizeof(new_file)-2..sizeof(new_file)] )
AUTODOC_H->add_file(name+new_file);
}
if( func )
call_out( (: evaluate($1) :), 2, func );
return;
}
reload = curr_change > last_changed || file_name(PO) == AUTODOC_H;
if( !reload ) {
// Check to see if the include files have changed.
foreach( my_name in keys(includes) ) {
if( unguarded( (: file_exists($(my_name)) :) ) ) {
new_file = AUTODOC_SAVE_DIR +
replace_string(my_name, "/", ".")+".o";
// Add all missing includes to the handler.
if( !unguarded( (: file_exists($(new_file)) :) ) ) {
event( users(), "inform", "Autodoc: Adding \""+
my_name+"\"", "autodoc");
AUTODOC_H->add_file(my_name);
reload = 1;
continue;
}
if( unguarded( (: stat($(my_name)) :) )[1] >
unguarded( (: stat($(new_file)) :) )[1] ) {
event( users(), "inform", "Autodoc: Updating \""+
my_name+"\" - \""+new_file+"\" outdated",
"autodoc");
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;
event( users(), "inform", "Autodoc: Parsing \""+file_name+"\"",
"autodoc");
if( catch( do_parse_file(func) ) )
evaluate(func);
} else {
if( num_failed_tries ) {
num_failed_tries = 0;
save_file();
}
event( users(), "inform", "Autodoc: Skipping \""+file_name+"\"",
"autodoc");
if( func )
call_out( (: evaluate($1) :), 2, func );
}
} else {
event( users(), "inform", "Autodoc: No file \""+file_name+"\"",
"autodoc");
num_failed_tries++;
save_file();
if( func )
call_out( (: evaluate($1) :), 2, func );
}
} else {
if( func )
call_out( (: evaluate($1) :), 2, func );
}
} /* parse_file() */
/** @ignore yes */
private void do_parse_file( function func ) {
int num;
// Darn, changed while we were reading it.
if( file_size(file_name) != -2 &&
unguarded( (: stat(file_name) :) )[1] > last_changed )
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();
if( func )
call_out( (: evaluate($1) :), 2, func );
} else {
call_out( (: do_parse_file :), 1, 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() { return 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); }
/** @ignore yes */
mapping query_all_includes() { return 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; }
/** @ignore yes */
void dest_me() { destruct(TO); }