jueves, 30 de abril de 2015

miniSL parte 26 - Funciones Nativas sobre Enteros (I)

En esta entrada del lenguaje MiniSL vamos a empezar a definir las funciones predefinidas. Empezaremos por las operaciones básicas para enteros.

Todas las funciones predefinidas se almacenan en celdas NATIVE_VAL. Estas celdas almacenan un puntero a la función que vamos a definir. Al evaluarlas se ejecuta el siguiente código en la función Evaluate()

case NATIVE_VAL: return aux->native(*this, *c.operands, envir);

El primer argumento de una función nativa es el propio objeto Script. Usamos este objeto para crear nuevas celdas en el montículo o evaluar subexpresiones. El segundo argumento es la celda con la lista de operandos. Es importante aquí distinguir entre un operando y un argumento. Un operando es una expresión que todavía no se ha evaluado, mientras que un argumento es el valor obtenido de evaluar esa expresión en el entorno dinámico. Finalmente, ese entorno dinámico es el que se pasa en el tercer argumento.

Supongamos que queremos sumar dos números. Lo que tendríamos que hacer para hacer eso es evaluar los operandos para obtener los argumentos (recordemos que la evaluación de una lista es la lista de los resultados de la evaluación, ver en la parte 14 el caso  CONS_CTOR), sumarlos y crear una nueva celda con el resultado. Lo más tedioso es comprobar que los argumentos existen y navegar por la lista para obtenerlos. Sería algo como esta función:

CELL& Native_Add2(Script& script, CELL& args, CELL& envir)
{ 
 CELL* a=&script.Evaluate(args, envir);
 if(a->type==CONS_CTOR) 
 {
  int l=a->head->GetInteger();
  a=a->tail;
  if(a->type==CONS_CTOR)
   return script.CreateInteger(l + a->head->GetInteger()); 
 } 
 throw L"Argument missing";
}

Realmente, podemos definir las operaciones básicas para cero, uno o múltiples argumentos. Bastaría usar un bucle. Tomamos como resultado de sumar ningún argumento el elemento neutro de la suma que es el cero.

CELL& Native_AddN(Script& script, CELL& args, CELL& envir) 
{
 CELL* a=&script.Evaluate(args, envir); 
 int r=0;
 if(a->type==CONS_CTOR) 
 {
  r=a->head->GetInteger(); 
  for(a=a->tail; a->type==CONS_CTOR; a=a->tail)
   r=r + a->head->GetInteger();
 }
 return script.CreateInteger(r);
}

Y fácilmente podemos hacer una macro que cambie el + por lo que sea y el 0 por lo que sea. El único truco es usar el operador de concatenación de tokens ## para generar cada vez un nombre distinto.

#define NATIVE_INTEGER_BINARY(name, emptyval, op) \
CELL& Native_##name(Script& script, CELL& args, CELL& envir) \
{ CELL* a=&script.Evaluate(args, envir); \
 int r=emptyval; \
 if(a->type==CONS_CTOR) \
 { r=a->head->GetInteger(); \
  for(a=a->tail; a->type==CONS_CTOR; a=a->tail) \
   r=r op a->head->GetInteger(); \
 } \
 return script.CreateInteger(r); \
}

Y usamos esta macro para definir la suma, resta, multiplicación, and bit a bit, or bit a bit y xor bit a bit.

NATIVE_INTEGER_BINARY(Add, 0, +)
NATIVE_INTEGER_BINARY(Sub, 0, -)
NATIVE_INTEGER_BINARY(Mul, 1, *)
NATIVE_INTEGER_BINARY(BAnd, -1, &)
NATIVE_INTEGER_BINARY(BOr, 0, |)
NATIVE_INTEGER_BINARY(BXor, 0, ^)

No hemos definido ni la división ni el módulo ya que requieren la comprobación de que el denominador se distinto a cero. Lo dejamos para la siguiente entrada de esta serie.