/**
* Universal data initialization handler.
* The data initializer can be used to initialize databases for use in other
* handlers. The database is defined in a user-provided text file, with
* a format similar to the virtual object compiler. The input file is
* converted to a temporary object, which allows fairly complicated
* expressions to be used as data values. The initializer can handle
* arrays and mappings (nested to any level (theoretically)), with a base
* element of any type, including mixed and classes.
* <p>
* To initialize a variable, assign it the value returned by
* compile_data() in the initializer. compile_data() takes an array of
* filenames as its only argument.
* <p>
* <b>File format</b>
* The data file uses the following keywords. Each keyword is followed
* by the required data. The data can be spread over multiple lines,
* following the same rules as for LPC code, but the keywords must be at
* the beginning of the line (preceded by optional whitespace).
* <dl>
* <dt>::data:: < array | mapping > [ of ] ... < base >
* <dd>
* This keyword defines the structure of the data. The word "of" is
* optional. "array" and "mapping" may be repeated as many times as
* desired. "base" is the base type of the data. For classes, it would
* be of the form "class <classname>". For types other than classes,
* the base isn't really
* used at this time, but something needs to be there to keep the parser
* in line. Some examples:
* <b>
* <pre>::data:: array of mapping of array of int
* ::data:: mapping of mapping of mixed
* ::data:: mapping of array of class myclass</pre>
* There should only be one ::data:: keyword in the list of files passed
* to compile_data(). Also, note that classes need to be defined before
* this statement. This can be done either with ::quote:: or
* ::#include::.
* <dt>::item <index> [,] ... :: [ value ]
* <dd>This keyword defines the value for one item of the data. <index> is
* repeated as often as necessary, given the structure declared in the
* ::data:: statement. For mappings, the index can be any valid mapping key.
* For arrays, the index can be either a number, or the strings i, i++,
* or ++i, for current index, current index (incrementing afterwards), or
* next index. The value can (and probably should) be omitted for
* classes, with the field values specified with the "->" keyword below.
* Examples (corresponding to the three ::data:: examples above):
* <pre>
* ::item 0, "item 1", 2:: 42
* ::item "a", "b":: ({ 1, "fred", ({ 2, 3 }) })
* ::item "x" i++::
* </pre>
* <dt> ::-><field>:: <value>
* <dd>This allows the fields of items of type class to be assigned
* individually. In general, the preceding ::item:: keyword should not
* have been given a value. The class must have been defined previously,
* either with an ::#include:: directive, or with the ::quote:: keyword.
* Examples:
* <pre>
* ::Quote::
* class item_data {
* string *season;
* mixed quant;
* string ob;
* }
*
* ::Data:: mapping of class item_data
* :item "white clover"::
* ::->season:: ({ "spring" })
* ::->quant:: ({ 3, 4 })
* ::->ob:: "/std/plant"
* </pre>
* These statements set the season, quant, and ob fields of the mapping
* <dt>::quote:: <LPC code>
* <dd>This keyword allows specific LPC statements to be inserted in the
* object that creates the database. To use this effectively requires a
* little understanding of the translation process. First, all lines
* associated with a given keyword are folded into one line. This means
* that using the "//" comment delimiter in a ::quote:: will cause the
* remainder of the quoted statements to be ignored. Second, the
* prototype of the function that returns the data isn't written until
* the ::data:: keyword is encountered. Therefore, any "global"
* statements (such as class definitions) should be included or quoted
* before the ::data:: line. The easiest way to see what's going on is
* to try a few examples and look at the resulting .c file (which is the
* first data file name with ".c" stuck on the end).
* </dl>
* @example
* mixed data;
* data = "/handlers/data"->compile_data(({ "/save/file1.dat",
* "/save/file2.dat" }));
* // This will catenate the two files into one, translate it, and return
* // the data. Of course, someone has to create the data files also.
* @author Jeremy
*/
#define DEBUG !
#define DC_DELIM 0
#define DC_ARRAY 1
#define DC_MAPPING 2
#define DC_OF 3
#define DC_CLASS 4
#define DC_STRING 5
#define DC_ITEM 6
#define DC_NUMBER 7
#define DC_GREY 8
// This is the number of characters it reads before starting a new
// function. I set it to half of the biggest set of files that caused
// me problems. It can be adjusted as needed.
#define MAX_SUBF_SIZE 16000
#define WHITESPACE(c) (c == 10 || c == 32 || c == '\n')
#ifdef DEBUG
# define Error(s) write(s); log_file( "DATA_COMPILER", s);
#else
# define Error(s) ;
#endif
string std_euid;
void create() {
std_euid = "/secure/master"->creator_file(file_name(this_object()));
seteuid(std_euid);
//Error("Note: euid at creation is " + geteuid() + "\n");
}
int tmp_file_no;
private string strip_string( string str ) {
int i, j;
if (!str || str == "") return "";
j = strlen( str ) - 1;
for( ; WHITESPACE( str[ i ] ) && i <= j; i++ ) ;
for( ; WHITESPACE( str[ j ] ) && j > i; j-- ) ;
return str[ i..j ];
} /* strip_space() */
private mixed cleanup_assoc( mixed parse ) {
int j;
// Clean out whitespace (seems like there should be an easier
// way to do this...)
for (j = 0; j < sizeof(parse[0]); j++) {
if ((parse[1][j] == DC_DELIM) || (parse[1][j] == DC_OF)) {
parse[0][j] = 0;
parse[1][j] = 0;
}
}
parse[0] -= ({ 0 });
parse[1] -= ({ 0 });
return parse;
} /* cleanup_assoc */
/**
* Actualy compiles the files down.
* See the header file for a more detailed explaination
* @param path the files to parse
*/
mixed compile_data( string *path ) {
string tmp_name, data = "", file_data = "", s1, tmp_val, base, keyword;
string *segments, *ind, *val, cur_index;
int i, j, t, debug_file, class_pending, stat, subfunc_cnt,
subfunc_char_cnt, data_keyword_found;
int *index_types;
mixed parse, index_max;
// Most of this is blatantly stolen from /global/virtual/compiler.c
if (!sizeof(path))
return 0;
tmp_name = path[0] + "_dc.c";
if( find_object( tmp_name ) )
tmp_name->dest_me();
if (file_size(tmp_name) > 0) {
if ((stat = seteuid("Root")) == 0) {
// This always happens. But everything seems to work,
// so I'll just take it out.
// Error("Error: couldn't set euid to Root (" + stat + ", " +
// "secure/master"->valid_seteuid(this_object(), "Root") +
// ")\n");
}
stat = unguarded((: rm, tmp_name :));
if (!stat) {
Error("Error: couldn't remove old .c file (" +
geteuid(this_object()) + ", " +
"secure/master"->valid_seteuid(this_object(), "Root") +
")\n");
seteuid(std_euid);
return 0;
}
//Error("Note: " + tmp_name + " removed (supposedly).\n");
}
seteuid(std_euid);
for (i = 0; i < sizeof(path); i++) {
if (file_size(path[i]) <= 0)
continue;
data += read_file( path[i] );
}
if (!data) {
Error("Error: file(s) not found.\n");
return 0;
}
/* Lines beginning with a # are a comment... */
/* Break into segments at comments */
segments = explode( "$\n" + data, "\n#" );
if( !segments ) {
Error( "prop_to_fun() : Nothing but comments?\n" );
return 0;
}
/* Remove dummy $ (?) */
segments[ 0 ] = segments[ 0 ][ 1..(sizeof(segments[ 0 ]) - 1) ];
/* Remove comment lines */
for( i = 1; i < sizeof( segments ); i++ ) {
if( sscanf( segments[ i ], "%s\n%s", s1, segments[ i ] ) != 2 ) {
segments[ i ] = "";
}
}
/* Join segments together again */
data = implode( segments, "\n" );
/* See example file for explanation of syntax. */
segments = explode( strip_string( data ), "::" );
/* sizeof(segments) can be odd if the last line has no argument */
if (sizeof( segments ) % 2) {
segments += ({""});
}
ind = allocate( sizeof( segments ) / 2 );
val = allocate( sizeof( segments ) / 2 );
for( i = 0; i < sizeof( ind ); i++ ) {
ind[ i ] = segments[ i * 2 ];
//val[ i ] = replace( segments[ i * 2 + 1 ], "\n", " " );
// This preserves the newlines; it makes it more readable and avoids
// line length problems.
val[ i ] = strip_string( segments[ i * 2 + 1 ] );
/* look for virtual compiler meta char */
if( ind[ i ][ 0..0 ] == "#" ) {
ind[ i ] = lower_case( ind[ i ] );
if( ind[ i ] == "#debug" ) {
/* debug errent virtual programs, ie, don't rm */
/* the .c file if debug_file is non-zero */
sscanf( val[ i ], "%d", debug_file );
} else if( ind[ i ] == "#include" ) {
/* include the file in setup(), just before the */
/* object is cloned. */
tmp_val = val[i];
file_data += "#include " + replace( tmp_val, " ", "" ) + "\n";
}
}
}
for( i = 0; i < sizeof( ind ); i++ ) {
keyword = lower_case( ind[ i ] );
if( keyword[ 0..0 ] == "#" ) {
/* it's a virtual keyword don't stick it in the .c file */
continue;
}
// Keep track of how big the function is. The easiest way
// to judge the amount of code is by counting the characters.
subfunc_char_cnt += sizeof(ind[i]) + sizeof(val[i]);
if (keyword == "data") {
if (data_keyword_found) {
Error("Error: more than one data keyword found.\n");
return 0;
}
data_keyword_found = 1;
// This declares the structure of the database
file_data += "void dest_me() { destruct( this_object() ); }\n\n";
// Break up code into multiple functions; big files can
// run into trouble compiling.
parse = reg_assoc(val[i],
({ "array", "mapping", "of", "class +[^\t ]+",
"[^\t ]+" }),
({ DC_ARRAY, DC_MAPPING, DC_OF, DC_CLASS,
DC_GREY }),
DC_DELIM);
parse = cleanup_assoc( parse );
//printf("parse = %O\n", parse);
for (j = 0; (j < sizeof(parse[0])) && !index_max; j++) {
switch (parse[1][j]) {
case DC_ARRAY:
break;
case DC_MAPPING:
break;
case DC_CLASS:
base = implode(parse[0][j..], " ");
file_data += base + " item;\n";
case DC_GREY:
index_types = parse[1][0..j-1];
index_max = allocate(sizeof(index_types));
break;
default:
Error("Error: data parse error 1 (" + parse[0][j] + ")\n");
return 0;
}
}
if (index_types[0] == DC_MAPPING)
file_data += "mapping data = ([ ]);\n\n";
else
file_data += "mixed data;\n\n";
for (j = 0; j < sizeof(index_types); j++) {
switch (index_types[j]) {
case DC_ARRAY:
index_max[j] = -1;
break;
case DC_MAPPING:
//index_max[j] = ([ ]);
break;
default:
Error("Error: illegal index type found ("
+ index_types[j] + ")\n");
return 0;
}
}
file_data += "mixed data_return_" + subfunc_cnt + "() {\n";
subfunc_cnt++;
continue;
}
if (keyword[0..3] == "item") {
// This is where the actual array values get assigned
// First take care of pending class assignments
if (class_pending) {
file_data += " data" + cur_index + " = item;\n";
class_pending = 0;
}
// Check to see if we should break to a new function (this
// is the safest place, since we can't break up an item
// definition).
if (subfunc_char_cnt > MAX_SUBF_SIZE) {
// This could theoretically fail if we somehow got more than
// MAX_SUBF_SIZE characters before the first "data" keyword.
// So it goes...
file_data += "}\n\n";
file_data += "mixed data_return_" + subfunc_cnt + "() {\n";
subfunc_cnt++;
subfunc_char_cnt = 0;
}
parse = reg_assoc( ind[i], ({ "\"[^\"]*\"", "item", "[0-9]+",
"[^,\t ]+"}),
({ DC_STRING, DC_ITEM, DC_NUMBER, DC_GREY }));
parse = cleanup_assoc( parse );
//printf("parse = %O\nindex_max = %O\n", parse, index_max);
cur_index = "";
for (j = 0; j < sizeof(index_types); j++) {
switch (index_types[j]) {
case DC_ARRAY:
// Index can be a number, "i", "i++", or "++i"
if (parse[1][j+1] != DC_NUMBER) {
if (parse[0][j+1] == "i") {
parse[0][j+1] = index_max[j] + "";
} else if (parse[0][j+1] == "i++") {
parse[0][j+1] = index_max[j] + "";
index_max[j]++;
} else if (parse[0][j+1] == "++i") {
index_max[j]++;
parse[0][j+1] = index_max[j] + "";
} else {
Error("Error: illegal index for array (" +
parse[0][j+1] + ")\n");
return 0;
}
}
if ((t = to_int(parse[0][j+1])) > index_max[j]) {
file_data += " data" + cur_index
+ " = allocate(" + (t-index_max[j]) + ");\n";
index_max[j] = t;
}
break;
case DC_MAPPING:
if (j) {
file_data += " if (!mappingp(data" + cur_index + "))"
+ " data" + cur_index + " = ([]);\n";
}
break;
default:
Error("Error: illegal index type found (" + index_types[j]
+ ")\n");
return 0;
} /* switch */
cur_index += "[" + parse[0][j+1] + "]";
} /* for */
if (strip_string(val[i]) != "") {
//printf("val[i] = %O\n", val[i]);
file_data += " data" + cur_index + " = " + val[i] + ";\n";
}
continue;
} /* if */
if (keyword[0..1] == "->") {
// This is for handling fields of classes
if (!class_pending) {
file_data += " item = new( " + base + " );\n";
class_pending = 1;
}
file_data += " item->" + ind[i][2..] + " = " + val[i] + ";\n";
continue;
}
if (keyword == "quote") {
// A backdoor for putting in specific LPC lines
file_data += val[i] + "\n";
continue;
}
} /* for */
if (class_pending) {
file_data += " data" + cur_index + " = item;\n";
}
file_data += "}\n\n";
file_data += "mixed data_return() {\n";
for (i = 0; i < subfunc_cnt; i++) {
file_data += " data_return_" + i + "();\n";
}
file_data += " return data;\n}\n";
seteuid("Root");
unguarded((: write_file, tmp_name, file_data, 1 :));
seteuid(std_euid);
return tmp_name->data_return();
} /* compile_data() */