Throwback Friday: Nummeriere mein ZX-Spektrum-BASIC-Listing neu

15

Die erste Programmiersprache, der ich ausgesetzt war, war Sinclair BASIC . Wie bei vielen BASIC-Dialekten müssen alle Quellcodezeilen nummeriert werden .

Infolgedessen war die Verwendung des GO TOBefehls idiomatisch und springt zur angegebenen Zeilennummer (keine Bezeichnungen).

Es gibt auch einen zugehörigen GO SUBBefehl, der als rudimentärer Funktionsaufruf verwendet werden kann. Die Ausführung springt wieder zur angegebenen Zeilennummer, aber wenn ein RETURNBefehl erreicht ist, springt die Ausführung zurück zum nächsten Befehl nach dem GO SUB.

In ähnlicher Weise RUNstartet der Befehl die Programmausführung in der angegebenen Zeile neu.

Jeder, der Zeit in einem zeilennummerierten BASIC-Interpreter verbracht hat, hat gelernt, ein lückenhaftes Nummerierungsschema zu verwenden. Dies erleichtert das Einfügen neuer Codezeilen. Trotzdem müssen Sie möglicherweise immer noch neue Zeilen zwischen fortlaufend nummerierten Zeilen einfügen.


Bei einer Linie nummerierten BASIC Protokoll als Eingang, Ausgang das gleiche Programm , aber neu nummeriert , so dass die Zeilennummern bei 10 und Erhöhung in Schritten von 10 starten Die Eingangs Listing kann GO TOoder GO SUBBefehle, so verbunden sind die Zahlen mit diesen auch angepasst werden müssen.

  • GO TOund GO SUBBefehle stehen entweder in eigenen Zeilen oder am Zeilenende IF THEN. Es ist sicher zu sagen, dass ^(\d+) .*GO (TO|SUB) (\d+)$es ausreicht, um solche Linien zu treffen. Diese Befehle in Anführungszeichen sollten ignoriert werden.

  • RUNBefehle werden immer in eigenen Zeilen stehen. In diesem Fall ist eine Zeilennummer optional. Fehlt es, startet der Interpreter einfach oben im Programm.

  • Wenn ein GO TO, GO SUBoder RUNBefehl verweist auf eine nicht vorhandene Linie, dann wird es stattdessen auf die nächste definierte Linie springen. Ihr Eintrag muss sich damit befassen und sicherstellen, dass solche Zeilenreferenzen festgelegt sind, damit sie auf die richtige Zeile verweisen. Das Verhalten kann undefiniert sein, wenn in einem dieser Befehle eine Zeilennummer nach dem Programmende angegeben wird.

  • Zeilennummern sind immer positive ganze Zahlen von 1 bis 9999 (gemäß Handbuch). Dies bedeutet, dass Eingabeprogramme niemals mehr als 999 Zeilen haben.

  • Eingabezeilen werden immer in numerisch aufsteigender Reihenfolge nummeriert.

  • Für die Zwecke dieser Herausforderung enthalten Eingabeauflistungen nur druckbare ASCII-Daten. Sie müssen sich nicht um den ZX-Zeichensatz kümmern. Having said that, wenn Ihr Eintrag in ZX BASIC tatsächlich geschrieben oder geeignete z80 Montage / Maschinencode (und es gibt Emulatoren heraus dort ), dann können Sie wählen für Ihre Eingabe in der codiert werden ZX - Zeichensatz statt.

  • Sie dürfen keine neu nummerierten Bibliotheken oder Dienstprogramme verwenden, die speziell auf diesen Zweck zugeschnitten sind.

Beispiel Eingabe:

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"

Beispielausgabe:

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"

Ich wollte auf ein ZX BASIC-Handbuch verlinken. Das Beste, was ich finden konnte, scheint http://www.worldofspectrum.org/ZXBasicManual/index.html zu sein, aber dies scheint ein toter Link zu sein. Die Wayback-Maschine hat jedoch eine Kopie .

Digitales Trauma
quelle
7
Auch conrgats auf die 5000. Frage!
FryAmTheEggman
1
Nostalgie-Zeit - mein erster PC war ein Spectrum 48K und eines meiner ersten Assembler-Programme war ein
Renumerierer
2
@ edc65 Hast du noch deinen Assembler-Code neu nummeriert? Dann können Sie es gerne als Antwort posten!
Digital Trauma
1
Der Testfall sollte mindestens ein goto / gosub in einem String-Literal enthalten.
Peter Taylor
1
Ich fand eine Erwähnung: die ZX81 ermöglicht berechneten GOTOs GOSUBs und wie inGOTO 100 + A*10 und Anhang C der ZX Spectrum Handbuch Listen GO TOals numerischen Ausdruck akzeptiert (keine Beschränkung auf Konstanten). Im Folgenden werden die Vorteile von Computing GOTOfür ZX80 und ZX81 erörtert . Übrigens habe ich keine Ahnung, warum der Speicherplatz in der Spectrum-Version hinzugefügt wurde.
Toby Speight

Antworten:

5

JavaScript (ES6) 177

Bearbeiten Der (kostspielige) Scan für die nächste gültige Zeilennummer wurde hinzugefügt

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

PRÜFUNG

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
quelle
1
Sieht gut aus. Mein +1 steht :)
Digital Trauma
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}})}

Dies kann wahrscheinlich ein bisschen mehr Golf gespielt werden.

Erweitert

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} } 
            )
}
Hotkeys
quelle
Kein Grund, die Methode anzuwenden .min. Verwenden Sie {min %line-map.keys».Num.grep:*>=$1stattdessen
Ven
1

Visual Basic für Applikationen, 288 Byte

Ich konnte nicht widerstehen, eine Lösung in einem BASIC-Dialekt zu geben. Funktioniert wahrscheinlich mit Visual Basic 6 / .NET oder anderen modernen Varianten mit geringfügigen Änderungen.

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

Ich habe viele Ein-Buchstaben-Variablen aus Gründen der Übersichtlichkeit verwendet. Außerdem habe ich alle unnötigen Leerzeichen unterdrückt (VBE erweitert sie beim Import automatisch). Die Byteanzahl gilt für die endgültige .BAS-Datei mit CHR (10) als Zeilenumbruch.

Die Subroutine, die über das VBE-Direktfenster aufgerufen werden kann, öffnet ein Sinclair BASIC-Programm (der erste Parameter ist der Pfad zu einer ASCII-Datei - mit CHR (10) als Zeilenvorschub - die das Programm enthält), nummeriert die Zeilen neu und schreibt die Ergebnisse in eine Variant-Variable (zweiter Parameter).

Die Idee ist, Iterierte auf allen möglichen Quellzeilennummern, aufsteigende Reihenfolge, und für jeden, ersetzen Sie sofort alle passenden Zeilennummern sowie GO TO, GO SUBund RUNReferenzen mit der nächsten verfügbaren Nummer Ziellinie. Mit diesem Ansatz benötigen wir keine Übersetzungstabelle. Die Zielzeilennummer wird jedes Mal erhöht, wenn eine Übereinstimmung in der Quellzeilennummer gefunden wird, sodass "falsche" Zeilenreferenzen automatisch an die nächste gültige Nummer angepasst werden. Zeilenumbruchzeichen werden als Markierungen für Zeilenanfang und -ende verwendet, und eine CHR (0) - die im Programm nie verwendet wird, da sie nicht gedruckt werden kann - wird als temporäre Markierung verwendet, um zu vermeiden, dass dieselbe Zeile mehrmals neu nummeriert wird.

Einige Bemerkungen:

  • Aus Gründen der Übersichtlichkeit verwenden wir die kleinere mögliche Zeichenfolge für eine Übereinstimmung mit den Sprunganweisungen. Wenn wir das Zeilenende in unseren Suchzeichenfolgen verwenden, laufen wir nicht Gefahr, Anführungszeichen oder Benutzerfunktionen (die in Sinclair immer Klammern verwenden) aufzunehmen. GO TOerfordert aufgrund des FOR ... TOKonstrukts einen größeren String (zB compare 50 FOR X=AGO TO 100and 50 GO TO 100)

  • Der Code unterstützt keine Anweisungen in der Form GO TO200(ohne Leerzeichen), obwohl das ZX-Handbuch impliziert, dass es sich bei mehreren Beispielen um gültigen Code handelt (die Bearbeitung würde ein Dutzend Bytes mehr kosten).

  • Der Code fügt eine neue Zeile am Anfang und eine weitere am Ende des Programms hinzu. Ich könnte dies am Ende bereinigen (ein Dutzend weitere Bytes), aber ich denke, dass der ZX wahrscheinlich leere Zeilen ignorieren würde.

Unten eine besser lesbare Version:

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
quelle
Übrigens wäre eine QBasic-Lösung viel länger, da QBasic, soweit ich mich erinnere, keine eingebaute String-Ersetzungsfunktion hat.
DLosc
Ich denke, Sie haben Recht ... vergessen
dnep
1

Pip -rn , 63 Bytes

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

Probieren Sie es online!

Erläuterung

Installieren

Das -rFlag liest das gesamte stdin und speichert es als eine Liste von Zeilen in der lokalen Variablen g. Die globale Variable tist auf 10 vorinitialisiert, und die globale Variable sist auf vorinitialisiert " ".

Yg

Zieht die Liste der Zeilen gin die globale Variable ein y, damit sie in der Funktion verfügbar ist, die wir definieren werden.

Zeilennummern-Übersetzungsfunktion

Wir konstruieren eine Funktion, die aus einer beliebigen Zeilennummer im ursprünglichen Nummerierungsschema (einschließlich einer nicht vorhandenen) die entsprechende Zeilennummer im neuen Nummerierungsschema abbildet.

Angenommen, wir haben diese Zeilen:

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

Wir möchten 1 bis 10, 2-4 bis 20 und 5-9 bis 30 zuordnen. Wenn wir eine Liste der ursprünglichen Zeilennummern ( [1; 4; 9]) haben, können wir mit einer Filteroperation herausfinden, wie viele dieser Zahlen kleiner sind als die Zeilennummer, die wir konvertieren wollen. Multiplizieren Sie dieses Ergebnis mit 10 und addieren Sie 10, und wir haben die gewünschte Antwort.

Wenn Sie beispielsweise 9 konvertieren, sind zwei Zeilennummern (1 und 4) kleiner als 9. 2 * 10 + 10 ergibt 30. Wenn Sie 3 konvertieren, ist eine Zeilennummer (1) kleiner als 3. 1 * 10 + 10 gibt 20.

Hier ist der Code (leicht modifiziert, um leichter zu lesen):

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

Erster Ersatz: GO TO, GO SUB, undRUN

Der Rest des Programms ist ein einzelner Ausdruck, der geinige reguläre Ausdrücke ersetzt (die vektorisieren und auf jede Zeile in angewendet werden g).

Hier ist der erste Ersatz:

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

Die Regex paßt jeder RUN, GO SUBund GO TO, gefolgt von einer Zahl, gefolgt von Zeilenende. Dies stellt sicher, dass es weder innerhalb von Zeichenfolgen noch RUNohne Zeilennummer übereinstimmt .

Die Reihenfolge der Erfassungsgruppen ist wichtig. Die erste Gruppe fängt den Befehl (eine RUN, GO SUBoder GO TO). Die zweite Gruppe erfasst, falls verwendet, entweder SUBoder TO. Wir müssen diesen Teil nicht erfassen, aber eine nicht erfassende Gruppe würde zusätzliche Bytes benötigen. Dann erfasst die dritte Gruppe die Zeilennummer.

Wir verwenden eine Rückruffunktion für den Ersatz. Mit Callback - Funktionen in Pip ist das ganze Spiel das erste Argument a, und die Fängergruppen sind , um die nachfolgenden Argumente b, c, d, und e. Wir haben also den Befehl in der ersten Gruppe, die rein geht b, und die Zeilennummer in der dritten Gruppe, die rein geht d. Die einzige Änderung , die wir machen müssen ist die Zeilennummer durch unsere Umwandlungsfunktion zu übergeben, die Lisp-Stil genannt wird: (nd). Dann verknüpfen wir das mit bund einem Leerzeichen und geben es zurück.

Zweiter Ersatz: Zeilennummern

Alles, was konvertiert werden muss, sind die Zeilennummern am Anfang der Zeilen.

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

Der reguläre Ausdruck entspricht einer Ziffernfolge am Anfang einer Zeile. Wieder verwenden wir eine Rückruffunktion; Dieses Mal ist die Konvertierungsfunktion nselbst ausreichend, da die gesamte Übereinstimmung (erstes Argument a) die Zahl ist, die konvertiert werden soll.

Da dies der letzte Ausdruck im Programm ist, druckt Pip das Ergebnis automatisch aus. Das -nFlag trennt die Ergebnisliste mit Zeilenumbrüchen.

DLosc
quelle