Browse Source

Merge pull request #1176 from Spooner/add_sprintf

Add sprintf
Juan Linietsky 10 years ago
parent
commit
66d32b75b3
6 changed files with 1006 additions and 20 deletions
  1. 1 0
      .gitignore
  2. 339 1
      bin/tests/test_string.cpp
  3. 292 4
      core/ustring.cpp
  4. 6 2
      core/ustring.h
  5. 14 0
      core/variant_op.cpp
  6. 354 13
      demos/3d/platformer/stage.xml

+ 1 - 0
.gitignore

@@ -20,6 +20,7 @@ tools/editor/doc_data_compressed.h
 tools/editor/editor_icons.cpp
 -fpic
 .fscache
+log.txt
 
 # Android specific
 platform/android/java/local.properties

+ 339 - 1
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
 	
 };

+ 292 - 4
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;
+}

+ 6 - 2
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;
 

+ 14 - 0
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;

File diff suppressed because it is too large
+ 354 - 13
demos/3d/platformer/stage.xml


Some files were not shown because too many files changed in this diff