Piątek zwrotny: Zmień numerację mojej listy ZX Spectrum BASIC

15

Pierwszym językiem programowania, na który byłem narażony, był Sinclair BASIC . Podobnie jak wiele dialektów BASIC, wymaga numerowania wszystkich linii kodu źródłowego .

W rezultacie użycie GO TOpolecenia było idiomatyczne i powoduje wykonanie skoku do podanego numeru linii (bez etykiet).

Istnieje również pokrewne GO SUBpolecenie, które może być użyte jako podstawowe wywołanie funkcji. Ponownie wykonanie przeskakuje do podanego numeru wiersza, ale po osiągnięciu RETURNpolecenia wykonanie powraca do następnej instrukcji po GO SUB.

Podobnie RUNpolecenie zrestartuje wykonanie programu w danym wierszu.

Każdy, kto spędził czas w tłumaczu BASIC o numerze wiersza, nauczy się korzystać ze schematu numeracji z przerwami. Dzieje się tak, aby łatwiej wstawiać nowe wiersze kodu. Jednak nawet wtedy nadal może być konieczne wstawienie nowych linii między kolejnymi numerami linii.


Biorąc pod uwagę numer BASIC o numerze wiersza jako dane wejściowe, wypisuje ten sam program, ale przenumerowany tak, że numery linii zaczynają się od 10 i są zwiększane o krok 10. Liczba wejść może mieć GO TOlub GO SUBpolecenia, więc liczby z nimi związane również muszą zostać dostosowane.

  • GO TOa GO SUBpolecenia są albo w osobnych wierszach, albo na ich końcu IF THEN. Można powiedzieć, że ^(\d+) .*GO (TO|SUB) (\d+)$wystarczy dopasować takie linie. Te polecenia w cudzysłowie należy zignorować.

  • RUNpolecenia będą zawsze na swoich liniach. W takim przypadku numer linii jest opcjonalny. Jeśli go brakuje, tłumacz zaczyna po prostu na górze programu.

  • Jeśli polecenie GO TO, GO SUBlub RUNodwołuje się do nieistniejącej linii, przeskakuje do następnej zdefiniowanej linii. Twój wpis musi sobie z tym poradzić i upewnić się, że wszelkie odniesienia do linii są stałe, aby wskazywały prawidłową linię. Zachowanie może być niezdefiniowane, jeśli w jednym z tych poleceń podany jest numer linii po zakończeniu programu.

  • Numery linii zawsze będą dodatnimi liczbami całkowitymi od 1 do 9999 (zgodnie z instrukcją). Oznacza to, że programy wejściowe nigdy nie będą miały więcej niż 999 linii.

  • Linie wejściowe będą zawsze numerowane w kolejności rosnącej liczbowo.

  • Na potrzeby tego wyzwania zestawienia wejściowe będą zawierały tylko ASCII do wydruku. Nie musisz się martwić o zestaw znaków ZX. Mimo, że jeśli wejście jest faktycznie napisane w BASIC ZX lub właściwego montażu Z80 / kod maszynowy (i istnieją emulatory odchodzący istnieje ), a następnie można wybrać za Twój wkład mają być zakodowane w zestawie znaków ZX zamiast.

  • Nie możesz używać żadnych bibliotek lub programów numeracyjnych specjalnie dostosowanych do tego celu.

Przykładowe dane wejściowe:

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"

Przykładowe dane wyjściowe:

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"

Chciałem połączyć się z instrukcją obsługi ZX BASIC. Najlepsze, co mogłem znaleźć, to http://www.worldofspectrum.org/ZXBasicManual/index.html, ale wydaje się, że jest to martwy link. Maszyna powrotna ma jednak kopię .

Cyfrowa trauma
źródło
7
Gratuluję również zadania 5000. pytania!
FryAmTheEggman
1
Czas nostalgii - moim pierwszym komputerem był Spectrum 48K, a jednym z moich programów pierwszego montażu był
numerator
2
@ edc65 Czy nadal masz kod zmiany numeracji? Jeśli tak, zachęcamy do opublikowania go jako odpowiedzi!
Cyfrowa trauma
1
Przypadek testowy powinien zawierać co najmniej jeden goto / gosub w literałach łańcuchowych.
Peter Taylor,
1
Znalazłem wzmiankę: ZX81 pozwala na obliczone GOTO i GOSUB jak wGOTO 100 + A*10 , a dodatek C do ZX Spectrum Manual wymienia GO TOjako akceptację wyrażenia liczbowego (bez ograniczenia do stałych). Oto omówienie zalet obliczeń GOTOna ZX80 i ZX81. BTW, nie mam pojęcia, dlaczego przestrzeń została dodana w wersji Spectrum.
Toby Speight,

Odpowiedzi:

5

JavaScript (ES6) 177

Edytuj Dodano (kosztowne) skanowanie następnego ważnego numeru linii

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>

edc65
źródło
1
Wygląda dobrze. Moje +1 stoi :)
Cyfrowa trauma
2

Perl 6, 147 145 144 142 bajtów

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

Prawdopodobnie można to nieco pograć w golfa.

Rozszerzony

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} } 
            )
}
Skróty klawiszowe
źródło
nie ma powodu, aby używać tej metody .min. użyj {min %line-map.keys».Num.grep:*>=$1zamiast tego
Ven
1

Visual Basic for Applications, 288 bajtów

Nie mogłem się oprzeć podaniu rozwiązania w dialekcie BASIC. Prawdopodobnie działa z Visual Basic 6 / .NET lub innymi nowoczesnymi wariantami z niewielkimi zmianami.

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

Dla zwięzłości użyłem wielu literowych zmiennych. Ponadto stłumiłem wszystkie niepotrzebne białe znaki (VBE automatycznie je rozszerza podczas importu). Liczba bajtów dotyczy końcowego pliku .BAS, z CHR (10) jako znakiem nowej linii.

Podprogram, który można wywołać z bezpośredniego okna VBE, otwiera program Sinclair BASIC (pierwszym parametrem jest ścieżka do pliku ASCII - z CHR (10) jako nową linią - zawierającą program), przenumeruje linie i zapisze wyniki w zmiennej Variant (drugi parametr).

Pomysł jest iteracyjne na wszystkich możliwych numerów linii source porządku rosnącym, a dla każdego z nich należy wymienić naraz wszystkich liczb dopasowanie linii, jak również GO TO, GO SUBi RUNodniesień do następnej dostępnej linii numer docelowy. Korzystając z tego podejścia, nie potrzebujemy żadnego rodzaju tabeli tłumaczeń. Numer linii docelowej jest zwiększany za każdym razem, gdy zostanie znalezione dopasowanie w numerze linii źródłowej, więc „błędne” odniesienia do linii są automatycznie dostosowywane do następnej poprawnej liczby. Znaki nowej linii są używane jako znaczniki początku i końca linii, a CHR (0) - nigdy nie używany w programie, ponieważ nie można go wydrukować - jest używany jako znacznik tymczasowy, aby uniknąć wielokrotnego numerowania tej samej linii.

Kilka uwag:

  • Dla zwięzłości używamy mniejszego możliwego ciągu w celu dopasowania do skaczących instrukcji. Używając końca wiersza w naszych ciągach wyszukiwania, nie ryzykujemy dołączenia cytowanych zdarzeń lub funkcji użytkownika (które zawsze używają nawiasów w Sinclair). GO TOwymaga większego ciągu ze względu na FOR ... TOkonstrukcję (np. porównaj 50 FOR X=AGO TO 100i 50 GO TO 100)

  • Kod nie obsługuje instrukcji w formie GO TO200(bez białych znaków), chociaż instrukcja ZX sugeruje, że jest poprawnym kodem na kilku przykładach (zajęłoby to kilkanaście więcej bajtów, aby sobie z tym poradzić).

  • Kod dodaje nowy wiersz na początku, a drugi na końcu programu. Mogę to wyczyścić na końcu (kilkanaście kolejnych bajtów), ale sądzę, że ZX prawdopodobnie zignoruje puste linie.

Poniżej, bardziej czytelna wersja:

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
źródło
BTW, rozwiązanie QBasic byłoby znacznie dłuższe, ponieważ QBasic nie ma wbudowanej funkcji zamiany łańcucha, o ile pamiętam.
DLosc
Myślę, że masz rację ... zapomniałeś o tym
dnep
1

Pip -rn , 63 bajty

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

Wypróbuj online!

Wyjaśnienie

Ustawiać

-rFlag czyta wszystkie stdin i zapisuje go jako lista linii w zmiennej lokalnej g. Zmienna globalna tjest wstępnie zainicjalizowana na 10, a zmienna globalna sjest wstępnie zainicjalizowana " ".

Yg

Wyciąga listę linii gdo zmiennej globalnej y, aby była dostępna w funkcji, którą zamierzamy zdefiniować.

Funkcja tłumaczenia numeru wiersza

Konstruujemy funkcję, która mapuje z dowolnego numeru linii w oryginalnym schemacie numeracji (w tym nieistniejącym) na odpowiedni numer linii w nowym schemacie numeracji.

Załóżmy, że mamy następujące linie:

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

Chcemy zmapować 1 do 10, 2-4 do 20 i 5-9 do 30. Jeśli mamy listę oryginalnych numerów linii ( [1; 4; 9]), możemy użyć operacji filtrowania, aby dowiedzieć się, ile z tych liczb jest mniej niż numer linii, którą próbujemy przekonwertować. Pomnóż ten wynik przez 10 i dodaj 10, a my mamy pożądaną odpowiedź.

Na przykład podczas konwersji 9 występują dwie liczby linii (1 i 4) mniejsze niż 9. 2 * 10 + 10 daje 30. Podczas konwersji 3 liczba linii (1) jest mniejsza niż 3. 1 * 10 + 10 daje 20.

Oto kod (nieco zmodyfikowany, aby był łatwiejszy do odczytania):

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

Pierwsza wymiana: GO TO, GO SUB, iRUN

Reszta programu jest pojedynczym wyrażeniem, które pobiera gi wykonuje kilka zamienników wyrażeń regularnych (które wektoryzują, odnosząc się do każdej linii g).

Oto pierwszy zamiennik:

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

Wyrażenie regularne pasuje do dowolnego z RUN, GO SUBa GO TOpo nim następuje liczba, a następnie koniec linii. Zapewnia to, że nie pasuje do ciągów, ani nie pasuje RUNbez numeru linii.

Kolejność grup przechwytywania jest ważna. Pierwsza grupa rejestruje polecenia (jeden RUN, GO SUB, OR GO TO). Druga grupa, jeśli są stosowane, to rejestruje albo SUBalbo TO. Nie musimy przechwytywać tej części, ale grupa nie przechwytująca wymagałaby dodatkowych bajtów. Następnie trzecia grupa przechwytuje numer linii.

Do zamiany używamy funkcji zwrotnej. Dzięki funkcji zwrotnych w Pip, cały mecz jest pierwszym argumentem a, a grupy przechwytywania w kolejności są kolejne argumenty b, c, d, i e. Mamy więc polecenie w pierwszej grupie, która wchodzi b, i numer linii w trzeciej grupie, która wchodzi d. Jedyną zmianą musimy zrobić to przekazać numer wiersza przez naszą funkcję konwersji, który jest nazywany Lisp-style: (nd). Następnie łączymy to razem z bspacją i zwracamy .

Druga zamiana: numery linii

Wszystko, co pozostało do konwersji, to numery linii na początku linii.

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

Wyrażenie regularne dopasowuje ciąg cyfr na początku wiersza. Ponownie używamy funkcji zwrotnej; tym razem nwystarczy sama funkcja konwersji , ponieważ całe dopasowanie (pierwszy argument a) to liczba, którą chcemy przekonwertować.

Ponieważ jest to ostatnie wyrażenie w programie, Pip automatycznie drukuje wynik. -nFlag oddziela listę wyników z nowej linii.

DLosc
źródło