/* -*- LPC -*- */
/*
* $Locker: $
* $Id: data.c,v 1.14 2003/03/19 01:01:56 ceres Exp $
* $Log: data.c,v $
* Revision 1.14 2003/03/19 01:01:56 ceres
* Increased compilation speed
* .
*
* Revision 1.13 2002/11/18 01:40:54 ceres
* modified to support binary true/false
*
* Revision 1.12 2002/08/16 01:12:06 pinkfish
* Make it not call broken fps.
*
* Revision 1.11 2002/08/16 01:04:04 pinkfish
* Make it skip compiling files with dested fps.
*
* Revision 1.10 2002/06/15 00:58:52 pinkfish
* Fix up some issues.
*
* Revision 1.9 2002/03/11 03:21:49 pinkfish
* Add in the file name to the call back.
*
* Revision 1.8 2002/03/10 23:52:10 pinkfish
* Make it more resiliant to runtime errors and parse errors.
*
* Revision 1.7 2002/03/10 23:50:36 pinkfish
* Add in a lisp like data compiler.
*
* Revision 1.6 2001/05/14 10:00:54 taffyd
* Tweaks for better support for arrays.
*
* Revision 1.5 1999/11/16 03:35:50 jeremy
* Fixed it so it breaks up the data into multiple functions,
* so it should be able to handle much bigger data sets now.
*
* Also added the overwrite flag to write_file(); dunno why it
* didn't bite us every time.
*
* Also removed all the #ifdef DEBUG and just #ifdef'd the
* definition of Error(). I can't read code that has #ifdefs
* all through it.
*
* Revision 1.4 1999/03/06 15:06:40 taffyd
* Put in debug mode for now, till problem gets sorted out.
*
* Revision 1.3 1999/03/05 07:14:26 ceres
* Made it log only when debugging
*
* Revision 1.2 1998/04/27 01:33:00 jeremy
* Now preserves newlines in source file. Makes the output easier to
* debug and avoids "line too long" errors.
*
* Revision 1.1 1998/01/06 05:03:33 ceres
* Initial revision
*
*/
/**
* 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>
* Added in some code to allow it to compile lisp like files into a
* mapping as well.
* <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 = "/obj/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
*/
#include <data.h>
#include <function.h>
#define CALLOUT_DELAY 0
#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, allocated_data;
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 );
allocated_data = 0;
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;
allocated_data = 1;
}
break;
case DC_MAPPING:
if (j) {
file_data += " if (!mapp(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";
if ( !allocated_data ) {
for (j = 0; j < sizeof(index_types); j++) {
switch (index_types[j]) {
case DC_ARRAY:
file_data += " data = allocate(" + (to_int(index_max[j]) + 1) + ");\n";
break;
}
}
}
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() */
class data_node {
int type;
mixed value;
}
class queue_node {
string name;
class data_node data;
}
class compile_data {
string file_name;
function call_back;
int current_line;
int look_for;
int last_chunk_compile;
int file_len;
class queue_node* depths;
}
private nosave class compile_data* _to_compile = ({ });
void start_compile();
void parse_chunk(class compile_data data, string chunk);
#define DATA_UNKNOWN 0
#define DATA_CHILDREN 1
#define DATA_NUMBER 2
#define DATA_STRING 3
#define DATA_LIST 4
#define OPEN_BRACKET 1
#define START_ARGUMENT 2
#define END_BRACKET 3
#define END_STRING 4
#define START_LIST 5
#define END_NUMBER 6
#define ARGUMENT_VALUE 7
#define ARGUMENT_NAME 8
#define REST_OF_ARGUMENT 9
#define END_STRING_LIST 10
#define END_NUMBER_LIST 11
#define END_LIST 12
#define CHUNK_SIZE 10
/**
* Compiles up the file into the useful soul format. It also tells
* the soul about it.
* <p>
* See the soul data files in /save/new_soul for details of the format
* for the soul files. The file has to come from the soul save
* directory or the call will not work.
* @param fname the name of the file to compile
*/
void compile_file(string fname, function call_back) {
class compile_data data;
/*
* First, do we have read access to the file.
* and is it actually a file?
*/
if (file_size(fname) == -1) {
tell_object(this_player(), "The file "+
fname+" does not exist.\n");
return ;
}
if (file_size(fname) == -2) {
tell_object(this_player(), "The file "+
fname+" is a directory exist.\n");
return ;
}
data = new(class compile_data);
data->file_name = fname;
data->call_back = call_back;
data->look_for = OPEN_BRACKET;
_to_compile += ({ data });
start_compile();
} /* compile_file() */
/** @ignore yes */
void start_compile() {
class compile_data data;
/* We are already compiling */
if (!sizeof(_to_compile) || _to_compile[0]->last_chunk_compile) {
return ;
}
data = _to_compile[0];
data->last_chunk_compile = time();
data->current_line = 1;
data->look_for = OPEN_BRACKET;
data->file_len = file_length(data->file_name);
//current_result = new(class compile_node, type : 0, children : ([ ]) );
data->depths = ({ new(class queue_node,
data : new(class data_node, type : 0, value : ([ ]) )) });
call_out("compile_chunk", CALLOUT_DELAY);
} /* start_compile() */
/** @ignore yes */
void compile_chunk() {
string chunk;
int end;
class compile_data data;
data = _to_compile[0];
data->last_chunk_compile = time();
if (data->current_line > data->file_len ||
functionp(data->call_back) & FP_OWNER_DESTED) {
call_out("start_compile", CALLOUT_DELAY);
_to_compile = _to_compile[1..];
if (!(functionp(data->call_back) & FP_OWNER_DESTED)) {
evaluate(data->call_back, data->file_name, data->depths[0]->data->value);
}
return ;
}
if (data->current_line+CHUNK_SIZE > data->file_len) {
end = data->file_len+1;
} else {
end = data->current_line+CHUNK_SIZE;
}
chunk = unguarded((: read_file, data->file_name, data->current_line,
end-data->current_line :));
data->current_line = end;
call_out("compile_chunk", CALLOUT_DELAY);
parse_chunk(data, chunk);
} /* compile_chunk() */
/**
* This will return a normal integer, or a dice class. The dice class
* allows for things of the form 5d6+10 or 3d11-10.
* @return the dice class
*/
mixed to_diceint(string str) {
class data_dice dice;
string s1;
if (strsrch(str, "d") != -1) {
dice = new(class data_dice);
if (sscanf(str, "%dd%s", dice->number, s1) == 2) {
if (strsrch(s1, "+")) {
if (sscanf(s1, "%d+%d", dice->die, dice->modifier) != 2) {
dice->die = to_int(s1);
}
} else if (strsrch(s1, "-")) {
if (sscanf(s1, "%d-%d", dice->die, dice->modifier) != 2) {
dice->die = to_int(s1);
} else {
dice->modifier = - dice->modifier;
}
} else {
dice->die = to_int(s1);
}
}
return dice;
}
return to_int(str);
} /* to_diceint() */
/** @ignore yes */
void parse_chunk(class compile_data data, string chunk) {
/* Now. What are we looking for? */
/* Love, a nice place in the world, a happy bag full of groceries. */
string *bits;
string s1;
string s2;
string s3;
int pos;
int chunk_size;
int start;
class data_node node;
// Scan out all the comments.
while (sscanf(chunk, "%s#%s\n%s", s1, s2, s3) == 3) {
chunk = s1 + s3;
}
chunk_size = strlen(chunk);
pos = 0;
bits = explode(chunk, "(");
while (pos < chunk_size) {
switch (data->look_for) {
case OPEN_BRACKET :
while (pos < chunk_size && (chunk[pos] == ' ' ||
chunk[pos] == '\t' || chunk[pos] == '\n')) {
pos++;
}
if (pos == chunk_size) {
break;
}
if (chunk[pos] == ')') {
data->look_for = END_BRACKET;
break;
}
if (chunk[pos] == '(') {
chunk = chunk[pos+1..];
chunk_size = strlen(chunk);
pos = 0;
node = new(class data_node, type : 0);
data->depths += ({ new(class queue_node, data : node) });
data->look_for = ARGUMENT_NAME;
} else {
pos = chunk_size;
}
break;
case ARGUMENT_NAME :
/* We look for the first non-space, non-tab, non-nl */
while (pos < chunk_size && (chunk[pos] == ' ' ||
chunk[pos] == '\t' || chunk[pos] == '\n')) {
pos++;
}
if (pos == chunk_size) {
break;
}
start = pos;
/* Ok, now we search for the next one. */
while (pos < chunk_size && chunk[pos] != ' ' &&
chunk[pos] != '\t' && chunk[pos] != '\n') {
pos++;
}
/* Thats it. Our argument name. */
data->depths[<1]->name = chunk[start..pos-1];
data->look_for = ARGUMENT_VALUE;
break;
case ARGUMENT_VALUE :
case START_LIST :
while (pos < chunk_size && (chunk[pos] == ' ' ||
chunk[pos] == '\t' || chunk[pos] == '\n')) {
pos++;
}
if (pos >= chunk_size) {
break;
}
switch (chunk[pos]) {
case '(' :
if (data->look_for == START_LIST) {
debug_printf("Error, found a bracket inside a list.\n");
pos = chunk_size;
break;
}
// Change it to look for a bracket.
data->look_for = OPEN_BRACKET;
data->depths[<1]->data->type = DATA_CHILDREN;
data->depths[<1]->data->value = ([ ]);
break;
case '"' :
if (data->look_for == START_LIST) {
data->look_for = END_STRING_LIST;
data->depths[<1]->data->value += ({ "" });
} else {
data->look_for = END_STRING;
data->depths[<1]->data->value = "";
}
pos++;
data->depths[<1]->data->type = DATA_STRING;
break;
case '0'..'9' :
// Number.
if (data->look_for == START_LIST) {
data->look_for = END_NUMBER_LIST;
data->depths[<1]->data->value += ({ "" });
} else {
data->look_for = END_NUMBER;
data->depths[<1]->data->value = "";
}
data->depths[<1]->data->type = DATA_NUMBER;
break;
case '}' :
if (data->look_for == START_LIST) {
data->look_for = END_BRACKET;
pos++;
data->depths[<1]->data->type = DATA_LIST;
} else {
debug_printf("End of list without a start of list.\n");
pos = chunk_size;
}
break;
case '{' :
if (data->look_for == START_LIST) {
debug_printf("Cannot have nested lists.\n");
pos = chunk_size;
} else {
data->look_for = START_LIST;
}
data->depths[<1]->data->value = ({ });
data->depths[<1]->data->type = DATA_LIST;
pos++;
break;
default :
if(chunk[pos] = 't')
if(chunk[pos..pos+3] == "true" || chunk[pos..pos+2] == "yes") {
if(chunk[pos..pos+3] == "true")
pos += 3;
else
pos += 2;
data->depths[<1]->data->value = 1;
data->depths[<1]->data->type = DATA_NUMBER;
data->look_for = END_BRACKET;
break;
}
debug_printf("Unknown data type %s in %s\n",
chunk[pos..pos+5], chunk);
pos = chunk_size;
break;
}
break;
case END_LIST :
while (pos < chunk_size && (chunk[pos] == ' ' ||
chunk[pos] == '\t' || chunk[pos] == '\n')) {
pos++;
}
if (pos >= chunk_size) {
break;
}
switch (chunk[pos]) {
case ',' :
pos++;
data->look_for = START_LIST;
break;
case '}' :
data->look_for = START_LIST;
break;
default :
debug_printf("Expected , or } not %s\n", chunk[pos..pos+5]);
pos = chunk_size;
break;
}
break;
case END_BRACKET :
if (sscanf(chunk[pos..], "%s)%s", s1, s2)) {
if (arrayp(data->depths[<2]->data->value[data->depths[<1]->name])) {
data->depths[<2]->data->value[data->depths[<1]->name] += ({
data->depths[<1]->data->value });
} else if (sizeof(data->depths) == 2) {
data->depths[<2]->data->value[data->depths[<1]->name] = ({
data->depths[<1]->data->value });
} else if (!undefinedp(data->depths[<2]->data->value[data->depths[<1]->name])) {
data->depths[<2]->data->value[data->depths[<1]->name] = ({
data->depths[<2]->data->value[data->depths[<1]->name],
data->depths[<1]->data->value });
} else {
data->depths[<2]->data->value[data->depths[<1]->name] = data->depths[<1]->data->value;
}
data->depths = data->depths[0..<2];
chunk = s2;
chunk_size = strlen(s2);
pos = 0;
data->look_for = OPEN_BRACKET;
}
break;
case END_NUMBER_LIST :
case END_NUMBER :
// Look for the first non-number.
start = pos;
while (pos < chunk_size && ((chunk[pos] >= '0' &&
chunk[pos] <= '9') ||
chunk[pos] == 'd' || chunk[pos] == '+' ||
chunk[pos] == '-')) {
pos++;
}
if (data->look_for == END_NUMBER) {
data->depths[<1]->data->value += chunk[start..pos - 1];
} else {
data->depths[<1]->data->value[<1] += chunk[start..pos - 1];
}
if (pos < chunk_size) {
if (data->look_for == END_NUMBER) {
data->depths[<1]->data->value = to_diceint(data->depths[<1]->data->value);
data->look_for = END_BRACKET;
} else {
data->depths[<1]->data->value[<1] = to_diceint(data->depths[<1]->data->value[<1]);
data->look_for = END_LIST;
}
}
break;
case END_STRING_LIST :
case END_STRING :
if (sscanf(chunk[pos..], "%s\"%s", s1, s2)) {
if (strlen(s1) > 0 && s1[strlen(s1)-1] == '\\') {
if (data->look_for == END_STRING) {
data->depths[<1]->data->value += replace(s1[0..strlen(s1)-2], "\n", "")+"\"";
} else {
data->depths[<1]->data->value[<1] += replace(s1[0..strlen(s1)-2], "\n", "")+"\"";
}
chunk = s2;
pos = 0;
chunk_size = strlen(s2);
} else {
if (data->look_for == END_STRING) {
data->depths[<1]->data->value += replace(s1, "\n", "");
data->look_for = END_BRACKET;
} else {
data->depths[<1]->data->value[<1] += replace(s1, "\n", "")+"\"";
data->look_for = END_LIST;
}
chunk = s2;
pos = 0;
chunk_size = strlen(s2);
}
} else {
if (data->look_for == END_STRING) {
data->depths[<1]->data->value += replace(chunk, "\n", "");
} else {
data->depths[<1]->data->value[<1] += chunk;
}
pos = chunk_size;
}
break;
default :
debug_printf("Horrible error "+data->look_for+" (" +
data->file_name + ") " + data->current_line + " " +
chunk[pos..] + "\n");
pos = chunk_size;
break;
}
}
//debug_printf("%O\n", data);
} /* parse_chunk() */