/* Copyright (C) 2005 Nach ( http://nsrt.edgeemu.com ) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Config file handler creator by Nach (C) 2005 */ #if !defined(__GNUC__) && !defined(_MSC_VER) #error You are using an unsupported compiler #endif #include #include #include #include #include #include #include using namespace std; #include #ifdef _MSC_VER //MSVC typedef int ssize_t; #define strcasecmp stricmp #define __WIN32__ #endif #if defined(__MSDOS__) || defined(__WIN32__) #define SLASH_STR "\\" #else #define SLASH_STR "/" #endif #define LINE_LENGTH 2048*10 char line[LINE_LENGTH]; set defines; stack ifs; typedef vector str_array; str_array memsets; enum cfg_value_type { single_value, quoted_value, parameterized_value }; struct cfg_var_struct { string name; string object; cfg_value_type type; size_t size; }; typedef vector cfg_var_array; cfg_var_array cfg_vars; size_t current_line_number = 0; size_t current_column_number = 0; void show_error_loc(const char *str) { cerr << "Error: parse problem occured at " << current_line_number << ":" << current_column_number << ". " << str << "." << endl; } void check_existing_var(string& str) { for (cfg_var_array::iterator i = cfg_vars.begin(); i != cfg_vars.end(); i++) { if (i->name == str) { cerr << "Duplicate definition of \"" << i->name << "\" found on line " << current_line_number << "." << endl; break; } } } void add_config_var(string& name, const char *object, cfg_value_type val, size_t size) { check_existing_var(name); cfg_var_struct cfg_var = {name, object, val, size}; cfg_vars.push_back(cfg_var); //cout << "Name: " << name << "; Value Type: " << val << "; Size: " << size << endl; } //Find next matching character which is not escaped char *find_next_match(char *str, char match_char) { char *pos = 0; while (*str) { if (*str == match_char) { pos = str; } if (*str == '\\') { if (str[1]) { str++; } else { break; } } str++; } return(pos); } //This is like strtok(), except this understands quoted characters and updates error locations char *get_token(char *str, char *delim) { static char *pos = 0; char *token = 0; if (str) //Start a new string? { pos = str; } if (pos) { //Skip delimiters while (*pos && strchr(delim, *pos)) { pos++; } if (*pos) { token = pos; //Skip non-delimiters while (*pos && !strchr(delim, *pos)) { //Skip quoted characters if ((*pos == '\"') || (*pos == '\'')) { char *match_pos = 0; if ((match_pos = find_next_match(pos+1, *pos))) { pos = match_pos; } } pos++; } if (*pos) { *pos++ = '\0'; } } } if (token) { current_column_number = token - line; } return(token); } //Like strchr() but understands quoted characters char *find_chr(char *str, char match_char) { char *pos = 0; while (*str) { if (*str == match_char) { pos = str; break; } //Skip quoted characters if ((*str == '\"') || (*str == '\'')) { char *match_pos = 0; if ((match_pos = find_next_match(str+1, *str))) { str = match_pos; } } str++; } return(pos); } //Convert $AB12 and 0AB12h style hex to 0xAB12 hex string hex_convert(string str) { size_t dollar_pos; while ((dollar_pos = str.find("$")) != string::npos) { str.replace(dollar_pos, 1, "0x"); } while (tolower(str[0]) == 'h') { str.erase(0, 1); } size_t h_pos; while ((h_pos = str.find_first_of("hH")) != string::npos) { size_t h_len = 1; while ((h_pos-h_len) && isxdigit(str[h_pos-h_len])) { h_len++; } str.erase(h_pos, 1); str.insert(h_pos-h_len+1, "0x"); } return(str); } //Ascii numbers to integer, with support for mathematics in the string ssize_t enhanced_atoi(const char *str) { ssize_t num = 0; //Make sure result file doesn't exist if (remove("eatio.res") && (errno != ENOENT)) { cerr << "Error: Can not get accurate value information (eatio.res)." << endl; } //Biggest cheat of all time ofstream out_stream("eatio.c"); if (out_stream) { out_stream << "#include \n" << "int main()\n" << "{\n" << " FILE *fp = fopen(\"eatio.res\", \"w\");\n" << " if (fp)\n" << " {\n" << " fprintf(fp, \"%d\", " << hex_convert(str) << ");\n" << " fclose(fp);\n" << " }\n" << " return(0);\n" << "}\n\n"; out_stream.close(); #ifdef _MSC_VER //MSVC system("cl /nologo /Feeatio.exe eatio.c"); #else system("gcc -o eatio.exe eatio.c -s"); #endif system("."SLASH_STR"eatio.exe"); remove("eatio.c"); remove("eatio.exe"); remove("eatio.obj"); //Needed for stupid MSVCs which leave object files lying around ifstream in_stream("eatio.res"); if (in_stream) { in_stream >> num; in_stream.close(); } else { cerr << "Error: Can not get accurate value information (eatio.res)." << endl; } remove("eatio.res"); } return(num); } //Standard atoi(), but shows error if string isn't all a number ssize_t safe_atoi(string& str) { if (!str.length()) { str = "X"; } //Force error const char *p = str.c_str(); for (p = ((*p == '-') ? p+1 : p); *p; p++) { if (!isdigit(*p)) { show_error_loc("Not a number"); } } return(atoi(str.c_str())); } #define var_type_is_char(var_type) !strcmp(var_type+strlen(var_type)-strlen("char"), "char") #define var_type_is_short(var_type) !strcmp(var_type+strlen(var_type)-strlen("short"), "short") #define var_type_is_int(var_type) !strcmp(var_type+strlen(var_type)-strlen("int"), "int") #define short_scale "*sizeof(short)" #define int_scale "*sizeof(int)" //Return the comment from global line variable char *get_comment() { char *comment = find_chr(line, ';'); if (comment) { *comment = 0; comment++; if (isspace(comment[strlen(comment)-1])) { comment[strlen(comment)-1] = 0; } } return(comment); } void output_comment(ostream& c_stream, const char *comment) { if (comment) { c_stream << " //" << comment; } c_stream << "\n"; } bool all_spaces(const char *str) { while (*str) { if (!isspace(*str)) { return(false); } str++; } return(true); } //Convert asm types to C types char *convert_asm_type(const char *str, bool unsigned_var = true) { char *var_type = 0; if (!strcasecmp(str, "dd")) { var_type = "unsigned int"; } else if (!strcasecmp(str, "dw")) { var_type = "unsigned short"; } else if (!strcasecmp(str, "db")) { var_type = "unsigned char"; } else { show_error_loc("Not a valid type"); } if (var_type && !unsigned_var) { var_type += strlen("unsigned "); } return(var_type); } void output_parser_start(ostream& c_stream) { c_stream << "/*\n" << "Config file handler generated by Nach's Config file handler creator.\n" << "*/\n" << "\n" << "#include \n" << "#include \n" << "#include \n" << "#include \n" << "\n" << "\n" << "#define LINE_LENGTH " << LINE_LENGTH << "\n" << "static char line[LINE_LENGTH];\n" << "\n" << "\n" << "static char *encode_string(const char *str)\n" << "{\n" << " size_t i = 0;\n" << " line[i++] = '\\\"';\n" << " while (*str)\n" << " {\n" << " if ((*str == '\\\\') ||\n" << " (*str == '\\\"') ||\n" << " (*str == '\\\'') ||\n" << " (*str == '\\n') ||\n" << " (*str == '\\t'))\n" << " {\n" << " line[i++] = '\\\\';\n" << " }\n" << " line[i++] = *str++;\n" << " }\n" << " line[i++] = '\\\"';\n" << " line[i] = 0;\n" << " return(line);\n" << "}\n" << "\n" << "static char *decode_string(char *str)\n" << "{\n" << " size_t str_len = strlen(str), i = 0;\n" << " char *dest = str;\n" << " \n" << " if ((str_len > 1) && (*str == '\\\"') && (str[str_len-1] == '\\\"'))\n" << " {\n" << " memmove(str, str+1, str_len-2);\n" << " str[str_len-2] = 0;\n" << "\n" << " while (*str)\n" << " {\n" << " if (*str == '\\\\')\n" << " {\n" << " str++;\n" << " }\n" << " dest[i++] = *str++;\n" << " }\n" << " }\n" << " dest[i] = 0;\n" << " return(dest);\n" << "}\n" << "\n" << "static char *find_next_match(char *str, char match_char)\n" << "{\n" << " char *pos = 0;\n" << "\n" << " while (*str)\n" << " {\n" << " if (*str == match_char)\n" << " {\n" << " pos = str;\n" << " }\n" << " if (*str == '\\\\')\n" << " {\n" << " if (str[1])\n" << " {\n" << " str++;\n" << " }\n" << " else\n" << " {\n" << " break;\n" << " }\n" << " }\n" << " str++;\n" << " }\n" << " return(pos);\n" << "}\n" << "\n" << "static char *find_str(char *str, char *match_str)\n" << "{\n" << " char *pos = 0;\n" << "\n" << " while (*str)\n" << " {\n" << " if (strchr(match_str, *str))\n" << " {\n" << " pos = str;\n" << " break;\n" << " }\n" << " if ((*str == '\\\"') || (*str == '\\\''))\n" << " {\n" << " char *match_pos = 0;\n" << " if ((match_pos = find_next_match(str+1, *str)))\n" << " {\n" << " str = match_pos;\n" << " }\n" << " }\n" << " str++;\n" << " }\n" << " return(pos); \n" << "}\n" << "\n" << "\n"; } void output_cheader_start(ostream& cheader_stream) { cheader_stream << "/*\n" << "Config file handler header generated by Nach's Config file handler creator.\n" << "*/\n" << "\n" << "#ifdef __cplusplus\n" << " extern \"C\" {\n" << "#endif\n" << "\n" << "unsigned char read_cfg_vars(const char *);\n" << "unsigned char write_cfg_vars(const char *);\n" << "\n"; } void output_cheader_end(ostream& cheader_stream) { cheader_stream << "\n" << "#ifdef __cplusplus\n" << " }\n" << "#endif\n" << "\n"; } void output_init_var(ostream& c_stream) { c_stream << "\n" << "static void init_cfg_vars()\n" << "{\n" << " static unsigned char init_done = 0;\n" << " if (!init_done)\n" << " {\n" << " init_done = 1;\n" << "\n"; for (str_array::iterator i = memsets.begin(); i != memsets.end(); i++) { c_stream << " " << *i << "\n"; } c_stream << " }\n" << "}\n"; } void output_array_write(ostream& c_stream, const char *type) { c_stream << "\n" << "static void write_" << type << "_array(FILE *fp, const char *var_name, " << type << " *var, size_t size)\n" << "{\n" << " size_t i;\n" << " fprintf(fp, \"%s=%d\", var_name, (int)*var);\n" << " for (i = 1; i < size; i++)\n" << " {\n" << " fprintf(fp, \",%d\", (int)(var[i]));\n" << " }\n" << " fprintf(fp, \"\\n\");\n" << "}\n"; } void output_write_var(ostream& c_stream) { output_array_write(c_stream, "char"); output_array_write(c_stream, "short"); output_array_write(c_stream, "int"); c_stream << "\n" << "unsigned char write_cfg_vars(const char *file)\n" << "{\n" << " FILE *fp = 0;\n" << "\n" << " init_cfg_vars();\n" << "\n" << " if ((fp = fopen(file, \"w\")))\n" << " {\n"; for (cfg_var_array::iterator i = cfg_vars.begin(); i != cfg_vars.end(); i++) { if (i->type == parameterized_value) { c_stream << " write_" << convert_asm_type(i->object.c_str(), false) << "_array(fp, \"" << i->name << "\", " << i->name << ", " << i->size << ");\n"; } else { c_stream << " fprintf(fp, \"" << i->name << "="; if (i->type == single_value) { c_stream << "%d\\n\", " << i->name; } else { c_stream << "%s\\n\", encode_string(" << i->name << ")"; } c_stream << ");\n"; } } c_stream << " fclose(fp);\n" << "\n" << " return(1);\n" << " }\n" << " return(0);\n" << "}\n"; } void output_array_read(ostream& c_stream, const char *type) { c_stream << "\n" << "static void read_" << type << "_array(char *line, " << type << " *var, size_t size)\n" << "{\n" << " size_t i;\n" << " char *token;\n" << " *var = atoi(strtok(line, \", \\t\\r\\n\"));\n" << " for (i = 1; (i < size) && (token = strtok(0, \", \\t\\r\\n\")); i++)\n" << " {\n" << " var[i] = atoi(token);\n" << " }\n" << "}\n"; } void output_read_var(ostream& c_stream) { output_array_read(c_stream, "char"); output_array_read(c_stream, "short"); output_array_read(c_stream, "int"); c_stream << "\n" << "unsigned char read_cfg_vars(const char *file)\n" << "{\n" << " FILE *fp = 0;\n" << "\n" << " init_cfg_vars();\n" << "\n" << " if (!(fp = fopen(file, \"r\")))\n" << " {\n" << " write_cfg_vars(file);\n" << " return(0);\n" << " }\n" << "\n" << " while (!feof(fp))\n" << " {\n" << " char *p, *var, *value;\n" << "\n" << " fgets(line, LINE_LENGTH, fp);\n" << " if ((p = find_str(line, \";\"))) { *p = 0; }\n" << " if ((p = strchr(line, '=')))\n" << " {\n" << " *p = 0;\n" << " var = line;\n" << " value = p+1;\n" << " while (isspace(*var)) { var++; }\n" << " while (isspace(*value)) { value++; }\n" << " if ((p = find_str(var, \" \\t\\r\\n\"))) { *p = 0; }\n" << " if ((p = find_str(value, \" \\t\\r\\n\"))) { *p = 0; }\n" << " if (!*var || !*value) { continue; }\n" << " }\n" << " else\n" << " {\n" << " continue;\n" << " }\n" << "\n"; for (cfg_var_array::iterator i = cfg_vars.begin(); i != cfg_vars.end(); i++) { c_stream << " if (!strcmp(var, \"" + i->name + "\")) { "; if (i->type == single_value) { c_stream << i->name << " = atoi(value);"; } else if (i->type == parameterized_value) { c_stream << "read_" << convert_asm_type(i->object.c_str(), false) << "_array(value, " << i->name << ", " << i->size << ");"; } else { c_stream << "*" << i->name << " = 0; " << "strncat(" << i->name << ", decode_string(value), sizeof(" << i->name << ")-1);"; } c_stream << " }\n"; } c_stream << " }\n" << "\n" << " fclose(fp);\n" << " write_cfg_vars(file);\n" << " return(1);\n" << "}\n"; } void handle_directive(char *instruction, char *label) { if (!strcasecmp(instruction, "define")) { if (label) { defines.insert(label); } else { show_error_loc("Could not get define label"); } } else if (!strcasecmp(instruction, "undef")) { if (label) { defines.erase(label); } else { show_error_loc("Could not get undefine label"); } } else if (!strcasecmp(instruction, "ifdef")) { if (label) { if (defines.find(label) != defines.end()) { ifs.push(true); } else { ifs.push(false); } } else { show_error_loc("Could not get ifdef label"); } } else if (!strcasecmp(instruction, "else")) { if (label) { show_error_loc("Processor directive else does not accept labels"); } else { if (ifs.empty()) { show_error_loc("Processor directive else without ifdef"); } else { bool process = !ifs.top(); ifs.pop(); ifs.push(process); } } } else if (!strcasecmp(instruction, "elifdef") || !strcasecmp(instruction, "elseifdef")) { if (label) { if (ifs.top()) { ifs.pop(); ifs.push(false); } else if (defines.find(label) != defines.end()) { ifs.pop(); ifs.push(true); } } else { show_error_loc("Could not get elseifdef label"); } } else if (!strcasecmp(instruction, "endif")) { if (label) { show_error_loc("Processor directive endif does not accept labels"); } else { if (ifs.empty()) { show_error_loc("Processor directive endif without ifdef"); } else { ifs.pop(); } } } else { show_error_loc("Unknown processor directive"); } } void parser_generate(istream& psr_stream, ostream& c_stream, ostream& cheader_stream) { output_parser_start(c_stream); if (cheader_stream) { output_cheader_start(cheader_stream); } while (!psr_stream.eof()) { char *token; char *comment; psr_stream.getline(line, LINE_LENGTH); current_line_number++; comment = get_comment(); if (all_spaces(line)) { output_comment(c_stream, comment); continue; } if ((token = get_token(line, " ")) && (strcasecmp(token, "NEWSYM") || (token = get_token(0, " ,")))) { if ((*token == '#') || (*token == '%')) { handle_directive(token+1, get_token(0, " ")); continue; } if (!ifs.empty() && !ifs.top()) { continue; } string varname = token; if ((token = get_token(0, " ,"))) { size_t array = 0; if (strcasecmp(token, "times") || ((token = get_token(0, " ")) && (array = enhanced_atoi(token)) && (token = get_token(0, " ")))) { char *asm_type = token; char *var_type = convert_asm_type(asm_type); if (var_type) { string initial_value = get_token(0, " ,\n"); ostringstream var_init(""); if (((initial_value[0] == '\"') && (initial_value[initial_value.length()-1] == '\"')) || ((initial_value[0] == '\'') && (initial_value[initial_value.length()-1] == '\''))) { //Make sure it's double quoted initial_value[0] = '\"'; initial_value[initial_value.length()-1] = '\"'; if (!array) { array = initial_value.length()-1; //Size minus quotes plus null } var_init << "char " << varname << "[" << array << "];"; ostringstream memset_line; if (initial_value.length()-2 < array) { memset_line << "strcpy(" << varname << ", " << initial_value << ");"; } else { memset_line << "strncpy(" << varname << ", " << initial_value << ", " << (array-1) << "); " << varname << "[" << array << "] = 0;"; } memsets.push_back(memset_line.str()); add_config_var(varname, asm_type, quoted_value, 0); } else { ssize_t init_value_num = safe_atoi(initial_value); if (init_value_num < 0) { var_type += strlen("unsigned "); } var_init << var_type << " " << varname; if (array) { if (var_type_is_char(var_type) || !init_value_num) { var_init << "[" << array << "]"; ostringstream memset_line; memset_line << "memset(" << varname << ", " << init_value_num << ", " << array; if (var_type_is_short(var_type)) { memset_line << short_scale; } else if (var_type_is_int(var_type)) { memset_line << int_scale; } memset_line << ");"; memsets.push_back(memset_line.str()); } else { var_init << "[" << array << "] = {"; for (size_t i = array; i > 1; i--) { c_stream << init_value_num << ","; } var_init << init_value_num << "%d}"; } add_config_var(varname, asm_type, parameterized_value, array); } else { if ((token = get_token(0, " ,\n"))) { array = 1; var_init << "[] = {" << init_value_num; do { var_init << "," << atoi(token); array++; } while((token = get_token(0, " ,\n"))); var_init << "}"; add_config_var(varname, asm_type, parameterized_value, array); } else { var_init << " = " << init_value_num; add_config_var(varname, asm_type, single_value, 0); } } var_init << ";"; } c_stream << var_init.str(); if (cheader_stream) { string header_data = var_init.str(); size_t equal_pos; if ((equal_pos = header_data.find("=")) != string::npos) { header_data.erase(equal_pos-1); header_data.append(";"); } cheader_stream << "extern " << header_data << "\n"; } } //Else already handled } else { show_error_loc("Could not get array size"); } } else { show_error_loc("Could not get type"); } } else { show_error_loc("Could not get variable name"); } output_comment(c_stream, comment); } output_init_var(c_stream); output_write_var(c_stream); output_read_var(c_stream); c_stream << "\n"; if (cheader_stream) { output_cheader_end(cheader_stream); } if (!ifs.empty()) { cerr << "Error: " << ifs.size() << " ifdef segments have no endif." << endl; } } int main(size_t argc, const char **argv) { const char *cheader_file = 0; size_t param_pos = 1; for (; param_pos < argc; param_pos++) { if (!strncmp(argv[param_pos], "-D", 2)) { defines.insert(argv[param_pos]+2); } else if (!strcmp(argv[param_pos], "-cheader")) { param_pos++; cheader_file = argv[param_pos]; } else { break; } } if ((argc-param_pos) != 2) { cout << "Config file handler creator by Nach (C) 2005\n" << "\n" << "Usage:\n" << "parsegen [options] \n" << "\n" << "\n" << "Options:\n" << "\n" << " -Ddefine Define a processor director. Example: -D__MSDOS__\n" << " Can specify multiple defines.\n" << "\n" << " -cheader Create a C/C++ header with the following name.\n" << " Example -cheader cfgvars.h\n" << endl; return(1); } const char *psr_file = argv[param_pos+1], *c_file = argv[param_pos]; int ret_val = 0; ifstream psr_stream(psr_file); if (psr_stream) { ofstream c_stream(c_file); if (c_stream) { ofstream cheader_stream; if (cheader_file) { cheader_stream.open(cheader_file); if (cheader_stream) { parser_generate(psr_stream, c_stream, cheader_stream); } else { cerr << "Error opening " << cheader_file << " for writing." << endl; ret_val |= 8; } cheader_stream.close(); } else { parser_generate(psr_stream, c_stream, cheader_stream); } c_stream.close(); } else { cerr << "Error opening " << c_file << " for writing." << endl; ret_val |= 2; } psr_stream.close(); } else { cerr << "Error opening " << psr_file << " for reading." << endl; ret_val |= 4; } return(0); }