objective-c - numero - string.format visual basic
¿Cómo comprobar que dos cadenas de formato son compatibles? (2)
Ejemplos:
"Something %d" and "Something else %d" // Compatible
"Something %d" and "Something else %f" // Not Compatible
"Something %d" and "Something %d else %d" // Not Compatible
"Something %d and %f" and "Something %2$f and %1$d" // Compatible
Pensé que debería haber alguna función C para esto, pero no obtengo ningún resultado de búsqueda relevante. Me refiero a que el compilador está comprobando que la cadena de formato y los argumentos coinciden, por lo que el código para verificar esto ya está escrito. La única pregunta es cómo puedo llamarlo.
Estoy usando Objective-C, así que si hay una solución específica para Objective-C también está bien.
Comprobar si 2 cadenas de formato printf()
son compatibles es un ejercicio de análisis de formato.
C, al menos, no tiene una función estándar de comparación en tiempo de ejecución, como por ejemplo:
int format_cmp(const char *f1, const char *f2); // Does not exist
Formatos como "%d %f"
y "%i %e"
son obviamente compatibles ya que ambos esperan un int
y un float/double
. Nota: float
se promueve para double
como short
y signed char
se promueve a int
.
Los formatos "%*.*f"
y "%i %d %e"
son compatibles, pero no obvios: ambos esperan un int
, int
y float/double
.
Los formatos "%hhd"
y "%d"
esperan un int
, aunque el primero tendrá sus valores para el signed char
antes de imprimir.
Los formatos "%d"
y "%u"
no son compatibles. A pesar de que muchos sistemas se comportaron como se esperaba. Nota: Por lo general, char
fomentará a int
.
Los formatos "%d"
y "%ld"
no son estrictamente compatibles. En un sistema de 32 bits hay equivalentes, pero no en general. Por supuesto, el código se puede modificar para acomodar esto. OTOH "%lf"
y "%f"
son compatibles debido a las promociones de argumentos habituales de float
a double
.
Los formatos "%lu"
y "zu"
pueden ser compatibles, pero eso depende de la implementación de unsigned long
y size_t
. Las adiciones al código podrían permitir esta o equivalencias relacionadas.
Algunas combinaciones de modificadores y especificadores no se definen como "zp"
. Lo siguiente no deshabilita tales combinaciones esotéricas, pero sí las compara.
Los modificadores como "$"
son extensiones del estándar C y no se implementan en lo siguiente.
La prueba de compatibilidad para printf()
difiere de scanf()
.
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
typedef enum {
type_none,
type_int,
type_unsigned,
type_float,
type_charpointer,
type_voidpointer,
type_intpointer,
type_unknown,
type_type_N = 0xFFFFFF
} type_type;
typedef struct {
const char *format;
int int_queue;
type_type type;
} format_T;
static void format_init(format_T *state, const char *format);
static type_type format_get(format_T *state);
static void format_next(format_T *state);
void format_init(format_T *state, const char *format) {
state->format = format;
state->int_queue = 0;
state->type = type_none;
format_next(state);
}
type_type format_get(format_T *state) {
if (state->int_queue > 0) {
return type_int;
}
return state->type;
}
const char *seek_flag(const char *format) {
while (strchr("-+ #0", *format) != NULL)
format++;
return format;
}
const char *seek_width(const char *format, int *int_queue) {
*int_queue = 0;
if (*format == ''*'') {
format++;
(*int_queue)++;
} else {
while (isdigit((unsigned char ) *format))
format++;
}
if (*format == ''.'') {
if (*format == ''*'') {
format++;
(*int_queue)++;
} else {
while (isdigit((unsigned char ) *format))
format++;
}
}
return format;
}
const char *seek_mod(const char *format, int *mod) {
*mod = 0;
if (format[0] == ''h'' && format[1] == ''h'') {
format += 2;
} else if (format[0] == ''l'' && format[1] == ''l'') {
*mod = (''l'' << CHAR_BIT) + ''l'';
format += 2;
} else if (strchr("ljztL", *format)) {
*mod = *format;
format++;
} else if (strchr("h", *format)) {
format++;
}
return format;
}
const char *seek_specifier(const char *format, int mod, type_type *type) {
if (strchr("di", *format)) {
*type = type_int;
format++;
} else if (strchr("ouxX", *format)) {
*type = type_unsigned;
format++;
} else if (strchr("fFeEgGaA", *format)) {
if (mod == ''l'') mod = 0;
*type = type_float;
format++;
} else if (strchr("c", *format)) {
*type = type_int;
format++;
} else if (strchr("s", *format)) {
*type = type_charpointer;
format++;
} else if (strchr("p", *format)) {
*type = type_voidpointer;
format++;
} else if (strchr("n", *format)) {
*type = type_intpointer;
format++;
} else {
*type = type_unknown;
exit(1);
}
*type |= mod << CHAR_BIT; // Bring in modifier
return format;
}
void format_next(format_T *state) {
if (state->int_queue > 0) {
state->int_queue--;
return;
}
while (*state->format) {
if (state->format[0] == ''%'') {
state->format++;
if (state->format[0] == ''%'') {
state->format++;
continue;
}
state->format = seek_flag(state->format);
state->format = seek_width(state->format, &state->int_queue);
int mod;
state->format = seek_mod(state->format, &mod);
state->format = seek_specifier(state->format, mod, &state->type);
return;
} else {
state->format++;
}
}
state->type = type_none;
}
// 0 Compatible
// 1 Not Compatible
// 2 Not Comparable
int format_cmp(const char *f1, const char *f2) {
format_T state1;
format_init(&state1, f1);
format_T state2;
format_init(&state2, f2);
while (format_get(&state1) == format_get(&state2)) {
if (format_get(&state1) == type_none)
return 0;
if (format_get(&state1) == type_unknown)
return 2;
format_next(&state1);
format_next(&state2);
}
if (format_get(&state1) == type_unknown)
return 2;
if (format_get(&state2) == type_unknown)
return 2;
return 1;
}
Nota: solo se realizan pruebas mínimas. Se podrían agregar muchas consideraciones adicionales.
Deficiencias conocidas: modificadores hh,h,l,ll,j,z,t
con n
. l
con s,c
.
[Editar]
OP comenta sobre preocupaciones de seguridad. Esto cambia la naturaleza de la publicación y la comparación de una de igualdad a una de seguridad. Me imagino que uno de los patrones (A) sería un patrón de referencia y el siguiente (B) sería la prueba. La prueba sería "¿B es al menos tan segura como A?". Ejemplo A = "%.20s"
y B1 = "%.19s"
, B2 = "%.20s"
, B3 = "%.21s"
. B1
y B2
pasan la prueba de seguridad ya que no extraen más el 20 char
. B3
es un problema ya que pasa el límite de referencia de 20 caracteres. Además, cualquier ancho no calificado con %s %[ %c
es un problema de seguridad: en el patrón de referencia o prueba. El código de esta respuesta no aborda este problema.
Como se mencionó, el código aún no maneja los modificadores con "%n"
.
[2018 edit]
En relación con "Formatos "%d"
y "%u"
no son compatibles.": Esto es para que los valores se impriman en general. Para valores en el rango [0..INT_MAX]
, cualquiera de los formatos puede funcionar por C11dr §6.5.2.2 6.
Mi comprensión de lo que quieres es que, básicamente, quieres un método que pueda mirar dos cadenas y detectar si ambos tienen los mismos tipos de valores. O algo así como esas líneas ... Si es así, prueba esto (o algo parecido a esto):
-(int)checkCompatible:(NSString *)string_1 :(NSString *)string_2 {
// Separate the string into single elements.
NSArray *stringArray_1 = [string_1 componentsSeparatedByString:@" "];
NSArray *stringArray_2 = [string_2 componentsSeparatedByString:@" "];
// Store only the numbers for comparison in a new array.
NSMutableArray *numbers_1 = [[NSMutableArray alloc] init];
NSMutableArray *numbers_2 = [[NSMutableArray alloc] init];
// Make sure the for loop below, runs for the appropriate
// number of cycles depending on which array is bigger.
int loopMax = 0;
if ([stringArray_1 count] > [stringArray_2 count]) {
loopMax = (int)[stringArray_1 count];
}
else {
loopMax = (int)[stringArray_2 count];
}
// Now go through the stringArray''s and store only the
// numbers in the mutable array''s. This will be used
// during the comparison stage.
for (int loop = 0; loop < loopMax; loop++) {
NSCharacterSet *notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
if (loop < [stringArray_1 count]) {
if ([[stringArray_1 objectAtindex:loop] rangeOfCharacterFromSet:notDigits].location == NSNotFound) {
// String consists only of the digits 0 through 9.
[numbers_1 addObject:[stringArray_1 objectAtindex:loop]];
}
}
if (loop < [stringArray_2 count]) {
if ([[stringArray_2 objectAtindex:loop] rangeOfCharacterFromSet:notDigits].location == NSNotFound) {
// String consists only of the digits 0 through 9.
[numbers_2 addObject:[stringArray_2 objectAtindex:loop]];
}
}
}
// Now look through the mutable array''s
// and perform the type comparison,.
if ([numbers_1 count] != [numbers_2 count]) {
// One of the two strings has more numbers
// than the other, so they are NOT compatible.
return 1;
}
else {
// Both string have the same number of numbers
// numbers so lets go through them to make
// sure the numbers are of the same type.
for (int loop = 0; loop < [numbers_1 count]; loop++) {
// Check to see if the number in the current array index
// is a float or an integer. All the numbers in the array have
// to be the SAME type, in order for the strings to be compatible.
BOOL check_float_1 = [[NSScanner scannerWithString:[numbers_1 objectAtindex:loop]] scanFloat:nil];
BOOL check_int_1 = [[NSScanner scannerWithString:[numbers_1 objectAtindex:loop]] scanInt:nil];
BOOL check_float_2 = [[NSScanner scannerWithString:[numbers_2 objectAtindex:loop]] scanFloat:nil];
BOOL check_int_2 = [[NSScanner scannerWithString:[numbers_2 objectAtindex:loop]] scanInt:nil];
if (check_float_1 == YES) {
if (check_float_2 == NO) {
return 1;
}
}
else if (check_int_1 == YES) {
if (check_int_2 == NO) {
return 1;
}
}
else {
// Error of some sort......
return 1;
}
}
// All the numbers in the strings are of the same
// type (otherwise we would NOT have reached
// this point). Therefore the strings are compatible.
return 0;
}
}