Retrocesso sexta-feira: renumere minha listagem BASIC do espectro ZX

15

A primeira linguagem de programação à qual fui exposto foi o Sinclair BASIC . Como muitos dialetos BASIC, ele exige que todas as linhas de código-fonte sejam numeradas .

Como resultado, o uso do GO TOcomando era idiomático e salta a execução para o número da linha fornecido (sem rótulos).

Também existe um GO SUBcomando relacionado que pode ser usado como uma chamada de função rudimentar. Novamente, a execução salta para o número de linha especificado, mas quando um RETURNcomando é alcançado, a execução retorna para a próxima instrução após a GO SUB.

Da mesma forma, o RUNcomando reiniciará a execução do programa na linha especificada.

Qualquer pessoa que tenha passado algum tempo em um intérprete do BASIC numerado por linha aprenderá a usar um esquema de numeração com lacunas. Isso é mais fácil para inserir novas linhas de código. No entanto, mesmo assim, você ainda pode precisar inserir novas linhas entre as linhas numeradas consecutivamente.


Dada uma listagem BASIC numerada por linha como entrada, produza o mesmo programa, mas renumerado, de modo que os números de linha iniciem em 10 e aumentem em etapas de 10. A listagem de entrada pode ter GO TOou GO SUBcomandos, portanto, os números associados a eles também devem ser ajustados.

  • GO TOe os GO SUBcomandos estão em suas próprias linhas ou no final das IF THENlinhas. É seguro dizer que ^(\d+) .*GO (TO|SUB) (\d+)$é suficiente para corresponder a essas linhas. Esses comandos entre aspas devem ser ignorados.

  • RUNOs comandos sempre estarão em suas próprias linhas. Nesse caso, um número de linha é opcional. Se estiver faltando, o intérprete simplesmente inicia na parte superior do programa.

  • Se um GO TO, GO SUBou RUNreferências de comando uma linha inexistente, então ele vai em vez ir para a próxima linha definida. Sua entrada precisa lidar com isso e garantir que essas referências de linha sejam corrigidas para que aponte para a linha correta. O comportamento pode ser indefinido se um número de linha após o final do programa for fornecido em um desses comandos.

  • Os números de linha sempre serão inteiros positivos de 1 a 9999 (conforme o manual). Isso significa que os programas de entrada nunca terão mais de 999 linhas.

  • As linhas de entrada sempre serão numeradas em ordem numericamente crescente.

  • Para os fins deste desafio, as listagens de entrada conterão apenas ASCII imprimível. Você não precisa se preocupar com o conjunto de caracteres ZX. Dito isto, se sua entrada for realmente escrita no ZX BASIC ou no código / conjunto de máquinas z80 apropriado (e houver emuladores por ), você poderá escolher que sua entrada seja codificada no conjunto de caracteres ZX .

  • Você não pode usar nenhuma numeração de bibliotecas ou utilitários especificamente personalizados para esse fim.

Exemplo de entrada:

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"

Saída de exemplo:

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"

Eu queria vincular a um manual do ZX BASIC. O melhor que pude encontrar parece ser http://www.worldofspectrum.org/ZXBasicManual/index.html, mas esse parece ser um link morto. A máquina wayback tem uma cópia .

Trauma Digital
fonte
7
Também conrgats em fazer a pergunta 5000!
FryAmTheEggman
1
Tempo Nostalgia - o meu primeiro PC era um Spectrum 48K e um dos meus primeiros programas de montagem era um renumberer
edc65
2
@ edc65 Você ainda tem seu código de montagem de renumeração? Em caso afirmativo, seria muito bem-vindo publicá-lo como resposta!
Digital Trauma
1
O caso de teste deve incluir pelo menos um goto / gosub em uma string literal.
Peter Taylor
1
Encontrei uma menção: o ZX81 permite GOTOs e GOSUBs computados como emGOTO 100 + A*10 , e o Apêndice C do Manual do ZX Spectrum lista GO TOcomo aceitando uma expressão numérica (sem restrição de constantes). Aqui está uma discussão dos méritos dos computados GOTOno ZX80 e no ZX81. BTW, eu não tenho idéia do por que o espaço foi adicionado na versão Spectrum.
precisa saber é o seguinte

Respostas:

5

JavaScript (ES6) 177

Editar Adicionada a varredura (cara) para o próximo número de linha 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`
`

TESTE

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
fonte
1
Parece bom. Meu +1 fica :)
Digital Trauma
2

Perl 6, 147 145 144 142 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}})}

Provavelmente isso pode ser um pouco mais complicado.

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 atalho
fonte
nenhuma razão para usar o método .min. use {min %line-map.keys».Num.grep:*>=$1vez
Ven
1

Visual Basic for Applications, 288 bytes

Eu não pude resistir a dar uma solução em um dialeto BASIC. Provavelmente funciona com o Visual Basic 6 / .NET ou outras variantes modernas com pequenas alterações.

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

Eu usei muitas variáveis ​​de uma letra para concisão. Além disso, suprimi todos os espaços em branco desnecessários (o VBE os expande automaticamente na importação). A contagem de bytes é para o arquivo .BAS final, com CHR (10) como nova linha.

A sub-rotina, que pode ser invocada na janela imediata do VBE, abre um programa Sinclair BASIC (o primeiro parâmetro é o caminho para um arquivo ASCII - com CHR (10) como nova linha - contendo o programa), renumera as linhas e grava os resultados em uma variável Variant (segundo parâmetro).

A idéia é fazer uma iteração em todos os números de linha fonte possíveis, ordem ascendente, e para cada um, substituir de uma só vez todos os números de linha correspondentes, bem como GO TO, GO SUBe RUNreferências com o próximo número de linha de destino disponíveis. Usando essa abordagem, não precisamos de nenhum tipo de tabela de conversão. O número da linha de destino é incrementado toda vez que uma correspondência no número da linha de origem é encontrada, para que as referências de linha "incorretas" sejam ajustadas automaticamente para o próximo número válido. Os caracteres de nova linha são usados ​​como marcadores de início e fim de linha, e um CHR (0) - nunca usado no programa, pois não é imprimível - é usado como um marcador temporário, para evitar renumerar a mesma linha várias vezes.

Algumas observações:

  • Por uma questão de concisão, usamos a menor seqüência possível para uma correspondência com as instruções de salto. Usando o final de linha em nossas cadeias de pesquisa, não corremos o risco de incluir ocorrências citadas ou funções de usuário (que sempre usam parênteses no Sinclair). GO TOrequer uma string maior por causa da FOR ... TOconstrução (por exemplo, compare 50 FOR X=AGO TO 100e 50 GO TO 100)

  • O código não suporta instruções no formulário GO TO200(sem espaço em branco), embora o manual do ZX implique que seja um código válido em vários exemplos (custaria uma dúzia de bytes a mais para lidar com ele).

  • O código adiciona uma nova linha no início e outra no final do programa. Eu poderia limpar isso no final (mais uma dúzia de bytes), mas acho que o ZX provavelmente ignoraria as linhas em branco.

Abaixo, uma versão mais legível:

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
fonte
BTW, uma solução QBasic seria muito mais longa, pois o QBasic não possui uma função de substituição de cadeia incorporada, tanto quanto me lembro.
DLosc
Acho que você está certo ... esqueci disso
DNEP
1

Pip -rn , 63 bytes

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

Experimente online!

Explicação

Configuração

O -rsinalizador lê todo o stdin e o armazena como uma lista de linhas na variável local g. A variável global té pré-inicializada para 10 e a variável global sé pré-inicializada para " ".

Yg

Arruma a lista de linhas gpara a variável global y, para que fique disponível dentro da função que estamos prestes a definir.

Função de conversão de número de linha

Construímos uma função que mapeia de qualquer número de linha no esquema de numeração original (incluindo um inexistente) para o número de linha correspondente no novo esquema de numeração.

Suponha que tenhamos estas linhas:

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

Queremos mapear 1 a 10, 2-4 a 20 e 5-9 a 30. Se tivermos uma lista dos números de linha originais ( [1; 4; 9]), podemos usar uma operação de filtro para descobrir quantos desses números são menos do que o número da linha que estamos tentando converter. Multiplique esse resultado por 10 e adicione 10, e temos a resposta desejada.

Por exemplo, ao converter 9, há dois números de linha (1 e 4) menores que 9. 2 * 10 + 10 fornece 30. Ao converter 3, há um número de linha (1) menor que 3. 1 * 10 + 10 dá 20.

Aqui está o código (ligeiramente modificado para facilitar a leitura):

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

Primeira substituição: GO TO, GO SUBeRUN

O restante do programa é uma expressão única que recebe ge faz algumas substituições de expressões regulares (que vetorizam, se aplicam a cada linha inserida g).

Aqui está a primeira substituição:

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

A regex corresponde a qualquer um de RUN, GO SUBe GO TO, seguida por um número, seguido pelo final da linha. Isso garante que ele não corresponda a cadeias internas nem a RUNum número de linha.

A ordem dos grupos de captura é importante. O primeiro grupo capta o comando (de um RUN, GO SUBou GO TO). O segundo grupo, se usado, captura um SUBou TO. Não precisamos capturar esta parte, mas um grupo que não captura captura exigiria bytes extras. Em seguida, o terceiro grupo captura o número da linha.

Usamos uma função de retorno de chamada para a substituição. Com funções de callback em Pip, todo o jogo é o primeiro argumento a, e os grupos de captura no fim são os argumentos posteriores b, c, d, e e. Portanto, temos o comando no primeiro grupo, que entra b, e o número da linha no terceiro grupo, que entra d. A única mudança que precisamos fazer é passar o número da linha através da nossa função de conversão, que é chamado Lisp-style: (nd). Em seguida, concatenamos isso junto com be um espaço e o devolvemos.

Segunda substituição: números de linha

Tudo o que resta para converter são os números de linha no início das linhas.

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

A regex corresponde a uma sequência de dígitos no início de uma linha. Novamente, usamos uma função de retorno de chamada; dessa vez, a nprópria função de conversão é suficiente, pois a correspondência inteira (primeiro argumento a) é o número que queremos converter.

Como essa é a última expressão do programa, o Pip imprime automaticamente o resultado. O -nsinalizador separa a lista de resultados com novas linhas.

DLosc
fonte