diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | bin/tests/test_string.cpp | 340 | ||||
-rw-r--r-- | core/ustring.cpp | 296 | ||||
-rw-r--r-- | core/ustring.h | 8 | ||||
-rw-r--r-- | core/variant_op.cpp | 14 |
5 files changed, 652 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore index 1e53f3712b..3bf450aca8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ tools/editor/register_exporters.cpp tools/editor/doc_data_compressed.h tools/editor/editor_icons.cpp -fpic +log.txt # Android specific platform/android/java/local.properties diff --git a/bin/tests/test_string.cpp b/bin/tests/test_string.cpp index 66238b066d..4aaddd8ac1 100644 --- a/bin/tests/test_string.cpp +++ b/bin/tests/test_string.cpp @@ -487,7 +487,7 @@ struct test_27_data { bool test_27() { - OS::get_singleton()->print("\n\nTest 26: begins_with\n"); + OS::get_singleton()->print("\n\nTest 27: begins_with\n"); test_27_data tc[] = { {"res://foobar", "res://", true}, {"res", "res://", false}, @@ -504,11 +504,348 @@ bool test_27() { } if (!state) { OS::get_singleton()->print("\n\t Failure on:\n\t\tstring: ", tc[i].data, "\n\t\tbegin: ", tc[i].begin, "\n\t\texpected: ", tc[i].expected ? "true" : "false", "\n"); + break; } }; return state; }; + +bool test_28() { + + OS::get_singleton()->print("\n\nTest 28: sprintf\n"); + + bool success, state = true; + char output_format[] = "\tTest:\t%ls => %ls (%s)\n"; + String format, output; + Array args; + + // %% + format = "fish %% frog"; + args.clear(); + output = format.sprintf(args); + success = (output == String("fish % frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + //////// INTS + + // Int + format = "fish %d frog"; + args.clear(); + args.push_back(5); + output = format.sprintf(args); + success = (output == String("fish 5 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Int left padded with zeroes. + format = "fish %05d frog"; + args.clear(); + args.push_back(5); + output = format.sprintf(args); + success = (output == String("fish 00005 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Int left padded with spaces. + format = "fish %5d frog"; + args.clear(); + args.push_back(5); + output = format.sprintf(args); + success = (output == String("fish 5 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Int right padded with spaces. + format = "fish %-5d frog"; + args.clear(); + args.push_back(5); + output = format.sprintf(args); + success = (output == String("fish 5 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Int with sign (positive). + format = "fish %+d frog"; + args.clear(); + args.push_back(5); + output = format.sprintf(args); + success = (output == String("fish +5 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Negative int. + format = "fish %d frog"; + args.clear(); + args.push_back(-5); + output = format.sprintf(args); + success = (output == String("fish -5 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Hex (lower) + format = "fish %x frog"; + args.clear(); + args.push_back(45); + output = format.sprintf(args); + success = (output == String("fish 2d frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Hex (upper) + format = "fish %X frog"; + args.clear(); + args.push_back(45); + output = format.sprintf(args); + success = (output == String("fish 2D frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Octal + format = "fish %o frog"; + args.clear(); + args.push_back(99); + output = format.sprintf(args); + success = (output == String("fish 143 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + ////// REALS + + // Real + format = "fish %f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 99.990000 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real left-padded + format = "fish %11f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 99.990000 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real right-padded + format = "fish %-11f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 99.990000 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real given int. + format = "fish %f frog"; + args.clear(); + args.push_back(99); + output = format.sprintf(args); + success = (output == String("fish 99.000000 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real with sign (positive). + format = "fish %+f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish +99.990000 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real with 1 decimals. + format = "fish %.1f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 100.0 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real with 12 decimals. + format = "fish %.12f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 99.990000000000 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Real with no decimals. + format = "fish %.f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 100 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + /////// Strings. + + // String + format = "fish %s frog"; + args.clear(); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == String("fish cheese frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // String left-padded + format = "fish %10s frog"; + args.clear(); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == String("fish cheese frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // String right-padded + format = "fish %-10s frog"; + args.clear(); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == String("fish cheese frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + ///// Characters + + // Character as string. + format = "fish %c frog"; + args.clear(); + args.push_back("A"); + output = format.sprintf(args); + success = (output == String("fish A frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Character as int. + format = "fish %c frog"; + args.clear(); + args.push_back(65); + output = format.sprintf(args); + success = (output == String("fish A frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + ///// Dynamic width + + // String dynamic width + format = "fish %*s frog"; + args.clear(); + args.push_back(10); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == String("fish cheese frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Int dynamic width + format = "fish %*d frog"; + args.clear(); + args.push_back(10); + args.push_back(99); + output = format.sprintf(args); + success = (output == String("fish 99 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Float dynamic width + format = "fish %*.*f frog"; + args.clear(); + args.push_back(10); + args.push_back(3); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == String("fish 99.990 frog")); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + ///// Errors + + // More formats than arguments. + format = "fish %s %s frog"; + args.clear(); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // More arguments than formats. + format = "fish %s frog"; + args.clear(); + args.push_back("hello"); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Incomplete format. + format = "fish %10"; + args.clear(); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Bad character in format string + format = "fish %&f frog"; + args.clear(); + args.push_back("cheese"); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Too many decimals. + format = "fish %2.2.2f frog"; + args.clear(); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // * not a number + format = "fish %*f frog"; + args.clear(); + args.push_back("cheese"); + args.push_back(99.99); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Character too long. + format = "fish %c frog"; + args.clear(); + args.push_back("sc"); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + // Character bad type. + format = "fish %c frog"; + args.clear(); + args.push_back(Array()); + output = format.sprintf(args); + success = (output == ""); + OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + if (!success) state = false; + + return state; +} + typedef bool (*TestFunc)(void); TestFunc test_funcs[] = { @@ -540,6 +877,7 @@ TestFunc test_funcs[] = { test_25, test_26, test_27, + test_28, 0 }; diff --git a/core/ustring.cpp b/core/ustring.cpp index 581cc29440..476ab3f936 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -34,6 +34,7 @@ #include "io/md5.h" #include "ucaps.h" #include "color.h" +#include "variant.h" #define MAX_DIGITS 6 #define UPPERCASE(m_c) (((m_c)>='a' && (m_c)<='z')?((m_c)-('a'-'A')):(m_c)) #define LOWERCASE(m_c) (((m_c)>='A' && (m_c)<='Z')?((m_c)+('a'-'A')):(m_c)) @@ -981,7 +982,7 @@ String String::num(double p_num,int p_decimals) { } -String String::num_int64(int64_t p_num) { +String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { bool sign=p_num<0; int64_t num=ABS(p_num); @@ -990,7 +991,7 @@ String String::num_int64(int64_t p_num) { int chars=0; do { - n/=10; + n/=base; chars++; } while(n); @@ -1002,8 +1003,15 @@ String String::num_int64(int64_t p_num) { c[chars]=0; n=num; do { - c[--chars]='0'+(n%10); - n/=10; + int mod = n%base; + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars]=a+(mod - 10); + } else { + c[--chars]='0'+mod; + } + + n/=base; } while(n); if (sign) @@ -3518,4 +3526,284 @@ String rtoss(double p_val) { return String::num_scientific(p_val); } +// Right-pad with a character. +String String::rpad(int min_length, const String& character) const { + String s = *this; + int padding = min_length - s.length(); + if (padding > 0) { + for (int i = 0; i < padding; i++) s = s + character; + } + + return s; +} +// Left-pad with a character. +String String::lpad(int min_length, const String& character) const { + String s = *this; + int padding = min_length - s.length(); + if (padding > 0) { + for (int i = 0; i < padding; i++) s = character + s; + } + + return s; +} + +// sprintf is implemented in GDScript via: +// "fish %s pie" % "frog" +// "fish %s %d pie" % ["frog", 12] +String String::sprintf(const Array& values) const { + + String formatted; + CharType* self = (CharType*)c_str(); + int num_items = values.size(); + bool in_format = false; + int value_index = 0; + int min_chars; + int min_decimals; + bool in_decimals; + bool pad_with_zeroes; + bool left_justified; + bool show_sign; + + + for (; *self; self++) { + const CharType c = *self; + + if (in_format) { // We have % - lets see what else we get. + switch (c) { + case '%': { // Replace %% with % + formatted += chr(c); + in_format = false; + break; + } + case 'd': // Integer (signed) + case 'o': // Octal + case 'x': // Hexadecimal (lowercase) + case 'X': { // Hexadecimal (uppercase) + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } + + if (!values[value_index].is_num()) { + ERR_EXPLAIN("a number is required"); + ERR_FAIL_V(""); + } + + int64_t value = values[value_index]; + int base; + bool capitalize = false; + switch (c) { + case 'd': base = 10; break; + case 'o': base = 8; break; + case 'x': base = 16; break; + case 'X': base = 16; capitalize = true; break; + } + // Get basic number. + String str = String::num_int64(value, base, capitalize); + + // Sign. + if (show_sign && value >= 0) { + str = str.insert(0, "+"); + } + + // Padding. + String pad_char = pad_with_zeroes ? String("0") : String(" "); + if (left_justified) { + str = str.rpad(min_chars, pad_char); + } else { + str = str.lpad(min_chars, pad_char); + } + + formatted += str; + ++value_index; + in_format = false; + + break; + } + case 'f': { // Float + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } + + if (!values[value_index].is_num()) { + ERR_EXPLAIN("a number is required"); + ERR_FAIL_V(""); + } + + double value = values[value_index]; + String str = String::num(value, min_decimals); + + // Pad decimals out. + str = str.pad_decimals(min_decimals); + + // Show sign + if (show_sign && value >= 0) { + str = str.insert(0, "+"); + } + + // Padding + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + + formatted += str; + ++value_index; + in_format = false; + + break; + } + case 's': { // String + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } + + String str = values[value_index]; + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case 'c': { + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } + + // Convert to character. + String str; + if (values[value_index].is_num()) { + int value = values[value_index]; + if (value < 0) { + ERR_EXPLAIN("unsigned byte integer is lower than maximum") + ERR_FAIL_V(""); + } else if (value > 255) { + ERR_EXPLAIN("unsigned byte integer is greater than maximum") + ERR_FAIL_V(""); + } + str = chr(values[value_index]); + } else if (values[value_index].get_type() == Variant::STRING) { + str = values[value_index]; + if (str.length() != 1) { + ERR_EXPLAIN("%c requires number or single-character string"); + ERR_FAIL_V(""); + } + } else { + ERR_EXPLAIN("%c requires number or single-character string"); + ERR_FAIL_V(""); + } + + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + formatted += str; + ++value_index; + in_format = false; + break; + } + case '-': { // Left justify + left_justified = true; + break; + } + case '+': { // Show + if positive. + show_sign = true; + break; + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + int n = c - '0'; + if (in_decimals) { + min_decimals *= 10; + min_decimals += n; + } else { + if (c == '0' && min_chars == 0) { + pad_with_zeroes = true; + } else { + min_chars *= 10; + min_chars += n; + } + } + break; + } + case '.': { // Float separtor. + if (in_decimals) { + ERR_EXPLAIN("too many decimal points in format"); + ERR_FAIL_V(""); + } + in_decimals = true; + min_decimals = 0; // We want to add the value manually. + break; + } + + case '*': { // Dyanmic width, based on value. + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } + + if (!values[value_index].is_num()) { + ERR_EXPLAIN("* wants number"); + ERR_FAIL_V(""); + } + + int size = values[value_index]; + + if (in_decimals) { + min_decimals = size; + } else { + min_chars = size; + } + + ++value_index; + break; + } + + default: { + ERR_EXPLAIN("unsupported format character"); + ERR_FAIL_V(""); + } + } + } else { // Not in format string. + switch (c) { + case '%': + in_format = true; + // Back to defaults: + min_chars = 0; + min_decimals = 6; + pad_with_zeroes = false; + left_justified = false; + show_sign = false; + in_decimals = false; + break; + default: + formatted += chr(c); + } + } + } + + if (in_format) { + ERR_EXPLAIN("incomplete format"); + ERR_FAIL_V(""); + } + + if (value_index != values.size()) { + ERR_EXPLAIN("not all arguments converted during string formatting"); + ERR_FAIL_V(""); + } + + return formatted; +} diff --git a/core/ustring.h b/core/ustring.h index e1d6761742..af5ffb7c35 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -31,6 +31,7 @@ #include "typedefs.h" #include "vector.h" +#include "array.h" /** @author red <red@killy> @@ -127,10 +128,13 @@ public: String insert(int p_at_pos,String p_string) const; String pad_decimals(int p_digits) const; String pad_zeros(int p_digits) const; + String lpad(int min_length,const String& character=" ") const; + String rpad(int min_length,const String& character=" ") const; + String sprintf(const Array& values) const; static String num(double p_num,int p_decimals=-1); static String num_scientific(double p_num); static String num_real(double p_num); - static String num_int64(int64_t p_num); + static String num_int64(int64_t p_num,int base=10,bool capitalize_hex=false); static String chr(CharType p_char); static String md5(const uint8_t *p_md5); bool is_numeric() const; @@ -203,7 +207,7 @@ public: String xml_unescape() const; String c_escape() const; String c_unescape() const; - + String percent_encode() const; String percent_decode() const; diff --git a/core/variant_op.cpp b/core/variant_op.cpp index fbb5e2631d..21bbc8c7ee 100644 --- a/core/variant_op.cpp +++ b/core/variant_op.cpp @@ -736,6 +736,20 @@ void Variant::evaluate(const Operator& p_op, const Variant& p_a, const Variant& } #endif _RETURN( p_a._data._int % p_b._data._int ); + + } else if (p_a.type==STRING) { + const String *str=reinterpret_cast<const String*>(p_a._data._mem); + + if (p_b.type==ARRAY) { + // e.g. "frog %s %d" % ["fish", 12] + const Array *arr=reinterpret_cast<const Array*>(p_b._data._mem); + _RETURN(str->sprintf(*arr)); + } else { + // e.g. "frog %d" % 12 + Array arr; + arr.push_back(p_b); + _RETURN(str->sprintf(arr)); + } } r_valid=false; |