Throwback Friday: rinumerare il mio elenco BASIC dello spettro ZX


15

Il primo linguaggio di programmazione a cui sono stato esposto è stato Sinclair BASIC . Come molti dialetti BASIC, richiede che tutte le righe del codice sorgente siano numerate .

Di conseguenza, l'uso del GO TOcomando era idiomatico e salta l'esecuzione al numero di riga specificato (nessuna etichetta).

Inoltre esiste un GO SUBcomando correlato che può essere utilizzato come chiamata di funzione rudimentale. Ancora una volta, l'esecuzione passa al numero di riga specificato, ma quando RETURNviene raggiunto un comando, l'esecuzione torna all'istruzione successiva dopo il GO SUB.

Allo stesso modo il RUNcomando riavvierà l'esecuzione del programma sulla riga indicata.

Chiunque abbia trascorso del tempo in un interprete BASIC con numero di riga avrà imparato a utilizzare uno schema di numerazione con spazi vuoti. In questo modo è più semplice inserire nuove righe di codice. Tuttavia, anche in questo caso, potresti ancora ritrovarti a dover inserire nuove righe tra le righe numerate consecutive.


Dato un elenco BASIC con numero di riga come input, emette lo stesso programma ma rinumerato in modo tale che i numeri di riga inizino da 10 e aumentino con incrementi di 10. L'elenco di input può avere GO TOo GO SUBcomandi, quindi anche i numeri associati a questi devono essere regolati.

  • GO TOe i GO SUBcomandi sono sulle loro stesse linee o alla fine delle IF THENlinee. È sicuro dire che ^(\d+) .*GO (TO|SUB) (\d+)$è sufficiente per abbinare tali linee. Questi comandi tra virgolette devono essere ignorati.

  • RUNi comandi saranno sempre sulla loro linea. In questo caso un numero di riga è facoltativo. Se manca, l'interprete si avvia semplicemente nella parte superiore del programma.

  • Se una GO TO, GO SUBo RUNriferimenti di comando una linea inesistente, allora sarà invece passare alla riga successiva definita. La tua voce deve occuparsi di questo e assicurarsi che tali riferimenti di riga siano fissi in modo che puntino alla riga corretta. Il comportamento potrebbe essere indefinito se in uno di questi comandi viene indicato un numero di riga dopo la fine del programma.

  • I numeri di riga saranno sempre numeri interi positivi da 1 a 9999 (come da manuale). Ciò significa che i programmi di input non avranno mai più di 999 righe.

  • Le righe di input saranno sempre numerate in ordine numerico crescente.

  • Ai fini di questa sfida, gli elenchi di input conterranno solo ASCII stampabili. Non devi preoccuparti del set di caratteri ZX. Detto questo, se la tua voce è effettivamente scritta in ZX BASIC o nel codice assembly / macchina z80 appropriato (e ci sono emulatori fuori ), puoi scegliere di inserire il tuo input nel set di caratteri ZX .

  • Non è possibile utilizzare librerie o utilità di rinumerazione appositamente studiate per questo scopo.

Esempio di input:

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"

Esempio di output:

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"

Volevo collegarmi a un manuale ZX BASIC. Il migliore che ho trovato sembra essere http://www.worldofspectrum.org/ZXBasicManual/index.html, ma questo sembra essere un collegamento non funzionante. La macchina del ritorno ha una copia però .


7
Anche i conrgati nel porre la 5000esima domanda!
FryAmTheEggman,

1
Tempo di nostalgia - il mio primo PC era uno Spectrum 48K e uno dei miei primi programmi di assemblaggio era un rinumeratore
edc65,

2
@ edc65 Hai ancora il codice dell'assieme rinumerazione? Se è così, saresti molto lieto di pubblicarlo come risposta!
Trauma digitale,

1
Il caso di test dovrebbe includere almeno un goto / gosub in un valore letterale di stringa.
Peter Taylor,

1
Ho trovato una menzione: lo ZX81 consente GOTO e GOSUB calcolati come inGOTO 100 + A*10 , e l' Appendice C degli elenchi ZX Spectrum ManualGO TO come accettare un'espressione numerica (nessuna restrizione alle costanti). Ecco una discussione sui meriti calcolati GOTOsu ZX80 e ZX81. A proposito, non ho idea del perché lo spazio sia stato aggiunto nella versione Spectrum.
Toby Speight,

Risposte:


5

JavaScript (ES6) 177

Modifica Aggiunta la scansione (costosa) per il successivo numero di riga valido

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`
`

TEST

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>


1
Sembra buono. Il mio +1 sta :)
Digital Trauma,

2

Perl 6, 147 145 144 142 byte

{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}})}

Questo può probabilmente essere risolto un po 'di più.

allargato

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} } 
            )
}

alcun motivo per utilizzare il metodo .min. usa {min %line-map.keys».Num.grep:*>=$1invece
Ven

1

Visual Basic, Applications Edition, 288 byte

Non ho resistito a dare una soluzione in un dialetto BASIC. Funziona probabilmente con Visual Basic 6 / .NET o altre varianti moderne con modifiche minori.

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

Ho usato molte variabili di una lettera per concisione. Inoltre, ho soppresso tutti gli spazi bianchi non necessari (VBE li espande automaticamente all'importazione). Il conteggio dei byte è per il file .BAS finale, con CHR (10) come newline.

La subroutine, che può essere richiamata dalla finestra immediata di VBE, apre un programma Sinclair BASIC (il primo parametro è il percorso di un file ASCII - con CHR (10) come newline - contenente il programma), rinumerare le righe e scrivere i risultati in una variabile Variant (secondo parametro).

L'idea è quella di iterare su tutti i possibili numeri di riga di origine, in ordine crescente e per ciascuno, sostituire contemporaneamente tutti i numeri di riga corrispondenti GO TO, GO SUBnonché i RUNriferimenti con il successivo numero di riga di destinazione disponibile. Utilizzando questo approccio non abbiamo bisogno di alcun tipo di tabella di traduzione. Il numero di riga di destinazione viene incrementato ogni volta che viene trovata una corrispondenza nel numero di riga di origine, quindi i riferimenti di riga "errati" vengono adeguati automaticamente al successivo numero valido. I caratteri di nuova riga vengono utilizzati come marcatori di inizio e fine riga e un CHR (0) - mai utilizzato nel programma in quanto non stampabile - viene utilizzato come marcatore temporaneo, per evitare di rinumerare la stessa riga più volte.

Alcune osservazioni:

  • Per concisione, usiamo la stringa più piccola possibile per una corrispondenza con le dichiarazioni saltanti. Usando la fine della riga sulle nostre stringhe di ricerca non corriamo il rischio di includere occorrenze tra virgolette o funzioni utente (che usano sempre la parentesi in Sinclair). GO TOrichiede una stringa più grande a causa del FOR ... TOcostrutto (es. confronta 50 FOR X=AGO TO 100e 50 GO TO 100)

  • Il codice non supporta le istruzioni nel modulo GO TO200(senza spazi bianchi), sebbene il manuale ZX implichi che è un codice valido su diversi esempi (costerebbe una dozzina di byte in più per gestirlo).

  • Il codice aggiunge una nuova riga all'inizio e un'altra alla fine del programma. Potrei ripulirlo alla fine (una dozzina di byte in più) ma immagino che lo ZX probabilmente ignorerebbe le righe vuote.

Di seguito, una versione più leggibile:

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

A proposito, una soluzione QBasic sarebbe molto più lunga, dal momento che QBasic non ha una funzione di sostituzione delle stringhe per quanto ricordo.
DLosc,

Penso che tu abbia ragione ... te ne sei dimenticato
dnep

1

Pip -rn , 63 byte

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

Provalo online!

Spiegazione

Impostare

Il -rflag legge tutto lo stdin e lo memorizza come un elenco di righe nella variabile locale g. La variabile globale tè preinizializzata su 10 e la variabile globale sè preinizializzata su " ".

Yg

Sposta l'elenco di righe gnella variabile globale y, in modo che sia disponibile all'interno della funzione che stiamo per definire.

Funzione di traduzione del numero di riga

Costruiamo una funzione che mappa da qualsiasi numero di riga nello schema di numerazione originale (incluso uno inesistente) al numero di riga corrispondente nel nuovo schema di numerazione.

Supponiamo di avere queste linee:

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

Vogliamo mappare da 1 a 10, da 2-4 a 20 e da 5-9 a 30. Se abbiamo un elenco dei numeri di riga originali ( [1; 4; 9]), possiamo usare un'operazione di filtro per scoprire quanti di questi numeri sono inferiori rispetto al numero di riga che stiamo cercando di convertire. Moltiplica quel risultato per 10 e aggiungi 10, e abbiamo la risposta desiderata.

Ad esempio, quando si converte 9, ci sono due numeri di riga (1 e 4) in meno di 9. 2 * 10 + 10 dà 30. Quando si converte 3, c'è un numero di riga (1) in meno di 3. 1 * 10 + 10 dà 20.

Ecco il codice (leggermente modificato per essere più facile da leggere):

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

In primo luogo la sostituzione: GO TO, GO SUB, eRUN

Il resto del programma è una singola espressione che accetta ge fa un paio di sostituzioni regex (che vettorizzano, applicando a ciascuna riga in g).

Ecco la prima sostituzione:

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

La regex corrisponde a uno qualsiasi di RUN, GO SUBe GO TO, seguito da un numero, seguito dalla fine della riga. Ciò garantisce che non corrisponda alle stringhe interne, né corrisponda RUNsenza un numero di riga.

L'ordine dei gruppi di acquisizione è importante. Il primo gruppo cattura il comando (uno dei RUN, GO SUBo GO TO). Il secondo gruppo, se utilizzato, cattura SUBo TO. Non è necessario acquisire questa parte, ma un gruppo non acquisito richiederebbe byte extra. Quindi il terzo gruppo acquisisce il numero di riga.

Usiamo una funzione di richiamata per la sostituzione. Con funzioni di callback in Pip, l'intera partita è il primo argomento a, ei gruppi di cattura in ordine sono gli argomenti successivi b, c, d, e e. Quindi abbiamo il comando nel primo gruppo, che entra b, e il numero di riga nel terzo gruppo, che entra d. L'unico cambiamento che abbiamo bisogno di fare è quello di passare il numero di linea attraverso il nostro funzione di conversione, che si chiama stile Lisp: (nd). Quindi concateniamo questo insieme a buno spazio e lo restituiamo.

Seconda sostituzione: numeri di riga

Tutto ciò che resta da convertire sono i numeri di riga all'inizio delle righe.

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

La regex corrisponde a una serie di cifre all'inizio di una riga. Usiamo ancora una funzione di callback; questa volta, la funzione di conversione nstessa è sufficiente, poiché l'intera corrispondenza (primo argomento a) è il numero che vogliamo convertire.

Poiché questa è l'ultima espressione nel programma, Pip stampa automaticamente il risultato. Il -nflag separa l'elenco dei risultati con nuove righe.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.