viernes, julio 28, 2006

ST2JS - Traductor de Smalltalk a JavaScript (Parte II)

¡Se va la segunda!

Ayer, después de publicar la primera parte de esta serie, logré "terminar" (¿se termina alguna ves de desarrollar el software?) una versión del paquete lo suficientemente completa y estable como para ejecutar un Test-Runner en un browser de internet, desde código JavaScript, completamente generado desde clases Smalltalk.

Para los más valientes publiqué, tanto en SqueakMap como en SqueakSource, un paquete Monticello que funciona tanto en la versión 3.8 como en la 3.9beta de Squeak. Al instalar el paquete se abrirán 2 Workspaces con algo de información sobre como probar el paquete.

Quizás lo más interesante de ver sea el St2jS - TestRunner:


El St2jS - TestRunner es, como su nombre indica, un TestRunner de un framework de UnitTesting. Lo particular de este TestRunner es que está completamente escrito en Smallltalk, y funciona completamente del lado del browser de internet. Además de los tests (que se ven en verde), se incluyen un conjunto de clases con objetos "de base" (Object, Boolean, Point, WriteStream, etc).

La misma herramienta nos permite ver los segmentos de código generado con las opciones: browse all (muestra todo el código JS generado, en un sólo archivo), browse library (muestra el código generado correspondiente a las implementaciones de #jsLibrarySource), browse class: (muestra el código de una clase completa) y browse (muestra el fuente del método test correspondiente).

También podemos re-ejecutar los tests del lado del browser de internet con las opciones run all (ejecuta todos los tests del lado del browser de internet) y run (ejecuta el test correspondiente en el browser de internet).

Y, también, podemos (desde el browser de internet) disparar funcionalidades del lado del Squeak con las opciones squeak: run (ejecuta el método test en Squeak) y squeak: browse (abre el browser de clases, en Squeak, apuntando al método test).

Otra funcionalidad ya implementada, y necesaria para cualquier programador que se digne de ser un Smalltalker, es el Inspector:


Para obtener un inspector, hay que descomentar la última sentencia del método S2SBaseTestCase>>testInspect y recargar el browser de internet. El mismo inspector nos permite abrir otros inspectores para las partes del objeto inspeccionado. En este caso, si presionamos el botón [inspect] que está a la derecha del punto, obtenemos:



¡Continuará!


UPDATE: Para los que no quieran quieran perder tiempo instalando nada, les dejo un HTML, con todo el código JS incluido, que debiera funcionar en cualquier navegador. Viendo el fuente del documento se puede ver el código generado. St2jS - Test Runner.html

UPDATE 2: Una versión más mueva del HTML independiente: St2jS - Test Runner.html.

jueves, julio 27, 2006

ST2JS - Traductor de Smalltalk a JavaScript (Parte I)

Hace tiempo que no publico nada en el blog, pero eso no fue debido a que estuve de vacaciones. Lo que ocurre es que sigo muy ocupado investigando sobre las posibilidades que brindan los navegadores de Internet actuales.

Live-Wiki

Para publicar los resultados de la investigación estoy haciendo un Live-Wiki. El Live-Wiki será Wiki sin modo edición, donde se se modificará el documento directamente (ver EditInPlace). Los cambios hechos en las páginas del wiki, serán enviadas al instante (usando Comet) a todos los usuarios conectados al wiki (y viendo esa página). También habrá un chat para los usuarios conectados a la página. Iré contando más del Live-Wiki conforme vaya avanzando en el desarrollo.

JavaScript

Para sacar beneficio de las prestaciones que ofrecen los navegadores de internet actuales es necesario escribir una buena cantidad de código JavaScript. El JavaScript es un lindo lenguajecito con algunos conceptos de objetos y funcionales que lenguajes supuestamente serios ni sueñan en tratar de proveer. Por otro lado, es un lenguaje basado en prototipos (no en clases) que permite jugar con diferentes políticas de herencia. Las funciones son, como corresponde, objetos y esto permite encapsular comportamiento de una forma similar a los bloques de Smalltalk. Para mejorar el panorama, las funciones en JavaScript son verdaderos block-closures.


ST2JS - Traductor de Smalltalk a JavaScript

Las características de JavaScript mencionadas anteriormente, usadas con picardía, permiten hacer un traductor de Smalltalk a JavaScript que respete toda la semántica del Smalltalk.

La semántica de las metaclases de Smalltalk podría lograrse utilizando un modelo similar al explicado en el paper "self includes: Smalltalk" (de Mario Wolczko).

Los bloques de Smalltalk se traducen más o menos directamente a funciones de JS. Sólo hay que considerar 2 diferencias: el this/self y la diferencia entre el return/^.

En Smalltalk, la pseudovariable self apunta al receptor del método donde se evalúa el bloque. En cambio, en JS, la pseudovariable this apunta al objeto función. Como las funciones de JS son verdaderos block-closures, este problema se resuelve fácilmente de la siguiente forma:

En Smalltalk:

SampleClass>>foo
    self collection do:[:each |
        each = 2
            ifTrue:[self bar: 1].
    ].

En JavaScript:

SampleClass.prototype.foo = function() {
    /* En este punto, this y self son lo mismo: El receptor del mensaje que activó el método */
    var self = this;
    self.collection().do_(function(each) {
     if (each = 2) {
         self.bar_(1); /* En este punto, this apunta a la función y no al receptor. Por eso usamos la variable self apoyándonos en los true-block-closures de JS */
     }
    });
}


La otra diferencia requiere un poco más de "magia" para resolverlo. El punto es que en Smalltalk, el ^ usado dentro de un bloque, hace terminar no sólo la evaluación del bloque, sino también del método donde se activó el bloque. En cambio, en JS, el return sólo sale de la función (es decir de 1 sólo nivel).

En Smalltalk:

SampleClass>>bar
    self collection do:[:each |
        each = 2
            ifTrue:[^ self bar].
    ].

En JavaScript:

SampleClass.prototype.bar = function() {
    var self = this;
    try {
        self.collection().do_(function(each) {
         if (each = 2) {
             throw new ReturnValue(self.bar_()); /* La excepción viajará hasta el catch(), es decir hasta el método */
         }
        });
    }
    catch (err) {
        if (err.constructor == ReturnValue) {
            return err.value;
        }
        else {
            throw err;
        }
    }
    return self;
}