From 255dd6bfc32aafebdf6b83dd7ddc25ceb9dd7e86 Mon Sep 17 00:00:00 2001 From: Lari Nieminen Date: Wed, 30 Nov 2016 23:08:58 +0200 Subject: [PATCH] Implemented support for optional macro arguments --- changelog | 2 + src/serialization/preprocessor.cpp | 129 +++++++++++++++++++++++++---- src/serialization/preprocessor.hpp | 9 +- 3 files changed, 121 insertions(+), 19 deletions(-) diff --git a/changelog b/changelog index 741b9bf7b79f..f7a1edde8277 100644 --- a/changelog +++ b/changelog @@ -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 diff --git a/src/serialization/preprocessor.cpp b/src/serialization/preprocessor.cpp index 0ccaab5155a3..2728356ee857 100644 --- a/src/serialization/preprocessor.cpp +++ b/src/serialization/preprocessor.cpp @@ -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()) @@ -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 { @@ -911,6 +950,7 @@ bool preprocessor_data::get_chunk() } } } + } } if (found_enddef != 7) { target_.error("Unterminated preprocessor definition", linenum_); @@ -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"; } @@ -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 *defines = new std::map; + 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); + + std::map *temp_defines = new std::map; + 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& 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 *defines = - new std::map; - 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, "", diff --git a/src/serialization/preprocessor.hpp b/src/serialization/preprocessor.hpp index 4679adc32191..12deea029e20 100644 --- a/src/serialization/preprocessor.hpp +++ b/src/serialization/preprocessor.hpp @@ -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;