summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--bin/tests/test_string.cpp340
-rw-r--r--core/ustring.cpp296
-rw-r--r--core/ustring.h8
-rw-r--r--core/variant_op.cpp14
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;