scieee AI-readable full text Open interactive document viewer

Generador d'aplicacions natives per a Snap!

Hintze Molero, Adrian

Abstract

This project consisted in the development of an application able to generate executable files from a Snap! project. The executables are based on a reduced execution environment generated by our application and are built and sent to the end user by a Web Service.

Full text

Generador de aplicaciones nativas para Snap! Universidad Politécnica de Cataluña (UPC) Facultad de Informática de Barcelona (FIB) Trabajo final de Grado en Ingeniería Informática Especialidad de computación Autor: Adrian Hintze Molero Director: Jordi Delgado Pin (Dept. CS, UPC) Octubre 2014 Índice 1 Abstract..................................................................................................................1 1.1 Castellano........................................................................................................1 1.2 Català...............................................................................................................1 1.3 English..............................................................................................................2 2 Introducción..........................................................................................................3 2.1 Snap!................................................................................................................4 2.2 Objetivos..........................................................................................................5 2.3 Impacto social, ambiental y económico...........................................................6 2.3.1 Impacto social.......................................................................................................6 2.3.2 Impacto ambiental................................................................................................6 2.3.3 Impacto económico..............................................................................................6 2.4 Estado del arte.................................................................................................7 3 Planificación........................................................................................................10 3.1 Planificación inicial.........................................................................................10 3.2 Implementación de la Parte Técnica..............................................................11 3.3 Preparación de la Entrega Final.....................................................................11 4 Presupuesto........................................................................................................12 4.1 Recursos Humanos........................................................................................12 4.2 Recursos no Humanos...................................................................................13 4.2.1 Hardware............................................................................................................13 4.2.2 Software.............................................................................................................14 4.2.3 Total....................................................................................................................14 4.3 Total................................................................................................................14 5 Generación de ejecutables.................................................................................15 5.1 NW.js..............................................................................................................15 6 Generación del entorno de ejecución...............................................................18 6.1 Estilo del código de Snap...............................................................................18 6.2 Estructura general del código........................................................................20 6.3 Viabilidad de la modificación programática del código fuente de Snap.........20 6.4 Interfaces........................................................................................................22 6.4.1 Información sobre el código a eliminar..............................................................23 6.5 Justificación del sistema................................................................................24 6.6 Gramática.......................................................................................................25 6.6.1 ANTLR4..............................................................................................................25 6.6.2 Gramática personalizada...................................................................................28 6.6.3 Proceso de creación de la gramática.................................................................28 6.7 Algoritmo........................................................................................................29 6.7.1 Identificación de elementos relevantes..............................................................29 6.7.2 Definición............................................................................................................30 7 Implementación...................................................................................................32 7.1 Estilo de aplicación........................................................................................32 7.2 Módulos de la aplicación................................................................................33 7.3 Interfaces........................................................................................................33 7.3.1 Atributos pragma................................................................................................34 7.3.2 Uso de las interfaces..........................................................................................36 7.3.3 Pragmas en el código.........................................................................................36 7.4 Modificador de código Snap...........................................................................37 7.4.1 Estructuras de datos..........................................................................................37 7.4.2 Parser/ANTLR4..................................................................................................38 7.4.2.1 SnapTreeWalker.........................................................................................38 7.4.3 Punto de entrada................................................................................................39 7.4.4 PragmaParser....................................................................................................40 7.4.5 WalkListener.......................................................................................................40 7.4.6 CheckListener.....................................................................................................41 7.5 Aplicación Web...............................................................................................42 7.5.1 Página Web........................................................................................................42 7.5.1.1 Cuerpo de la página....................................................................................42 7.5.1.2 Estilo de la página.......................................................................................43 7.5.1.3 Scripts de la página.....................................................................................43 7.5.2 Web Service.......................................................................................................46 8 Resultados...........................................................................................................47 8.1 Generación de ejecutables............................................................................47 8.2 Interfaces y Pragmas.....................................................................................47 8.3 Modificación del código fuente.......................................................................49 9 Mejoras futuras...................................................................................................51 10 Conclusiones.....................................................................................................52 11 Bibliografía.........................................................................................................53 1 Abstract 1.1 Castellano Este proyecto es la implementación de una iniciativa del centro Citilab Cornellà. Concretamente, se ha tratado de crear una herramienta que amplíe las posibilidades del lenguaje de programación por bloques Snap!, desarrollado por la universidad de Berkeley. El objetivo principal ha sido desarrollar un sistema que permita generar ejecutables para diversas plataformas a partir de un proyecto creado en Snap!. Esto se realiza convirtiendo la aplicación de Snap!, que por defecto es un entorno de desarrollo completo, en un simple entorno de ejecución en el que solo se muestre y tenga acceso al resultado de la ejecución del código. Para crear este entorno se ha desarrollado una herramienta que es capaz de generarlo a partir del código fuente original de Snap!, el cual está escrito en JavaScript, dada una cierta cantidad de información sobre el mismo. En este entorno se puede insertar fácilmente un proyecto Snap! y generar, integrando este conjunto con otras tecnologías, el ejecutable para el usuario final de manera automatizada. 1.2 Català Aquest projecte és la implementació d'una iniciativa del centre Citilab Cornellà. En concret, s'ha tractat de crear una eina que permeti ampliar les possibilitats del llenguatge de programació per blocs Snap!, desenvolupat per la universitat de Berkeley. L'objectiu principal ha estat desenvolupar un sistema que permeti generar executables per a diverses plataformes a partir d'un projecte creat en Snap!. Aquesta tasca es realitza convertint l'entorn de desenvolupament de Snap!, que per defecte és un entorn de desenvolupament complet, en un entorn d'execució simple en el que només es mostri i tingui accés al resultat de la execució del codi Snap!. Per crear aquest entorn s'ha desenvolupat una eina que és capaç de generar-lo a partir del codi font original de Snap!, el qual està escrit en JavaScript, donada una certa quantitat d'informació sobre el mateix. En aquest entorn es pot inserir amb facilitat un projecte Snap! i generar, integrant aquest conjunt amb altres tecnologies, l'executable per a l'usuari final de manera automatitzada. 1 1.3 English This project is the implementation of an idea by the Citilab Cornellà center. More concretely, it has been the development of a tool that expands the functionality of the visual programming language Snap!, which has been developed by the Berkeley university. The prime objective has been to develop a system that allows the generation of executable files for different platforms from a project created with Snap!. This is accomplished by transforming Snap!, which is by default an integrated development environment, into a simple execution environment that only shows and allows access to the code's execution result. To create this environment a tool has been developed that is able to generate it automatically given a certain amount of information about Snap!'s source code and the source code itself, which is written in JavaScript. It is then simple to insert into this environment a Snap! project and generate, automatically, integrating it with other technologies, an executable file for the end user. 2 2 Introducción Los lenguajes de programación gráficos, y entre ellos los educativos, como por ejemplo Scratch1 (desarrollado por el MIT2), llevan despertando gran interés desde hace años en diferentes sectores tanto dentro del ámbito de la informática como fuera de este. El hecho de ser una aplicación gráfica los hace mucho más amigables a usuarios con poca o nula experiencia en el campo de la programación, abstrayéndolos de aspectos técnicos que no son parte de la lógica de la aplicación que se quiere desarrollar. Un ejemplo podría ser la eliminación de errores sintácticos, los cuales suelen ser una gran barrera para programadores noveles. Uno de estos lenguajes que han aparecido recientemente (año 2011) es Snap!3 (de aquí en adelante simplemente Snap), una reimplementación de Scratch escrita completamente en JavaScript para poder ser ejecutada en cualquier navegador independientemente del entorno. El lenguaje dispone de todos los elementos que se esperan de un lenguaje de programación competente, como pueden ser listas (de listas), funciones, etc. Esto lo ha convertido en una herramienta a tener en cuenta a la hora de introducir al público a la programación. Por ejemplo, la universidad de Berkeley ha desarrollado un curso alrededor de la herramienta llamado “The Beauty and Joy of Computing” 4. A pesar de todo, este tipo de lenguajes aún son vistos por muchos como herramientas poco serias que no son útiles a la larga. Este hecho ha llevado a centros como Citilab a pensar, e implementar, mejoras para el lenguaje. Una de estas mejoras, que es la que se ha llevado a cabo en este trabajo, es la posibilidad de, una vez creado un proyecto5 Snap, generar un ejecutable para cualquiera de los entornos de escritorio principales que existen hoy en día [1][2] (no móviles) : Windows, Mac OS X y Linux. Creemos que poder crear una aplicación que pueda ser exportada y utilizada independientemente del entorno de Snap dará a la herramienta una dimensión totalmente nueva y que hará que aprender a usarlo se vea como algo realmente útil que permite obtener un resultado palpable y no una simple “curiosidad”, que desaparece en cuanto se cierra el navegador. 1https://scratch.mit.edu/about/ 2 Massachussets Institute of Technology 3http://snap.berkeley.edu/ 4http://bjc.berkeley.edu/website/Mission.html 5 Un proyecto Snap se refiere a un código creado a partir de bloques de Snap, archivos multimedia y otros elementos internos a Snap que en conjunto forman un programa que puede ser interpretado y ejecutado por Snap. 3 2.1 Snap! Debido a que Snap es la base de este TFG6 parece conveniente realizar una pequeña introducción a esta herramienta. Snap es un lenguaje de programación gráfico por bloques implementado en sus totalidad en JavaScript7, de manera que es ejecutable desde cualquier navegador (moderno). Esto quiere decir que los programas se construyen arrastrando y conectando bloques dentro del entorno de la aplicación. A la vez que se construye el programa es posible ejecutar este código y visualizar sus resultados en la parte de la aplicación llamada Stage. Snap es pues, un intérprete de código donde este, en vez de representarse en forma de texto, se representa con bloques. Snap, como reimplementación de Scratch, comparte una de sus características más importantes. No solo es un intérprete de un lenguaje de programación, sino que se trata también de un entorno de ejecución y programación en vivo basado en lenguajes de la familia Smalltalk8 (como podrían ser Smalltalk-80, Squeak o Pharo). 6 Trabajo de Fin de Grado 7https://en.wikipedia.org/wiki/JavaScript 8http://c2.com/cgi/wiki?SmalltalkLanguage 4 Figura 1: Interfaz de la aplicación Snap siendo ejecutada en un navegador. En la esquina superior derecha podemos ver la Stage mostrando el resultado de la ejecución del código construido en el panel central El objetivo principal de Snap es ser una herramienta educativa que permita a gente que no está familiarizada con la programación introducirse en el tema de forma más sencilla. Eso no quiere decir que Snap sea un lenguaje limitado, dispone de múltiples herramientas propias de lenguajes de alto nivel como podrían ser: •Listas heterogéneas •Funciones de orden superior •Continuaciones Todo esto hace que Snap pueda ser también una herramienta dirigida a alumnos de instituto o incluso estudiantes universitarios (de primer año o de otras especialidades que no sea la informática). 2.2 Objetivos Este trabajo tiene dos objetivos principales. El primero y más importante consiste en conseguir que los usuarios finales de Snap tengan una manera de, una vez han creado su proyecto en Snap, poder crear un ejecutable a partir del mismo. Es decir, tener una forma de extraer su programa de la aplicación Snap de manera que puedan utilizarlo de forma independiente en múltiples plataformas, compartirlo con otras personas, etc. Este ejecutable al iniciarse mostrará solamente la Stage de Snap y el resultado de la ejecución del programa. Básicamente se quiere imitar la compilación de un programa escrito en un lenguaje convencional como podría ser C, siendo el resultado de esta operación un ejecutable que se puede utilizar independientemente de la herramienta de desarrollo que se haya utilizado y pudiendo trasladar este ejecutable a diferentes máquinas. El segundo objetivo del proyecto viene derivado de las necesidades técnicas que surgen del primero. Hemos comentado como queremos que el ejecutable solamente muestre la Stage de Snap, la cual, por un lado, no es más que un pequeño subconjunto de la interfaz de Snap y por otro solo necesita de una fracción del código fuente. Debido a esto, para la generación del ejecutable no queremos utilizar el código fuente original de Snap, sino una versión modificada que nos proporcione la funcionalidad requerida y no contenga elementos innecesarios. Esta versión obviamente se podría desarrollar manualmente, pero debido a que Snap es un proyecto en desarrollo, suficientes cambios al proyecto original pueden dejar obsoleto este entorno de ejecución generado manualmente. Esto quiere decir que habría que mantener una nueva rama de código de manera paralela al código principal de Snap. El objetivo es pues conseguir ahorrar este trabajo, automatizando la creación del entorno de ejecución a partir del código fuente de Snap. 5 2.3 Impacto social, ambiental y económico Debido a que este proyecto se trata más de una prueba piloto para ver como responde a ella los usuarios y desarrolladores de Snap que no cumplir un objetivo perfectamente delimitado, es algo difícil cuantificar con exactitud el impacto que tendrá. Aún así podemos hablar, en lineas generales que esperamos de él. 2.3.1 Impacto social A pesar de la importancia de la informática en la sociedad moderna, una gran parte de la población tiene poco o nulo conocimiento del aspecto técnico o de como interactuar directamente con un ordenador para que este realice una tarea arbitraria, es decir, programarlo. Lenguajes como Snap están intentando disminuir esta disonancia haciendo la programación un concepto más accesible y menos lejano al público general. Pero su alcance aún es limitado debido a una gran cantidad de prejuicios que reciben estos lenguajes. Se espera que el desarrollo de nuevas aplicaciones y la ampliación y mejora de existentes (lo que realiza este proyecto) ayude a popularizar estas herramientas y en consecuencia, aumente el interés y el estudio de la programación y la informática en general. 2.3.2 Impacto ambiental El proyecto consiste en el desarrollo de un software que no afectará a ningún proceso externo. Aparte del impacto indirecto derivado por su uso (generación de electricidad para hacer funcionar los ordenadores), no se puede hablar realmente de un impacto ambiental serio y este punto es negligible. 2.3.3 Impacto económico No se espera ningún impacto económico directo ya que tanto Snap como la herramienta que desarrollada son libres y de código abierto. Indirectamente se espera que este trabajo potencie y siga empujando hacia delante el desarrollo de lenguajes de programación educativos y accesibles. Hoy en día la informática es una pieza clave en una gran cantidad de actividades económicas, y una sociedad más educada en este ámbito será capaz de utilizarla más eficientemente y ser más productiva. 6 4.2 Recursos no Humanos En este proyecto los gastos en recursos no humanos básicamente se han dividido en gastos en hardware y software. Para los cálculos de la amortización de los recursos no humanos se han utilizado los siguientes datos: •Días laborables en un año: 249 •Horas de trabajo en un día: 4 (media jornada, igual que se ha trabajado en este proyecto) •Duración del proyecto: 500 horas 4.2.1 Hardware Recurso Precio (€) Unidades Vida útil (años) Amortización (€) Ordenador de sobremesa (personalizado) 1100,00 1 5 110,44 Pantalla Samsung LS23C350 HS/ZA 150,00 1 5 15,10 Ratón Razer Taipan 80,00 1 2 17,00 Teclado Logitech K290 31,00 1 2 6,60 Portátil Asus N61JQ 1000,00 1 4 106,30 Total 2361,00 5 255,44 13 4.2.2 Software Solo se ha contado una licencia de Microsoft Windows, ya que el portátil incluía una preinstalada. Recurso Precio (€) Unidades Vida útil (años) Amortización (€) Windows 7 Home Premium 64 bits 360,00 1 4 38,27 Ubuntu 14.04.2 64 bits 0,00 1 4 0,00 OpenOffice 4.1.1 0,00 1 3 0,00 LaTex 0,00 1 3 0,00 Node.js 0,00 1 2 0,00 Eclipse Luna 0,00 1 3 0,00 Total 360,00 6 38,27 4.2.3 Total Coste total de los recursos no humanos: Tipo Precio (€) Hardware 255,44 Software 38,27 Total 293,71 4.3 Total Coste del proyecto: Tipo Precio (€) Recursos humanos 17.360 Recursos no humanos 293,71 Otros gastos 270,00 Total 17.923,71 A pesar de que ha habido cambios menores respecto a la planificación inicial el presupuesto se ha mantenido al inicialmente propuesto. 14 5 Generación de ejecutables Como se ha comentado, se pretende generar ejecutables (concretamente para las plataformas: Windows, Mac OS X y Linux) a partir de programas escritos en Snap, utilizando un entorno de ejecución derivado del código fuente del mismo. Rápidamente podemos ver que no es posible obtener un ejecutable tradicional ya que Snap está escrito en JavaScript el cual es un lenguaje interpretado (y no tiene posibilidad de ser compilado). Como pequeño inciso, indicar que técnicamente es posible traducir un cierto subconjunto de JavaScript a otros lenguajes [11] y eventualmente a uno compilable, como podría ser C. Pero este es un proceso limitado y fuera del alcance de este trabajo. Una solución sería olvidarse completamente del entorno de ejecución en JavaScript e implementar uno en C++, Java, etc, pero esto sería una cantidad de trabajo de entrada muy grande y el mantenimiento sería también increíblemente costoso, ya que habría que mantenerse al día con todos los cambios que sufra el código de Snap y asegurarse de que el intérprete se comporta igual que el original. La solución por la que se ha optado finalmente es utilizar una tecnología, en concreto NW.js11, que nos permita crear este ejecutable de manera indirecta, encapsulando el entorno de ejecución de Snap. 5.1 NW.js NW.js permite dos cosas fundamentales. Poder ejecutar aplicaciones web en local y la posibilidad de crear ejecutables a partir de estas aplicaciones. Por defecto para abrir un proyecto con NW.js, este requiere que el código fuente sea empaquetado en un archivo ZIP junto a un pequeño fichero de configuración. Alternativamente, este ZIP se puede integrar (de diversas maneras, según la plataforma) junto al ejecutable. De esta manera se evita que el usuario tenga que tener NW.js instalado previamente en su sistema y el ejecutable no tendrá ninguna dependencia de terceros. Una limitación de este proceso es que requiere acompañar el ejecutable de las librerías y demás recursos utilizados por NW.js, por lo que no podremos enviar un solo fichero al usuario (a no ser de que la plataforma permita encapsular todo el ejecutable y sus dependencias como por ejemplo Mac OS X). Habrá que enviar un archivo comprimido que contenga el ejecutable y los recursos. Aún así, se cree que esta solución es el mejor compromiso al que se puede llegar actualmente. 11 Herramienta creada para poder ejecutar localmente aplicaciones escritas en JavaScript, HTML, node.js y otras tecnologías web - https://github.com/nwjs/nw.js/blob/nw13/README.md 15 El resultado final que obtiene un usuario al generar un ejecutable con nuestra aplicación es: •Un archivo comprimido que el usuario puede transportar y compartir con facilidad •El contenido de este archivo es: ◦Un ejecutable para la plataforma que le usuario haya escogido ▪Windows ▪Linux ▪Mac OS X ◦Las librerías necesarias para poder utilizarlo 16 Figura 2: Aplicación generada para Windows. Podemos ver el ejecutable correspondiente al proyecto junto otros ficheros requeridos por NW.js 17 Figura 3: El mismo ejecutable generado para Mac OS X (visualizado en Windows). Podemos ver que el formato es mucho más sencillo ya que se reduce a un solo directorio .app Figura 4: El mismo ejecutable generado para Linux. Aprovechamos la existencia de archivos .desktop para encapsularlo en un directorio de manera parecida a Mac OS X 6 Generación del entorno de ejecución 6.1 Estilo del código de Snap Antes de continuar hablando sobre el segundo objetivo parece adecuado hacer una introducción al código fuente de Snap ya que es una parte integral para poder analizar los requisitos y decisiones del segundo objetivo. Snap utiliza un solo elemento HTML5, un Canvas sobre el cual es dibujada toda la aplicación mediante el código JavaScript. Tiene como base Morphic.js (creado por el mismo autor de Snap), un entorno básico basado en Squeak que ofrece las funcionalidades básicas del sistema [12]. Algunos ejemplos serían: •Drag and drop •Interacción a tiempo real del usuario con el sistema •Herramienta de mano Sobre este framework está construido Snap propiamente dicho. Snap amplía en gran medida este entorno añadiendo todo el lenguaje de programación por bloques (lo cual incluye el intérprete, la representación gráfica...), diferentes áreas a la interfaz, etc. También dispone de herramientas adicionales como un editor gráfico sencillo o un sistema para guardar y cargar proyectos de la nube, entre otros. El código fuente de Snap está escrito en JavaScript, pero con restricciones, estas vienen dadas por el estilo de programación y convenciones seguidas por los autores, pero también por restricciones impuestas por la herramienta JSLint. Los únicos fichero que no siguen exactamente estas reglas de estilo son Morphic.js, debido a que como base de todo el sistema necesita utilizar algunas funcionalidades de JavaScript que no se ven en el resto del código, y Store.js, la clase que se encarga de cargar y guardar proyectos Snap. 18 Las reglas de estilo más relevantes (no todas) serían: JSLint [13][14] •Prohíbe el uso de diversos operadores problemáticos como podrían ser with o == •Prohíbe el uso asignaciones en posiciones en las que se espera una expresión, por ejemplo en la condición de una instrucción if •Punto y coma obligatorio •Evita el uso de bucles de tipo for en la mayoría de situaciones, sustituyéndolo por funciones que iteran colecciones como podría ser forEach •Evita el uso de -- y ++ •Evita el uso de this •Reglas de indentación estrictas Snap [15] •Evitar frameworks (por ejemplo jQuery12) •Evitar acceder al DOM13 •Evitar namespaces y módulos •Evitar pasar this como argumento (utilizar una variable llamada myself a la que se le haya asignado this) •Evitar crear todas las clases de la misma manera estandarizada y añadir todas las propiedades en la constructora o bien en un método init llamado desde la misma. •Evitar operadores ternarios anidados •Evitar el uso de expresiones regulares •Evitar funciones excesivamente largas, especialmente funciones que necesiten funciones auxiliares •Evitar llamadas a funciones con un número de argumentos diferente al declarado 12 La librería JavaScript con uso más extendido hoy en día. Su objetivo es simplificar la programación en el cliente. 13 Del inglés Document Object Model. Se refiere a la API (Application Programming Interface) que permite acceso a elementos en documentos XML, HTML y XHTML 19 6.2 Estructura general del código Este código aparte de las convenciones de estilo seguidas arriba tiene otras características importantes. •Totalmente orientado a objetos: esto quiere decir que todo el código (excepto unas pocas funciones auxiliares) es o bien funciones constructoras o bien declaración de métodos y atributos para las diferentes clases. •El código está dividido lo máximo posible en diferentes métodos lo que lleva a trozos de código cortos y no excesivamente complejos. •Se evita añadir o eliminar atributos de objetos y/o clases en tiempo de ejecución: esto solo se realiza o bien en la constructora o en una función init de la clase, la cual se llama desde la función constructora. •Variables suelen contener siempre el mismo tipo de objeto: de no ser así, tienden a ser objetos estrechamente relacionados entre si, como por ejemplo objetos que heredan de la misma clase. 6.3 Viabilidad de la modificación programática del código fuente de Snap Teniendo en cuenta las características de JavaScript [16] hay que analizar si es siquiera posible modificar un código escrito en él, alterando su funcionamiento de forma programática durante un análisis estático. Al ser JavaScript un lenguaje interpretado tiene una gran cantidad de funcionalidades que hacen esta tarea complicada. Algunos ejemplos que nos afectan serían: •Tipado dinámico var a = 5; //a's value is a Number a = { foo: function () {return 'hello';} }; //a's value is an Object with one property a = 'hello'; //a's value is a String a.foo(); //won't cause an error until this line of code is executed •Capacidad de añadir y eliminar propiedades a objetos y clases en tiempo de ejecución var obj = { }; obj.a = 5; console.log(obj.a); //prints 5 delete obj.a; console.log(obj.a); //prints undefined 20 •Acceso de forma no explícita a estas propiedades var obj = { a: 'hello' }; console.log(obj.a); //prints hello var prop = 'a'; console.log(obj.[prop]); //prints hello •Llamadas a funciones con más o menos argumentos de los declarados var foo = function (a, b) { if (b) return a + b; else return a; } foo(5); //returns 5; foo(5, 6); //return 11 Como podemos ver hay barreras importantes que hacen que la tarea sea increíblemente compleja. De hecho modificar de la manera pretendida código arbitrario JavaScript es seguramente una tarea imposible. Pero este trabajo no trata con código arbitrario sino con uno muy concreto, el código fuente de Snap. Como hemos visto en el punto anterior, el lenguaje en el que está escrito Snap es realmente un subconjunto de JavaScript con un estilo y una estructura muy concretas. El problema principal a la hora de modificar programáticamente el código serían las herramientas dinámicas de las que dispone el lenguaje y Snap las utiliza poco y en situaciones definidas y concretas. Otro punto importante es que la modificación es relativamente sencilla, siempre estamos eliminando código, y en caso de modificarlo, es solo para mantener su funcionamiento correcto teniendo en cuenta las partes de código que ya no existen. No añadimos código ni hacemos modificaciones arbitrarias. Gracias a esto, podemos afrontar el problema como si estuviésemos trabajando con un lenguaje mucho más estricto y estático, usando nuestro conocimiento sobre el código Snap en caso de que nos encontremos con alguna situación ambigua. Aún así, todavía queda una cuestión abierta. Si queremos realizar un análisis estático del código, necesitamos saber el tipo de las variables, retorno de funciones, etc. Esta información no se encuentra en ninguna parte del código ya que no forma parte de la sintaxis del lenguaje y por lo tanto es imposible de obtener sin ayuda externa (durante un análisis estático). Veremos como se ha solucionado este problema en el siguiente apartado. 21 6.4 Interfaces Nos encontramos con la necesidad de conocer previamente los tipos que tienen los diferentes elementos del código. En concreto necesitamos: •Tipo de retorno de funciones •Tipo de argumentos de funciones •Tipo de las propiedades de clases Los tipos de las variables no se han incluido ya que pueden inducirse directamente del código, evaluando las expresiones que les asignan un valor, si tenemos la información antes mencionada. Y como en Snap siempre se declara y asigna un valor a las variables antes de su uso14, esto no representa un problema. La solución por la que se ha optado finalmente es crear Interfaces. Estas son archivos a las que llamaremos de esta manera en referencia a las interfaces de Java, las cuales son muy parecidas en forma y parcialmente en su objetivo. Su funcionamiento es sencillo. El usuario escribe en ellas, en ningún orden en concreto: •Los tipos de retorno y el tipo de los argumentos de todas las funciones declaradas a nivel de documento •Para cada clase contenida en el documento el usuario escribirá los tipos de sus atributos e, igual que con las funciones globales, los tipos de retorno y de los argumentos de sus métodos. Con estos archivos pues, se tiene toda la información necesaria para poder analizar estáticamente el código ya que podemos saber en todo momento el tipo de todas las variables, llamadas a funciones, etc, y tomar por lo tanto decisiones informadas sobre como modificarlo correctamente. 14 Con una excepción, ver: 8.2 Interfaces y Pragmas 22 6.7 Algoritmo Hemos visto a lo largo de este documento los diferentes elementos que necesitamos en preparación a a poder implementar el código que modificará el entorno. Analizaremos ahora como es el código que realiza esta operación y definiremos el algoritmo. Esta definición será en pseudocódigo y en alto nivel, no entraremos en como la eficiencia. Esto será comentado en el apartado de implementación. 6.7.1 Identificación de elementos relevantes Para poder analizar el código y modificarlo hay una serie de tareas que habrá que realizar durante el algoritmo, veamos cuáles son: •Propagación correcta de tipos: necesitamos saber en cada momento que tipo tiene en cada momento cada variable para poder comprobar si los accesos a sus propiedades son válidos en el contexto de las interfaces que se han proporcionado. También tenemos que controlar que tipo retornan diferentes instrucciones, como por ejemplo llamadas a funciones. Gracias a las interfaces este proceso es relativamente sencillo. Básicamente basta con evaluar el código siguiendo las reglas del lenguaje y utilizar los metadatos de las interfaces cuando sea necesario. •Detectar en que punto del código nos encontramos: una misma instrucción puede tener que ser tratada de forma algo distinta según el contexto en el que se encuentre dentro del código. Esto se realiza con la ayuda del Parse Tree como se ha comentado apartados anteriores. •Validez: cada nodo deberá de comprobar su validez, esta dependerá de diferentes aspectos, como la validez de sus hijos, acceso a clases que no aparecen en las interfaces, etc. •Reescritura del código: en caso de que el nodo no sea válido pueden darse dos casos: ◦El nodo se invalida y su código desaparece del resultado final ◦El código del nodo se reescribe de forma que sea válido en el nuevo código modificado •Retorno de información: similarmente a como nodos padres tienen que indicar el contexto del código, cada nodo hijo tendrá que devolver a su padre información sobre él. Por ejemplo su código, su validez, etc. 29 6.7.2 Definición I. Si el tipo del nodo en que nos encontramos se puede obtener evaluando solamente el nodo retornamos este directamente. Por ejemplo: ◦Una llamada a un método de una clase. El tipo de retorno se obtiene directamente de la interfaz. ◦Un literal de tipo Number. Su tipo siempre es el mismo, siempre que se encuentre uno en el código sabemos automáticamente cuál es. II. Si el nodo en que nos encontramos calcula su tipo a partir de sus hijos obtenemos el tipo de estos, calculamos el propio y lo retornamos. Por ejemplo: ◦El tipo de una operación de suma deriva del tipo de sus operandos. III. Si nos encontramos en una declaración o asignación, y el tipo de la parte izquierda no viene ya definido por la interfaz (propiedad de una clase), el tipo de la parte izquierda pasa a ser el que obtengamos de evaluar la parte derecha. El algoritmo de modificación de código es: I. Visitar la raíz del árbol, es decir el nodo que representa la regla de entrada al código de la gramática. II. En cada nodo, después de ejecutar el método de entrada: Si nos queda algún hijo sin visitar: Visitar el primer hijo sin visitar que nos encontremos En caso contrario: Salir del nodo El código para estas dos operaciones es: Visitar un nodo: Ejecutar el método de entrada del nodo. En este: I. Modificar adecuadamente la información de contexto de la regla, la cual da a los hijos información sobre el contexto del código en el que se encuentran II. Hacer las operaciones de preprocesamiento propias a la regla que se esté tratando. Estas pueden ser variadas y dependen en gran medida de que regla se esté tratando, su número de hijos, etc. 30 Salir de un nodo: Ejecutar el método de salida del nodo. En este: I. Comprobar la validez de los hijos. Esta operación es sencilla ya que cada nodo informa a su padre de su validez. II. Comprobar la validez propia. Un nodo es válido si y solo si: 1. Todos sus hijos son válidos 2. No accede ninguna clase, propiedad de clase ni función global que no aparezca en las interfaces 3. Si accede a una variable local esta tiene que ser válida. Es decir, le ha sido asignado un valor y la operación ha sido válida siguiendo estas reglas III. Si el nodo resulta no ser válido: 1. Si es posible, reescribir el código y aceptar como válido el nodo. Este proceso es diferente para cada regla, pero tiene que mantener las tres condiciones antes mencionadas por lo que que las operaciones que se pueden realizar para recuperar la validez son: i. Reescribir el código evitando utilizar el perteneciente a hijos no válidos ii. Reescribir el código evitando acceder elementos no declarados 2. En caso contrario marcamos el nodo como inválido. IV. Si el nodo es válido detectar su tipo para realizar la propagación V. Devolver el código del nodo, su tipo y su validez al padre 31 7 Implementación 7.1 Estilo de aplicación Como esta aplicación está pensada para ser usada por los usuarios de Snap, el primer paso fue escoger que tipo de aplicación íbamos a desarrollar. Se barajaron dos opciones principales: •Aplicación web •Aplicación de escritorio A pesar de que la aplicación de escritorio tiene algunas ventajas, rápidamente se optó por crear una aplicación web. Estos son los motivos principales: •Mayor facilidad de uso y comodidad para el usuario final: una aplicación web no tiene que ser descargada ni instalada por el usuario. Este simplemente accede a ella desde su navegador y al acabar de utilizarla la cierra y se olvida. Queremos esta sencillez de uso en nuestra aplicación ya que en caso contrario podría echar atrás a potenciales usuarios. •Mismo estilo que Snap: Snap también es una aplicación pensada para ser utilizada desde el navegador por motivos similares a los expuestos. Escoger este estilo para nuestra aplicación nos permite mantener una continuidad con Snap y dar un sensación de familiaridad al usuario. •Posibilita la integración con Snap: A pesar de que ahora mismo nuestra aplicación se accede desde su propia página, si eventualmente se integra en la Snap se podrá hacer muy fácilmente. Bastará con mover el código que hace la llamada al Web Service a Snap. •Mantenimiento más sencillo: al encontrarse toda la aplicación en un servidor controlado por el desarrollador es mucho más sencillo modificar, ampliar o aplicar parches a la aplicación en caso necesario, de forma transparente al usuario final. 32 7.2 Módulos de la aplicación Se han creado dos módulos diferenciados que trabajan conjuntamente para cumplir los objetivos. •Aplicación web: formada por una página web que el usuario final utilizará para acceder a la funcionalidad del sistema. También disponemos de un Web Service que se ejecutará en un servidor y será el encargado de generar y enviar al usuario los ejecutables a partir de sus programas. •Creador del entorno de ejecución: este programa es el encargado de obtener la información sobre el código Snap y el código fuente original del mismo y, a partir de estos, generar el entorno de ejecución reducido que se utilizará para generar los ejecutables y dejarlo a disposición del Web Service. Para implementar ambas partes del trabajo se han utilizado tecnologías lo más portables, extendidas y estandarizadas posibles. Esto es así porque el sistema acabará ejecutándose en un servidor del cual desconocemos sus características, además es posible que a lo largo de su vida útil sea trasladado a diferentes máquinas y queremos que sea posible ejecutarlo en todas. También se quiere evitar de esta manera que alguna de las tecnologías utilizadas acabe siendo obsoleta o el sistema sea difícil de mantener en el futuro. 7.3 Interfaces Como se ha comentado previamente en este documento toda la información externa sobre el código que es necesaria para modificarlo se introduce en el programa mediante estos archivos, que escribirá o bien el encargado de la aplicación o otra persona que tenga un cierto conocimiento del código Snap. Aunque técnicamente son una parte del modificador de código debido a su importancia las explicaremos aquí en detalle. Las interfaces se crearán utilizando lo que llamaremos pragmas. Estos se llaman así ya que se utilizarán en nuestra aplicación de manera similar a los que utilizan compiladores de otros lenguajes. Estos tendrán el siguiente formato: /*@ { [passthrough] name: nombre, class: tipo, [var], [type: tipo_de_elementos_de_lista], [return: tipo_retorno_función], [parameters: parámetros_función], [properties: propiedades_del_objeto] } !*/ Las propiedades pueden estar en cualquier orden. Importante destacar que este formato es que es recursivo, excepto la apertura (/*@) y el cierre (!*/), los cuales se utilizan solo una vez el resto puede estar anidado. Esto nos permite representar cualquier objeto o clase JavaScript de complejidad arbitraria. 33 Se ha utilizado por un lado el formato de comentarios multilínea JavaScript. Esto es así ya que estos pragmas también pueden estar embebidos en el mismo código JavaScript original y queremos que este siga siendo funcional. Por otro, utilizamos un formato muy similar a JSON18 [21]. Esta decisión se ha tomado debido a que es fácil de parsear y tratar simultáneamente, además Snap está muy relacionado con JavaScript y así mantenemos una cierta continuidad. Como hemos implementado nuestro propio parser, hemos implementado alguna sintaxis que no es del todo correspondiente con la de JSON para facilitar la declaración de los pragmas. 7.3.1 Atributos pragma passthrough Este atributo debe de utilizarse solo. Su objetivo es indicar que la información de la interfaz es necesaria durante la ejecución del programa, pero no queremos modificar el código de este fichero. name Siempre obligatorio. Indica el nombre de la variable, propiedad de objeto, etc, que esté representando el pragma. class La clase del ítem. Soportamos también objetos multiclase. Esto es importante ya que a veces algunas variables utilizadas en Snap pueden contener valores de clases diferentes. En caso de una sola clase podemos escribir simplemente su nombre. class : foo Esta clase puede estar representada por un pragma si es compleja. Si utilizamos un pragma este tiene que estar entre llaves como si se tratase de una lista de clases: class : { {class: Array, type: foo} } Para multiclase se utiliza una lista separada por comas cerrada por llaves. Los elementos de la lista pueden ser strings o pragmas. class : { {class: Array, type: foo}, bar, Number } var Campo que tienen que llevar obligatoriamente variables y funciones declaradas a nivel de documento. Esto es necesario para diferenciar funciones constructoras de la clase de la cual crean objetos. 18 JavaScript Object Notation 34 type Solamente es necesario si el ítem es de la clase Array. Este campo indica el tipo de los elementos. Puede también ser multiclase. /*@ { name: arr, class: Array, type: foo } !*/ return Solamente es necesario si el ítem es de la clase Function. Este campo indica el tipo de los elementos. Puede también ser multiclase. En caso de una función que devuelva void 19 se puede escribir de manera simplificada. /*@ { name: foo, class: Function, return: Number } !*/ //returns a Number /*@ { name: bar, class: Function} !*/ //void parameters Solamente es necesario si el ítem es de la clase Function. Este campo indica el nombre y tipo de sus argumentos. Los argumentos pueden ser multiclase. /*@ { name: foo, class: Function, parameters: { {name: arg1, class: {Number, String}} } }!*/ properties Igual en su formato a parameters pero indica las propiedades de un objeto o clase. /*@{ name : CustomReporterBlockMorph, class : ReporterBlockMorph, properties : {{ name : init, class : Function, parameters : { {name : isPredicate, class : Boolean} } }} }!*/ Este ejemplo define una clase llamada CustomReporterBlockMorph que hereda de la clase ReporterBlockMorph y tiene un método llamado init que retorna void y dispone de un parámetro llamado isPredicate, el cual es un Boolean. Podemos ver como se puede usar esta estructura para definir cualquier clase que nos encontremos en el código Snap utilizando siempre la misma estructura. 19 En JavaScript una función que no retorna ningún valor retorna undefined. 35 7.3.2 Uso de las interfaces Las interfaces se utilizan de la siguiente manera: •La interfaz contiene un pragma para cada función declarada a nivel de documento. También tiene uno para cada clase declarada en el documento. Obviamente solo hay que incluir los que se quieran mantener en el código modificado. Cada uno de estos pragmas incluye información sobre las propiedades que se quieren mantener en el código final. •Se crea una interfaz por cada fichero del código fuente de Snap que tendrá el mismo nombre que el original pero una extensión .info. •En caso de que un fichero no contenga ningún código que queramos mantener en el código modificado no es necesario crear una interfaz para él. •Si queremos mantener el fichero entero y este no contiene ningún código que sea utilizado fuera del mismo basta con crear una interfaz que solo contenga un pragma de passthrough. •Si queremos mantener el fichero sin modificarlo, pero este contiene clases utilizadas en otras partes del código (por ejemplo Morphic.js), podemos omitir los atributos parameter en la interfaz debido a que estos solo son necesarios para la modificación. Además si sabemos muy bien que métodos y atributos se utilizan fuera del fichero solo necesitamos dar información para estos (y podemos omitir todo lo que sea autocontenido). Existe también un fichero extra llamado system.info que contiene información sobre clases, funciones, etc, de JavaScript. Este fichero contiene todos los elementos proporcionados por el lenguaje que utiliza Snap (y algunos más en caso de que algún día se utilicen). 7.3.3 Pragmas en el código Como se verá más adelante en el apartado de resultados hay algunas excepciones en que una interfaz no es suficiente para dar toda la información necesaria ya que se requiere indicar propiedades de variables locales. Podrían obviamente añadirse en el archivo de interfaz, pero se ha encontrado que este sistema es poco práctico así que se ha decidido que esta información se añada directamente en el código, antes de la variable correspondiente. Este mismo sistema también podría utilizarse para informar sobre funciones declaradas en el interior de otras. Pero esto no ha sido necesario ya Snap las utiliza muy poco20 y en lugares que son eliminados del código de ejecución reducido. 20 Si que utiliza muchas funciones anónimas cortas como argumento de funciones. Pero estas se tratan diferente. 36 7.4 Modificador de código Snap El programa encargado de modificar el código fuente original de Snap y convertirlo en la versión reducida que describan las interfaces. Se trata de un solo programa escrito en su totalidad en Java, lo que permite moverlo a la misma máquina en la que alojemos el Web Service sin demasiada dificultad. Esto último no es estrictamente necesario, ya que para el funcionamiento correcto del Web Service solo es necesario que disponga de los ficheros JavaScript que componen el entorno de ejecución, pero si se ejecutan en la mismo máquina, el programa Java dejará los ficheros de manera automática en el lugar correspondiente. También abre la puerta a permitir que el Web Service llame a la aplicación en caso de que sea necesario en alguna ampliación futura del sistema. Gracias a la cantidad de herramientas que ofrece Java por defecto no ha sido necesario utilizar ninguna librería externa a excepción de ANTLR4. Los únicos prerequisitos que necesita para ejecutarse son: •Tener a disposición una carpeta con el código fuente original de Snap •Disponer de una carpeta con las interfaces que el usuario ha tenido que generar previamente Analizaremos ahora las partes más importantes del programa. 7.4.1 Estructuras de datos Este proyecto no necesita estructuras de datos demasiado especializadas pero si que utiliza una gran cantidad y variedad de ellas. Las estructuras personalizadas que hemos necesitado suelen encapsular otras más básicas y sirven sobretodo para facilitar y estandarizar el acceso a los datos a través de todo el programa ofreciendo métodos de acceso y modificación de los datos. •Estructuras estándar: en todos estos casos hemos utilizado directamente la implementación dada por Java. Se han utilizado colas, pilas, maps, listas y sets. El uso mas importante ha sido una pila de listas que se utiliza para guardar la información de retorno de los nodos. Antes de entrar en un nuevo nodo se empila una nueva cola. En esta todos los hijos colocan su información de retorno, esto permite al padre accederla en orden. Se utiliza una pila para que imite el stack que crea el recorrido recursivo del árbol. Finalmente al abandonar el nodo se ejecuta una operación de pop y queda en primera posición la cola del siguiente nodo al que se retornará. El resto de estructuras han sido utilizadas para implementar el resto de estructuras de datos y para atributos necesarios en varias clases. 37 •ClassInfo: una estructura sencilla pero vital para la aplicación. Consiste en una tupla que almacena toda la información dada en las interfaces para cada elemento declarado en ellas. También se utiliza para las variables que se vayan declarando a lo largo del código. Lo interesante de esta estructura es que sirve tanto para todos los elementos que nos podamos encontrar en el código como podrían ser variables, funciones, clases, etc. Esto hace el procesado más sencillo y homogéneo, además hace muy sencillo introducir los datos de los pragma en la aplicación. •Scope: una estructura recursiva que almacena las variables de un ámbito de visibilidad, los cuales están formados por clausuras en JavaScript [22]. Implementan lo que en un compilador sería la Symbol Table [23]. Cada Scope tiene un puntero a su padre, lo que permite restaurarlo una vez el Scope actual es abandonado y destruido y también permite buscar símbolos por toda la cadena de ámbitos de visibilidad hasta llegar a nivel de documento (el último ámbito existente en JavaScript). Para implementarlo se ha utilizado un Map que relaciona el nombre de la variable con el ClassInfo que guarda su información. A pesar de que existen otras posibilidades más sofisticadas, en Snap siempre trabajamos con ámbitos de visibilidad pequeños debido a su estructura, por lo que un Map es más que suficiente en términos de rendimiento y funcionalidad. •WalkInfo: esta estructura también se trata de una tupla, pero en este caso sirve para almacenar la información de retorno de los nodos. Contiene información tan vital como su validez, el código que lo representa, su tipo, etc. 7.4.2 Parser/ANTLR4 Las clases encargadas de parsear el código y crear la estructura de datos con la que se recorrerá el árbol resultante son las generadas por ANTLR4 a partir de nuestra gramática. Estas simplemente han sido añadidas a nuestro proyecto junto a las librerías de ANTLR4 propiamente dicho. El objetivo de usar ANTLR4 era ahorrarnos trabajo así que simplemente hemos supuesto que funcionan correctamente y no hemos investigado excesivamente su funcionamiento. Una excepción es la clase SnapTreeWalker, que realiza el trabajo de caminar por el árbol generado. 7.4.2.1 SnapTreeWalker Consiste en una modificación del código por defecto que utiliza ANTLR4 para recorrer el Parse Tree generado a partir de un código Snap. La modificación introduce el concepto de saltarse la visita a un hijo. Esto se ha hecho por temas de eficiencia, ya que muchas veces podemos ver que un nodo es inválido y en ese caso no tiene sentido visitar a sus hijos. 38 45 Figura 9: Página web de Snap! visualizada en el mismo sistema que la anterior figura Figura 8: Aspecto de la página en el mismo entorno después de una petición al servidor, esperando su respuesta 7.5.2 Web Service Este es el programa que se ejecutará en el servidor y atenderá las peticiones enviadas desde la página web. Está implementado en Node.js (que utiliza JavaScript) y el mismo programa hace a su vez de servidor web, escuchando el puerto que se le haya asignado, manejando el protocolo Http, etc. Gracias a que Node hace esto extremadamente sencillo se evita tener que instalar más programas en el servidor. El Web Service sirve por un lado la página web que se ha explicado en el punto anterior a las peticiones GET que lleguen por el puerto asignado a la aplicación. Por otro se encarga de atender peticiones POST que lleguen por el mismo puerto, pero a la dirección /gen_exec. Las peticiones a esta dirección son las que envía la página web para pedir la generación de un ejecutable. Una vez llega una de estas peticiones el proceso es el siguiente: •Validar la información contenida en el POST: este proceso evita introducir datos incorrectos, corruptos o diseñados por un usuario malintencionado en el sistema. •Crear el ejecutable: todo el proceso se realiza en memoria por lo que evitamos tener que escribir ficheros, lo que ralentizaría el sistema, obligaría a realizar tareas de cleanup, etc: ◦Añadir el proyecto del usuario al entorno de ejecución seleccionado por el mismo, el cual puede ser una Snap completo o el entorno de ejecución reducido que hemos creado en este trabajo. Esta tarea es sencilla ya que el proyecto completo consistirá en una string en formato XML que crea el propio Snap. ◦Seguidamente se genera un archivo ZIP con el resultado del punto anterior y el archivo de configuración usado por NW.js, en el que se incluyen las preferencias indicada por el usuario. ◦Este ZIP se adjunta al binario y las librerías de NW.js correspondientes con tal de crear un ejecutable portable. El proceso es diferente para cada sistema. •Ofrecer al cliente el archivo como descarga: finalmente se ofrece el fichero con todo el contenido generado en los pasos anteriores como descarga al cliente que ha hecho la petición. •Manejo de errores: si durante alguna de las operaciones en el POST se produce un error se enviará un código de error 500 al cliente para que pueda informar al usuario de que no se ha podido generar su ejecutable. También si alguien accede a una dirección equivocada se le redireccionará a una página de error 404. 46 8 Resultados A continuación analizaremos los resultados del sistema, en que medida cumple los diversos objetivos que nos habíamos propuesto y su funcionamiento en general. El resultado más importante a destacar, es el hecho de que el sistema funciona correctamente y nos ofrece la funcionalidad que deseábamos. Somos capaces de, a partir del código fuente original de Snap, generar un entorno de ejecución reducido sin necesidad de tener que modificar código manualmente. El Web Service por su parte es capaz de crear ejecutables para todas las plataformas escogidas, recibiendo solamente un documento XML que contenga un proyecto Snap. Estos ejecutables independientemente de la plataforma para la que sean creados, son totalmente autocontenidos, por lo tanto pueden usarse y moverse libremente sin depender de otras herramientas. 8.1 Generación de ejecutables El resultado de la generación de ejecutables ha sido excelente y exactamente como se había previsto. Somos capaces de generar en el servidor, a partir de cualquier proyecto Snap, ejecutables para las tres plataformas que nos habíamos propuesto y estos son totalmente portables y no tienen ninguna dependencia a terceros. La generación de ejecutables es sencilla y no requiere de la descarga ni instalación de ningún tipo de software adicional por parte del usuario. Los ejecutables además de funcionar correctamente también cumplen otros aspectos no funcionales, como mostrar un icono relevante a Snap, tener títulos y nombres de ventana que dependen del proyecto introducido, etc. 8.2 Interfaces y Pragmas El sistema de interfaces y pragmas que hemos implementado cumple los requisitos que esperábamos. Somos capaces de introducir información al sistema sobre todas las clases que conforman el código rellenando con pragmas los archivos de interfaces correspondientes. Uno de los objetivos de las interfaces era evitar tener que introducir pragmas en el mismo código, esto no se ha podido conseguir del todo. 47 Pragmas en el código Ha sido necesario aparte de las interfaces añadir cuatro pragmas en el código. Pasemos a ver en que contexto y como afecta esto a la usabilidad del sistema: •Adición dinámica de propiedades a un objeto: para poder realizar la modificación de código necesitamos saber previamente que propiedades va a tener un objeto. Si en el código estas van siendo añadidas dinámicamente necesitamos un pragma antes de su declaración que nos indique cuáles va a poseer, ya que en caso contrario, debido al funcionamiento de las interfaces, tenemos que suponer que la propiedad accedida tiene que ser eliminada. Como hemos visto, Snap no utiliza esta forma de programar que ofrece JavaScript en general. Pero en Store.js, al estar algo al margen del estilo general de Snap, esto ocurre dos veces y además en una variable local, lo que impide que podamos añadir la información a la interfaz correspondiente. Aún así, al ocurrir solo en un archivo en el que ya esperábamos que pudiese darse el caso y solo ocurre con dos objetos, los cuales aparecen en el mismo método, no creemos que sea un problema muy grave que afecte negativamente a nuestra solución. Si finalmente este trabajo se une a Snap sería tan sencillo como reescribir la declaración de los objetos para permitir inducir el tipo de sus propiedades en el análisis estático o bien mantener los pragmas. •Loops: más concretamente el uso de una variable para guardar el último elemento tratado en el loop. var cell, lastCell; for (i = 0; i < end; i += 3) { cell = this.frame.contents.children[i]; label = this.frame.contents.children[i + 1]; button = this.frame.contents.children[i + 2]; if (lastCell) { cell.setTop(lastCell.bottom()); } // irrelevant code lastCell = cell; } Como podemos observar, lastCell no es asignada hasta el final del loop, pero se utiliza previamente. Hasta su asignación no podemos inducir su tipo, por este motivo la llamada a su método bottom se consideraría inválido a la hora de analizar el código (ya que en el análisis estático el if es analizado antes que la asignación). Por este motivo se necesita un pragma que nos indique previamente el tipo de lastCell. Este caso a diferencia del anterior se podría evitar mediante el uso de algoritmos más complejos y se comenta en el apartado de mejoras futuras. Este uso se encuentra dos veces en el código y a diferencia del anterior si que es más importante tenerlo en cuenta ya que las reglas de estilo no impiden que en un futuro se añadan más asignaciones de este tipo en loops, por lo que reescribir el código en este caso no sería una solución válida. Nuestro modificador de código es el que tiene que saber manejar este caso. 48 8.3 Modificación del código fuente Para realizar la modificación del código fuente que este proyecto requería hemos necesitado crear interfaces para 10 de los 15 ficheros del código fuente de Snap (técnicamente son 46, pero la diferencia a pesar de ser código JavaScript simplemente definen objetos con las traducciones para diferentes idiomas). Los archivos que no han recibido interfaz son eliminados completamente y todas sus clases que sean referenciadas en otros archivos desaparecen del código generado. De los diez archivos mantenidos, cuatro no son tratados y su interfaz existe simplemente para introducir información sobre sus clases en el programa para permitir su modificación. En estos casos la interfaz se puede reducir solo a las propiedades que se usen fuera de ellas, ahorrando trabajo. El hecho de que estos archivos no se modifiquen es que forman parte de la fundación del sistema de Snap, definen clases que son totalmente independientes del resto y de las cuales se necesita toda su funcionalidad para que Snap pueda funcionar. Por estos motivos no merece la pena invertir tiempo en crear una interfaz completa para estos archivos ya que nunca se dará el caso de que usen clases o métodos que se quieran eliminar del entorno de ejecución reducido. Concretamente los archivos son: •Morphic.js: como se ha comentado con anterioridad la fundación de todo Snap y un fichero algo especial ya que no sigue todas las convenciones de estilo de Snap. •xml.js: implementa un parser y codificador xml sencillos. Es un sistema completamente desconectado de Snap y se utiliza básicamente en operaciones de guardado y cargado de proyectos. •threads.js: este archivo contiene clases que se encargan de manejar toda la interpretación del código representado por los bloques de Snap. •blocks.js: este archivo contiene la definición de los bloques de código y forma la base del lenguaje de Snap. El resto de archivos son todos pertenecientes a diferentes partes de Snap, todos ellos son procesados ya que se referencian entre ellos, por lo tanto cada uno de estos archivos ha recibido una interfaz completa. Con completa nos referimos a una que defina completamente todos los elementos mientras queramos que estos se mantengan en el código final. La modificación de código funciona correctamente, pero es algo difícil justificarla por escrito. La corrección se ha comprobado experimentalmente, modificando las diferentes actualizaciones de Snap que han ido apareciendo a lo largo del proyecto y creando ejecutables para múltiples proyectos Snap utilizando el entorno modificado, que en conjunto engloban todas las funcionalidades requeridas, todos ellos se han ejecutado satisfactoriamente. 49 Un dato interesante es la cantidad de código eliminado, es decir, que no es necesario para la la interpretación del código y el renderizado de la Stage en Snap, en nuestro entorno de ejecución reducido. El cálculo que haremos será aproximado, pero nos dará una idea general de en que medida se ha reducido el entorno de Snap. Esta comparación tiene sentido ya que el output del modificador de código utiliza el mismo estilo que el código original. Reducción de código en los archivos tratados Archivo Líneas de código archivo original Líneas de código archivo reducido Reducción (%) byob.js 3422 251 92.7 gui.js 6719 267 96.0 lists.js 702 471 32.0 objects.js 7801 3999 48.7 store.js 1975 756 61.7 widgets.js 3293 1199 63.6 Total 23912 6943 71.0 La cantidad real eliminada debe de ser algo inferior debido a elementos como los comentarios. Aún así, es interesante ver que se ha eliminado una cantidad de código importante. La mayoría del código existe para renderizar y procesar partes de Snap que no nos interesan en nuestro entorno de ejecución reducido, así que tiene sentido eliminarlas. Esto se nota especialmente en los dos archivos que mas influyen en la interfaz, byob.js y gui.js. Los cuales han sido reducidos casi completamente. También vemos que nuestro modificador de código trabaja con una cantidad de código grande, lo cual es otra prueba indirecta que soporta su corrección. 50 9 Mejoras futuras A pesar de que los resultados de la aplicación han sido los esperados esto no quiere decir que este finalizada. Debido a la limitación de tiempo y recursos de este proyecto no se han podido incluir todos los elementos que se pretendían, además también hay tareas de mantenimiento y nuevas features que se podrían añadir que no formaban parte de los objetivos iniciales. Los puntos más importantes serían: •Arreglo de bugs: a pesar de que no hemos detectado ninguno hacia el final del desarrollo y se ha dedicado bastante tiempo a buscar y eliminar tantos como ha sido posible, es muy probable que aún existan diversos errores en el código que no han salido a la luz con el input que se ha dado al programa hasta ahora. Estos deberían de ser eliminados cuando vayan surgiendo a medida que se utilice la aplicación. •Actualización para nuevas versiones de ECMAScript: no tanto una mejora como un mantenimiento. ECMAScript es un lenguaje que hoy en día aún recibe numerosas revisiones y, en caso de que Snap pase a utilizar nuevas herramientas que se añadan al lenguaje, estas tienen que incluirse en la aplicación. •Introducción de algoritmos más sofisticados de análisis de código: a pesar de que esta aplicación no tiene que realizar operaciones tan complicadas como un compilador y funciona con los algoritmos algo más básicos empleados durante el desarrollo, se beneficiaría del uso de algoritmos más complejos y con más alcance para, por ejemplo, poder reducir algo de la información que tiene que indicar el usuario o reduciendo la redundancia del código generado. Por ejemplo se podrían acabar de eliminar todos los pragma que actualmente hay que añadir al código mediante un algoritmo que, en caso de encontrarse un loop lo analizase por adelantado para poder obtener los tipos de las variables y evitar casos como el comentado en el apartado anterior. •Permitir ejecutables para más plataformas: ahora mismo este es capaz de generar todos los tipos de ejecutables que se habían propuesto en el trabajo. Pero sería interesante incluir aún más plataformas, como por ejemplo las móviles. •Ampliar las posibilidades de los ejecutables creados con un entorno completo de Snap para permitir el uso de todas sus features e incluso ampliarlas (aprovechando la tecnología NW.js) para poder, eventualmente, llegar a una versión de escritorio de Snap que sea capaz de leer y escribir en disco, etc. 51 10 Conclusiones En este proyecto hemos desarrollado una aplicación capaz de reducir el código fuente de Snap a un subconjunto arbitrario que define el usuario. Esta herramienta se ha utilizado para poder generar un entorno de ejecución reducido para proyectos Snap que a su vez se ha hecho servir para crear ejecutables para distintos sistemas operativos a partir de programas Snap. El resultado ha sido satisfactorio, con información básica sobre las clases que componen el código podemos crear un código JavaScript correcto y funcional que cumple los requisitos que nos habíamos propuesto. Los ejecutables generados por la aplicación muestran solamente el resultado de la ejecución del código y son fáciles de obtener, trasladar y usar por el usuario, sin necesidad por su parte de realizar trabajo extra. Todo el trabajo es realizado de forma transparente en nuestro servidor y los ejecutables generados no necesitan ningún programa de terceros para funcionar. Todo el sistema, tanto el Web Service como el modificador de código es totalmente portable, utilizando tecnologías que no están ligadas a una plataforma concreta. A pesar de todo, debido a limitaciones de tiempo y recursos han quedado algunos puntos de mejora pendientes. El más importante sería eliminar completamente la necesidad de añadir pragmas en el mismo código fuente ya que este es un punto importante para los desarrolladores originales de Snap (la no modificación del código fuente original) y ayudaría a la integración de esta herramienta en Snap, de la misma manera que otras partes de Snap que comenzaron como plugins de terceros han acabado integrándose en su rama principal. Otro punto interesante sería la implementación de algoritmos, existentes o nuevos, que mejorasen la eficiencia del modificador de código, reduciendo redundancia, resolviendo ambigüedades de forma más robusta, etc. Algunos han tenido que quedar fuera debido a que su complejidad habría requerido mucho más tiempo de desarrollo y testeo del que se ha dispuesto y no son estrictamente necesarios para alcanzar la funcionalidad deseada. En conclusión, se ha conseguido crear una aplicación interesante y potente que puede ser, y se espera que sea, la base de futuras mejoras, experimentos y proyectos relacionados con Snap. 52 11 Bibliografía 1: Usage share of operating systems [En línea] <https://en.wikipedia.org/wiki/Usage_share_of_operating_systems> [Consulta: octubre 2015] 2: OS Platform Statistics [En línea] <http://www.w3schools.com/browsers/browsers_os.asp> [Consulta: octubre 2015] 3: Interpreted language, Languages usually compiled to a bytecode [En línea] <https://en.wikipedia.org/wiki/Interpreted_language#Languages_usually_compiled_to_a_bytecode> [Consulta: octubre 2015] 4: ACM Reference Format: Maloney, J., Resnick, M., Rusk, N., Silverman, B., and Eastmond, E. 2010. The scratch programming language and environment. ACM Trans. Comput. Educ. 10, 4, Article 16 (November 2010), 15 pages. DOI = 10.1145/1868358.1868363. http://doi.acm.org/10.1145/1868358.1868363. 5: BYOB (Scratch Modification) [En línea] <http://wiki.scratch.mit.edu/wiki/Build_Your_Own_Blocks_(Scratch_Modification)> [Consulta: octubre 2015] 6: Porting Scratch Projects [En línea] <http://wiki.scratch.mit.edu/wiki/Porting_Scratch_Projects> [Consulta: octubre 2015] 7: Built in Scratch2exe [En línea] <https://web.archive.org/web/20120101192818/http://suggest.scratch.mit.edu/forum s/60449-suggestions/suggestions/1240331-built-in-scratch2exe> [Consulta: octubre 2015] 8: ScratchToJAR [En línea] <https://github.com/Gbear605/ScratchToJAR/blob/master/README> [Consulta: octubre 2015] 9: Scratch To SWF [En línea] <https://sites.google.com/site/junebeetle23/home> [Consulta: octubre 2015] 10: Scratch compiler [En línea] <https://scratch.mit.edu/discuss/topic/27690/> [Consulta: octubre 2015] 11: pinecone: A JavaScript->Lua converter [En línea] <https://github.com/zekesonxx/pinecone/blob/master/README.md> [Consulta: octubre 2015] 53 12: morphic.js - a lively WEB_GUY inspired by Squeak [En línea] <https://github.com/jmoenig/Snap--Build-Your-Own-Blocks/blob/master/morphic.txt> [Consulta: octubre 2015] 13: JSLint Help [En línea] <http://www.jslint.com/help.html> [Consulta: octubre 2015] 14: Code conventions [En línea] <http://javascript.crockford.com/code.html> [Consulta: octubre 2015] 15: contributing to BYOB4 [En línea] <https://github.com/jmoenig/Snap--Build-Your-Own-Blocks/blob/master/contributing%20to%20BYOB4.txt> [Consulta: octubre 2015] 16: ECMAScript Language Specification [En línea] <http://www.ecma-international.org/ecma-262/5.1/Ecma-262.pdf> [Consulta: octubre 2015] 17: Parsing – Computer Languages [En línea] <https://en.wikipedia.org/wiki/Parsing#Computer_languages> [Consulta: octubre 2015] 18: Parsing – Computer Languages [En línea] <https://www.cs.rochester.edu/~nelson/courses/csc_173/grammars/cfg.html> [Consulta: octubre 2015] 19: Parsing – Computer Languages [En línea] <http://www.antlr.org/about.html> [Consulta: octubre 2015] 20: ECMAScript [En línea] <http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf> [Consulta: octubre 2015] 21: JSON [En línea] <http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf> [Consulta: octubre 2015] 22: JavaScript Closures [En línea] <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures> [Consulta: octubre 2015] 23: Symbol Table [En línea] <http://www.cse.aucegypt.edu/~rafea/csce447/slides/table.pdf> [Consulta: octubre 2015] 54