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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b030c504db662d7f49036a6041dee51f0d13aa2404217dc559f2e00aa5c4e580
4
- data.tar.gz: d68f421809b736f303eb80f478991f8754bca61b6494c5d7d726d9b36c1ded3e
3
+ metadata.gz: 8036de50cb43b2151d011b88570f88351dd0b23fd2d939a799896d35ef23cf03
4
+ data.tar.gz: 34ce4e930b6c931387e1c48f7a27a871bf20f1d7063e228ff93085c969bb7f10
5
5
  SHA512:
6
- metadata.gz: 58376b89c2ad1dcef8612629933ec57755fc8c6403731e62758907cde5b84ce55e113697ccb9efaba51ab860c7a9e2a2463142ca6648d7dfa8afe0313b3f164c
7
- data.tar.gz: 0b237718755c33c28366d744c13b04e9e38a6fee1205fcd7d7e0cc97b2f9dc0e2a07576fdf4a492cd3a66aec7f39b75cd3ef1b9a4b006d8de886c1e238a738d7
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
- * Lee la estructura secuencial.
48
- * Los comentarios a `REM`.
49
- * Definir variables `String` e `Integer`.
50
- * Método `puts` a `PRINT`. Se aceptan los String embebidos.
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
+
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruby2Basic
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -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
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "ruby2basic/version"
4
- require_relative "ruby2basic/zxspectrum"
5
-
4
+ require_relative "ruby2basic/zxspectrum/transpiler"
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.0
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
-