Vendredi: Renuméroter ma liste BASIC du spectre ZX

15

Le premier langage de programmation auquel j'ai été exposé était Sinclair BASIC . Comme de nombreux dialectes BASIC, il nécessite que toutes les lignes de code source soient numérotées .

En conséquence, l'utilisation de la GO TOcommande était idiomatique et saute l'exécution au numéro de ligne donné (pas d'étiquettes).

Il existe également une GO SUBcommande associée qui peut être utilisée comme un appel de fonction rudimentaire. Encore une fois, l'exécution passe au numéro de ligne donné, mais lorsqu'une RETURNcommande est atteinte, l'exécution revient à l'instruction suivante après le GO SUB.

De même, la RUNcommande redémarrera l'exécution du programme sur la ligne donnée.

Quiconque a passé du temps dans un interprète BASIC numéroté en ligne aura appris à utiliser un schéma de numérotation avec des lacunes. C'est pour qu'il soit plus facile d'insérer de nouvelles lignes de code. Cependant, même dans ce cas, vous pouvez toujours avoir besoin d'insérer de nouvelles lignes entre des lignes numérotées consécutivement.


Étant donné une liste BASIC numérotée en ligne en entrée, sortez le même programme mais renuméroté de sorte que les numéros de ligne commencent à 10 et incrémentent par étapes de 10. La liste d'entrée peut avoir GO TOou des GO SUBcommandes, de sorte que les numéros associés à ceux-ci doivent également être ajustés.

  • GO TOet les GO SUBcommandes sont sur leurs propres lignes ou à la fin des IF THENlignes. Son sûr ^(\d+) .*GO (TO|SUB) (\d+)$est suffisant pour correspondre à de telles lignes. Ces commandes entre guillemets doivent être ignorées.

  • RUNles commandes seront toujours sur leurs propres lignes. Dans ce cas, un numéro de ligne est facultatif. S'il est manquant, alors l'interpréteur démarre simplement en haut du programme.

  • Si une commande GO TO, GO SUBou fait RUNréférence à une ligne inexistante, elle passera à la place à la ligne définie suivante. Votre entrée doit y faire face et s'assurer que ces références de ligne sont fixes afin qu'elles pointent vers la ligne correcte. Le comportement peut être indéfini si un numéro de ligne après la fin du programme est indiqué dans l'une de ces commandes.

  • Les numéros de ligne seront toujours des entiers positifs de 1 à 9999 (selon le manuel). Cela signifie que les programmes d'entrée n'auront jamais plus de 999 lignes.

  • Les lignes d'entrée seront toujours numérotées dans l'ordre croissant numérique.

  • Aux fins de ce défi, les listes d'entrées ne contiendront que de l'ASCII imprimable. Vous n'avez pas à vous soucier du jeu de caractères ZX. Cela dit, si votre entrée est effectivement écrit dans ZX BASIC ou approprié z80 montage / code machine (et il y a des émulateurs sur là - bas ), vous pouvez choisir pour votre entrée à coder dans le caractère ZX jeu à la place.

  • Vous ne pouvez utiliser aucune bibliothèque ou utilitaire de renumérotation spécialement conçu à cet effet.

Exemple d'entrée:

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"

Exemple de sortie:

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"

Je voulais créer un lien vers un manuel ZX BASIC. Le meilleur que j'ai pu trouver semble être http://www.worldofspectrum.org/ZXBasicManual/index.html mais cela semble être un lien mort. La machine de renvoi a cependant une copie .

Traumatisme numérique
la source
7
Félicitations également pour avoir posé la 5000e question!
FryAmTheEggman
1
Temps de nostalgie - mon premier PC était un Spectrum 48K et l'un de mes premiers programmes d'assemblage était un renuméroteur
edc65
2
@ edc65 Avez-vous toujours votre code d'assemblage renuméroter? Si c'est le cas, vous serez le bienvenu pour le poster comme réponse!
Digital Trauma
1
Le cas de test doit inclure au moins un goto / gosub dans un littéral de chaîne.
Peter Taylor
1
J'ai trouvé une mention: le ZX81 autorise les GOTO et les GOSUB calculés comme dansGOTO 100 + A*10 , et l' annexe C du ZX Spectrum Manual répertorie GO TOcomme acceptant une expression numérique (pas de restriction aux constantes). Voici une discussion sur les mérites de calcul GOTOsur ZX80 et ZX81. BTW, je ne sais pas pourquoi l'espace a été ajouté dans la version Spectrum.
Toby Speight

Réponses:

5

JavaScript (ES6) 177

Modifier Ajout de l'analyse (coûteuse) du prochain numéro de ligne valide

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

TESTER

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
la source
1
Cela semble bon. My +1 stands :)
Digital Trauma
2

Perl 6, 147 145 144 144 142 octets

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

Cela peut probablement être joué un peu plus.

Étendu

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} } 
            )
}
Raccourcis clavier
la source
aucune raison d'utiliser la méthode .min. utiliser à la {min %line-map.keys».Num.grep:*>=$1place
Ven
1

Visual Basic pour Applications, 288 octets

Je n'ai pas pu résister à donner une solution dans un dialecte BASIC. Fonctionne probablement avec Visual Basic 6 / .NET ou d'autres variantes modernes avec des modifications mineures.

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

J'ai utilisé beaucoup de variables à une lettre pour plus de concision. De plus, j'ai supprimé tous les espaces blancs inutiles (VBE les étend automatiquement lors de l'importation). Le nombre d'octets est pour le fichier .BAS final, avec CHR (10) comme nouvelle ligne.

Le sous-programme, qui peut être invoqué depuis la fenêtre immédiate de VBE, ouvre un programme Sinclair BASIC (le premier paramètre est le chemin vers un fichier ASCII - avec CHR (10) en tant que nouvelle ligne - contenant le programme), renuméroter les lignes et écrire les résultats dans une variable Variant (deuxième paramètre).

L'idée est d'itérer sur tous les numéros de ligne source possibles, dans l'ordre croissant, et pour chacun, de remplacer à la fois tous les numéros de ligne correspondants ainsi que GO TO, GO SUBetRUN les références avec le prochain numéro de ligne cible disponible. En utilisant cette approche, nous n'avons besoin d'aucune sorte de table de traduction. Le numéro de ligne cible est incrémenté chaque fois qu'une correspondance dans le numéro de ligne source est trouvée, de sorte que les références de ligne "incorrectes" sont ajustées automatiquement au numéro valide suivant. Les caractères de nouvelle ligne sont utilisés comme marqueurs de début et de fin de ligne, et un CHR (0) - jamais utilisé dans le programme car il n'est pas imprimable - est utilisé comme marqueur temporaire, pour éviter de renuméroter plusieurs fois la même ligne.

Quelques remarques:

  • Par souci de concision, nous utilisons la plus petite chaîne possible pour une correspondance avec les instructions sautantes. En utilisant la fin de ligne sur nos chaînes de recherche, nous ne courons pas le risque d'inclure des occurrences citées ou des fonctions utilisateur (qui utilisent toujours des parenthèses dans Sinclair). GO TOnécessite une chaîne plus grande en raison de la FOR ... TOconstruction (par exemple, comparer 50 FOR X=AGO TO 100et 50 GO TO 100)

  • Le code ne prend pas en charge les instructions du formulaire GO TO200(sans espace), bien que le manuel ZX implique qu'il s'agit d'un code valide sur plusieurs exemples (cela coûterait une douzaine d'octets de plus pour le traiter).

  • Le code ajoute une nouvelle ligne au début et une autre à la fin du programme. Je pourrais nettoyer cela à la fin (une douzaine d'octets de plus) mais je suppose que le ZX ignorerait probablement les lignes vides.

Ci-dessous, une version plus lisible:

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
la source
BTW, une solution QBasic serait beaucoup plus longue, car QBasic n'a pas de fonction de remplacement de chaîne intégrée pour autant que je me souvienne.
DLosc
Je pense que vous avez raison ... oublié cela
dnep
1

Pip -rn , 63 octets

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

Essayez-le en ligne!

Explication

Installer

L' -rindicateur lit tout stdin et le stocke sous forme de liste de lignes dans la variable locale g. La variable globale test préinitialisée à 10 et la variable globale sest préinitialisée à " ".

Yg

Met la liste des lignes gdans la variable globale y, afin qu'elle soit disponible dans la fonction que nous sommes sur le point de définir.

Fonction de traduction des numéros de ligne

Nous construisons une fonction qui mappe à partir de n'importe quel numéro de ligne dans le schéma de numérotation d'origine (y compris un numéro inexistant) vers le numéro de ligne correspondant dans le nouveau schéma de numérotation.

Supposons que nous ayons ces lignes:

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

Nous voulons mapper 1 à 10, 2-4 à 20 et 5-9 à 30. Si nous avons une liste des numéros de ligne d'origine ( [1; 4; 9]), nous pouvons utiliser une opération de filtrage pour savoir combien de ces nombres sont moins que le numéro de ligne que nous essayons de convertir. Multipliez ce résultat par 10 et ajoutez 10, et nous avons la réponse souhaitée.

Par exemple, lors de la conversion de 9, il y a deux numéros de ligne (1 et 4) inférieurs à 9. 2 * 10 + 10 donne 30. Lors de la conversion de 3, il y a un numéro de ligne (1) inférieur à 3. 1 * 10 + 10 donne 20.

Voici le code (légèrement modifié pour être plus facile à lire):

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

Le premier remplacement: GO TO, GO SUBetRUN

Le reste du programme est une expression unique qui prend get fait quelques remplacements de regex (qui vectorisent, s'appliquent à chaque ligne g).

Voici le premier remplacement:

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

L'expression régulière correspond à l'un des RUN, GO SUBet GO TO, suivi d'un nombre, suivi de la fin de la ligne. Cela garantit qu'il ne correspond pas à l'intérieur des chaînes, ni à RUNun numéro de ligne.

L'ordre des groupes de capture est important. Le premier groupe saisit la commande ( l' un des RUN, GO SUBou GO TO). Le deuxième groupe, s'il est utilisé, capture soit SUBou TO. Nous n'avons pas besoin de capturer cette partie, mais un groupe non capturant nécessiterait des octets supplémentaires. Ensuite, le troisième groupe capture le numéro de ligne.

Nous utilisons une fonction de rappel pour le remplacement. Avec des fonctions de rappel dans Pip, l'intégralité du match est le premier argument a, et les groupes de capture dans l' ordre sont les arguments suivants b, c, det e. Nous avons donc la commande dans le premier groupe, qui entre b, et le numéro de ligne dans le troisième groupe, qui entre d. Le seul changement que nous devons faire est de passer le numéro de ligne grâce à notre fonction de conversion, qui est appelé style Lisp: (nd). Ensuite, nous concaténons cela avec bun espace et le renvoyons.

Deuxième remplacement: numéros de ligne

Il ne reste plus qu'à convertir les numéros de ligne au début des lignes.

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

L'expression régulière correspond à une série de chiffres au début d'une ligne. Encore une fois, nous utilisons une fonction de rappel; cette fois, la fonction de conversion nelle-même est suffisante, car la correspondance entière (premier argument a) est le nombre que nous voulons convertir.

Comme il s'agit de la dernière expression du programme, Pip imprime automatiquement le résultat. Le -ndrapeau sépare la liste des résultats par des sauts de ligne.

DLosc
la source