Skip to content

Commit

Permalink
Implemented support for optional macro arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
ln-zookeeper committed Dec 3, 2016
1 parent cc5071f commit 255dd6b
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 19 deletions.
2 changes: 2 additions & 0 deletions changelog
Expand Up @@ -227,6 +227,8 @@ Version 1.13.6:
supported) keys
* Individual [terrain_graphics] rotations can now be skipped entirely by
using "skip" in the rotations= list.
* WML macros can now include optional arguments with default values that the
caller can override.
* Miscellaneous and bug fixes:
* A new way to make units invulnerable for debugging: select the unit and type
";unit invulnerable=yes". This method operates by decreasing the opponent's hit
Expand Down
129 changes: 114 additions & 15 deletions src/serialization/preprocessor.cpp
Expand Up @@ -884,12 +884,13 @@ bool preprocessor_data::get_chunk()
skip_spaces();
int linenum = linenum_;
std::vector< std::string > items = utils::split(read_line(), ' ');
std::map< std::string, std::string> optargs;
if (items.empty()) {
target_.error("No macro name found after #define directive", linenum);
}
std::string symbol = items.front();
items.erase(items.begin());
int found_enddef = 0;
int found_arg = 0, found_enddef = 0;
std::string buffer;
for(;;) {
if (in_.eof())
Expand All @@ -898,10 +899,48 @@ bool preprocessor_data::get_chunk()
if (d == '\n')
++linenum_;
buffer += d;
if (d == '#')
found_enddef = 1;
else if (found_enddef > 0)
if (++found_enddef == 7) {
if (d == '#') {
if (in_.peek() == 'a') {
found_arg = 1;
} else {
found_enddef = 1;
}
} else {
if (found_arg > 0 && ++found_arg == 4) {
if (std::equal(buffer.end() - 3, buffer.end(), "arg")) {
buffer.erase(buffer.end() - 3, buffer.end());

skip_spaces();
std::string argname = read_word();
skip_eol();

std::string argbuffer;

int found_endarg = 0;
for(;;) {
if (in_.eof())
break;
char e = in_.get();
if (e == '\n')
++linenum_;
argbuffer += e;

if (e == '#') {
found_endarg = 1;
} else if (found_endarg > 0 && ++found_endarg == 7) {
if (std::equal(argbuffer.end() - 6, argbuffer.end(), "endarg")) {
argbuffer.erase(argbuffer.end() - 7, argbuffer.end());
optargs[argname] = argbuffer;
break;
} else {
target_.error("Unterminated #arg definition", linenum_);
}
}
}
}
}

if (found_enddef > 0 && ++found_enddef == 7) {
if (std::equal(buffer.end() - 6, buffer.end(), "enddef"))
break;
else {
Expand All @@ -911,6 +950,7 @@ bool preprocessor_data::get_chunk()
}
}
}
}
}
if (found_enddef != 7) {
target_.error("Unterminated preprocessor definition", linenum_);
Expand All @@ -932,7 +972,7 @@ bool preprocessor_data::get_chunk()
}

buffer.erase(buffer.end() - 7, buffer.end());
(*target_.defines_)[symbol] = preproc_define(buffer, items, target_.textdomain_,
(*target_.defines_)[symbol] = preproc_define(buffer, items, optargs, target_.textdomain_,
linenum, target_.location_);
LOG_PREPROC << "defining macro " << symbol << " (location " << get_location(target_.location_) << ")\n";
}
Expand Down Expand Up @@ -1124,26 +1164,85 @@ bool preprocessor_data::get_chunk()
}
else if (target_.depth_ < 100 && (macro = target_.defines_->find(symbol)) != target_.defines_->end())
{
preproc_define const &val = macro->second;
const preproc_define &val = macro->second;
size_t nb_arg = strings_.size() - token.stack_pos - 1;
if (nb_arg != val.arguments.size())
size_t optional_arg_num = 0;

std::map<std::string, std::string> *defines = new std::map<std::string, std::string>;
const std::string& dir = filesystem::directory_name(val.location.substr(0, val.location.find(' ')));

for (size_t i = 0; i < nb_arg; ++i) {
if (i < val.arguments.size()) {
// Normal mandatory arguments

(*defines)[val.arguments[i]] = strings_[token.stack_pos + i + 1];
} else {
// These should be optional argument overrides

std::string str = strings_[token.stack_pos + i + 1];
size_t equals_pos = str.find_first_of("=");

if (equals_pos != std::string::npos) {
size_t argname_pos = str.substr(0, equals_pos).find_last_of(" \n") + 1;

std::string argname = str.substr(argname_pos, equals_pos - argname_pos);

if (val.optional_arguments.find(argname) != val.optional_arguments.end()) {
(*defines)[argname] = str.substr(equals_pos + 1);

optional_arg_num++;

DBG_PREPROC << "Found override for " << argname << " in call to macro " << symbol << "\n";
} else {
std::ostringstream warning;
warning << "Unrecognized optional argument passed to macro '" << symbol << "': '" << argname << "'";
target_.warning(warning.str(), linenum_);

optional_arg_num++; // To prevent the argument number check from blowing up
}
}
}
}

// If the macro definition has any optional arguments, insert their defaults
if (val.optional_arguments.size() > 0) {
for (const auto &arg : val.optional_arguments) {
if (defines->find(arg.first) == defines->end()) {
std::ostringstream res;
preprocessor_streambuf *buf = new preprocessor_streambuf(target_);

buf->textdomain_ = target_.textdomain_;
std::istream in(buf);

std::istringstream *buffer = new std::istringstream(arg.second);

This comment has been minimized.

Copy link
@GregoryLundberg

GregoryLundberg Jan 13, 2018

Contributor

buffer appears to be leaked.

This comment has been minimized.

Copy link
@AI0867

AI0867 Jan 13, 2018

Member

the new preprocessor_data takes ownership using a smart ptr.


std::map<std::string, std::string> *temp_defines = new std::map<std::string, std::string>;
temp_defines->insert(defines->begin(), defines->end());

new preprocessor_data(*buf, buffer, val.location, "", val.linenum, dir, val.textdomain, temp_defines, false);
res << in.rdbuf();

DBG_PREPROC << "Setting default for optional argument " << arg.first << " in macro " << symbol << "\n";

(*defines)[arg.first] = res.str();

delete buf;
}
}
}

if (nb_arg - optional_arg_num != val.arguments.size())
{
const std::vector<std::string>& locations = utils::quoted_split(val.location, ' ');
std::ostringstream error;
error << "Preprocessor symbol '" << symbol << "' defined at "
<< get_filename(locations[0]) << ":" << val.linenum << " expects "
<< val.arguments.size() << " arguments, but has "
<< nb_arg << " arguments";
<< nb_arg - optional_arg_num << " arguments";
target_.error(error.str(), linenum_);
}
std::istringstream *buffer = new std::istringstream(val.value);
std::map<std::string, std::string> *defines =
new std::map<std::string, std::string>;
for (size_t i = 0; i < nb_arg; ++i) {
(*defines)[val.arguments[i]] = strings_[token.stack_pos + i + 1];
}
pop_token();
const std::string& dir = filesystem::directory_name(val.location.substr(0, val.location.find(' ')));
if (!slowpath_) {
DBG_PREPROC << "substituting macro " << symbol << '\n';
new preprocessor_data(target_, buffer, val.location, "",
Expand Down
9 changes: 5 additions & 4 deletions src/serialization/preprocessor.hpp
Expand Up @@ -32,13 +32,14 @@ typedef std::map< std::string, preproc_define > preproc_map;

struct preproc_define
{
preproc_define() : value(), arguments(), textdomain(), linenum(0), location() {}
explicit preproc_define(const std::string& val) : value(val), arguments(), textdomain(), linenum(0), location() {}
preproc_define(const std::string& val, std::vector< std::string > const &args,
preproc_define() : value(), arguments(), optional_arguments(), textdomain(), linenum(0), location() {}
explicit preproc_define(const std::string& val) : value(val), arguments(), optional_arguments(), textdomain(), linenum(0), location() {}
preproc_define(const std::string& val, const std::vector< std::string > &args, const std::map< std::string, std::string> &optargs,
const std::string& domain, int line, const std::string& loc)
: value(val), arguments(args), textdomain(domain), linenum(line), location(loc) {}
: value(val), arguments(args), optional_arguments(optargs), textdomain(domain), linenum(line), location(loc) {}
std::string value;
std::vector< std::string > arguments;
std::map< std::string, std::string> optional_arguments;
std::string textdomain;
int linenum;
std::string location;
Expand Down

0 comments on commit 255dd6b

Please sign in to comment.