jueves, 27 de junio de 2013

miniSL parte 20 - Lectura de nombres y símbolos

En esta entrada vamos a describir cómo se analizan los tokens de nombres y símbolos. Los nombres son muy simples en su composición. Son caracteres alfanuméricos o un subrayado (guión bajo). Sólo llamaremos a ReadName() cuando el primer carácter sea alfabético, por lo que no haremos esta comprobación otra vez.

Script::TOKEN Script::ReadName(ISTREAM& i)
{
 STRING s;
 while(unsigned(i.peek())<256 && (isalnum(i.peek()) || i.peek()=='_') && i.good())
  s+=i.get();

 if(i.bad()) throw L"Error while reading an identifier or a keyword";

 return TOKEN(T_NAME, &CreateName(s));
}

La lectura de símbolos es algo más delicada. En definitiva, toda secuencia de caracteres simbólicos va a ser un símbolo; excepto cuando hay un igual. De esa manera podremos analizar cosas como “a=-3” de la manera correcta “a = -3”.

CELL& Script::ReadSymbol(ISTREAM& i)
{
 STRING s;
 while(CELL::IsSymbol(i.peek()) && i.good())
 {
  s+=i.get();

Aquí es donde comprobamos el igual. Como queremos heredar de C/C++ el símbolo “==” para la igualdad, hemos de asegurarnos que no se rompa este doble igual.

  if(i.peek()!='=' && *(s.end()-1)=='=')
   break;
 }

Internamente, un símbolo es un nombre así que usamos CreateName().

 return CreateName(s);
}

Finalmente, para que funcione el algoritmo de playa de agujas, hemos de establecer la precedencia y asociatividad de cada símbolo. Esto lo hace la siguiente función que asume que los símbolos serán similares a los del C/C++.

int Script::SymbolPrecedenceAndAssoc(STRING const& s)
{

Los símbolos que acaban en “=” como “+=” o “%=” son asignaciones (prioridad 11 muy baja) o relacionales (mayor igual o menor igual, prioridad 40).

 bool assign_end= s.at(s.size()-1)=='=';

 switch(s.at(0))
 {
 case '*': case '/': case '%':   return assign_end ? 11 : 100;
 case '+': case '-':      return assign_end ? 11 : 90;

Como comentamos antes, el caso de los relacionales es algo especial ya que tenemos que distinguir “<<” (desplazamiento) de “<=” (relacional) y de “<<=” (asignación).

 case '>': case '<':
  if(s.size()>1 && s.at(0)==s.at(1)) return assign_end ? 11 : 80;
  else        return 40;

Los símbolos que empiezan por “&” también tienen un tratamiento especial ya que debemos distinguir “&&” (lógico) de “&” (operación de bits) y “&=” (asignación).

 case '&':
  if(s.size()>1 && s.at(0)==s.at(1)) return assign_end? 11 : 30 ;
  else        return assign_end? 11 : 70 ;

Los que empiezan por “|” son similares a los que empiezan con “&”, pero con una prioridad algo menor.

 case '|': case '^':
  if(s.size()>1 && s.at(0)==s.at(1)) return assign_end? 11 : 20;
  else        return assign_end? 11 : 60;

Finalmente, los símbolos “!=”, “==”, “~=” y “?=” vamos a tratarlos como relacionales con prioridad 50. El “=” es asignación y tendrá prioridad 11. Finalmente, si no acaba en “=”, será operación con prioridad 54.

 case '!': case '=': case '~': case '?':
  if(assign_end)      return s.size()==1 ? 11 : 50;
  else        return 54;

Si no conocemos el símbolo, error.

 default:
  throw L"Unknown symbol";
 }
}

En la próxima entrada veremos la lectura de números. Es algo más compleja porque vamos a leer números enteros en base decimal o hexadecimal, pero veremos algunos trucos para simplificar el código.