Split the preprocessor from the config system. No feature change yet.
This commit is contained in:
parent
a1f3c79ad3
commit
e77411ae2a
10 changed files with 647 additions and 395 deletions
|
@ -92,6 +92,8 @@ wesnoth_SOURCES = about.cpp \
|
|||
unit_types.cpp \
|
||||
video.cpp \
|
||||
wassert.cpp \
|
||||
serialization/preprocessor.cpp \
|
||||
serialization/string_utils.cpp \
|
||||
widgets/button.cpp \
|
||||
widgets/combo.cpp \
|
||||
widgets/label.cpp \
|
||||
|
@ -176,6 +178,8 @@ wesnoth_SOURCES = about.cpp \
|
|||
util.hpp \
|
||||
video.hpp \
|
||||
wassert.hpp \
|
||||
serialization/preprocessor.hpp \
|
||||
serialization/string_utils.hpp \
|
||||
widgets/button.hpp \
|
||||
widgets/combo.hpp \
|
||||
widgets/label.hpp \
|
||||
|
@ -254,6 +258,8 @@ wesnoth_editor_SOURCES = editor/editor.cpp \
|
|||
unit_display.cpp \
|
||||
unit_types.cpp \
|
||||
video.cpp \
|
||||
serialization/preprocessor.cpp \
|
||||
serialization/string_utils.cpp \
|
||||
widgets/button.cpp \
|
||||
widgets/file_chooser.cpp \
|
||||
widgets/label.cpp \
|
||||
|
@ -329,6 +335,8 @@ wesnoth_editor_SOURCES = editor/editor.cpp \
|
|||
unit_display.hpp \
|
||||
unit_types.hpp \
|
||||
video.hpp \
|
||||
serialization/preprocessor.hpp \
|
||||
serialization/string_utils.hpp \
|
||||
widgets/button.hpp \
|
||||
widgets/file_chooser.hpp \
|
||||
widgets/label.hpp \
|
||||
|
|
|
@ -15,6 +15,8 @@ campaignd_SOURCES = campaign_server.cpp \
|
|||
../network_worker.cpp \
|
||||
../publish_campaign.cpp \
|
||||
../thread.cpp \
|
||||
../serialization/preprocessor.cpp \
|
||||
../serialization/string_utils.cpp \
|
||||
../zipios++/xcoll.cpp \
|
||||
../game_events.hpp \
|
||||
../config.hpp \
|
||||
|
@ -25,6 +27,8 @@ campaignd_SOURCES = campaign_server.cpp \
|
|||
../network.hpp \
|
||||
../network_worker.hpp \
|
||||
../publish_campaign.hpp \
|
||||
../serialization/preprocessor.hpp \
|
||||
../serialization/string_utils.hpp \
|
||||
../thread.hpp \
|
||||
../zipios++/xcoll.hpp
|
||||
|
||||
|
|
360
src/config.cpp
360
src/config.cpp
|
@ -27,6 +27,7 @@
|
|||
#include "game_events.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "log.hpp"
|
||||
#include "serialization/preprocessor.hpp"
|
||||
#include "util.hpp"
|
||||
#include "wassert.hpp"
|
||||
#include "wesconfig.h"
|
||||
|
@ -59,367 +60,8 @@ bool portable_isspace(char c)
|
|||
return isnewline(c) || isspace(c);
|
||||
}
|
||||
|
||||
|
||||
line_source get_line_source(const std::vector<line_source>& line_src, int line)
|
||||
{
|
||||
line_source res(line,"",0);
|
||||
std::vector<line_source>::const_iterator it =
|
||||
std::upper_bound(line_src.begin(),line_src.end(),res);
|
||||
if(it != line_src.begin()) {
|
||||
--it;
|
||||
res.file = it->file;
|
||||
res.fileline = it->fileline + (line - it->linenum);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
//this function takes a macro and parses it into the macro followed by its
|
||||
//arguments. Arguments are seperated by spaces, but an argument appearing inside
|
||||
//braces is treated as a single argument.
|
||||
std::vector<std::string> parse_macro_arguments(const std::string& macro)
|
||||
{
|
||||
const std::vector<std::string> args = config::split(macro,' ');
|
||||
std::vector<std::string> res;
|
||||
if(args.empty()) {
|
||||
res.push_back("");
|
||||
return res;
|
||||
}
|
||||
|
||||
res.push_back(args.front());
|
||||
|
||||
bool in_braces = false;
|
||||
for(std::vector<std::string>::const_iterator i = args.begin()+1; i != args.end(); ++i) {
|
||||
size_t begin = 0, end = i->size();
|
||||
if((*i)[0] == '(') {
|
||||
++begin;
|
||||
}
|
||||
|
||||
if(!in_braces) {
|
||||
res.push_back("");
|
||||
}
|
||||
|
||||
if((*i)[i->size()-1] == ')') {
|
||||
in_braces = false;
|
||||
--end;
|
||||
}
|
||||
|
||||
res.back() += " " + i->substr(begin,end-begin);
|
||||
config::strip(res.back());
|
||||
|
||||
if(begin == 1 && end == i->size()) {
|
||||
in_braces = true;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void internal_preprocess_file(const std::string& fname,
|
||||
preproc_map& defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line);
|
||||
|
||||
void internal_preprocess_data(const std::string& data,
|
||||
preproc_map& defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line,
|
||||
const std::string& fname, int srcline)
|
||||
{
|
||||
bool in_quotes = false;
|
||||
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
const char c = *i;
|
||||
if(c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
}
|
||||
|
||||
if(c == '{') {
|
||||
int bracket_depth = 1;
|
||||
std::stringstream newfile;
|
||||
for(++i; i != data.end(); ++i) {
|
||||
if(*i == '{') {
|
||||
bracket_depth++;
|
||||
} else if(*i == '}') {
|
||||
bracket_depth--;
|
||||
if(bracket_depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newfile << *i;
|
||||
}
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
const std::string newfilename = newfile.str();
|
||||
std::vector<std::string> items = parse_macro_arguments(newfilename);
|
||||
const std::string symbol = items.front();
|
||||
|
||||
//if this is a known pre-processing symbol, then we insert
|
||||
//it, otherwise we assume it's a file name to load
|
||||
if(defines_map.count(symbol) != 0) {
|
||||
items.erase(items.begin());
|
||||
|
||||
const preproc_define& val = defines_map[symbol];
|
||||
if(val.arguments.size() != items.size()) {
|
||||
ERR_CF << "preprocessor symbol '" << symbol << "' has "
|
||||
<< items.size() << " arguments, "
|
||||
<< val.arguments.size() << " expected: '" << newfilename << "'\n";
|
||||
}
|
||||
|
||||
std::string str = val.value;
|
||||
|
||||
//substitute in given arguments
|
||||
for(size_t n = 0; n != val.arguments.size(); ++n) {
|
||||
const std::string& replace_with = (n < items.size()) ? items[n] : "";
|
||||
|
||||
int subs = 0;
|
||||
|
||||
const std::string item = "{" + val.arguments[n] + "}";
|
||||
std::string::size_type pos = str.find(item);
|
||||
while(pos != std::string::npos) {
|
||||
++subs;
|
||||
str.replace(pos,item.size(),replace_with);
|
||||
const std::string::size_type new_pos = str.find(item);
|
||||
if(new_pos < pos+replace_with.size()) {
|
||||
ERR_CF << "macro substitution in symbol '" << symbol
|
||||
<< "' could lead to infinite recursion. Aborting.\n";
|
||||
break;
|
||||
}
|
||||
|
||||
pos = new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
internal_preprocess_data(str,defines_map,depth,res,NULL,line,fname,srcline);
|
||||
} else if(depth < 20) {
|
||||
std::string prefix;
|
||||
std::string nfname;
|
||||
|
||||
#ifdef USE_ZIPIOS
|
||||
if(newfilename != "" && newfilename[0] == '~') {
|
||||
// I do not know of any valid use of {~xxx} when {xxx} is
|
||||
// not used, and zipios takes care of both
|
||||
LOG_CF << "ignoring reference to '" << newfilename << "'\n";
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
#ifndef USE_ZIPIOS
|
||||
//if the filename begins with a '~', then look
|
||||
//in the user's data directory. If the filename begins with
|
||||
//a '@' then we look in the user's data directory,
|
||||
//but default to the standard data directory if it's not found
|
||||
//there.
|
||||
if(newfilename != "" && (newfilename[0] == '~' || newfilename[0] == '@')) {
|
||||
nfname = newfilename;
|
||||
nfname.erase(nfname.begin(),nfname.begin()+1);
|
||||
nfname = get_user_data_dir() + "/data/" + nfname;
|
||||
|
||||
LOG_CF << "got relative name '" << newfilename << "' -> '" << nfname << "'\n";
|
||||
|
||||
if(newfilename[0] == '@' && file_exists(nfname) == false && is_directory(nfname) == false) {
|
||||
nfname = "data/" + newfilename.substr(1);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if(newfilename.size() >= 2 && newfilename[0] == '.' &&
|
||||
newfilename[1] == '/' ) {
|
||||
//if the filename begins with a "./", then look
|
||||
//in the same directory as the file currrently
|
||||
//being preprocessed
|
||||
nfname = newfilename;
|
||||
nfname.erase(nfname.begin(),nfname.begin()+2);
|
||||
nfname = directory_name(fname) + nfname;
|
||||
|
||||
} else {
|
||||
#ifdef USE_ZIPIOS
|
||||
if(newfilename != "" && newfilename[0] == '@') {
|
||||
nfname = newfilename;
|
||||
nfname.erase(nfname.begin(),nfname.begin()+1);
|
||||
nfname = "data/" + nfname;
|
||||
} else
|
||||
#endif
|
||||
|
||||
nfname = "data/" + newfilename;
|
||||
}
|
||||
|
||||
internal_preprocess_file(nfname,
|
||||
defines_map, depth+1,res,
|
||||
lines_src,line);
|
||||
}
|
||||
} else {
|
||||
const std::string& str = read_file(newfilename);
|
||||
res.insert(res.end(),str.begin(),str.end());
|
||||
line += std::count(str.begin(),str.end(),'\n');
|
||||
}
|
||||
|
||||
if(lines_src != NULL) {
|
||||
lines_src->push_back(line_source(line,fname,srcline));
|
||||
}
|
||||
} else if(c == '#' && !in_quotes) {
|
||||
//we are about to skip some things, so keep track of
|
||||
//the start of where we're skipping, so we can count
|
||||
//the number of newlines, so we can track the line number
|
||||
//in the source file
|
||||
const std::string::const_iterator begin = i;
|
||||
|
||||
//if this is the beginning of a pre-processing definition
|
||||
static const std::string hash_define("#define");
|
||||
if(size_t(data.end() - i) > hash_define.size() &&
|
||||
std::equal(hash_define.begin(),hash_define.end(),i)) {
|
||||
|
||||
i += hash_define.size();
|
||||
|
||||
i = std::find_if(i,data.end(),isgraph);
|
||||
|
||||
const std::string::const_iterator end = std::find_if(i,data.end(),isnewline);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
const std::string items(i,end);
|
||||
std::vector<std::string> args = config::split(items,' ');
|
||||
const std::string symbol = args.front();
|
||||
args.erase(args.begin());
|
||||
|
||||
std::stringstream value;
|
||||
static const std::string hash_enddef("#enddef");
|
||||
for(i = end+1; i <= data.end() - hash_enddef.size(); ++i) {
|
||||
if(std::equal(hash_enddef.begin(),hash_enddef.end(),i)) {
|
||||
break;
|
||||
}
|
||||
|
||||
value << *i;
|
||||
}
|
||||
|
||||
if(i > data.end() - hash_enddef.size()) {
|
||||
throw config::error("pre-processing condition unterminated in '" + fname + "': '" + items + "'");
|
||||
}
|
||||
|
||||
i += hash_enddef.size();
|
||||
|
||||
defines_map.insert(std::pair<std::string,preproc_define>(
|
||||
symbol,preproc_define(value.str(),args)));
|
||||
}
|
||||
|
||||
//if this is a pre-processing conditional
|
||||
static const std::string hash_ifdef("#ifdef");
|
||||
static const std::string hash_else("#else");
|
||||
static const std::string hash_endif("#endif");
|
||||
|
||||
if(size_t(data.end() - i) > hash_ifdef.size() &&
|
||||
std::equal(hash_ifdef.begin(),hash_ifdef.end(),i)) {
|
||||
i += hash_ifdef.size();
|
||||
while(i != data.end() && portable_isspace(*i))
|
||||
++i;
|
||||
|
||||
const std::string::const_iterator end = std::find_if(i,data.end(),portable_isspace);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
//if the symbol is not defined, then we want to skip
|
||||
//to the #endif or #else . Otherwise, continue processing
|
||||
//as normal. The #endif will just be treated as a comment
|
||||
//anyway.
|
||||
const std::string symbol(i,end);
|
||||
if(defines_map.count(symbol) == 0) {
|
||||
while(size_t(data.end() - i) > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i) &&
|
||||
!std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find_if(i,data.end(),isnewline);
|
||||
if(i == data.end())
|
||||
break;
|
||||
} else {
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
//if we come across a #else, it must mean that we found a #ifdef
|
||||
//earlier, and we should ignore until #endif
|
||||
if(size_t(data.end() - i) > hash_else.size() &&
|
||||
std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
while(size_t(data.end() - i) > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find_if(i,data.end(),isnewline);
|
||||
if(i == data.end())
|
||||
break;
|
||||
}
|
||||
|
||||
i = std::find_if(i,data.end(),isnewline);
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
srcline += std::count(begin,i,'\n');
|
||||
++line;
|
||||
|
||||
res.push_back('\n');
|
||||
} else {
|
||||
if(c == '\n') {
|
||||
++line;
|
||||
++srcline;
|
||||
}
|
||||
|
||||
res.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void internal_preprocess_file(const std::string& fname,
|
||||
preproc_map& defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line)
|
||||
{
|
||||
//if it's a directory, we process all files in the directory
|
||||
//that end in .cfg
|
||||
if(is_directory(fname)) {
|
||||
|
||||
std::vector<std::string> files;
|
||||
get_files_in_dir(fname,&files,NULL,ENTIRE_FILE_PATH);
|
||||
|
||||
for(std::vector<std::string>::const_iterator f = files.begin();
|
||||
f != files.end(); ++f) {
|
||||
if(is_directory(*f) || f->size() > 4 && std::equal(f->end()-4,f->end(),".cfg")) {
|
||||
internal_preprocess_file(*f,defines_map,depth,res,lines_src,line);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(lines_src != NULL) {
|
||||
lines_src->push_back(line_source(line,fname,1));
|
||||
}
|
||||
|
||||
internal_preprocess_data(read_file(fname),defines_map,depth,res,lines_src,line,fname,1);
|
||||
}
|
||||
|
||||
} //end anonymous namespace
|
||||
|
||||
std::string preprocess_file(const std::string& fname,
|
||||
const preproc_map* defines,
|
||||
std::vector<line_source>* line_sources)
|
||||
{
|
||||
log_scope("preprocessing file...");
|
||||
preproc_map defines_copy;
|
||||
if(defines != NULL)
|
||||
defines_copy = *defines;
|
||||
|
||||
std::vector<char> res;
|
||||
int linenum = 0;
|
||||
internal_preprocess_file(fname,defines_copy,0,res,line_sources,linenum);
|
||||
return std::string(res.begin(),res.end());
|
||||
}
|
||||
|
||||
config::config(const std::string& data,
|
||||
const std::vector<line_source>* line_sources)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "serialization/preprocessor.hpp"
|
||||
|
||||
//This module defines the interface to Wesnoth Markup Language (WML).
|
||||
//WML is a simple hierarchical text-based file format. The format
|
||||
//is defined in Wiki, under BuildingScenariosWML
|
||||
|
@ -26,42 +28,6 @@
|
|||
//sent across the network in this format. It is thus used extensively
|
||||
//throughout the game.
|
||||
|
||||
//an object which defines the location an error occurred at when
|
||||
//parsing WML files
|
||||
struct line_source
|
||||
{
|
||||
line_source(int ln,const std::string& fname, int line) :
|
||||
linenum(ln), file(fname), fileline(line)
|
||||
{}
|
||||
|
||||
int linenum;
|
||||
std::string file;
|
||||
int fileline;
|
||||
};
|
||||
|
||||
bool operator<(const line_source& a, const line_source& b);
|
||||
|
||||
struct preproc_define {
|
||||
preproc_define() {}
|
||||
explicit preproc_define(const std::string& val) : value(val) {}
|
||||
preproc_define(const std::string& val, const std::vector<std::string>& args)
|
||||
: value(val), arguments(args) {}
|
||||
std::string value;
|
||||
std::vector<std::string> arguments;
|
||||
};
|
||||
|
||||
typedef std::map<std::string,preproc_define> preproc_map;
|
||||
|
||||
inline bool operator==(const preproc_define& a, const preproc_define& b) { return a.value == b.value && a.arguments == b.arguments; }
|
||||
inline bool operator!=(const preproc_define& a, const preproc_define& b) { return !operator==(a,b); }
|
||||
|
||||
//function to use the WML preprocessor on a file, and returns the resulting
|
||||
//preprocessed file data. defines is a map of symbols defined. src is used
|
||||
//internally and should be set to NULL
|
||||
std::string preprocess_file(const std::string& fname,
|
||||
const preproc_map* defines=NULL,
|
||||
std::vector<line_source>* src=NULL);
|
||||
|
||||
typedef std::map<std::string,std::string> string_map;
|
||||
|
||||
//this object holds the schema by which config objects can be compressed and decompressed.
|
||||
|
|
419
src/serialization/preprocessor.cpp
Normal file
419
src/serialization/preprocessor.cpp
Normal file
|
@ -0,0 +1,419 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "global.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stack>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#if 0
|
||||
#include "config.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "util.hpp"
|
||||
#endif
|
||||
#include "filesystem.hpp"
|
||||
#include "log.hpp"
|
||||
#include "wesconfig.h"
|
||||
#include "serialization/preprocessor.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
|
||||
#define ERR_CF lg::err(lg::config)
|
||||
#define LOG_CF lg::info(lg::config)
|
||||
|
||||
bool line_source::operator<(line_source const &v) const {
|
||||
return linenum < v.linenum;
|
||||
}
|
||||
|
||||
bool preproc_define::operator==(preproc_define const &v) const {
|
||||
return value == v.value && arguments == v.arguments;
|
||||
}
|
||||
|
||||
line_source get_line_source(std::vector< line_source > const &line_src, int line)
|
||||
{
|
||||
line_source res(line, "", 0);
|
||||
std::vector< line_source >::const_iterator it =
|
||||
std::upper_bound(line_src.begin(), line_src.end(), res);
|
||||
if (it != line_src.begin()) {
|
||||
--it;
|
||||
res.file = it->file;
|
||||
res.fileline = it->fileline + (line - it->linenum);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
struct config {
|
||||
struct error {
|
||||
error(const std::string& msg) : message(msg) {}
|
||||
std::string message;
|
||||
};
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
const int max_recursion_levels = 100;
|
||||
|
||||
//this function takes a macro and parses it into the macro followed by its
|
||||
//arguments. Arguments are seperated by spaces, but an argument appearing inside
|
||||
//braces is treated as a single argument.
|
||||
std::vector<std::string> parse_macro_arguments(const std::string& macro)
|
||||
{
|
||||
const std::vector<std::string> args = utils::split(macro, ' ');
|
||||
std::vector<std::string> res;
|
||||
if(args.empty()) {
|
||||
res.push_back("");
|
||||
return res;
|
||||
}
|
||||
|
||||
res.push_back(args.front());
|
||||
|
||||
bool in_braces = false;
|
||||
for(std::vector<std::string>::const_iterator i = args.begin()+1; i != args.end(); ++i) {
|
||||
size_t begin = 0, end = i->size();
|
||||
if((*i)[0] == '(') {
|
||||
++begin;
|
||||
}
|
||||
|
||||
if(!in_braces) {
|
||||
res.push_back("");
|
||||
}
|
||||
|
||||
if((*i)[i->size()-1] == ')') {
|
||||
in_braces = false;
|
||||
--end;
|
||||
}
|
||||
|
||||
res.back() += " " + i->substr(begin,end-begin);
|
||||
utils::strip(res.back());
|
||||
|
||||
if(begin == 1 && end == i->size()) {
|
||||
in_braces = true;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void internal_preprocess_file(const std::string& fname,
|
||||
preproc_map& defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line);
|
||||
|
||||
void internal_preprocess_data(const std::string& data,
|
||||
preproc_map& defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line,
|
||||
const std::string& fname, int srcline)
|
||||
{
|
||||
bool in_quotes = false;
|
||||
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
const char c = *i;
|
||||
if(c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
}
|
||||
|
||||
if(c == '{') {
|
||||
int bracket_depth = 1;
|
||||
std::stringstream newfile;
|
||||
for(++i; i != data.end(); ++i) {
|
||||
if(*i == '{') {
|
||||
bracket_depth++;
|
||||
} else if(*i == '}') {
|
||||
bracket_depth--;
|
||||
if(bracket_depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newfile << *i;
|
||||
}
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
const std::string newfilename = newfile.str();
|
||||
std::vector<std::string> items = parse_macro_arguments(newfilename);
|
||||
const std::string symbol = items.front();
|
||||
|
||||
//if this is a known pre-processing symbol, then we insert
|
||||
//it, otherwise we assume it's a file name to load
|
||||
if(defines_map.count(symbol) != 0) {
|
||||
items.erase(items.begin());
|
||||
|
||||
const preproc_define& val = defines_map[symbol];
|
||||
if(val.arguments.size() != items.size()) {
|
||||
ERR_CF << "preprocessor symbol '" << symbol << "' has "
|
||||
<< items.size() << " arguments, "
|
||||
<< val.arguments.size() << " expected: '" << newfilename << "'\n";
|
||||
}
|
||||
|
||||
std::string str = val.value;
|
||||
|
||||
//substitute in given arguments
|
||||
for(size_t n = 0; n != val.arguments.size(); ++n) {
|
||||
const std::string& replace_with = (n < items.size()) ? items[n] : "";
|
||||
|
||||
int subs = 0;
|
||||
|
||||
const std::string item = "{" + val.arguments[n] + "}";
|
||||
std::string::size_type pos = str.find(item);
|
||||
while(pos != std::string::npos) {
|
||||
++subs;
|
||||
str.replace(pos,item.size(),replace_with);
|
||||
const std::string::size_type new_pos = str.find(item);
|
||||
if(new_pos < pos+replace_with.size()) {
|
||||
ERR_CF << "macro substitution in symbol '" << symbol
|
||||
<< "' could lead to infinite recursion. Aborting.\n";
|
||||
break;
|
||||
}
|
||||
|
||||
pos = new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
internal_preprocess_data(str,defines_map,depth,res,NULL,line,fname,srcline);
|
||||
} else if(depth < 20) {
|
||||
std::string prefix;
|
||||
std::string nfname;
|
||||
|
||||
#ifdef USE_ZIPIOS
|
||||
if(newfilename != "" && newfilename[0] == '~') {
|
||||
// I do not know of any valid use of {~xxx} when {xxx} is
|
||||
// not used, and zipios takes care of both
|
||||
LOG_CF << "ignoring reference to '" << newfilename << "'\n";
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
#ifndef USE_ZIPIOS
|
||||
//if the filename begins with a '~', then look
|
||||
//in the user's data directory. If the filename begins with
|
||||
//a '@' then we look in the user's data directory,
|
||||
//but default to the standard data directory if it's not found
|
||||
//there.
|
||||
if(newfilename != "" && (newfilename[0] == '~' || newfilename[0] == '@')) {
|
||||
nfname = newfilename;
|
||||
nfname.erase(nfname.begin(),nfname.begin()+1);
|
||||
nfname = get_user_data_dir() + "/data/" + nfname;
|
||||
|
||||
LOG_CF << "got relative name '" << newfilename << "' -> '" << nfname << "'\n";
|
||||
|
||||
if(newfilename[0] == '@' && file_exists(nfname) == false && is_directory(nfname) == false) {
|
||||
nfname = "data/" + newfilename.substr(1);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if(newfilename.size() >= 2 && newfilename[0] == '.' &&
|
||||
newfilename[1] == '/' ) {
|
||||
//if the filename begins with a "./", then look
|
||||
//in the same directory as the file currrently
|
||||
//being preprocessed
|
||||
nfname = newfilename;
|
||||
nfname.erase(nfname.begin(),nfname.begin()+2);
|
||||
nfname = directory_name(fname) + nfname;
|
||||
|
||||
} else {
|
||||
#ifdef USE_ZIPIOS
|
||||
if(newfilename != "" && newfilename[0] == '@') {
|
||||
nfname = newfilename;
|
||||
nfname.erase(nfname.begin(),nfname.begin()+1);
|
||||
nfname = "data/" + nfname;
|
||||
} else
|
||||
#endif
|
||||
|
||||
nfname = "data/" + newfilename;
|
||||
}
|
||||
|
||||
internal_preprocess_file(nfname,
|
||||
defines_map, depth+1,res,
|
||||
lines_src,line);
|
||||
}
|
||||
} else {
|
||||
const std::string& str = read_file(newfilename);
|
||||
res.insert(res.end(),str.begin(),str.end());
|
||||
line += std::count(str.begin(),str.end(),'\n');
|
||||
}
|
||||
|
||||
if(lines_src != NULL) {
|
||||
lines_src->push_back(line_source(line,fname,srcline));
|
||||
}
|
||||
} else if(c == '#' && !in_quotes) {
|
||||
//we are about to skip some things, so keep track of
|
||||
//the start of where we're skipping, so we can count
|
||||
//the number of newlines, so we can track the line number
|
||||
//in the source file
|
||||
const std::string::const_iterator begin = i;
|
||||
|
||||
//if this is the beginning of a pre-processing definition
|
||||
static const std::string hash_define("#define");
|
||||
if(size_t(data.end() - i) > hash_define.size() &&
|
||||
std::equal(hash_define.begin(),hash_define.end(),i)) {
|
||||
|
||||
i += hash_define.size();
|
||||
|
||||
i = std::find_if(i,data.end(),isgraph);
|
||||
|
||||
const std::string::const_iterator end = std::find_if(i, data.end(), utils::isnewline);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
const std::string items(i,end);
|
||||
std::vector<std::string> args = utils::split(items, ' ');
|
||||
const std::string symbol = args.front();
|
||||
args.erase(args.begin());
|
||||
|
||||
std::stringstream value;
|
||||
static const std::string hash_enddef("#enddef");
|
||||
for(i = end+1; i <= data.end() - hash_enddef.size(); ++i) {
|
||||
if(std::equal(hash_enddef.begin(),hash_enddef.end(),i)) {
|
||||
break;
|
||||
}
|
||||
|
||||
value << *i;
|
||||
}
|
||||
|
||||
if(i > data.end() - hash_enddef.size()) {
|
||||
throw config::error("pre-processing condition unterminated in '" + fname + "': '" + items + "'");
|
||||
}
|
||||
|
||||
i += hash_enddef.size();
|
||||
|
||||
defines_map.insert(std::pair<std::string,preproc_define>(
|
||||
symbol,preproc_define(value.str(),args)));
|
||||
}
|
||||
|
||||
//if this is a pre-processing conditional
|
||||
static const std::string hash_ifdef("#ifdef");
|
||||
static const std::string hash_else("#else");
|
||||
static const std::string hash_endif("#endif");
|
||||
|
||||
if(size_t(data.end() - i) > hash_ifdef.size() &&
|
||||
std::equal(hash_ifdef.begin(),hash_ifdef.end(),i)) {
|
||||
i += hash_ifdef.size();
|
||||
while(i != data.end() && utils::portable_isspace(*i))
|
||||
++i;
|
||||
|
||||
const std::string::const_iterator end = std::find_if(i, data.end(),
|
||||
utils::portable_isspace);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
//if the symbol is not defined, then we want to skip
|
||||
//to the #endif or #else . Otherwise, continue processing
|
||||
//as normal. The #endif will just be treated as a comment
|
||||
//anyway.
|
||||
const std::string symbol(i,end);
|
||||
if(defines_map.count(symbol) == 0) {
|
||||
while(size_t(data.end() - i) > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i) &&
|
||||
!std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find_if(i,data.end(), utils::isnewline);
|
||||
if(i == data.end())
|
||||
break;
|
||||
} else {
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
//if we come across a #else, it must mean that we found a #ifdef
|
||||
//earlier, and we should ignore until #endif
|
||||
if(size_t(data.end() - i) > hash_else.size() &&
|
||||
std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
while(size_t(data.end() - i) > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find_if(i, data.end(), utils::isnewline);
|
||||
if(i == data.end())
|
||||
break;
|
||||
}
|
||||
|
||||
i = std::find_if(i, data.end(), utils::isnewline);
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
srcline += std::count(begin,i,'\n');
|
||||
++line;
|
||||
|
||||
res.push_back('\n');
|
||||
} else {
|
||||
if(c == '\n') {
|
||||
++line;
|
||||
++srcline;
|
||||
}
|
||||
|
||||
res.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void internal_preprocess_file(const std::string& fname,
|
||||
preproc_map& defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line)
|
||||
{
|
||||
//if it's a directory, we process all files in the directory
|
||||
//that end in .cfg
|
||||
if(is_directory(fname)) {
|
||||
|
||||
std::vector<std::string> files;
|
||||
get_files_in_dir(fname,&files,NULL,ENTIRE_FILE_PATH);
|
||||
|
||||
for(std::vector<std::string>::const_iterator f = files.begin();
|
||||
f != files.end(); ++f) {
|
||||
if(is_directory(*f) || f->size() > 4 && std::equal(f->end()-4,f->end(),".cfg")) {
|
||||
internal_preprocess_file(*f,defines_map,depth,res,lines_src,line);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(lines_src != NULL) {
|
||||
lines_src->push_back(line_source(line,fname,1));
|
||||
}
|
||||
|
||||
internal_preprocess_data(read_file(fname),defines_map,depth,res,lines_src,line,fname,1);
|
||||
}
|
||||
|
||||
} //end anonymous namespace
|
||||
|
||||
std::string preprocess_file(std::string const &fname,
|
||||
const preproc_map* defines,
|
||||
std::vector<line_source>* line_sources)
|
||||
{
|
||||
log_scope("preprocessing file...");
|
||||
preproc_map defines_copy;
|
||||
if(defines != NULL)
|
||||
defines_copy = *defines;
|
||||
|
||||
std::vector<char> res;
|
||||
int linenum = 0;
|
||||
internal_preprocess_file(fname,defines_copy,0,res,line_sources,linenum);
|
||||
return std::string(res.begin(),res.end());
|
||||
}
|
57
src/serialization/preprocessor.hpp
Normal file
57
src/serialization/preprocessor.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SERIALIZATION_PREPROCESSOR_HPP_INCLUDED
|
||||
#define SERIALIZATION_PREPROCESSOR_HPP_INCLUDED
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
//an object which defines the location an error occurred at when
|
||||
//parsing WML files
|
||||
struct line_source
|
||||
{
|
||||
line_source(int ln, std::string const &fname, int line)
|
||||
: linenum(ln), file(fname), fileline(line) {}
|
||||
|
||||
int linenum;
|
||||
std::string file;
|
||||
int fileline;
|
||||
bool operator<(line_source const &) const;
|
||||
};
|
||||
|
||||
line_source get_line_source(std::vector< line_source > const &line_src, int line);
|
||||
|
||||
struct preproc_define
|
||||
{
|
||||
preproc_define() {}
|
||||
explicit preproc_define(std::string const &val) : value(val) {}
|
||||
preproc_define(std::string const &val, std::vector< std::string > const &args)
|
||||
: value(val), arguments(args) {}
|
||||
std::string value;
|
||||
std::vector< std::string > arguments;
|
||||
bool operator==(preproc_define const &) const;
|
||||
bool operator!=(preproc_define const &v) const { return !operator==(v); }
|
||||
};
|
||||
|
||||
typedef std::map< std::string, preproc_define > preproc_map;
|
||||
|
||||
//function to use the WML preprocessor on a file, and returns the resulting
|
||||
//preprocessed file data. defines is a map of symbols defined. src is used
|
||||
//internally and should be set to NULL
|
||||
std::string preprocess_file(std::string const &fname,
|
||||
preproc_map const *defines = NULL,
|
||||
std::vector< line_source > *src = NULL);
|
||||
|
||||
#endif
|
94
src/serialization/string_utils.cpp
Normal file
94
src/serialization/string_utils.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include <cctype>
|
||||
|
||||
#include "serialization/string_utils.hpp"
|
||||
|
||||
namespace utils {
|
||||
|
||||
bool isnewline(char c)
|
||||
{
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
//make sure that we can use Mac, DOS, or Unix style text files on any system
|
||||
//and they will work, by making sure the definition of whitespace is consistent
|
||||
bool portable_isspace(char c)
|
||||
{
|
||||
// returns true only on ASCII spaces
|
||||
if ((unsigned char)c >= 128)
|
||||
return false;
|
||||
return isnewline(c) || isspace(c);
|
||||
}
|
||||
|
||||
//make sure we regard '\r' and '\n' as a space, since Mac, Unix, and DOS
|
||||
//all consider these differently.
|
||||
bool notspace(char c)
|
||||
{
|
||||
return !portable_isspace(c);
|
||||
}
|
||||
|
||||
std::string &strip(std::string &str)
|
||||
{
|
||||
//if all the string contains is whitespace, then the whitespace may
|
||||
//have meaning, so don't strip it
|
||||
std::string::iterator it = std::find_if(str.begin(), str.end(), notspace);
|
||||
if (it == str.end())
|
||||
return str;
|
||||
|
||||
str.erase(str.begin(), it);
|
||||
str.erase(std::find_if(str.rbegin(), str.rend(), notspace).base(), str.end());
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
std::vector< std::string > split(std::string const &val, char c, int flags)
|
||||
{
|
||||
std::vector< std::string > res;
|
||||
|
||||
std::string::const_iterator i1 = val.begin();
|
||||
std::string::const_iterator i2 = val.begin();
|
||||
|
||||
while (i2 != val.end()) {
|
||||
if (*i2 == c) {
|
||||
std::string new_val(i1, i2);
|
||||
if (flags & STRIP_SPACES)
|
||||
strip(new_val);
|
||||
if (!(flags & REMOVE_EMPTY) || !new_val.empty())
|
||||
res.push_back(new_val);
|
||||
++i2;
|
||||
if (flags & STRIP_SPACES) {
|
||||
while (i2 != val.end() && *i2 == ' ')
|
||||
++i2;
|
||||
}
|
||||
|
||||
i1 = i2;
|
||||
} else {
|
||||
++i2;
|
||||
}
|
||||
}
|
||||
|
||||
std::string new_val(i1, i2);
|
||||
if (flags & STRIP_SPACES)
|
||||
strip(new_val);
|
||||
if (!(flags & REMOVE_EMPTY) || !new_val.empty())
|
||||
res.push_back(new_val);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
50
src/serialization/string_utils.hpp
Normal file
50
src/serialization/string_utils.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SERIALIZATION_STRING_UTILS_HPP_INCLUDED
|
||||
#define SERIALIZATION_STRING_UTILS_HPP_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace utils {
|
||||
|
||||
bool isnewline(char c);
|
||||
bool portable_isspace(char c);
|
||||
bool notspace(char c);
|
||||
|
||||
// REMOVE_EMPTY : remove empty elements
|
||||
// STRIP_SPACES : strips leading and trailing blank spaces
|
||||
enum { REMOVE_EMPTY = 0x01, STRIP_SPACES = 0x02 };
|
||||
std::vector< std::string > split(std::string const &val, char c = ',', int flags = REMOVE_EMPTY | STRIP_SPACES);
|
||||
std::string join(std::vector< std::string > const &v, char c = ',');
|
||||
std::vector< std::string > quoted_split(std::string const &val, char c= ',',
|
||||
int flags = REMOVE_EMPTY | STRIP_SPACES, char quote = '\\');
|
||||
std::pair< int, int > parse_range(const std::string& str);
|
||||
bool notspace(char c);
|
||||
std::string &escape(std::string &str);
|
||||
std::string &unescape(std::string &str);
|
||||
std::string &strip(std::string &str);
|
||||
bool has_value(std::string const &values, std::string const &val);
|
||||
|
||||
typedef std::map< std::string, std::string > string_map;
|
||||
// function which will interpolate variables, starting with '$' in the string 'str' with
|
||||
// the equivalent symbols in the given symbol table. If 'symbols' is NULL, then game event
|
||||
// variables will be used instead
|
||||
std::string interpolate_variables_into_string(std::string const &str, string_map const *symbols = NULL);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -18,6 +18,8 @@ wesnothd_SOURCES = game.cpp \
|
|||
../network.cpp \
|
||||
../network_worker.cpp \
|
||||
../thread.cpp \
|
||||
../serialization/preprocessor.cpp \
|
||||
../serialization/string_utils.cpp \
|
||||
../zipios++/xcoll.cpp \
|
||||
game.hpp \
|
||||
../game_events.hpp \
|
||||
|
@ -32,6 +34,8 @@ wesnothd_SOURCES = game.cpp \
|
|||
../network.hpp \
|
||||
../network_worker.hpp \
|
||||
../thread.hpp \
|
||||
../serialization/preprocessor.hpp \
|
||||
../serialization/string_utils.hpp \
|
||||
../zipios++/xcoll.hpp
|
||||
|
||||
AM_CXXFLAGS = @SDL_CFLAGS@ -DLOCALEDIR=\"$(LOCALEDIR)\" -I$(srcdir)/..
|
||||
|
|
|
@ -20,10 +20,14 @@ exploder_SOURCES = exploder.cpp \
|
|||
../game_config.cpp \
|
||||
../sdl_utils.cpp \
|
||||
../log.cpp \
|
||||
../serialization/preprocessor.cpp \
|
||||
../serialization/string_utils.cpp \
|
||||
../zipios++/xcoll.cpp \
|
||||
exploder_composer.hpp \
|
||||
exploder_cutter.hpp \
|
||||
exploder_utils.hpp \
|
||||
../serialization/preprocessor.hpp \
|
||||
../serialization/string_utils.hpp \
|
||||
../zipios++/xcoll.hpp \
|
||||
../gettext.cpp
|
||||
|
||||
|
@ -37,10 +41,14 @@ cutter_SOURCES = cutter.cpp \
|
|||
../game_config.cpp \
|
||||
../sdl_utils.cpp \
|
||||
../log.cpp \
|
||||
../serialization/preprocessor.cpp \
|
||||
../serialization/string_utils.cpp \
|
||||
../zipios++/xcoll.cpp \
|
||||
exploder_composer.hpp \
|
||||
exploder_cutter.hpp \
|
||||
exploder_utils.hpp \
|
||||
../serialization/preprocessor.hpp \
|
||||
../serialization/string_utils.hpp \
|
||||
../zipios++/xcoll.hpp \
|
||||
../gettext.cpp
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue