Split the preprocessor from the config system. No feature change yet.

This commit is contained in:
Guillaume Melquiond 2005-02-20 19:22:31 +00:00
parent a1f3c79ad3
commit e77411ae2a
10 changed files with 647 additions and 395 deletions

View file

@ -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 \

View file

@ -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

View file

@ -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)
{

View file

@ -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.

View 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());
}

View 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

View 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;
}
}

View 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

View file

@ -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)/..

View file

@ -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