From 7a41f8c604143eec16c232cffda122b095c5108f Mon Sep 17 00:00:00 2001 From: "Bil Bas (Spooner)" Date: Sat, 10 Jan 2015 20:44:20 +0000 Subject: Added basic sprintf functionality (e.g. "fish %d %s" % [12, Vector2(1, 2)]) --- core/ustring.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++ core/ustring.h | 4 ++- core/variant_op.cpp | 14 ++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) (limited to 'core') diff --git a/core/ustring.cpp b/core/ustring.cpp index 581cc29440..a7359f112f 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -34,6 +34,8 @@ #include "io/md5.h" #include "ucaps.h" #include "color.h" +#include "variant.h" +#include #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; +} diff --git a/core/ustring.h b/core/ustring.h index e1d6761742..ccbbf5e5d2 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -31,6 +31,7 @@ #include "typedefs.h" #include "vector.h" +#include "array.h" /** @author red @@ -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; diff --git a/core/variant_op.cpp b/core/variant_op.cpp index ec43b1275c..533a9d6952 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(p_a._data._mem); + + if (p_b.type==ARRAY) { + // e.g. "frog %s %d" % ["fish", 12] + const Array *arr=reinterpret_cast(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; -- cgit v1.2.3 From 6306254d37292fa2590799c1fdd08536326465d5 Mon Sep 17 00:00:00 2001 From: "Bil Bas (Spooner)" Date: Sun, 1 Feb 2015 18:42:36 +0000 Subject: Completed implementing standard formatting. --- core/ustring.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++------------- core/ustring.h | 4 +- 2 files changed, 135 insertions(+), 40 deletions(-) (limited to 'core') diff --git a/core/ustring.cpp b/core/ustring.cpp index a7359f112f..222c445b43 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -35,7 +35,6 @@ #include "ucaps.h" #include "color.h" #include "variant.h" -#include #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)) @@ -983,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); @@ -992,7 +991,7 @@ String String::num_int64(int64_t p_num) { int chars=0; do { - n/=10; + n/=base; chars++; } while(n); @@ -1004,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) @@ -3520,41 +3526,84 @@ 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] -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"; + int min_chars; + int num_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 '%': // Manage %% as % + 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) + 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); + 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; } else { @@ -3562,14 +3611,28 @@ String String::sprintf(const Array& values) const { } break; - - case 'f': // Float + } + case 'f': { // Float if (values[value_index].is_num()) { - char buffer[OUTPUT_BUFFER_SIZE]; double value = values[value_index]; - ::sprintf(buffer, "%f", value); + String str = String::num(value, num_decimals); + + // Pad decimals out. + str = str.pad_decimals(num_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 += String(buffer); + formatted += str; ++value_index; in_format = false; } else { @@ -3577,26 +3640,49 @@ String String::sprintf(const Array& values) const { } break; + } + case 's': { // String + String str = values[value_index]; + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } - case 's': // String - String value = values[value_index]; - formatted += value; + formatted += str; ++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 '-': { // 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) { + num_decimals *= 10; + num_decimals += n; + } else { + if (c == '0' && min_chars == 0) { + pad_with_zeroes = true; + } else { + min_chars *= 10; + min_chars += n; + } + } + break; + } + case '.': // Float separtor. + in_decimals = true; + num_decimals = 0; // We want to add the value manually. + break; // case '*': // Dyanmic width, based on value. // break; @@ -3608,6 +3694,13 @@ String String::sprintf(const Array& values) const { switch (c) { case '%': in_format = true; + // Back to defaults: + min_chars = 0; + num_decimals = 6; + pad_with_zeroes = false; + left_justified = false; + show_sign = false; + in_decimals = false; break; default: formatted += chr(c); diff --git a/core/ustring.h b/core/ustring.h index ccbbf5e5d2..af5ffb7c35 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -128,11 +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; -- cgit v1.2.3 From af7c8bdf236b7c572bc33a44e3bb64fecdaa99d9 Mon Sep 17 00:00:00 2001 From: "Bil Bas (Spooner)" Date: Sun, 1 Feb 2015 20:18:38 +0000 Subject: Completed more complex formatting. --- core/ustring.cpp | 211 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 154 insertions(+), 57 deletions(-) (limited to 'core') diff --git a/core/ustring.cpp b/core/ustring.cpp index 222c445b43..476ab3f936 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -3554,15 +3554,17 @@ 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 num_decimals; + int min_decimals; bool in_decimals; bool pad_with_zeroes; bool left_justified; bool show_sign; + for (; *self; self++) { const CharType c = *self; @@ -3577,71 +3579,88 @@ String String::sprintf(const Array& values) const { case 'o': // Octal case 'x': // Hexadecimal (lowercase) case 'X': { // Hexadecimal (uppercase) - if (values[value_index].is_num()) { - 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); + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } - // Sign. - if (show_sign && value >= 0) { - str = str.insert(0, "+"); - } + 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); - // 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); - } + // Sign. + if (show_sign && value >= 0) { + str = str.insert(0, "+"); + } - formatted += str; - ++value_index; - in_format = false; + // Padding. + String pad_char = pad_with_zeroes ? String("0") : String(" "); + if (left_justified) { + str = str.rpad(min_chars, pad_char); } else { - // TODO: Error? + str = str.lpad(min_chars, pad_char); } - + + formatted += str; + ++value_index; + in_format = false; + break; } case 'f': { // Float - if (values[value_index].is_num()) { - double value = values[value_index]; - String str = String::num(value, num_decimals); + if (value_index >= values.size()) { + ERR_EXPLAIN("not enough arguments for format string"); + ERR_FAIL_V(""); + } - // Pad decimals out. - str = str.pad_decimals(num_decimals); + if (!values[value_index].is_num()) { + ERR_EXPLAIN("a number is required"); + ERR_FAIL_V(""); + } - // Show sign - if (show_sign && value >= 0) { - str = str.insert(0, "+"); - } + double value = values[value_index]; + String str = String::num(value, min_decimals); - // Padding - if (left_justified) { - str = str.rpad(min_chars); - } else { - str = str.lpad(min_chars); - } + // Pad decimals out. + str = str.pad_decimals(min_decimals); + + // Show sign + if (show_sign && value >= 0) { + str = str.insert(0, "+"); + } - formatted += str; - ++value_index; - in_format = false; + // Padding + if (left_justified) { + str = str.rpad(min_chars); } else { - // TODO: Error? + 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) { @@ -3655,6 +3674,47 @@ String String::sprintf(const Array& values) const { 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; @@ -3667,8 +3727,8 @@ String String::sprintf(const Array& values) const { case '5': case '6': case '7': case '8': case '9': { int n = c - '0'; if (in_decimals) { - num_decimals *= 10; - num_decimals += n; + min_decimals *= 10; + min_decimals += n; } else { if (c == '0' && min_chars == 0) { pad_with_zeroes = true; @@ -3679,16 +3739,43 @@ String String::sprintf(const Array& values) const { } break; } - case '.': // Float separtor. + case '.': { // Float separtor. + if (in_decimals) { + ERR_EXPLAIN("too many decimal points in format"); + ERR_FAIL_V(""); + } in_decimals = true; - num_decimals = 0; // We want to add the value manually. + 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; + } - // case '*': // Dyanmic width, based on value. - // break; + ++value_index; + break; + } - //default: - // TODO: error? + default: { + ERR_EXPLAIN("unsupported format character"); + ERR_FAIL_V(""); + } } } else { // Not in format string. switch (c) { @@ -3696,7 +3783,7 @@ String String::sprintf(const Array& values) const { in_format = true; // Back to defaults: min_chars = 0; - num_decimals = 6; + min_decimals = 6; pad_with_zeroes = false; left_justified = false; show_sign = false; @@ -3708,5 +3795,15 @@ String String::sprintf(const Array& values) const { } } + 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; } -- cgit v1.2.3