Retroceso viernes: renumerar mi listado BÁSICO del espectro ZX

15

El primer lenguaje de programación al que estuve expuesto fue Sinclair BASIC . Al igual que muchos dialectos BÁSICOS, requiere que todas las líneas del código fuente estén numeradas .

Como resultado, el uso del GO TOcomando era idiomático y salta la ejecución al número de línea dado (sin etiquetas).

También hay un GO SUBcomando relacionado que se puede usar como una llamada de función rudimentaria. Nuevamente, la ejecución salta al número de línea dado, pero cuando RETURNse alcanza un comando, la ejecución vuelve a la siguiente instrucción después de GO SUB.

Del mismo modo, el RUNcomando reiniciará la ejecución del programa en la línea dada.

Cualquiera que haya pasado algún tiempo en un intérprete BASIC numerado por línea habrá aprendido a usar un esquema de numeración con huecos. Esto es para que sea más fácil insertar nuevas líneas de código. Sin embargo, aun así, es posible que necesite insertar nuevas líneas entre líneas numeradas consecutivamente.


Dada una lista BASIC numerada con línea como entrada, genera el mismo programa pero renumerado de tal manera que los números de línea comienzan en 10 y se incrementan en pasos de 10. La lista de entrada puede tener GO TOo GO SUBcomandos, por lo que los números asociados con estos también deben ajustarse.

  • GO TOy los GO SUBcomandos están en sus propias líneas o al final de las IF THENlíneas. Es seguro decir que ^(\d+) .*GO (TO|SUB) (\d+)$es suficiente para que coincida con tales líneas. Estos comandos entre comillas deben ignorarse.

  • RUNlos comandos siempre estarán en sus propias líneas. En este caso, un número de línea es opcional. Si falta, el intérprete simplemente comienza en la parte superior del programa.

  • Si a GO TO, GO SUBo RUNreferencias de comandos de una línea inexistente, entonces en vez saltará a la siguiente línea definida. Su entrada debe ocuparse de esto y asegurarse de que cualquier referencia de línea sea fija para que apunte a la línea correcta. El comportamiento puede ser indefinido si se proporciona un número de línea después del final del programa en uno de estos comandos.

  • Los números de línea siempre serán enteros positivos del 1 al 9999 (según el manual). Esto significa que los programas de entrada nunca tendrán más de 999 líneas.

  • Las líneas de entrada siempre se numerarán en orden numérico ascendente.

  • Para los propósitos de este desafío, los listados de entrada solo contendrán ASCII imprimible. No necesita preocuparse por el juego de caracteres ZX. Una vez dicho esto, si su entrada es en realidad escrito en ZX BASIC o apropiarse z80 montaje / código de máquina (y hay emuladores fuera allí ), entonces usted puede elegir que su entrada a codificar en el conjunto de caracteres ZX en su lugar.

  • No puede utilizar ninguna biblioteca de renumeración o utilidades específicamente diseñadas para este propósito.

Entrada de ejemplo:

1 REM "A rearranged guessing game"
2 INPUT A: CLS
3 INPUT "Guess the number ", B
10 IF A=B THEN PRINT "Correct": STOP
100 IF A<B THEN GO SUB 125
120 IF A>B THEN GO SUB 122
121 GO TO 3
125 PRINT "Try again"
126 RETURN
127 REM "An example of GO TO 7 and GO SUB 13 in quotes"

Salida de ejemplo:

10 REM "A rearranged guessing game"
20 INPUT A: CLS
30 INPUT "Guess the number ", B
40 IF A=B THEN PRINT "Correct": STOP
50 IF A<B THEN GO SUB 80
60 IF A>B THEN GO SUB 80
70 GO TO 30
80 PRINT "Try again"
90 RETURN
100 REM "An example of GO TO 7 and GO SUB 13 in quotes"

Quería vincularme a un manual ZX BASIC. Lo mejor que pude encontrar parece ser http://www.worldofspectrum.org/ZXBasicManual/index.html, pero este parece ser un enlace inactivo. Sin embargo, la máquina wayback tiene una copia .

Trauma digital
fuente
77
¡También congrue en hacer la pregunta número 5000!
FryAmTheEggman
1
Tiempo de nostalgia: mi primera PC fue una Spectrum 48K y uno de mis primeros programas de ensamblaje fue un renumerador
edc65
2
@ edc65 ¿Todavía tiene su código de ensamblaje de renumeración? Si es así, ¡sería bienvenido a publicarlo como respuesta!
Trauma digital
1
El caso de prueba debe incluir al menos un goto / gosub en un literal de cadena.
Peter Taylor
1
Encontré una mención: el ZX81 permite GOTOs y GOSUBs calculados como enGOTO 100 + A*10 , y el Apéndice C del ZX Spectrum Manual enumeraGO TOcomo aceptar una expresión numérica (sin restricción de constantes). Aquí hay una discusión sobre los méritos de computarGOTOen ZX80 y ZX81. Por cierto, no tengo idea de por qué se agregó el espacio en la versión Spectrum.
Toby Speight

Respuestas:

5

JavaScript (ES6) 177

Editar Se agregó el escaneo (costoso) para el siguiente número de línea válido

l=>l.split`
`.map((x,i)=>([,n,t]=x.match(/(\d+)(.*)/),l[n]=10*-~i,t),l=[]).map((x,i)=>10*-~i+x.replace(/(UN |GO TO |UB )(\d+)$/,(a,b,c)=>(l.some((v,i)=>i<c?0:a=b+v),a))).join`
`

PRUEBA

f=l=>
  l.split`\n`
  .map((x,i)=>([,n,t]=x.match(/(\d+)(.*)/),l[n]=10*-~i,t),l=[])
  .map((x,i)=>10*-~i+x.replace(/(UN |GO TO |UB )(\d+)$/,(a,b,c)=>(l.some((v,i)=>i<c?0:a=b+v),a)))
  .join`\n`
        
//TEST
console.log=x=>O.textContent+=x+'\n'
  
test=`1 REM "A rearranged guessing game"
2 INPUT A: CLS
3 INPUT "Guess the number ", B
10 IF A=B THEN PRINT "Correct": STOP
100 IF A<B THEN GO SUB 125
120 IF A>B THEN GO SUB 122
121 GO TO 3
125 PRINT "Try again"
126 RETURN`
console.log(test+'\n\n'+f(test))
<pre id=O></pre>

edc65
fuente
1
Se ve bien. My +1 stands :)
Trauma digital
2

Perl 6, 147 145 144 142 bytes

{my%a;.trans(/^^(\d+)/=>{%a{$0}=$+=10}).trans(/:s<!after \"\N*>(UN |GO TO |UB )(\d+)<!before \N*\">/=>{$0~%a{%a.keys».Num.grep(*>=$1).min}})}

Esto probablemente se pueda jugar un poco más.

Expandido

my &f = -> $s { 
    my %line-map; # This will map the old line numbers to the new ones

    $s.trans(/^^(\d+)/                    # This .trans creates the line number map
             => { %line-map{$0} = $+=10 } # as well as replaces the actual line numbers
            )\
      # This .trans replaces all the line numbers for each GO TO, GO SUB, RUN
      .trans(/:s<!after \"\N*>(UN |GO TO |UB )(\d+)<!before \N*\">/ 
             => {$0 ~ %line-map{%line-map.keys».Num.grep(*>=$1).min} } 
            )
}
Teclas de acceso rápido
fuente
No hay razón para usar el método .min. utilizar {min %line-map.keys».Num.grep:*>=$1en su lugar
Ven
1

Visual Basic para aplicaciones, 288 bytes

No pude resistir dar una solución en un dialecto BÁSICO. Probablemente funciona con Visual Basic 6 / .NET u otras variantes modernas con cambios menores.

Sub n(t,a)
f=Chr(10)
u=Chr(0)
Open t For Input As 1
a=f &Input(LOF(1),1)&f
Close
j=10
For i=1 To 9999
q=f &j &u
g=" GO TO "
w=i &f
m=j &f
a=Replace(Replace(Replace(Replace(a,g &w,g &m),f &i &" ",q),"B "&w,"B "&m),"UN "&w,"UN "&m)
If InStr(1,a,q)Then j=j+10
Next
a=Replace(a,u," ")
End Sub

Usé muchas variables de una letra para concisión. Además, eliminé todos los espacios en blanco innecesarios (VBE los expande automáticamente en la importación). El recuento de bytes es para el archivo final .BAS, con CHR (10) como nueva línea.

La subrutina, que se puede invocar desde la ventana inmediata de VBE, abre un programa Sinclair BASIC (el primer parámetro es la ruta a un archivo ASCII, con CHR (10) como nueva línea, que contiene el programa), renumera las líneas y escribe los resultados en una variable Variant (segundo parámetro).

La idea es iterar en todos los números de línea de origen posibles, orden ascendente y, para cada uno, reemplazar de una vez todos los números de línea coincidentes, así como GO TO, GO SUByRUN referencias con el siguiente número de línea de destino disponibles. Con este enfoque, no necesitamos ningún tipo de tabla de traducción. El número de línea objetivo se incrementa cada vez que se encuentra una coincidencia en el número de línea de origen, por lo que las referencias de línea "incorrectas" se ajustan automáticamente al siguiente número válido. Los caracteres de nueva línea se utilizan como marcadores de inicio y final de línea, y un CHR (0), nunca utilizado en el programa porque no es imprimible, se utiliza como marcador temporal, para evitar volver a numerar la misma línea varias veces.

Algunas observaciones:

  • Para ser concisos, usamos la cadena más pequeña posible para un partido con las declaraciones de salto. Al usar el final de línea en nuestras cadenas de búsqueda, no corremos el riesgo de incluir ocurrencias citadas o funciones de usuario (que siempre usan paréntesis en Sinclair). GO TOrequiere una cadena más grande debido a la FOR ... TOconstrucción (por ejemplo, comparar 50 FOR X=AGO TO 100y 50 GO TO 100)

  • El código no admite declaraciones en el formulario GO TO200(sin espacios en blanco), aunque el manual de ZX implica que es un código válido en varios ejemplos (costaría una docena más de bytes tratarlo).

  • El código agrega una nueva línea al principio y otra al final del programa. Podría limpiar esto al final (una docena más de bytes) pero creo que ZX probablemente ignoraría las líneas en blanco.

A continuación, una versión más legible:

Sub Renumber(ByVal ProgramPath As String, ByRef Program As Variant)

    Open ProgramPath For Input As #1
    Program = Chr(10) & Input(LOF(1), 1) & Chr(10)
    Close

    NewNumber = 10
    For OldNumber = 1 To 9999
        Program = Replace(Program, " GO TO" & OldNumber & Chr(10), " GO TO" & NewNumber & Chr(10)) 'self-explaining
        Program = Replace(Program, Chr(10) & OldNumber & " ", Chr(10) & NewNumber & Chr(0)) 'matches line number (and replaces whistespace with Chr(0) to avoid re-replacing
        Program = Replace(Program, "B " & OldNumber & Chr(10), "B " & NewNumber & Chr(10)) 'matches GO SUB
        Program = Replace(Program, "UN " & OldNumber & Chr(10), "UN " & NewNumber & Chr(10)) 'matches RUN
        If InStr(1, Program, Chr(10) & NewNumber & Chr(0)) Then NewNumber = NewNumber + 10 'if there is such a line, increment NewNumber
Next
Program = Replace(Program, Chr(0), " ") 'replace back Chr(0) with whitespace
End Sub
dnep
fuente
Por cierto, una solución QBasic sería mucho más larga, ya que QBasic no tiene una función de reemplazo de cadena integrada hasta donde recuerdo.
DLosc
Creo que tienes razón ... olvidé eso
dnep
1

Pip -rn , 63 bytes

Ygn:#{_<aFIy}*t+tgR`(RUN|GO (SUB|TO)) (\d+)$`{b.s.(nd)}R`^\d+`n

Pruébalo en línea!

Explicación

Preparar

El -rindicador lee todo el stdin y lo almacena como una lista de líneas en la variable local g. La variable global testá preinicializada a 10, y la variable global sestá preinicializada a " ".

Yg

Marca la lista de líneas gen la variable global y, para que esté disponible dentro de la función que estamos a punto de definir.

Función de traducción del número de línea

Construimos una función que se asigna desde cualquier número de línea en el esquema de numeración original (incluido uno inexistente) al número de línea correspondiente en el nuevo esquema de numeración.

Supongamos que tenemos estas líneas:

1 INPUT A
4 PRINT A
9 IF A=1 THEN GO TO 3

Queremos asignar 1 a 10, 2-4 a 20 y 5-9 a 30. Si tenemos una lista de los números de línea originales ( [1; 4; 9]), podemos usar una operación de filtro para averiguar cuántos de estos números son menos que el número de línea que estamos tratando de convertir. Multiplique ese resultado por 10 y agregue 10, y tenemos la respuesta deseada.

Por ejemplo, al convertir 9, hay dos números de línea (1 y 4) menores que 9. 2 * 10 + 10 da 30. Al convertir 3, hay un número de línea (1) menor que 3. 1 * 10 + 10 da 20.

Aquí está el código (ligeramente modificado para que sea más fácil de leer):

n:{#(_<aFIy)*t+t}
  {             }  Lambda function with parameter a:
        FIy         Filter y (the list of program lines) for
     _<a             lines that are numerically less than a
                    (In a numeric context, only the first run of digits on the line is considered)
   #(      )        Number of items in the filtered list
            *t+t    Times 10, plus 10
n:                 Assign that function to global variable n

En primer lugar la sustitución: GO TO, GO SUByRUN

El resto del programa es una sola expresión que toma gy realiza un par de reemplazos de expresiones regulares (que vectorizan, aplicando a cada línea g).

Aquí está el primer reemplazo:

g R `(RUN|GO (SUB|TO)) (\d+)$` {b.s.(nd)}

La expresión regular coincide con cualquiera de RUN, GO SUBy GO TO, seguido de un número, seguido del final de la línea. Esto asegura que no coincida con las cadenas internas, ni con RUNun número de línea.

El orden de los grupos de captura es importante. El primer grupo captura el comando (una de RUN, GO SUBo GO TO). El segundo grupo, si se usa, captura cualquiera SUBo TO. No necesitamos capturar esta parte, pero un grupo sin captura requeriría bytes adicionales. Luego, el tercer grupo captura el número de línea.

Utilizamos una función de devolución de llamada para el reemplazo. Con las funciones de devolución de llamada en la pipa, el encuentro es el primer argumento a, y los grupos de captura en el orden son los argumentos posteriores b, c, d, y e. Entonces tenemos el comando en el primer grupo, que entra b, y el número de línea en el tercer grupo, que entra d. El único cambio que tenemos que hacer es pasar el número de línea a través de nuestra función de conversión, que se llama Lisp de estilo: (nd). Luego concatenamos eso junto con bun espacio y lo devolvemos.

Segundo reemplazo: números de línea

Todo lo que queda por convertir son los números de línea al comienzo de las líneas.

(...) R `^\d+` n

La expresión regular coincide con una serie de dígitos al comienzo de una línea. Nuevamente usamos una función de devolución de llamada; esta vez, la función de conversión en nsí misma es suficiente, ya que toda la coincidencia (primer argumento a) es el número que queremos convertir.

Como esta es la última expresión del programa, Pip imprime automáticamente el resultado. La -nbandera separa la lista de resultados con nuevas líneas.

DLosc
fuente