articulos:zxbasic_suenyo_hecho_realidad

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
articulos:zxbasic_suenyo_hecho_realidad [24-03-2009 09:43] sromeroarticulos:zxbasic_suenyo_hecho_realidad [03-04-2009 06:45] (actual) sromero
Línea 51: Línea 51:
     * Finalmente, un ensamblador (el ZX BASIC contiene uno propio) y un enlazador de código objeto (el ZX BASIC no usa ninguno: por ahora todo se trabaja en ensamblador y se compila a binario directamente) realizan el resto del trabajo produciendo el binario final.     * Finalmente, un ensamblador (el ZX BASIC contiene uno propio) y un enlazador de código objeto (el ZX BASIC no usa ninguno: por ahora todo se trabaja en ensamblador y se compila a binario directamente) realizan el resto del trabajo produciendo el binario final.
  
-<code> +^ Frontend ^^ 
- ----------------------------------------------------------------------------------- +| Análisis Léxico | Convertir letras a palabras y símbolos | 
-|             | Análisis Léxico       Convertir letras a palabras y símbolos      +| Análisis Sintáctico | Comprobar la sintaxis y las frases | 
-| Frontend    |---------------------|-----------------------------------------------| +Análisis Semántico | Comprobar los tipos de variable,\\ declaraciones duplicadas o fuera de contexto.\\ Construcción del Árbol Sintáctico. | 
-|             | Análisis Sintáctico |   Comprobar la sintaxis y las frases          + Optimización del Árbol  || 
-            |---------------------|-----------------------------------------------| + Generación de Código Intermedio  || 
-|                                 | Comprobar los tipos de variable,              | + 
-|             | Análisis Semántico declaraciones duplicadas o fuera de contexto. +^ Backend para ZX Spectrum ^^ 
-|                                 Construcción del Árbol Sintáctico.            + Traducción a Ensamblador (Z80)  || 
-            |---------------------------------------------------------------------| + Optimización de Código Ensamblador (reordenación de registros, etc)  || 
-|                                 Optimización del Árbol                          | + Ensamblado: Traducción a Código Máquina (o Código Objeto)  |
-|             |---------------------------------------------------------------------+\\ 
-            |                  Generación de Código Intermedio                    | +
-|-------------|---------------------------------------------------------------------+
-            |                 Traducción a Ensamblador (Z80)                      | +
-| Backend     |---------------------------------------------------------------------+
-para        | Optimización de Código Ensamblador (reordenación de registros, etc) +
-| ZXSpectrum  |---------------------------------------------------------------------+
-            |      Ensamblado: Traducción a Código Máquina (o Código Objeto)      +
- ----------------------------------------------------------------------------------- +
-</code>+
  
 Prácticamente todos los compiladores actuales trabajan de forma similar. La ventaja de esto es que se pueden cambiar las capas de backend de manera que es posible compilar el mismo programa para distintas arquitecturas: se podría hacer que un programa en ZX BASIC compilara para Windows, Linux, Nintendo DS, teléfono móvil, PlayStation o cualquier otra plataforma. Evidentemente cada plataforma tiene su limitación, pero dado que el ZX es la más limitada de todas sin duda, esto no debería suponer problema alguno. Los compiladores de este tipo se llaman retargeable compilers (compiladores reorientables). Prácticamente todos los compiladores actuales trabajan de forma similar. La ventaja de esto es que se pueden cambiar las capas de backend de manera que es posible compilar el mismo programa para distintas arquitecturas: se podría hacer que un programa en ZX BASIC compilara para Windows, Linux, Nintendo DS, teléfono móvil, PlayStation o cualquier otra plataforma. Evidentemente cada plataforma tiene su limitación, pero dado que el ZX es la más limitada de todas sin duda, esto no debería suponer problema alguno. Los compiladores de este tipo se llaman retargeable compilers (compiladores reorientables).
Línea 127: Línea 118:
 ZX Basic tiene bastantes parámetros. Sólo veremos los más importantes. Si tecleas zxb -h tendrás una escueta ayuda. Puedes buscar más información en la Wiki del compilador, que está en http://www.boriel.com/wiki/en/index.php/ZXBasic en inglés. Los parámetros tienen una versión larga que empieza con doble guión (por ej. --tzx) y una versión corta equivalente (en este caso, -T). Los parámetros más importantes son: ZX Basic tiene bastantes parámetros. Sólo veremos los más importantes. Si tecleas zxb -h tendrás una escueta ayuda. Puedes buscar más información en la Wiki del compilador, que está en http://www.boriel.com/wiki/en/index.php/ZXBasic en inglés. Los parámetros tienen una versión larga que empieza con doble guión (por ej. --tzx) y una versión corta equivalente (en este caso, -T). Los parámetros más importantes son:
  
-    * -T o --tzx hace que el formato de salida sea un archivo de cinta .tzx +    * **-T** **--tzx** hace que el formato de salida sea un archivo de cinta .tzx 
-    * -t o --tap hace que el formato de salida sea un archivo de cinta .tap. Esta opción y la anterior son excluyentes. +    * **-t** **--tap** hace que el formato de salida sea un archivo de cinta .tap. Esta opción y la anterior son excluyentes. 
-    * -B o --BASIC hace que se genere un cargador BASIC que cargue nuestro programa. Si usas esta opción, necesariamente tienes que haber usado una de las anteriores. +    * **-B** **--BASIC** hace que se genere un cargador BASIC que cargue nuestro programa. Si usas esta opción, necesariamente tienes que haber usado una de las anteriores. 
-    * -a o --autorun hace que nuestro programa se ejecute automáticamente tras ser cargado. Esta opción obliga a que se use la opción --BASIC, ya que se requiere un cargador BASIC para que el programa se autoejecute. +    * **-a** **--autorun** hace que nuestro programa se ejecute automáticamente tras ser cargado. Esta opción obliga a que se use la opción --BASIC, ya que se requiere un cargador BASIC para que el programa se autoejecute. 
-    * -o <fichero de salida>. Permite especificar un nombre de archivo distinto para el programa resultante. Generalmente se usará el mismo nombre que el programa de código fuente (.bas), pero con una extensión distinta (.bin, .tap o .tzx) +    * **-o <fichero de salida>**. Permite especificar un nombre de archivo distinto para el programa resultante. Generalmente se usará el mismo nombre que el programa de código fuente (.bas), pero con una extensión distinta (.bin, .tap o .tzx) 
-    * -A o --asm Hace que no se genere el código objeto. En lugar de eso obtendremos el código ensamblador de todo nuestro programa (un archivo ASCII con extensión .asm). Esta opción es útil si queremos optimizar a mano nuestro programa. Se ha intentado que el código ensamblador generado sea bastante legible y con comentarios incluidos. +    * **-A** **--asm** Hace que no se genere el código objeto. En lugar de eso obtendremos el código ensamblador de todo nuestro programa (un archivo ASCII con extensión .asm). Esta opción es útil si queremos optimizar a mano nuestro programa. Se ha intentado que el código ensamblador generado sea bastante legible y con comentarios incluidos. 
-    * -S o --ORG cambia la dirección de comienzo de ejecución del código máquina. Como se dijo antes, por defecto es 32768, pero podemos hacer que se ejecute a partir de una dirección distinta (por ej. 28000) para obtener más memoria.+    * **-S** **--ORG** cambia la dirección de comienzo de ejecución del código máquina. Como se dijo antes, por defecto es 32768, pero podemos hacer que se ejecute a partir de una dirección distinta (por ej. 28000) para obtener más memoria.
  
 \\  \\ 
Línea 178: Línea 169:
 | Long | 4 bytes | Entero largo con signo | [-2147483648 a 2147483647] (más de 2.100 millones) | | Long | 4 bytes | Entero largo con signo | [-2147483648 a 2147483647] (más de 2.100 millones) |
 | Ulong | 4 bytes | Entero largo sin signo | [0 .. 4294967295] (de 0 a más de 4.200 millones) | | Ulong | 4 bytes | Entero largo sin signo | [0 .. 4294967295] (de 0 a más de 4.200 millones) |
-| Fixed | 4 bytes | Decimal con punto fijo | [-32767.9999847 .. 32767.9999847] con una precisión de 1 / 2^16 (0.000015 aprox.) |+| Fixed | 4 bytes | Decimal con punto fijo | [-32767.9999847 .. 32767.9999847] con una precisión de 1 / 2<nowiki>^</nowiki>16 (0.000015 aprox.) |
 | Float | 5 bytes | Decimal con punto flotante | Como en el ZX Spectrum | | Float | 5 bytes | Decimal con punto flotante | Como en el ZX Spectrum |
  
Línea 186: Línea 177:
  
 \\  \\ 
 +
 ===== Variables de Cadenas de Carácteres ===== ===== Variables de Cadenas de Carácteres =====
  
Línea 204: Línea 196:
  
  
-DIM: Declarando Variables+\\  
 +===== DIM: Declarando Variables =====
  
 Hemos visto que ZX BASIC usa distintos tipos de dato. Cuando usas una variable el compilador intenta adivinar su tipo (si es alfanumérica, entera, etc.), pero en general no va a poder hacerlo. Si no sabe qué tipo asignar, usará el punto flotante como el Sinclair Basic. Esto conlleva un desperdicio de memoria y mayor lentitud. Podemos indicarle al compilador el tipo de dato de una variable declarándola. Para declarar una variable se usa la palabra reservada DIM: Hemos visto que ZX BASIC usa distintos tipos de dato. Cuando usas una variable el compilador intenta adivinar su tipo (si es alfanumérica, entera, etc.), pero en general no va a poder hacerlo. Si no sabe qué tipo asignar, usará el punto flotante como el Sinclair Basic. Esto conlleva un desperdicio de memoria y mayor lentitud. Podemos indicarle al compilador el tipo de dato de una variable declarándola. Para declarar una variable se usa la palabra reservada DIM:
  
 +<code basic>
 REM Declaración de dos variables de tipo entero byte sin signo REM Declaración de dos variables de tipo entero byte sin signo
 DIM a, b uByte DIM a, b uByte
Línea 213: Línea 207:
 REM Declaración de una variable en punto Flotante con valor inicializado REM Declaración de una variable en punto Flotante con valor inicializado
 DIM longitud = 3.5 AS Float: REM longitud = 3.5 metros DIM longitud = 3.5 AS Float: REM longitud = 3.5 metros
 +</code>
  
 Si vas a declarar una variable, tienes que hacerlo antes de su primer uso. Si vas a declarar una variable, tienes que hacerlo antes de su primer uso.
  
-Arrays+\\  
 +===== Arrays ===== 
  
 Los arrays se declaran como en Sinclair BASIC, y admiten tantas dimensiones como quepan en memoria. No obstante, al contrario que en Sinclair Basic, la numeración de los índices no comienza en 1, sino en 0. Además, podemos declarar opcionalmente el tipo de elemento. Veamos un ejemplo: Los arrays se declaran como en Sinclair BASIC, y admiten tantas dimensiones como quepan en memoria. No obstante, al contrario que en Sinclair Basic, la numeración de los índices no comienza en 1, sino en 0. Además, podemos declarar opcionalmente el tipo de elemento. Veamos un ejemplo:
  
-DIM a(10) AS Float : REM un array de 11 flotantes, del 0 al 10, ambos inclusive+<code basic> 
 +REM un array de 11 flotantes, del 0 al 10, ambos inclusive 
 +DIM a(10) AS Float 
 +</code>
  
 Podemos trabajar normalmente como en Sinclair BASIC empezando desde índice uno, pero desperdiciaremos la posición 0. No obstante, también podemos declarar el índice mínimo y máximo del array de forma explícita: Podemos trabajar normalmente como en Sinclair BASIC empezando desde índice uno, pero desperdiciaremos la posición 0. No obstante, también podemos declarar el índice mínimo y máximo del array de forma explícita:
  
-DIM a(1 TO 10, 1 TO 5) AS Float : REM  esto es equivalente a DIM a(10, 5) en el BASIC de ZX Spectrum+<code basic> 
 +REM  esto es equivalente a DIM a(10, 5) en el BASIC de ZX Spectrum 
 +DIM a(1 TO 10, 1 TO 5) AS Float 
 +</code>
  
 Si queremos que por defecto los arrays comiencen en 1 (como en Sinclair BASIC) y no en 0, hay que usar la opción --array-base=1 al invocar al compilador. Si queremos que por defecto los arrays comiencen en 1 (como en Sinclair BASIC) y no en 0, hay que usar la opción --array-base=1 al invocar al compilador.
Línea 232: Línea 235:
 Es posible declarar arrays con valores inicializados. Esto es útil porque no existen ni READ, ni DATA, ni RESTORE. Lo haríamos así: Es posible declarar arrays con valores inicializados. Esto es útil porque no existen ni READ, ni DATA, ni RESTORE. Lo haríamos así:
  
 +<code basic>
 REM Definimos un array de 2 filas y 8 columnas REM Definimos un array de 2 filas y 8 columnas
 DIM MiUdg(1, 7) AS uByte => { {60, 66, 129, 129, 129, 129, 66, 60}, _ DIM MiUdg(1, 7) AS uByte => { {60, 66, 129, 129, 129, 129, 66, 60}, _
                               {24, 60, 60, 60, 126, 251, 247, 126}}                               {24, 60, 60, 60, 126, 251, 247, 126}}
 +</code>
  
 Observa el caracter de subrayado "_" al final de la primera línea del array, ya que hay que partirla (si no, quedaría muy larga). Observa el caracter de subrayado "_" al final de la primera línea del array, ya que hay que partirla (si no, quedaría muy larga).
  
-Sentencias de Control de Flujo+\\  
 +===== Sentencias de Control de Flujo ===== 
 + 
 +\\  
 +==== GO TO, GO SUB, RETURN ====
  
-GO TO, GO SUB, RETURN 
  
 Idénticas al BASIC de Sinclair, aunque se desaconseja su uso. GO TO puede escribirse junto, como GOTO. Ídem para GOSUB. Se pueden usar números de línea o etiquetas como vimos en un ejemplo anterior. Idénticas al BASIC de Sinclair, aunque se desaconseja su uso. GO TO puede escribirse junto, como GOTO. Ídem para GOSUB. Se pueden usar números de línea o etiquetas como vimos en un ejemplo anterior.
  
-FOR+\\  
 +==== FOR ==== 
  
 La sentencia para realizar bucles FOR se comporta de la misma forma que en ZX Basic. No obstante, al ser un lenguaje compilado, tienes que tener en cuenta algunas cosas. Por ejemplo, hacer un bucle FOR usando una variable en punto flotante es bastante más lento que hacerla con una de tipo entero. Su uso es prácticamente igual al del ZX Spectrum, pero hay algunas diferencias: La sentencia para realizar bucles FOR se comporta de la misma forma que en ZX Basic. No obstante, al ser un lenguaje compilado, tienes que tener en cuenta algunas cosas. Por ejemplo, hacer un bucle FOR usando una variable en punto flotante es bastante más lento que hacerla con una de tipo entero. Su uso es prácticamente igual al del ZX Spectrum, pero hay algunas diferencias:
Línea 257: Línea 267:
     * CONTINUE FOR que "continúa" el bucle, es decir, realiza un NEXT. En el BASIC de Sinclair poníamos un NEXT <variable>, pero en ZX BASIC esto no se permite. Hay que usar CONTINUE.     * CONTINUE FOR que "continúa" el bucle, es decir, realiza un NEXT. En el BASIC de Sinclair poníamos un NEXT <variable>, pero en ZX BASIC esto no se permite. Hay que usar CONTINUE.
  
-IF+\\  
 +==== IF ==== 
  
 Esta sentencia sí difiere del BASIC tradicional de Sinclair. Es más potente. Admite varias líneas después del THEN, y además incluye la cláusula ELSE ("en otro caso"). Además es necesario terminarla con un END IF. Un ejemplo: Esta sentencia sí difiere del BASIC tradicional de Sinclair. Es más potente. Admite varias líneas después del THEN, y además incluye la cláusula ELSE ("en otro caso"). Además es necesario terminarla con un END IF. Un ejemplo:
  
 +<code basic>
 IF a < b THEN IF a < b THEN
     PRINT "a es menor que b"     PRINT "a es menor que b"
Línea 267: Línea 280:
     PRINT "a no es menor que b"     PRINT "a no es menor que b"
 END IF END IF
 +</code>
  
 En Sinclair BASIC teníamos que ingeniárnoslas haciendo algo así: En Sinclair BASIC teníamos que ingeniárnoslas haciendo algo así:
  
 +<code basic>
 1000 IF a < b THEN PRINT "a es menor que b": PRINT "Esto es otra sentencia más" : GO TO 1030 1000 IF a < b THEN PRINT "a es menor que b": PRINT "Esto es otra sentencia más" : GO TO 1030
 1010 REM si no... 1010 REM si no...
 1020 PRINT "a no es menor que b" 1020 PRINT "a no es menor que b"
 1030 ... 1030 ...
 +</code>
  
-WHILE+\\  
 +==== WHILE ====
  
 Esta sentencia es nueva ZX BASIC y sirve también para hacer bucles. Es más potente que FOR porque el bucle se repite mientras se dé la condición que se indique. Si al empezar el bucle la condición es falsa, entonces no se llega a ejecutar ninguna iteración. Un ejemplo: Esta sentencia es nueva ZX BASIC y sirve también para hacer bucles. Es más potente que FOR porque el bucle se repite mientras se dé la condición que se indique. Si al empezar el bucle la condición es falsa, entonces no se llega a ejecutar ninguna iteración. Un ejemplo:
  
 +<code basic>
 WHILE a < 10 WHILE a < 10
     LET a = a + 1     LET a = a + 1
 END WHILE END WHILE
 +</code>
  
 La finalización del bucle tiene que terminarse con END WHILE o con WEND (son equivalentes). Al igual que con FOR, las sentencias EXIT WHILE y CONTINUE WHILE también pueden utilizarse con WHILE para terminar el bucle o continuar con la siguiente iteración. La finalización del bucle tiene que terminarse con END WHILE o con WEND (son equivalentes). Al igual que con FOR, las sentencias EXIT WHILE y CONTINUE WHILE también pueden utilizarse con WHILE para terminar el bucle o continuar con la siguiente iteración.
  
-DO ... UNTIL+\\  
 +==== DO ... UNTIL ====
  
 Similar a la anterior, pero aquí la comprobación de la condición se hace al final del bucle y éste se repite mientras no se cumpla la misma (es decir, mientras sea falsa). Al contrario que con WHILE, el bucle se ejecutará al menos una vez. Un ejemplo: Similar a la anterior, pero aquí la comprobación de la condición se hace al final del bucle y éste se repite mientras no se cumpla la misma (es decir, mientras sea falsa). Al contrario que con WHILE, el bucle se ejecutará al menos una vez. Un ejemplo:
  
 +<code basic>
 DO DO
     LET a = a + 1     LET a = a + 1
-UNTIL a >= 10+LOOP UNTIL a >= 10 
 +</code>
  
 Igualmente podemos usar CONTINUE DO y EXIT DO para adelantar el bucle o terminarlo anticipadamente. Igualmente podemos usar CONTINUE DO y EXIT DO para adelantar el bucle o terminarlo anticipadamente.
  
-DO ... WHILE+\\  
 + 
 +==== DO ... WHILE ====
  
 Existe también la construcción DO ... WHILE, idéntica a la anterior, solo que esta repite el bucle mientras la condición se cumpla. Existe también la construcción DO ... WHILE, idéntica a la anterior, solo que esta repite el bucle mientras la condición se cumpla.
  
-Manejo de Memoria+\\  
 +===== Manejo de Memoria ===== 
 + 
 +\\  
 +==== PEEK y POKE ====
  
-PEEK y POKE 
  
 Son idénticas al Sinclair BASIC, pero ahora, además, está extendidas. Tanto PEEK como POKE admiten especificar el tipo de dato que se guarda en memoria. Por defecto será de tipo byte sin signo (como en el ZX Spectrum). Así pues, las siguientes dos líneas son equivalentes: Son idénticas al Sinclair BASIC, pero ahora, además, está extendidas. Tanto PEEK como POKE admiten especificar el tipo de dato que se guarda en memoria. Por defecto será de tipo byte sin signo (como en el ZX Spectrum). Así pues, las siguientes dos líneas son equivalentes:
  
 +<code basic>
 LET a = PEEK 16384 LET a = PEEK 16384
 LET a = PEEK(uByte, 16384) LET a = PEEK(uByte, 16384)
 +</code>
  
 Pero a veces queremos guardar o leer un entero de 16 bits. En el mismo manual del ZX Spectrum viene un ejemplo. Estos valores, en el Z80 se leen de esta manera: Pero a veces queremos guardar o leer un entero de 16 bits. En el mismo manual del ZX Spectrum viene un ejemplo. Estos valores, en el Z80 se leen de esta manera:
  
 +<code basic>
 REM Guardamos en la variable 'a' la dirección de comienzo de los GDU REM Guardamos en la variable 'a' la dirección de comienzo de los GDU
 LET a = PEEK 23675 + 256 * PEEK 23676 : REM Forma tradicional LET a = PEEK 23675 + 256 * PEEK 23676 : REM Forma tradicional
 LET a = PEEK(Uinteger, 16384) LET a = PEEK(Uinteger, 16384)
 +</code>
  
 Ambas formas son equivalentes, pero la segunda es más eficiente (por el ensamblador generado) y más legible. Si se usa la segunda forma, los paréntesis son obligatorios. Igualmente, podemos guardar con POKE valores de más de un byte: Ambas formas son equivalentes, pero la segunda es más eficiente (por el ensamblador generado) y más legible. Si se usa la segunda forma, los paréntesis son obligatorios. Igualmente, podemos guardar con POKE valores de más de un byte:
  
 +<code basic>
 REM Cambiamos la dirección de los GDU según la variable 'a' REM Cambiamos la dirección de los GDU según la variable 'a'
  
Línea 324: Línea 356:
 REM Forma moderna REM Forma moderna
 POKE Uinteger 23675, a POKE Uinteger 23675, a
 +</code>
  
 Claramente, la segunda forma es más legible (y preferible). Además, se traduce de forma más eficiente a ensablador. ¡Ahora ya es posible (y extraño), guardar números flotantes con POKE en memoria! Prueba a hacer POKE Float 16384, PI. Claramente, la segunda forma es más legible (y preferible). Además, se traduce de forma más eficiente a ensablador. ¡Ahora ya es posible (y extraño), guardar números flotantes con POKE en memoria! Prueba a hacer POKE Float 16384, PI.
  
-Salida por Pantalla+\\ 
  
-PRINT+===== Salida por Pantalla ===== 
 + 
 +\\  
 +==== PRINT ====
  
 Se ha intentado que PRINT sea lo más compatible posible con el original. De hecho, a nivel sintáctico funciona igual. E incluso los códigos de color de la ROM se pueden utilizar. Esta implementación de PRINT no usa la rutina de la ROM (para mayor velocidad) sino que es propia, y permite imprimir en todas las filas de pantalla. Así pues, PRINT AT 22,0; es una sentencia legal. No existen canales. Se ha intentado que PRINT sea lo más compatible posible con el original. De hecho, a nivel sintáctico funciona igual. E incluso los códigos de color de la ROM se pueden utilizar. Esta implementación de PRINT no usa la rutina de la ROM (para mayor velocidad) sino que es propia, y permite imprimir en todas las filas de pantalla. Así pues, PRINT AT 22,0; es una sentencia legal. No existen canales.
  
-BORDER, PAPER, INK, INVERSE, BRIGHT, FLASH, OVER+\\  
 +==== BORDER, PAPER, INK, INVERSE, BRIGHT, FLASH, OVER ==== 
  
 Funcionan igual que en Sinclar BASIC. Para BORDER, usar un color mayor que 7 se suele ignorar, ya que sólo se toman los 3 bits más bajos. Para INK y PAPER si se puede usar el valor 8 (transparencia). Over tiene funcionalidades extra: OVER 1 actúa como en el ZX Spectrum e imprime realizando la operacion XOR. Pero se puede usar también OVER 2 y OVER 3 en conjunción con el comando PRINT. OVER 2 realiza un AND y OVER 3 realiza un OR. Esto se puede usar para crear efectos de filmation. Prueba el siguiente ejemplo de BORDER y compara su velocidad con la del Basic de la ROM: Funcionan igual que en Sinclar BASIC. Para BORDER, usar un color mayor que 7 se suele ignorar, ya que sólo se toman los 3 bits más bajos. Para INK y PAPER si se puede usar el valor 8 (transparencia). Over tiene funcionalidades extra: OVER 1 actúa como en el ZX Spectrum e imprime realizando la operacion XOR. Pero se puede usar también OVER 2 y OVER 3 en conjunción con el comando PRINT. OVER 2 realiza un AND y OVER 3 realiza un OR. Esto se puede usar para crear efectos de filmation. Prueba el siguiente ejemplo de BORDER y compara su velocidad con la del Basic de la ROM:
  
 +<code basic>
 10 BORDER 0: BORDER 1: GOTO 10 10 BORDER 0: BORDER 1: GOTO 10
 +</code>
  
-PLOT, DRAW y CIRCLE+\\  
 +==== PLOT, DRAW y CIRCLE ====
  
 Funcionan igual que en el Basic original... pero más rápido. PLOT usa la rutina de la ROM, por lo que se aplican todos atributos de color que usa el comando PLOT original. La diferencia ahora es que disponemos de los 192 puntos de pantalla para pintar. La coordenada (0, 0) es ahora la esquina inferior izquierda física de la pantalla. Eso significa que si usamos un comando de dibujado cualquiera, nuestros dibujos aparecerán 16 píxeles más abajo que en el Sinclair BASIC original, pues ahora disponemos de 16 líneas de altura más. Funcionan igual que en el Basic original... pero más rápido. PLOT usa la rutina de la ROM, por lo que se aplican todos atributos de color que usa el comando PLOT original. La diferencia ahora es que disponemos de los 192 puntos de pantalla para pintar. La coordenada (0, 0) es ahora la esquina inferior izquierda física de la pantalla. Eso significa que si usamos un comando de dibujado cualquiera, nuestros dibujos aparecerán 16 píxeles más abajo que en el Sinclair BASIC original, pues ahora disponemos de 16 líneas de altura más.
Línea 345: Línea 386:
 DRAW y CIRCLE están optimizadas (no son las de la ROM) y emplean el algoritmo de Bresenham, por lo que son algo más rápidas (especialmente CIRCLE). También se pueden dibujar arcos, con DRAW x, y, a como en el BASIC original. La rutina está copiada de la ROM, para simular el mismo comportamiento que la original, pero modificada para dibujar también en toda la pantalla, como las anteriores. El siguiente ejemplo está sacado del manual del ZX Spectrum (el reloj), pero está escrito con la nueva sintaxis, sin usar números de línea: DRAW y CIRCLE están optimizadas (no son las de la ROM) y emplean el algoritmo de Bresenham, por lo que son algo más rápidas (especialmente CIRCLE). También se pueden dibujar arcos, con DRAW x, y, a como en el BASIC original. La rutina está copiada de la ROM, para simular el mismo comportamiento que la original, pero modificada para dibujar también en toda la pantalla, como las anteriores. El siguiente ejemplo está sacado del manual del ZX Spectrum (el reloj), pero está escrito con la nueva sintaxis, sin usar números de línea:
  
 +<code basic>
 REM Del manual de ZX Spectrum 48K REM Del manual de ZX Spectrum 48K
 REM Un programa de Reloj REM Un programa de Reloj
Línea 377: Línea 419:
     PLOT 131, 107: DRAW sx, sy     PLOT 131, 107: DRAW sx, sy
 END WHILE END WHILE
 +</code>
  
-SCREEN$, ATTR, POINT+\\  
 +==== SCREEN$, ATTR, POINT ====
  
 Existen y se comportan como en Sinclair BASIC. Pero son funciones externas. Las funciones externas son aquellas que existen en un fichero .BAS aparte. En concreto, en el directorio library/ del compilador hay una biblioteca de funciones que irá creciendo con el tiempo. Para usarlas en tu programa, tienes que usar una directiva de preprocesador como la que sigue: Existen y se comportan como en Sinclair BASIC. Pero son funciones externas. Las funciones externas son aquellas que existen en un fichero .BAS aparte. En concreto, en el directorio library/ del compilador hay una biblioteca de funciones que irá creciendo con el tiempo. Para usarlas en tu programa, tienes que usar una directiva de preprocesador como la que sigue:
  
 +<code c>
 #include <screen.bas> #include <screen.bas>
 +</code>
  
 Los ficheros que las contienen son SCREEN.BAS, ATTR.BAS y POINT.BAS respectivamente. Si miras en ese directorio, verás que hay otras funciones. Enseguida veremos cómo definir nuestras propias funciones. Los ficheros que las contienen son SCREEN.BAS, ATTR.BAS y POINT.BAS respectivamente. Si miras en ese directorio, verás que hay otras funciones. Enseguida veremos cómo definir nuestras propias funciones.
  
-Sonido+\\  
 +===== Sonido =====
  
-BEEP+\\  
 +==== BEEP ====
  
 Por ahora, el único soporte de sonido es el comando BEEP, que usa la rutina de la ROM y es idéntico al de Sinclair BASIC. La única ventaja es que aquí, al disponer de mayor rapidez, podemos implementar algunos trucos de sonido. Existe un comando en la versión 128K del BASIC, PLAY que se espera poder implementar en futuras versiones. Por ahora, el único soporte de sonido es el comando BEEP, que usa la rutina de la ROM y es idéntico al de Sinclair BASIC. La única ventaja es que aquí, al disponer de mayor rapidez, podemos implementar algunos trucos de sonido. Existe un comando en la versión 128K del BASIC, PLAY que se espera poder implementar en futuras versiones.
Línea 394: Línea 442:
 El siguiente ejemplo está sacado del manual el ZX Spectrum (Frere Gustav del manual de ZX Spectrum 48K, capítulo 19): El siguiente ejemplo está sacado del manual el ZX Spectrum (Frere Gustav del manual de ZX Spectrum 48K, capítulo 19):
  
 +<code basic>
 10 PRINT "Frere Gustav" 10 PRINT "Frere Gustav"
 20 BEEP 1,0: BEEP 1,2: BEEP .5,3: BEEP.5,2: BEEP 1,0 20 BEEP 1,0: BEEP 1,2: BEEP .5,3: BEEP.5,2: BEEP 1,0
Línea 405: Línea 454:
 80 BEEP 1,0: BEEP 1,-5: BEEP 2,0 80 BEEP 1,0: BEEP 1,-5: BEEP 2,0
 90 BEEP 1,0: BEEP 1,-5: BEEP 2,0 90 BEEP 1,0: BEEP 1,-5: BEEP 2,0
 +</code>
  
-Funciones Matemáticas y Números aleatorios+\\  
 +===== Funciones Matemáticas y Números aleatorios =====
  
-STR$, VAL, PI, SIN, COS, TAN, ASN, ACS, ATN, EXP+\\  
 +==== STR$, VAL, PI, SIN, COS, TAN, ASN, ACS, ATN, EXP ====
  
 Estas funciones trabajan todas igual que en el BASIC original del ZX Spectrum a excepción de VAL. Como ya se dijo antes, VAL es un caso particular prácticamente imposible de compilar. Funciona como en la mayoría de los BASIC estándar: se convierte una cadena alfanumérica a punto flotante, pero esta cadena sólo puede contener un número (no una expresión). Si una cadena no se puede convertir, se devuelve 0. Luego VAL "x+x" devolverá 0 siempre, aunque la variable x esté definida. Estas funciones trabajan todas igual que en el BASIC original del ZX Spectrum a excepción de VAL. Como ya se dijo antes, VAL es un caso particular prácticamente imposible de compilar. Funciona como en la mayoría de los BASIC estándar: se convierte una cadena alfanumérica a punto flotante, pero esta cadena sólo puede contener un número (no una expresión). Si una cadena no se puede convertir, se devuelve 0. Luego VAL "x+x" devolverá 0 siempre, aunque la variable x esté definida.
  
-RANDOMIZE, RND+\\  
 +==== RANDOMIZE, RND ====
  
 Existen y se usan como en el ZX Spectrum con la diferencia de que la rutina de números aleatorios es más rápida ya que no usa la calculadora de punto flotante de la ROM sino registros y desplazamientos en ensamblador. Esta rutina es un generador lineal congruente (como la del ZX Spectrum) pero usa números de 32 bits, por lo que tiene un periodo de miles de millones (antes de que vuelva a repetirse la secuencia). Se ha sacado del libro Numerical Recipes in C (disponible gratuitamente en internet, aunque vale la pena comprarlo). Además, tiene una mejor dispersión. Prueba el siguiente programa en el ZX Spectrum, tanto en el BASIC de la ROM como en ZX BASIC (compilado): Existen y se usan como en el ZX Spectrum con la diferencia de que la rutina de números aleatorios es más rápida ya que no usa la calculadora de punto flotante de la ROM sino registros y desplazamientos en ensamblador. Esta rutina es un generador lineal congruente (como la del ZX Spectrum) pero usa números de 32 bits, por lo que tiene un periodo de miles de millones (antes de que vuelva a repetirse la secuencia). Se ha sacado del libro Numerical Recipes in C (disponible gratuitamente en internet, aunque vale la pena comprarlo). Además, tiene una mejor dispersión. Prueba el siguiente programa en el ZX Spectrum, tanto en el BASIC de la ROM como en ZX BASIC (compilado):
  
 +<code basic>
 10 LET x = INT(RND * 256): LET y = INT(RND * 175) 10 LET x = INT(RND * 256): LET y = INT(RND * 175)
 20 PLOT x, y 20 PLOT x, y
 30 GOTO 10 30 GOTO 10
 +</code>
  
 Aparte de la velocidad del programa compilado, notarás que en la versión del BASIC de Sinclair aparecen líneas diagonales. Ello indica que la aleatoriedad de los números no es tan alta en el BASIC de la ROM como se podría esperar. Aparte de la velocidad del programa compilado, notarás que en la versión del BASIC de Sinclair aparecen líneas diagonales. Ello indica que la aleatoriedad de los números no es tan alta en el BASIC de la ROM como se podría esperar.
  
-Entrada y salida+\\  
 +===== Entrada y salida =====
  
-INKEY$, IN, OUT+\\  
 +==== INKEY$, IN, OUT ====
  
 También están presentes y funcionan de forma idéntica a la de Sinclair BASIC... excepto por su velocidad, que es bastante mayor en el caso de IN y OUT. También están presentes y funcionan de forma idéntica a la de Sinclair BASIC... excepto por su velocidad, que es bastante mayor en el caso de IN y OUT.
  
-Caracteres gráficos, GDU y códigos de color+\\  
 +==== Caracteres gráficos, GDU y códigos de color ==== 
  
 Para poder introducir caracteres gráficos y códigos de color, se ha seguido el mismo convenio que BASIN (un entorno para programar y depurar programas BASIC del ZX Spectrum). Así, dentro de una cadena de caracteres, el caracter de barra invertida '\' tiene un significado especial. Así, para escribir un el GDU "A", escribiremos PRINT "\A". Si queremos escribir varios carácteres gráficos seguidos, "AB", escribiremos: PRINT "\A\B". Si queremos imprimir la barra invertida (este carácter existe en el Spectrum), usaremos doble barra: PRINT "\\" Para poder introducir caracteres gráficos y códigos de color, se ha seguido el mismo convenio que BASIN (un entorno para programar y depurar programas BASIC del ZX Spectrum). Así, dentro de una cadena de caracteres, el caracter de barra invertida '\' tiene un significado especial. Así, para escribir un el GDU "A", escribiremos PRINT "\A". Si queremos escribir varios carácteres gráficos seguidos, "AB", escribiremos: PRINT "\A\B". Si queremos imprimir la barra invertida (este carácter existe en el Spectrum), usaremos doble barra: PRINT "\\"
Línea 437: Línea 496:
  
 Los códigos son: Los códigos son:
-Código  Significa  Valores + 
-{iN}  Tinta  N = 0..7 +Código Significa Valores ^ 
-{pN}  Papel  N = 0..7 +{iN} Tinta N = 0..7 | 
-{fN}  Flash  N = 0..1 +{pN} Papel N = 0..7 | 
-{vN}  Inverse  N = 0..1 +{fN} Flash N = 0..1 | 
-{bN}  Brillo  N = 0..1+{vN} Inverse N = 0..1 | 
 +{bN} Brillo N = 0..1 |
  
 Además, puedes especificar cualquier caracter ASCII en decimal, usando \#xxx. Por ejemplo, el copyright puedes ponerlo como PRINT "\*" o bien como PRINT "\#127" Además, puedes especificar cualquier caracter ASCII en decimal, usando \#xxx. Por ejemplo, el copyright puedes ponerlo como PRINT "\*" o bien como PRINT "\#127"
  
-Funciones y Subrutinas+\\  
 +===== Funciones y Subrutinas =====
  
 Una de las grandes diferencias del ZX BASIC respecto a sinclair BASIC es la posibilidad de definir funciones y subrutinas, y que además estas contengan variables privadas. La única posibilidad que ofrecía el BASIC de Sinclair para definir funciones era con DEF FN, que aquí ya no existe. Para definir una función, usaremos la palabra reservada FUNCTION, así: Una de las grandes diferencias del ZX BASIC respecto a sinclair BASIC es la posibilidad de definir funciones y subrutinas, y que además estas contengan variables privadas. La única posibilidad que ofrecía el BASIC de Sinclair para definir funciones era con DEF FN, que aquí ya no existe. Para definir una función, usaremos la palabra reservada FUNCTION, así:
  
 +<code basic>
 FUNCTION mifuncion(x AS Integer, y AS Byte) AS Integer FUNCTION mifuncion(x AS Integer, y AS Byte) AS Integer
     REM Función que devuelve x + y     REM Función que devuelve x + y
     RETURN x + y     RETURN x + y
 END FUNCTION END FUNCTION
 +</code>
  
 Esta pequeña función recibe 2 parámetros, un entero de 16 bit en x y un byte en y, para devolver la suma de ambos (x + y). Esta pequeña función recibe 2 parámetros, un entero de 16 bit en x y un byte en y, para devolver la suma de ambos (x + y).
Línea 459: Línea 522:
 Para usar la función, sólo tienes que llamarla como como si fuera una función BASIC cualquiera: Para usar la función, sólo tienes que llamarla como como si fuera una función BASIC cualquiera:
  
 +<code basic>
 PRINT mifuncion(3, 5) : REM Imprime 8 PRINT mifuncion(3, 5) : REM Imprime 8
 +</code>
 Mira el ejemplo anterior del reloj, cómo define y usa una función. Mira el ejemplo anterior del reloj, cómo define y usa una función.
  
 También se puede llamar a una función sin más, como si fuera una subrutina: También se puede llamar a una función sin más, como si fuera una subrutina:
  
 +<code basic>
 mifuncion(3, 5) : REM suma 3 + 5 y luego los descarta y no hace nada con el resultado mifuncion(3, 5) : REM suma 3 + 5 y luego los descarta y no hace nada con el resultado
 +</code>
  
 Al igual que las funciones, también puedes definir subrutinas. Se definen usando la palabra reservada SUB. La sintaxis es muy similar a la anterior: Al igual que las funciones, también puedes definir subrutinas. Se definen usando la palabra reservada SUB. La sintaxis es muy similar a la anterior:
  
 +<code basic>
 SUB misubrutina(x AS Integer, y AS Byte) SUB misubrutina(x AS Integer, y AS Byte)
     REM Función que devuelve x + y     REM Función que devuelve x + y
     PRINT x + y     PRINT x + y
 END SUB END SUB
 +</code>
  
 Y la invocamos así Y la invocamos así
  
 +<code basic>
 misubrutina(3, 6) : REM imprime 9 misubrutina(3, 6) : REM imprime 9
 +</code>
  
 La diferencia principal es que SUB siempre tiene que invocarse. Nunca devuelve un resultado, al contrario que las funciones. De hecho, no puedes usar RETURN <valor> para salir de una subrutina, sino simplemente RETURN. Retornar de una subrutina o función y retornar de un GOSUB son cosas distintas. Al escribir simplemente RETURN, desde dentro de una función o subrutina siempre se supondrá que se retorna de la misma y no de un GOSUB. La diferencia principal es que SUB siempre tiene que invocarse. Nunca devuelve un resultado, al contrario que las funciones. De hecho, no puedes usar RETURN <valor> para salir de una subrutina, sino simplemente RETURN. Retornar de una subrutina o función y retornar de un GOSUB son cosas distintas. Al escribir simplemente RETURN, desde dentro de una función o subrutina siempre se supondrá que se retorna de la misma y no de un GOSUB.
Línea 484: Línea 554:
 Por último, las variables declaradas (DIM) dentro de una subrutina o función son privadas y no son accesibles desde fuera. Además, sólo usan memoria durante la ejecución de la función. Al salir de la misma, esa memoria se libera (se usa la pila de código máquina). Mira este ejemplo: Por último, las variables declaradas (DIM) dentro de una subrutina o función son privadas y no son accesibles desde fuera. Además, sólo usan memoria durante la ejecución de la función. Al salir de la misma, esa memoria se libera (se usa la pila de código máquina). Mira este ejemplo:
  
 +<code basic>
 SUB Ejemplo SUB Ejemplo
     DIM a as Integer: REM esta variable es privada     DIM a as Integer: REM esta variable es privada
Línea 495: Línea 566:
 Ejemplo() :  REM  llamamos a la subrutina ejemplo e imprime la variable privada Ejemplo() :  REM  llamamos a la subrutina ejemplo e imprime la variable privada
 PRINT "Despues de la subrutina, a ="; a PRINT "Despues de la subrutina, a ="; a
 +</code>
  
-ASM integrado+\\  
 +===== ASM integrado =====
  
 Es posible meter líneas de ASM directamente. Esto es una característica de muy bajo nivel, y se sale un poco de los propósitos de este artículo. Básicamente, se puede incrustar ASM directamente en el código así: Es posible meter líneas de ASM directamente. Esto es una característica de muy bajo nivel, y se sale un poco de los propósitos de este artículo. Básicamente, se puede incrustar ASM directamente en el código así:
  
 +<code basic>
 ASM ASM
 ... ...
 ... ...
 END ASM END ASM
 +</code>
  
 Como ZX Basic aspira a ser multiplataforma (reorientable), el código que hagas con ASM directo sólo compilará en la arquitectura que soporte ese ensamblador (en nuestro caso, Z80). Aquí tienes un ejemplo: Como ZX Basic aspira a ser multiplataforma (reorientable), el código que hagas con ASM directo sólo compilará en la arquitectura que soporte ese ensamblador (en nuestro caso, Z80). Aquí tienes un ejemplo:
  
 +<code asm>
 ASM ASM
 ld a, 0FFh ld a, 0FFh
 ld (16384), a ld (16384), a
 END ASM END ASM
 +</code>
  
 La idea de usar ASM directamente es en aquellos bucles que requieran mucha velocidad (por ejemplo, alguna subrutina de algún juego). Si miras la biblioteca de funciones, library/ verás que muchas funciones están definidas en ensamblador. La idea de usar ASM directamente es en aquellos bucles que requieran mucha velocidad (por ejemplo, alguna subrutina de algún juego). Si miras la biblioteca de funciones, library/ verás que muchas funciones están definidas en ensamblador.
  
-Lo que falta...+\\  
 +===== Lo que falta... =====
  
 No todo iba a ser perfecto. ZX Basic, por ser compilado, tiene cosas que le faltan. Por ejemplo, no existe INPUT. Te la tendrás que implementar (la mayoría de los juegos se implementan su propio INPUT; fíjate en El Hobbit). Ya hay una función INPUT implementada en en la biblioteca de funciones, por si te interesa esa. Prueba a usarla. No todo iba a ser perfecto. ZX Basic, por ser compilado, tiene cosas que le faltan. Por ejemplo, no existe INPUT. Te la tendrás que implementar (la mayoría de los juegos se implementan su propio INPUT; fíjate en El Hobbit). Ya hay una función INPUT implementada en en la biblioteca de funciones, por si te interesa esa. Prueba a usarla.
  
 +<code basic>
 REM Importamos la función input REM Importamos la función input
 #include <input.bas> #include <input.bas>
Línea 523: Línea 602:
 LET a$ = input(32): REM Input de una cadena de 32 caracteres como máximo LET a$ = input(32): REM Input de una cadena de 32 caracteres como máximo
 PRINT a$ PRINT a$
 +</code>
  
 Es muy difícil utilizar el INPUT de la ROM, que usa canales (que ya no existen aquí), y otras zonas del área de BASIC que pueden corromper el programa compilado o la pila de ejecución (aunque se supone que el CLEAR del cargador evitará esto). Tampoco tienen sentido READ, DATA y RESTORE. Son un desperdicio de memoria e imposibles de compilar tal y como las ofrece el BASIC de la ROM; aunque es probable que se implementen en un futuro con ciertas limitaciones. Es muy difícil utilizar el INPUT de la ROM, que usa canales (que ya no existen aquí), y otras zonas del área de BASIC que pueden corromper el programa compilado o la pila de ejecución (aunque se supone que el CLEAR del cargador evitará esto). Tampoco tienen sentido READ, DATA y RESTORE. Son un desperdicio de memoria e imposibles de compilar tal y como las ofrece el BASIC de la ROM; aunque es probable que se implementen en un futuro con ciertas limitaciones.
  
-Ejemplos+\\  
 +===== Ejemplos ===== 
  
 Este artículo está quedando muy extenso, así que lo cortaré aquí (si fuera para MicroHobby, daría para varios números). Quedan algunas cosas por explicar (como el alias de variables o las instrucciones de manejo de bits: rotaciones, etc.) En cuaquier caso, en el directorio examples tienes varios ejemplos que puedes compilar para aprender cómo funcionan. El ejemplo de la Bandera Inglesa está sacado del manual del ZX Spectrum y funciona tal cual está, sin ninguna modificación. Otros ejemplos, como el Snake (cortesía de Federico J. Alvarez Valero, año 2003) se han retocado para declarar explícitamente el tipo de dato de algunas variables (recordemos que trabajar con enteros es más rápido que punto flotante), y terninar los IF con sus END IF correspondientes. Este artículo está quedando muy extenso, así que lo cortaré aquí (si fuera para MicroHobby, daría para varios números). Quedan algunas cosas por explicar (como el alias de variables o las instrucciones de manejo de bits: rotaciones, etc.) En cuaquier caso, en el directorio examples tienes varios ejemplos que puedes compilar para aprender cómo funcionan. El ejemplo de la Bandera Inglesa está sacado del manual del ZX Spectrum y funciona tal cual está, sin ninguna modificación. Otros ejemplos, como el Snake (cortesía de Federico J. Alvarez Valero, año 2003) se han retocado para declarar explícitamente el tipo de dato de algunas variables (recordemos que trabajar con enteros es más rápido que punto flotante), y terninar los IF con sus END IF correspondientes.
  
-Y pasaron los años...+\\  
 +===== Y pasaron los años... =====
  
 ¿20 años? ¡Glup! Al menos se cumplió una parte del sueño (la otra no os la digo...). ¿Fue demasiado tarde? No lo creo. Al menos no en parte. ¿20 años? ¡Glup! Al menos se cumplió una parte del sueño (la otra no os la digo...). ¿Fue demasiado tarde? No lo creo. Al menos no en parte.
Línea 540: Línea 623:
 Suena un poco nostálgico, lo sé, pero creo que hemos tenido la suerte de haberlo podido vivir. Suena un poco nostálgico, lo sé, pero creo que hemos tenido la suerte de haberlo podido vivir.
  
 +\\ 
 +===== Enlaces =====
  
 +  * {{:articulos:zxbexamples.zip|Ejemplos en BASIC}} listos para compilar con ZXBasic.\\ (compilar con ''zxb -a -T -b programa.bas'').
  
 \\  \\ 
  • articulos/zxbasic_suenyo_hecho_realidad.1237887780.txt.gz
  • Última modificación: 24-03-2009 09:43
  • por sromero