ruby2basic 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +41 -4
- data/docs/1-pcbasic.md +45 -0
- data/docs/2-freebasic.md +46 -0
- data/docs/5-zxboriel.md +3 -91
- data/docs/keywords.md +58 -0
- data/lib/ruby2basic/version.rb +1 -1
- data/lib/ruby2basic/zxspectrum/transpiler.rb +157 -0
- data/lib/ruby2basic.rb +1 -2
- metadata +4 -2
- data/lib/ruby2basic/zxspectrum.rb +0 -146
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8036de50cb43b2151d011b88570f88351dd0b23fd2d939a799896d35ef23cf03
|
|
4
|
+
data.tar.gz: 34ce4e930b6c931387e1c48f7a27a871bf20f1d7063e228ff93085c969bb7f10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 278917f446cfeb862b08a6673bb9c3540f8b56365b2afec893c66213a81769007402d2a67b79da4c5be07eb95fc24d2ac84a856d27339651ab4ca4e9d45a45eb
|
|
7
|
+
data.tar.gz: 7ac28e6042fb3a5e480cb753203ff2a5f5850cf08fb44a4953cd50a52e418134952dd8ed0fcb242ad18cd4fc137f710f9892d507c2b8cb7d2a1f79e922aac99e
|
data/README.md
CHANGED
|
@@ -12,6 +12,7 @@ Este es un proyecto que hago sólo por diversión para generar programas BASIC p
|
|
|
12
12
|
## Instalación
|
|
13
13
|
|
|
14
14
|
* Instalar Ruby.
|
|
15
|
+
* Se requiren los siguientes paquetes: `gcc`, `make` y `ruby-dev` para compilar el parser.
|
|
15
16
|
* Instalar la gema `gem install ruby2basic`
|
|
16
17
|
|
|
17
18
|
## Usage
|
|
@@ -42,13 +43,49 @@ $ ./ruby2basic examples/03-bucle.rb
|
|
|
42
43
|
|
|
43
44
|
> Más [ejemplos](./examples/)
|
|
44
45
|
|
|
46
|
+
También se puede invocar a modo de biblioteca.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require "ruby2basic"
|
|
50
|
+
|
|
51
|
+
source = <<-CODE
|
|
52
|
+
puts "Hello, World!"
|
|
53
|
+
CODE
|
|
54
|
+
|
|
55
|
+
r2b = Ruby2Basic::ZXSpectrum::Transpiler.new
|
|
56
|
+
puts r2b.call(source)
|
|
57
|
+
|
|
58
|
+
#=> 10 PRINT "Hello, World!"
|
|
59
|
+
#=> 20 STOP
|
|
60
|
+
```
|
|
61
|
+
|
|
45
62
|
## Features
|
|
46
63
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*
|
|
50
|
-
*
|
|
64
|
+
Ruby:
|
|
65
|
+
|
|
66
|
+
* Lińeas de comentarios.
|
|
67
|
+
* Estructura secuencial.
|
|
68
|
+
* Variables con tipo `String` e `Integer`.
|
|
69
|
+
* Método `puts`. Se aceptan los String embebidos.
|
|
70
|
+
* Estructura repetitiva.
|
|
51
71
|
* Método `times` a `FOR`.
|
|
72
|
+
* Estructura condicional.
|
|
73
|
+
|
|
74
|
+
BASIC ZX Spectrum:
|
|
75
|
+
|
|
76
|
+
* REM: Líneas de comentarios.
|
|
77
|
+
* LET: Asigna valores a variables.
|
|
78
|
+
* PRINT: Muestra texto o números en pantalla.
|
|
79
|
+
* STOP: Detiene la ejecución (vital para separar el cuerpo principal de las subrutinas).
|
|
80
|
+
|
|
81
|
+
* FOR: Inicia un bucle con una variable de control.
|
|
82
|
+
* TO: Define el límite superior del bucle FOR.
|
|
83
|
+
* NEXT: Cierra el bucle FOR.
|
|
84
|
+
* IF: Evalúa una condición lógica.
|
|
85
|
+
* THEN: Indica la acción a seguir si el IF es verdadero.
|
|
86
|
+
* GOTO: Salto incondicional a una línea específica.
|
|
87
|
+
|
|
88
|
+
* STR$: Convierte un número en una cadena de texto.
|
|
52
89
|
|
|
53
90
|
## Contributing
|
|
54
91
|
|
data/docs/1-pcbasic.md
CHANGED
|
@@ -67,3 +67,48 @@ Ejemplo:
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
> **NOTA**: Para acceder a los archivos del sistema Linux, ejecutar como: `pcbasic --mount=C:/home/username/mis_programas`
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
# TODO
|
|
73
|
+
|
|
74
|
+
3. Ejemplo comparativo
|
|
75
|
+
|
|
76
|
+
Estilo "Retro" (Gritando):
|
|
77
|
+
Fragmento de código
|
|
78
|
+
|
|
79
|
+
10 BORDER 1: PAPER 7: INK 0: CLS
|
|
80
|
+
20 LET X = 10
|
|
81
|
+
30 PRINT AT 10, X; "HOLA"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
Notarás que la mayor diferencia es cómo manejamos la recursividad, ya que el BASIC antiguo no la soportaba de forma nativa.
|
|
85
|
+
1. Hola Mundo
|
|
86
|
+
En PC-BASIC (Estilo GW-BASIC)
|
|
87
|
+
Basic
|
|
88
|
+
|
|
89
|
+
10 CLS
|
|
90
|
+
20 PRINT "HOLA MUNDO DESDE PC-BASIC"
|
|
91
|
+
30 END
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
2. Factorial Iterativo (Bucle FOR)
|
|
95
|
+
En PC-BASIC
|
|
96
|
+
|
|
97
|
+
Aquí usamos una variable global F para el resultado y N para el número.
|
|
98
|
+
Basic
|
|
99
|
+
|
|
100
|
+
10 CLS
|
|
101
|
+
20 INPUT "Introduce un numero: ", N
|
|
102
|
+
30 F = 1
|
|
103
|
+
40 FOR I = 1 TO N
|
|
104
|
+
50 F = F * I
|
|
105
|
+
60 NEXT I
|
|
106
|
+
70 PRINT "El factorial es:"; F
|
|
107
|
+
80 END
|
|
108
|
+
|
|
109
|
+
Aquí es donde está el gran cambio tecnológico.
|
|
110
|
+
En PC-BASIC (Simulación con GOSUB)
|
|
111
|
+
|
|
112
|
+
PC-BASIC no permite que una función se llame a sí misma. Para simularlo, los programadores antiguos usaban una "pila" manual o, más comúnmente, simplemente no lo hacían porque el GOSUB no crea variables nuevas cada vez.
|
|
113
|
+
|
|
114
|
+
Nota: En PC-BASIC es mejor quedarse con la versión iterativa. Intentar hacer recursividad real requiere trucos muy complejos con POKE y PEEK.
|
data/docs/2-freebasic.md
CHANGED
|
@@ -147,3 +147,49 @@ Next
|
|
|
147
147
|
ErrorFatal:
|
|
148
148
|
Print "Algo salio muy mal. Cerrando..."
|
|
149
149
|
```
|
|
150
|
+
|
|
151
|
+
# TODO
|
|
152
|
+
|
|
153
|
+
En FreeBASIC
|
|
154
|
+
|
|
155
|
+
Usamos una función limpia y tipos de datos protegidos para evitar que el número se "desborde".
|
|
156
|
+
Fragmento de código
|
|
157
|
+
|
|
158
|
+
Function FactorialIter(n As Integer) As ULongInt
|
|
159
|
+
Dim res As ULongInt = 1
|
|
160
|
+
For i As Integer = 1 To n
|
|
161
|
+
res *= i
|
|
162
|
+
Next
|
|
163
|
+
Return res
|
|
164
|
+
End Function
|
|
165
|
+
|
|
166
|
+
Print "Factorial de 10 (Iterativo):"; FactorialIter(10)
|
|
167
|
+
|
|
168
|
+
3. Factorial Recursivo
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
En FreeBASIC (Estilo Moderno)
|
|
172
|
+
Fragmento de código
|
|
173
|
+
|
|
174
|
+
Cls
|
|
175
|
+
Print "Hola Mundo desde FreeBASIC"
|
|
176
|
+
Sleep ' Espera a que pulses una tecla
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
En FreeBASIC (Recursividad Real)
|
|
181
|
+
|
|
182
|
+
FreeBASIC maneja la memoria de forma automática, permitiendo que la función se llame a sí misma elegantemente.
|
|
183
|
+
Fragmento de código
|
|
184
|
+
|
|
185
|
+
Function FactorialRec(n As Integer) As ULongInt
|
|
186
|
+
If n <= 1 Then
|
|
187
|
+
Return 1
|
|
188
|
+
Else
|
|
189
|
+
Return n * FactorialRec(n - 1)
|
|
190
|
+
End If
|
|
191
|
+
End Function
|
|
192
|
+
|
|
193
|
+
Print "Factorial de 5 (Recursivo):"; FactorialRec(5)
|
|
194
|
+
Sleep
|
|
195
|
+
|
data/docs/5-zxboriel.md
CHANGED
|
@@ -282,14 +282,6 @@ La mayoría de los programadores que usan Boriel en openSUSE prefieren las minú
|
|
|
282
282
|
Diferenciación: Muchos usan mayúsculas para sus propias variables y minúsculas para los comandos del sistema (o al revés) para distinguir rápido qué es qué.
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
-
3. Ejemplo comparativo
|
|
286
|
-
|
|
287
|
-
Estilo "Retro" (Gritando):
|
|
288
|
-
Fragmento de código
|
|
289
|
-
|
|
290
|
-
10 BORDER 1: PAPER 7: INK 0: CLS
|
|
291
|
-
20 LET X = 10
|
|
292
|
-
30 PRINT AT 10, X; "HOLA"
|
|
293
285
|
|
|
294
286
|
Estilo "Boriel" (Moderno y limpio):
|
|
295
287
|
Fragmento de código
|
|
@@ -317,6 +309,8 @@ Zxboriel. Hola mundo y factorial iterativo y factorial recursivo
|
|
|
317
309
|
|
|
318
310
|
¡Excelente! Vamos a ver estos tres ejemplos en ZXBasic (Boriel). Notarás que el factorial recursivo es donde Boriel realmente brilla frente al BASIC antiguo, ya que el Spectrum original no permitía recursividad real de forma sencilla.
|
|
319
311
|
|
|
312
|
+
---
|
|
313
|
+
|
|
320
314
|
Aquí tienes el código en formato moderno (minúsculas y sin números de línea).
|
|
321
315
|
1. Hola Mundo
|
|
322
316
|
|
|
@@ -356,6 +350,7 @@ dim n as ubyte = 5
|
|
|
356
350
|
print "Factorial Iterativo de "; n; " es:"
|
|
357
351
|
print factorial_iter(n)
|
|
358
352
|
|
|
353
|
+
---
|
|
359
354
|
3. Factorial Recursivo
|
|
360
355
|
|
|
361
356
|
Boriel permite que una función se llame a sí misma. Nota importante: La recursividad en el Spectrum es lenta y puede agotar la memoria si el número es muy grande, pero para aprender la lógica es perfecta.
|
|
@@ -381,86 +376,3 @@ Cosas clave que debes saber:
|
|
|
381
376
|
ubyte para el contador: Como el Spectrum tiene poca memoria y un procesador de 8 bits, usar ubyte (0-255) para la variable n e i hace que el programa sea mucho más rápido que si usaras integer.
|
|
382
377
|
|
|
383
378
|
Indentación: Fíjate que he usado espacios para "meter hacia adentro" el código de los if y for. A Boriel no le importa, pero a tus ojos sí (hace el código mucho más profesional).
|
|
384
|
-
|
|
385
|
-
¿Cómo probarlo todo junto?
|
|
386
|
-
|
|
387
|
-
Puedes copiar los tres bloques en un solo archivo .bas, añadir un pause 0 al final para que la pantalla no se cierre, y usar el script que creamos antes:
|
|
388
|
-
./build.sh programa.bas
|
|
389
|
-
|
|
390
|
-
¿Te gustaría que te explique cómo optimizar aún más el código usando fixed (punto fijo) o prefieres intentar hacer un programa que pida el número por teclado con input?
|
|
391
|
-
|
|
392
|
-
Haz los ejemplos con pcbasic y freebasic
|
|
393
|
-
|
|
394
|
-
Para cerrar el círculo, aquí tienes los tres ejemplos comparados en PC-BASIC (el estilo de 1985 con números de línea) y FreeBASIC (el estilo moderno de 32/64 bits).
|
|
395
|
-
|
|
396
|
-
Notarás que la mayor diferencia es cómo manejamos la recursividad, ya que el BASIC antiguo no la soportaba de forma nativa.
|
|
397
|
-
1. Hola Mundo
|
|
398
|
-
En PC-BASIC (Estilo GW-BASIC)
|
|
399
|
-
Basic
|
|
400
|
-
|
|
401
|
-
10 CLS
|
|
402
|
-
20 PRINT "HOLA MUNDO DESDE PC-BASIC"
|
|
403
|
-
30 END
|
|
404
|
-
|
|
405
|
-
En FreeBASIC (Estilo Moderno)
|
|
406
|
-
Fragmento de código
|
|
407
|
-
|
|
408
|
-
Cls
|
|
409
|
-
Print "Hola Mundo desde FreeBASIC"
|
|
410
|
-
Sleep ' Espera a que pulses una tecla
|
|
411
|
-
|
|
412
|
-
2. Factorial Iterativo (Bucle FOR)
|
|
413
|
-
En PC-BASIC
|
|
414
|
-
|
|
415
|
-
Aquí usamos una variable global F para el resultado y N para el número.
|
|
416
|
-
Basic
|
|
417
|
-
|
|
418
|
-
10 CLS
|
|
419
|
-
20 INPUT "Introduce un numero: ", N
|
|
420
|
-
30 F = 1
|
|
421
|
-
40 FOR I = 1 TO N
|
|
422
|
-
50 F = F * I
|
|
423
|
-
60 NEXT I
|
|
424
|
-
70 PRINT "El factorial es:"; F
|
|
425
|
-
80 END
|
|
426
|
-
|
|
427
|
-
En FreeBASIC
|
|
428
|
-
|
|
429
|
-
Usamos una función limpia y tipos de datos protegidos para evitar que el número se "desborde".
|
|
430
|
-
Fragmento de código
|
|
431
|
-
|
|
432
|
-
Function FactorialIter(n As Integer) As ULongInt
|
|
433
|
-
Dim res As ULongInt = 1
|
|
434
|
-
For i As Integer = 1 To n
|
|
435
|
-
res *= i
|
|
436
|
-
Next
|
|
437
|
-
Return res
|
|
438
|
-
End Function
|
|
439
|
-
|
|
440
|
-
Print "Factorial de 10 (Iterativo):"; FactorialIter(10)
|
|
441
|
-
|
|
442
|
-
3. Factorial Recursivo
|
|
443
|
-
|
|
444
|
-
Aquí es donde está el gran cambio tecnológico.
|
|
445
|
-
En PC-BASIC (Simulación con GOSUB)
|
|
446
|
-
|
|
447
|
-
PC-BASIC no permite que una función se llame a sí misma. Para simularlo, los programadores antiguos usaban una "pila" manual o, más comúnmente, simplemente no lo hacían porque el GOSUB no crea variables nuevas cada vez.
|
|
448
|
-
|
|
449
|
-
Nota: En PC-BASIC es mejor quedarse con la versión iterativa. Intentar hacer recursividad real requiere trucos muy complejos con POKE y PEEK.
|
|
450
|
-
|
|
451
|
-
En FreeBASIC (Recursividad Real)
|
|
452
|
-
|
|
453
|
-
FreeBASIC maneja la memoria de forma automática, permitiendo que la función se llame a sí misma elegantemente.
|
|
454
|
-
Fragmento de código
|
|
455
|
-
|
|
456
|
-
Function FactorialRec(n As Integer) As ULongInt
|
|
457
|
-
If n <= 1 Then
|
|
458
|
-
Return 1
|
|
459
|
-
Else
|
|
460
|
-
Return n * FactorialRec(n - 1)
|
|
461
|
-
End If
|
|
462
|
-
End Function
|
|
463
|
-
|
|
464
|
-
Print "Factorial de 5 (Recursivo):"; FactorialRec(5)
|
|
465
|
-
Sleep
|
|
466
|
-
|
data/docs/keywords.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
# Listado de los Keywords de BASIC ZX Spectrum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Comandos de Control y Programa
|
|
6
|
+
|
|
7
|
+
* LET: Asigna valores a variables.
|
|
8
|
+
* PRINT: Muestra texto o números en pantalla.
|
|
9
|
+
* INPUT: Detiene el programa para pedir datos al usuario.
|
|
10
|
+
* LIST: Muestra el listado del código en memoria.
|
|
11
|
+
* RUN: Inicia la ejecución del programa.
|
|
12
|
+
* STOP: Detiene la ejecución (vital para separar el cuerpo principal de las subrutinas).
|
|
13
|
+
* NEW: Borra el programa actual de la memoria.
|
|
14
|
+
* REM: Se utiliza para insertar comentarios que el ordenador ignora.
|
|
15
|
+
|
|
16
|
+
Estructuras de Control (Bucles y Saltos)
|
|
17
|
+
|
|
18
|
+
* FOR: Inicia un bucle con una variable de control.
|
|
19
|
+
* TO: Define el límite superior del bucle FOR.
|
|
20
|
+
* STEP: Define el incremento del bucle (por defecto es 1).
|
|
21
|
+
* NEXT: Cierra el bucle FOR.
|
|
22
|
+
* IF: Evalúa una condición lógica.
|
|
23
|
+
* THEN: Indica la acción a seguir si el IF es verdadero.
|
|
24
|
+
* GOTO: Salto incondicional a una línea específica.
|
|
25
|
+
* GOSUB: Salto a una subrutina.
|
|
26
|
+
* RETURN: Regresa de una subrutina al punto posterior del GOSUB.
|
|
27
|
+
|
|
28
|
+
Comandos Gráficos y Sonido
|
|
29
|
+
|
|
30
|
+
* PLOT: Dibuja un punto en coordenadas (x, y).
|
|
31
|
+
* DRAW: Dibuja una línea desde la última posición.
|
|
32
|
+
* CIRCLE: Dibuja un círculo definido por coordenadas y radio.
|
|
33
|
+
* BEEP: Produce un sonido especificando duración y tono.
|
|
34
|
+
* INK: Cambia el color del trazo (0-7).
|
|
35
|
+
* PAPER: Cambia el color del fondo.
|
|
36
|
+
* BORDER: Cambia el color del borde de la pantalla.
|
|
37
|
+
* CLS: Limpia la pantalla (Clear Screen).
|
|
38
|
+
|
|
39
|
+
Funciones de Sistema y Manejo de Datos
|
|
40
|
+
|
|
41
|
+
* DIM: Reserva espacio en memoria para arrays (arreglos).
|
|
42
|
+
* DATA: Almacena datos dentro del programa.
|
|
43
|
+
* READ: Lee los valores almacenados en las líneas DATA.
|
|
44
|
+
* RESTORE: Reinicia el puntero de lectura de los DATA.
|
|
45
|
+
* LOAD / SAVE: Carga o guarda programas en cinta (o disco).
|
|
46
|
+
* RANDOMIZE: Inicializa el generador de números aleatorios.
|
|
47
|
+
* POKE: Escribe un valor directamente en una dirección de memoria.
|
|
48
|
+
* PEEK: Lee el valor de una dirección de memoria.
|
|
49
|
+
|
|
50
|
+
Funciones Matemáticas y de Cadena
|
|
51
|
+
|
|
52
|
+
* INT: Devuelve la parte entera de un número.
|
|
53
|
+
* RND: Genera un número aleatorio.
|
|
54
|
+
* STR$: Convierte un número en una cadena de texto.
|
|
55
|
+
* VAL: Convierte una cadena de texto en un valor numérico.
|
|
56
|
+
* LEN: Devuelve la longitud de una cadena.
|
|
57
|
+
* SIN / COS / TAN: Funciones trigonométricas.
|
|
58
|
+
|
data/lib/ruby2basic/version.rb
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#frozen_string_literal: true
|
|
2
|
+
require "prism"
|
|
3
|
+
|
|
4
|
+
module Ruby2Basic
|
|
5
|
+
module ZXSpectrum
|
|
6
|
+
class Transpiler
|
|
7
|
+
attr_accessor :oneline
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@oneline = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def reset
|
|
14
|
+
@lines = []
|
|
15
|
+
@line_num = 10
|
|
16
|
+
@subs = []
|
|
17
|
+
@labels = {}
|
|
18
|
+
@arrays = []
|
|
19
|
+
@string_vars = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(code)
|
|
23
|
+
reset
|
|
24
|
+
@result = Prism.parse(code)
|
|
25
|
+
@comments = @result.comments.map do |c|
|
|
26
|
+
{ line: c.location.start_line, text: c.location.slice.gsub(/^#\s*/, "") }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return "Error de sintaxis" unless @result.success?
|
|
30
|
+
process_nodes(@result.value.statements.body)
|
|
31
|
+
|
|
32
|
+
output = []
|
|
33
|
+
@arrays.each { |dim| output << "#{@line_num} #{dim}"; @line_num += 10 }
|
|
34
|
+
output += @lines
|
|
35
|
+
|
|
36
|
+
unless @oneline
|
|
37
|
+
output << "#{@line_num} STOP"
|
|
38
|
+
@line_num += 10
|
|
39
|
+
output += @subs
|
|
40
|
+
end
|
|
41
|
+
output.join("\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# MODIFICACIÓN: Separamos el comando del contenido para no aplicar upcase a todo
|
|
47
|
+
def add_basic(command, content = "", to_subs: false)
|
|
48
|
+
line = "#{@line_num} #{command.upcase} #{content}".strip
|
|
49
|
+
to_subs ? @subs << line : @lines << line
|
|
50
|
+
@line_num += 10
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def process_nodes(nodes, to_subs: false)
|
|
54
|
+
nodes.each do |node|
|
|
55
|
+
check_for_comments(node.location.start_line, to_subs)
|
|
56
|
+
process_node(node, to_subs: to_subs)
|
|
57
|
+
end
|
|
58
|
+
check_for_comments(1000000, to_subs)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_for_comments(current_node_line, to_subs)
|
|
62
|
+
while @comments.any? && @comments.first[:line] <= current_node_line
|
|
63
|
+
comment = @comments.shift
|
|
64
|
+
add_basic("REM", comment[:text], to_subs: to_subs)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def process_node(node, to_subs: false)
|
|
69
|
+
case node
|
|
70
|
+
when Prism::LocalVariableWriteNode
|
|
71
|
+
name = node.name.to_s
|
|
72
|
+
val = resolve(node.value)
|
|
73
|
+
|
|
74
|
+
if node.value.is_a?(Prism::StringNode) || node.value.is_a?(Prism::InterpolatedStringNode)
|
|
75
|
+
@string_vars << name unless @string_vars.include?(name)
|
|
76
|
+
add_basic("LET", "#{name}$ = #{val}", to_subs: to_subs)
|
|
77
|
+
else
|
|
78
|
+
add_basic("LET", "#{name} = #{val}", to_subs: to_subs)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
when Prism::CallNode
|
|
82
|
+
case node.name.to_s
|
|
83
|
+
when "puts"
|
|
84
|
+
arg = node.arguments&.arguments&.first
|
|
85
|
+
add_basic("PRINT", resolve(arg), to_subs: to_subs)
|
|
86
|
+
when "times"
|
|
87
|
+
limit = resolve(node.receiver).to_i - 1
|
|
88
|
+
var = "i" # Variable de control en minúscula
|
|
89
|
+
add_basic("FOR", "#{var} = 0 TO #{limit}", to_subs: to_subs)
|
|
90
|
+
process_nodes(node.block.body.body, to_subs: to_subs) if node.block&.body
|
|
91
|
+
add_basic("NEXT", var, to_subs: to_subs)
|
|
92
|
+
else
|
|
93
|
+
if @labels[node.name.to_s]
|
|
94
|
+
add_basic("GOSUB", @labels[node.name.to_s].to_s, to_subs: to_subs)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
when Prism::IfNode
|
|
99
|
+
cond = resolve(node.predicate)
|
|
100
|
+
stmt_count = node.statements&.body&.size || 0
|
|
101
|
+
jump_to = @line_num + (stmt_count * 10) + 10
|
|
102
|
+
add_basic("IF NOT", "#{cond} THEN GOTO #{jump_to}", to_subs: to_subs)
|
|
103
|
+
process_nodes(node.statements.body, to_subs: to_subs) if node.statements
|
|
104
|
+
|
|
105
|
+
when Prism::DefNode
|
|
106
|
+
method_name = node.name.to_s
|
|
107
|
+
@labels[method_name] = @line_num
|
|
108
|
+
add_basic("REM", "SUB: #{method_name}", to_subs: true)
|
|
109
|
+
process_nodes(node.body.body, to_subs: true) if node.body
|
|
110
|
+
add_basic("RETURN", "", to_subs: true)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def resolve(node)
|
|
115
|
+
return "0" if node.nil?
|
|
116
|
+
case node
|
|
117
|
+
when Prism::IntegerNode, Prism::FloatNode then node.slice
|
|
118
|
+
when Prism::StringNode then "\"#{node.content}\""
|
|
119
|
+
when Prism::LocalVariableReadNode
|
|
120
|
+
name = node.name.to_s
|
|
121
|
+
@string_vars.include?(name) ? "#{name}$" : name
|
|
122
|
+
|
|
123
|
+
when Prism::InterpolatedStringNode
|
|
124
|
+
parts = node.parts.map do |part|
|
|
125
|
+
if part.is_a?(Prism::StringNode)
|
|
126
|
+
"\"#{part.content}\""
|
|
127
|
+
elsif part.is_a?(Prism::EmbeddedStatementsNode)
|
|
128
|
+
inner = part.statements.body.first
|
|
129
|
+
res = resolve(inner)
|
|
130
|
+
var_name = inner.respond_to?(:name) ? inner.name.to_s : nil
|
|
131
|
+
if !inner.is_a?(Prism::StringNode) && !@string_vars.include?(var_name)
|
|
132
|
+
"STR$(#{res})"
|
|
133
|
+
else
|
|
134
|
+
res
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
resolve(part)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
parts.join(" + ")
|
|
141
|
+
|
|
142
|
+
when Prism::CallNode
|
|
143
|
+
if node.receiver && node.arguments && node.arguments.arguments.size == 1
|
|
144
|
+
left = resolve(node.receiver)
|
|
145
|
+
right = resolve(node.arguments.arguments.first)
|
|
146
|
+
op = node.name.to_s == "==" ? "=" : node.name.to_s
|
|
147
|
+
"#{left} #{op} #{right}"
|
|
148
|
+
else
|
|
149
|
+
node.name.to_s
|
|
150
|
+
end
|
|
151
|
+
else "0"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
data/lib/ruby2basic.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby2basic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Vargas Ruiz
|
|
@@ -38,6 +38,7 @@ extra_rdoc_files:
|
|
|
38
38
|
- docs/4-bwbasic.md
|
|
39
39
|
- docs/5-zxboriel.md
|
|
40
40
|
- docs/README.md
|
|
41
|
+
- docs/keywords.md
|
|
41
42
|
files:
|
|
42
43
|
- LICENSE
|
|
43
44
|
- README.md
|
|
@@ -48,9 +49,10 @@ files:
|
|
|
48
49
|
- docs/4-bwbasic.md
|
|
49
50
|
- docs/5-zxboriel.md
|
|
50
51
|
- docs/README.md
|
|
52
|
+
- docs/keywords.md
|
|
51
53
|
- lib/ruby2basic.rb
|
|
52
54
|
- lib/ruby2basic/version.rb
|
|
53
|
-
- lib/ruby2basic/zxspectrum.rb
|
|
55
|
+
- lib/ruby2basic/zxspectrum/transpiler.rb
|
|
54
56
|
homepage: https://github.com/dvarrui/ruby2basic
|
|
55
57
|
licenses:
|
|
56
58
|
- MPL-2.0
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#frozen_string_literal: true
|
|
2
|
-
require "prism"
|
|
3
|
-
|
|
4
|
-
module Ruby2Basic
|
|
5
|
-
class ZXSpectrum
|
|
6
|
-
def initialize(code)
|
|
7
|
-
@result = Prism.parse(code)
|
|
8
|
-
@comments = @result.comments.map do |c|
|
|
9
|
-
{ line: c.location.start_line, text: c.location.slice.gsub(/^#\s*/, "") }
|
|
10
|
-
end
|
|
11
|
-
@lines = []
|
|
12
|
-
@line_num = 10
|
|
13
|
-
@subs = []
|
|
14
|
-
@labels = {}
|
|
15
|
-
@arrays = []
|
|
16
|
-
@string_vars = []
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def transpile
|
|
20
|
-
return "Error de sintaxis" unless @result.success?
|
|
21
|
-
process_nodes(@result.value.statements.body)
|
|
22
|
-
|
|
23
|
-
output = []
|
|
24
|
-
@arrays.each { |dim| output << "#{@line_num} #{dim}"; @line_num += 10 }
|
|
25
|
-
output += @lines
|
|
26
|
-
output << "#{@line_num} STOP"
|
|
27
|
-
@line_num += 10
|
|
28
|
-
output += @subs
|
|
29
|
-
output.join("\n")
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
# MODIFICACIÓN: Separamos el comando del contenido para no aplicar upcase a todo
|
|
35
|
-
def add_basic(command, content = "", to_subs: false)
|
|
36
|
-
line = "#{@line_num} #{command.upcase} #{content}".strip
|
|
37
|
-
to_subs ? @subs << line : @lines << line
|
|
38
|
-
@line_num += 10
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def process_nodes(nodes, to_subs: false)
|
|
42
|
-
nodes.each do |node|
|
|
43
|
-
check_for_comments(node.location.start_line, to_subs)
|
|
44
|
-
process_node(node, to_subs: to_subs)
|
|
45
|
-
end
|
|
46
|
-
check_for_comments(1000000, to_subs)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def check_for_comments(current_node_line, to_subs)
|
|
50
|
-
while @comments.any? && @comments.first[:line] <= current_node_line
|
|
51
|
-
comment = @comments.shift
|
|
52
|
-
# REM se queda en mayúsculas, pero el texto del comentario se respeta
|
|
53
|
-
add_basic("REM", comment[:text], to_subs: to_subs)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def process_node(node, to_subs: false)
|
|
58
|
-
case node
|
|
59
|
-
when Prism::LocalVariableWriteNode
|
|
60
|
-
name = node.name.to_s
|
|
61
|
-
val = resolve(node.value)
|
|
62
|
-
|
|
63
|
-
if node.value.is_a?(Prism::StringNode) || node.value.is_a?(Prism::InterpolatedStringNode)
|
|
64
|
-
@string_vars << name unless @string_vars.include?(name)
|
|
65
|
-
add_basic("LET", "#{name}$ = #{val}", to_subs: to_subs)
|
|
66
|
-
else
|
|
67
|
-
add_basic("LET", "#{name} = #{val}", to_subs: to_subs)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
when Prism::CallNode
|
|
71
|
-
case node.name.to_s
|
|
72
|
-
when "puts"
|
|
73
|
-
arg = node.arguments&.arguments&.first
|
|
74
|
-
add_basic("PRINT", resolve(arg), to_subs: to_subs)
|
|
75
|
-
when "times"
|
|
76
|
-
limit = resolve(node.receiver).to_i - 1
|
|
77
|
-
var = "i" # Variable de control en minúscula
|
|
78
|
-
add_basic("FOR", "#{var} = 0 TO #{limit}", to_subs: to_subs)
|
|
79
|
-
process_nodes(node.block.body.body, to_subs: to_subs) if node.block&.body
|
|
80
|
-
add_basic("NEXT", var, to_subs: to_subs)
|
|
81
|
-
else
|
|
82
|
-
if @labels[node.name.to_s]
|
|
83
|
-
add_basic("GOSUB", @labels[node.name.to_s].to_s, to_subs: to_subs)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
when Prism::IfNode
|
|
88
|
-
cond = resolve(node.predicate)
|
|
89
|
-
stmt_count = node.statements&.body&.size || 0
|
|
90
|
-
jump_to = @line_num + (stmt_count * 10) + 10
|
|
91
|
-
add_basic("IF NOT", "#{cond} THEN GOTO #{jump_to}", to_subs: to_subs)
|
|
92
|
-
process_nodes(node.statements.body, to_subs: to_subs) if node.statements
|
|
93
|
-
|
|
94
|
-
when Prism::DefNode
|
|
95
|
-
method_name = node.name.to_s
|
|
96
|
-
@labels[method_name] = @line_num
|
|
97
|
-
add_basic("REM", "SUB: #{method_name}", to_subs: true)
|
|
98
|
-
process_nodes(node.body.body, to_subs: true) if node.body
|
|
99
|
-
add_basic("RETURN", "", to_subs: true)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def resolve(node)
|
|
104
|
-
return "0" if node.nil?
|
|
105
|
-
case node
|
|
106
|
-
when Prism::IntegerNode, Prism::FloatNode then node.slice
|
|
107
|
-
when Prism::StringNode then "\"#{node.content}\""
|
|
108
|
-
when Prism::LocalVariableReadNode
|
|
109
|
-
name = node.name.to_s
|
|
110
|
-
@string_vars.include?(name) ? "#{name}$" : name
|
|
111
|
-
|
|
112
|
-
when Prism::InterpolatedStringNode
|
|
113
|
-
parts = node.parts.map do |part|
|
|
114
|
-
if part.is_a?(Prism::StringNode)
|
|
115
|
-
"\"#{part.content}\""
|
|
116
|
-
elsif part.is_a?(Prism::EmbeddedStatementsNode)
|
|
117
|
-
inner = part.statements.body.first
|
|
118
|
-
res = resolve(inner)
|
|
119
|
-
var_name = inner.respond_to?(:name) ? inner.name.to_s : nil
|
|
120
|
-
|
|
121
|
-
if !inner.is_a?(Prism::StringNode) && !@string_vars.include?(var_name)
|
|
122
|
-
"STR$(#{res})"
|
|
123
|
-
else
|
|
124
|
-
res
|
|
125
|
-
end
|
|
126
|
-
else
|
|
127
|
-
resolve(part)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
parts.join("+")
|
|
131
|
-
|
|
132
|
-
when Prism::CallNode
|
|
133
|
-
if node.receiver && node.arguments && node.arguments.arguments.size == 1
|
|
134
|
-
left = resolve(node.receiver)
|
|
135
|
-
right = resolve(node.arguments.arguments.first)
|
|
136
|
-
op = node.name.to_s == "==" ? "=" : node.name.to_s
|
|
137
|
-
"#{left}#{op}#{right}"
|
|
138
|
-
else
|
|
139
|
-
node.name.to_s
|
|
140
|
-
end
|
|
141
|
-
else "0"
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|