Files
MagicSetEditor2/src/util/spec_sort.cpp
T
2020-05-12 21:57:37 +02:00

360 lines
11 KiB
C++

//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <util/spec_sort.hpp>
#include <util/error.hpp>
const Char REMOVED = _('\0');
const Char PLACEHOLDER = _('\3');
String spec_sort(const String& spec, String& input, String& ret);
// ----------------------------------------------------------------------------- : Iterator for reading specs
/// Iterator over a sort specification (for spec_sort)
class SpecIterator {
public:
SpecIterator(const String& spec, size_t pos = 0)
: spec(spec), pos(pos)
{}
wxUniChar value; ///< Current character
bool escaped; ///< Was the current character escaped?
bool preceded_by_space; ///< Was there a ' ' before this character?
/// Move to the next item in the specification.
/** returns false if we are at the end or encounter close.
*/
bool nextUntil(wxUniChar close, bool skip_space = true) {
if (pos >= spec.size()) {
value = 0;
if (close == 0) {
return false;
} else {
throw ParseError(String::Format(_("Expected '%c' in sort_rule specification"),close));
}
}
value = spec.GetChar(pos++);
preceded_by_space = false;
// skip whitespace
if (skip_space) {
while (value == _(' ')) {
if (pos >= spec.size()) {
if (close == 0) {
return false;
} else {
throw ParseError(String::Format(_("Expected '%c' in sort_rule specification"),close));
}
}
value = spec.GetChar(pos++);
preceded_by_space = true;
}
}
// escape?
if (value == _('\\')) {
escaped = true;
if (pos >= spec.size()) {
throw ParseError(String::Format(_("Expected '%c' in sort_rule specification"),close));
}
value = spec.GetChar(pos++);
} else {
escaped = false;
if (value == close) return false;
}
return true;
}
/// Read a whole parameter, terminated by close
String readParam(Char close, bool skip_space = true) {
String ret;
while(nextUntil(close)) ret += value;
return ret;
}
/// Read a parameter, matches nested parentheses, keeps escape sequences
String readRawParam(Char close1, Char close2 = 0) {
String ret;
int parens = 0;
while (nextUntil(0, false)) {
if (escaped) {
ret += _('\\');
} else {
if (parens == 0 && (value == close1 || value == close2)) break;
if (value == _('(')) parens++;
else if (value == _(')')) parens--;
}
ret += value;
}
return ret;
}
/// Does the current position match a keyword? If so, skip it
bool keyword(const Char* kw) {
if (value == kw[0]) {
if (is_substr(spec, pos, kw + 1)) {
pos += wxStrlen(kw + 1);
return true;
}
}
return false;
}
private:
const String& spec;
size_t pos;
};
// ----------------------------------------------------------------------------- : Sort functions
//using Bag = vector<wxUniChar>;
using Bag = String;
size_t count_and_remove(wxUniChar c, Bag& input) {
size_t count = 0;
size_t j=0;
for (size_t i=0 ; i < input.size() ; ++i) {
if (input[i] == c) {
count++;
input[i] = REMOVED;
} else {
//input[j++] = input[i];
}
}
//input.resize(j);
return count;
}
/// Sort a string using a specification using the shortest cycle method, see spec_sort
/** Removed used characters from input! */
void cycle_sort(const String& spec, Bag& input, Bag& ret) {
// count occurences of each item in spec
vector<size_t> counts;
for(auto s : spec) {
counts.push_back(count_and_remove(s, input));
}
// determine best start point
size_t best_start = 0;
size_t best_start_score = 0xffffffff;
for (size_t start = 0 ; start < spec.size() ; ++start) {
// score of a start position, can be considered as:
// - count saturated to binary
// - rotated left by start
// - interpreted as a binary number, but without trailing 0s
size_t score = 0, mul = 1;
for (size_t i = 0 ; i < spec.size() ; ++i) {
mul *= 2;
if (counts[(start + i) % spec.size()]) {
score = score * mul + 1;
mul = 1;
}
}
if (score < best_start_score) {
best_start_score = score;
best_start = start;
}
}
// add to return string
for (size_t i = 0 ; i < spec.size() ; ++i) {
size_t pos = (best_start + i) % spec.size();
ret.append(counts[pos], spec[pos]);
}
}
/// Sort a string, keeping the characters in the original order
/** Removed used characters from input! */
void mixed_sort(const String& spec, Bag& input, Bag& ret) {
size_t j = 0;
for (wxUniCharRef c : input) {
if (spec.find(c) != String::npos) {
ret += c;
c = REMOVED;
}
}
}
/// Sort a string, find a compound item
/** Removed used characters from input! */
void compound_sort(const String& spec, Bag& input, Bag& ret) {
size_t j=0;
for (size_t i=0 ; i < input.size() ; ++i) {
// match?
if (i+spec.size() <= input.size() && std::equal(spec.begin(), spec.end(), input.begin()+i)) {
i += spec.size() - 1;
ret += spec;
input[i] = REMOVED;
}
}
}
/// Sort things matching a pattern
void pattern_sort(const String& pattern, const String& spec, Bag& input, Bag& ret) {
if (pattern.size() > input.size()) return;
size_t end = input.size() - pattern.size() + 1;
size_t pos_new = 0;
//for (size_t pos = 0 ; pos < end ; ++pos) {
for (size_t pos = 0; pos < end; ++pos) {
if (pos + pattern.size() > input.size()) {
goto no_match;
}
{
// does the pattern match here?
String placeholders;
for (size_t j = 0; j < pattern.size(); ++j) {
wxUniChar c = input[pos + j];
wxUniChar p = pattern[j];
if (c == REMOVED) {
goto no_match;
} else if (p == PLACEHOLDER) {
placeholders += c;
} else if (c != p) {
goto no_match;
}
}
// we have a match
// sort placeholders
String new_placeholders = spec_sort(spec, placeholders);
if (new_placeholders.size() == placeholders.size()) {
// add to output, erase from input
size_t ph = 0;
for (size_t j = 0; j < pattern.size(); ++j) {
wxUniChar p = pattern[j];
if (p == PLACEHOLDER) {
ret += new_placeholders[ph++];
} else {
ret += p;
}
}
// skip over matched pattern
pos += pattern.size() - 1;
continue;
}
}
no_match:
input[pos_new++] = input[pos];
}
input.resize(pos_new);
}
/// Sort things in place, keep the rest of the input
void in_place_sort(const String& spec, String& input, String& ret) {
String result;
spec_sort(spec, input, result);
// restore into the same order as in 'input'
size_t pos_r = 0;
FOR_EACH_CONST(c, input) {
if (c == REMOVED) {
if (pos_r < result.size()) {
ret += result.GetChar(pos_r++);
}
} else {
ret += c;
}
}
input.clear(); // we ate all the input
}
// ----------------------------------------------------------------------------- : spec_sort
String spec_sort(const String& spec, String& input, String& ret) {
SpecIterator it(spec);
while(it.nextUntil(0)) {
if (it.escaped) { // single character, escaped
size_t count = count_and_remove(it.value, input);
ret.append(count, it.value);
} else if (it.value == _('<')) { // keep only a single copy
while (it.nextUntil(_('>'))) {
size_t pos = input.find_first_of(it.value);
if (pos != String::npos) {
input.erase(pos, 1);
ret += it.value; // input contains it.value
}
}
} else if (it.keyword(_("once("))) {
while (it.nextUntil(_(')'))) {
size_t pos = input.find_first_of(it.value);
if (pos != String::npos) {
input.erase(pos, 1);
ret += it.value; // input contains it.value
}
}
} else if (it.value == _('[')) { // in input order
mixed_sort(it.readParam(_(']')), input, ret);
} else if (it.keyword(_("mixed("))) {
mixed_sort(it.readParam(_(')')), input, ret);
} else if (it.keyword(_("cycle("))) {
cycle_sort(it.readParam(_(')')), input, ret);
} else if (it.value == _('(')) {
cycle_sort(it.readParam(_(')')), input, ret);
} else if (it.keyword(_("compound("))) { // compound item
compound_sort(it.readParam(_(')')), input, ret);
} else if (it.keyword(_("pattern("))) { // recurse with pattern
String pattern;
// read pattern
while (it.nextUntil(_(' '), false)) {
if (it.value == _('.') && !it.escaped) {
pattern += PLACEHOLDER;
} else {
pattern += it.value;
}
}
// read spec to apply to pattern
String sub_spec = it.readRawParam(_(')'));
// sort
pattern_sort(pattern, sub_spec, input, ret);
} else if (it.keyword(_("in_place("))) { // recurse without pattern
// read spec to apply to pattern
String sub_spec = it.readRawParam(_(')'));
in_place_sort(sub_spec, input, ret);
} else if (it.keyword(_("any()"))) { // remaining input
FOR_EACH_CONST(d, input) {
if (d != REMOVED) {
ret += d;
}
}
input.clear();
} else if (it.keyword(_("reverse_order("))) { // reverse order of preference
vector<String> parts;
while (it.value != _(')')) {
String sub_spec = it.readRawParam(_(')'),_(' '));
String part;
spec_sort(sub_spec, input, part);
parts.push_back(part);
}
// add parts in reverse order
reverse(parts.begin(), parts.end());
for (auto const& part : parts) {
ret += part;
}
} else if (it.keyword(_("ordered("))) { // in spec order
while (it.nextUntil(_(')'))) {
size_t count = count_and_remove(it.value, input);
ret.append(count, it.value);
}
} else { // single char
size_t count = count_and_remove(it.value, input);
ret.append(count, it.value);
}
}
return ret;
}
String spec_sort(const String& spec, String input) {
String ret;
spec_sort(spec, input, ret);
return ret;
}