Esta función es de las más complejas ya que tenemos que tratar con el escape de símbolos, incluyendo símbolos hexadecimales.
Empecemos por el principio y continuemos poco a poco.
La función devolverá una celda. Debido a que, según sea un nombre o no, la celda será STRING_LIT o NAME_CODE y no lo sabríamos por adelantado; hemos de añadir un flag create_name que indique a la función si quiere una cosa u otra.
CELL& Script::ReadString(ISTREAM& i, bool create_name) {
Lo primero es quitar las dobles comillas con las que empieza la cadena.
i.get();
Y vamos a ir leyendo mientras no encontremos el cierre de las comillas o haya habido un error. Usaremos la varible s para ir guardando lo que vayamos leyendo.
STRING s; while(i.peek()!='"' && i.good()) {
Aquí empiezan las complicaciones, si encontramos una barra de escape, debemos leer el siguiente carácter y actuar en consecuencia.
if(i.peek()=='\\') { i.get(); switch(i.get()) {
Los primeros casos son los códigos de control que se traducen directamente al C/C++. Para los curiosos, los códigos de control se listan en esta página.
case 'a': s+=L"\a"; break; case 'b': s+=L"\b"; break; case 'f': s+=L"\f"; break; case 'n': s+=L"\n"; break; case 'r': s+=L"\r"; break; case 't': s+=L"\t"; break; case 'v': s+=L"\v"; break; case '\'': s+=L"'"; break; case '"': s+=L"\""; break; case '\\': s+=L"\\"; break;
En nuestro lenguaje vamos a variar algunas cosas. Por ejemplo, no usamos la comilla simple, pero sí quiero escapar la interrogación.
case '?': s+=L"?"; break;
Si empieza por una equis, entramos en la descripción hexadecimal del carácter escapado. Vamos a ir leyendo dígitos hexadecimales y guardarlos en la variable x.
case 'x': { STRING x; while(unsigned(i.peek())<256 && isxdigit(i.peek()) && i.good()) x.push_back(i.get()); if(x.empty()) throw L"Expecting a hexadecimal digit after \\x";
Usamos la función wcstoul para convertir lo leído en un número hexadecimal. Sólo vamos a permitir hasta el primer plano del UNICODE.
int n=wcstoul(x.c_str(), NULL, 16); if(n>0xFFFF) throw L"Invalid hexadecimal character";
Metemos el carácter en la cadena que estamos construyendo y comprobamos que le sigue un punto y coma. Tampoco es usual tener esto en C, pero quita ambigüedades y añade robustez.
s.push_back(wchar_t(n)); if(i.get()!=';') throw L"Expecting semicolon after hex escape sequence"; } break;
En otro caso, no conocemos la secuencia de escape.
default: throw L"Unknown escape sequence"; } }
Si no era una secuencia de escape, lo introduce directamente en la cadena. Esto puede ser problemático porque copia directamente cualquier cosa, incluso las que no se ven, del código fuente a la cadena; pero como es un mini lenguaje, vamos a asumirlo.
else s+=i.get(); }
Finalmente, comprobamos algunas cosas como que terminemos en unas comillas o que no haya habido algún error.
if(i.bad()) throw L"Error while reading a string"; if(i.get()!='"') throw L"Unexpected end of file inside a string";
A la hora de retornar la celda, según sea el flag pasado, creamos una cadena o un nombre. Realmente, ahora me doy cuenta que es un error haberlo hecho así y es mejor usar composición de funciones. Para la siguiente versión.
return create_name ? CreateName(s) : CreateString(s); }
La siguiente parte de esta serie se va a dedicar a leer nombres y símbolos.
0 comentarios:
Publicar un comentario