Browse Source

Added basic sprintf functionality (e.g. "fish %d %s" % [12, Vector2(1, 2)])

Bil Bas (Spooner) 10 years ago
parent
commit
7a41f8c604
4 changed files with 193 additions and 2 deletions
  1. 78 1
      bin/tests/test_string.cpp
  2. 98 0
      core/ustring.cpp
  3. 3 1
      core/ustring.h
  4. 14 0
      core/variant_op.cpp

+ 78 - 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,87 @@ 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 = (format.sprintf(args) == String("fish % frog"));
+	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
+	if (!success) state = false;
+
+	// Int
+	format = "fish %d frog";
+	args.clear();
+	args.push_back(5);
+	output = format.sprintf(args);
+	success = (format.sprintf(args) == 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 = (format.sprintf(args) == 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 = (format.sprintf(args) == 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 = (format.sprintf(args) == String("fish 143 frog"));
+	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
+	if (!success) state = false;
+
+	// Real
+	format = "fish %f frog";
+	args.clear();
+	args.push_back(99.99);
+	output = format.sprintf(args);
+	success = (format.sprintf(args) == String("fish 99.990000 frog"));
+	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
+	if (!success) state = false;
+
+	// String
+	format = "fish %s frog";
+	args.clear();
+	args.push_back("cheese");
+	output = format.sprintf(args);
+	success = (format.sprintf(args) == String("fish cheese frog"));
+	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 +616,7 @@ TestFunc test_funcs[] = {
 	test_25,
 	test_26,
 	test_27,
+	test_28,
 	0
 	
 };

+ 98 - 0
core/ustring.cpp

@@ -34,6 +34,8 @@
 #include "io/md5.h"
 #include "ucaps.h"
 #include "color.h"
+#include "variant.h"
+#include <stdio.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))
@@ -3518,4 +3520,100 @@ String rtoss(double p_val) {
 	return String::num_scientific(p_val);
 }
 
+// sprintf is implemented in GDScript via:
+//   "fish %s pie" % "frog"
+//   "fish %s %d pie" % ["frog", 12]
+const int FORMAT_BUFFER_SIZE = 1024;
+const int OUTPUT_BUFFER_SIZE = 1024 * 100;
+String String::sprintf(const Array& values) const {
+
+	String formatted;
+	CharType* self = (CharType*)c_str();
+	bool in_format = false;
+	int value_index = 0;
+	char format_format[FORMAT_BUFFER_SIZE] = "%d";
+
+	for (; *self; self++) {
+		const CharType c = *self;
+
+		if (in_format) { // We have % - lets see what else we get.
+			switch (c) {
+				case '%': // Manage %% as %
+					formatted += chr(c);
+					in_format = false;
+					break;
+
+				case 'd': // Integer (signed)
+				case 'o': // Octal
+				case 'x': // Hexadecimal (lowercase)
+				case 'X': // Hexadecimal (uppercase)
+					if (values[value_index].is_num()) {
+						char buffer[OUTPUT_BUFFER_SIZE];
+						int value = values[value_index]; 
+						format_format[1] = c;
+						format_format[2] = 0;
+						::sprintf(buffer, format_format, value);
+						
+						formatted += String(buffer);
+						++value_index;
+						in_format = false;
+					} else {
+						// TODO: Error?
+					}
+					
+					break;
+
+				case 'f': // Float
+					if (values[value_index].is_num()) {
+						char buffer[OUTPUT_BUFFER_SIZE];
+						double value = values[value_index];
+						::sprintf(buffer, "%f", value);
+
+						formatted += String(buffer);
+						++value_index;
+						in_format = false;
+					} else {
+						// TODO: Error?
+					}
+					
+					break;
+
+				case 's': // String
+					String value = values[value_index];
+					formatted += value;
+					++value_index;
+					in_format = false;
+					break;
+
+				// case '-': // Left justify
+				// 	break;
+
+				// case '+': // Show + if positive.
+				// 	break;
+
+				// case '0': case '1': case '2': case '3': case '4':
+				// case '5': case '6': case '7': case '8': case '9':
+				// 	break;
+
+				// case '.': // Float separtor.
+				// 	break;
+
+				// case '*': // Dyanmic width, based on value.
+				// 	break;
 
+				//default:
+					// TODO: error?
+			}
+		} else { // Not in format string.
+			switch (c) {
+				case '%':
+					in_format = true;
+					break;
+				default:
+					formatted += chr(c);
+			}
+		}
+	}
+
+	return formatted;
+}

+ 3 - 1
core/ustring.h

@@ -31,6 +31,7 @@
 
 #include "typedefs.h"
 #include "vector.h"
+#include "array.h"
 
 /**
 	@author red <red@killy>
@@ -127,6 +128,7 @@ 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 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);
@@ -203,7 +205,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;