周五的返程:重新编号我的ZX Spectrum BASIC列表


15

我接触到的第一种编程语言是Sinclair BASIC。像许多BASIC方言一样,它要求对所有源代码行进行编号

结果,该GO TO命令的使用是惯用的,并且将执行跳转到给定的行号(无标签)。

还有一个相关的GO SUB命令,可以用作基本功能调用。同样,执行跳到给定的行号,但是当RETURN到达命令时,执行跳回到下一个指令GO SUB

同样,该RUN命令将在给定的行重新开始程序执行。

在行号BASIC解释器中花了任何时间的任何人都将学会使用带空格的编号方案。这样可以更轻松地插入新的代码行。但是即使那样,您仍然可能发现自己需要在连续编号的行之间插入新行。


给定行号为BASIC的列表作为输入,输出相同的程序,但重新编号,以使行号从10开始并以10递增。输入列表可能具有GO TOGO SUB命令,因此也必须调整与它们相关的数字。

  • GO TOGO SUB命令位于单独的行或行的末尾IF THEN。可以肯定地说^(\d+) .*GO (TO|SUB) (\d+)$,足以匹配这样的界线。这些带引号的命令应被忽略。

  • RUN命令将始终保持独立。在这种情况下,行号是可选的。如果缺少它,那么解释器仅从程序顶部开始。

  • 如果一个GO TOGO SUBRUN命令引用不存在的线,那么这反而跳转到下一个定义的线。您的输入需要处理此问题,并确保所有此类行引用均已修复,以便它们指向正确的行。如果在这些命令之一中给出了程序结束后的行号,则行为可能不确定。

  • 行号将始终为1到9999之间的正整数(根据手册)。这意味着输入程序永远不会超过999行。

  • 输入行将始终以数字升序编号。

  • 出于此挑战的目的,输入列表将仅包含可打印的ASCII。您无需担心ZX字符集。话虽如此,如果您的条目实际上是写在ZX BASIC或适当的Z80汇编/机器代码(也有模拟器那里),那么你可以选择你的投入在编码ZX字符集来代替。

  • 您不得使用专门为此目的量身定制的任何重新编号库或实用程序。

输入示例:

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"

示例输出:

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"

我想链接到ZX BASIC手册。我能找到的最好的似乎是http://www.worldofspectrum.org/ZXBasicManual/index.html,但这似乎是一个无效的链接。那个回程机器却有副本


7
也要问第5000个问题!
FryAmTheEggman '16

1
怀旧的时间-我的第一台PC是Spectrum 48K,而我的第一个汇编程序是一个重新编号器
edc65

2
@ edc65您还有重新编号的汇编代码吗?如果是这样,非常欢迎您将其发布为答案!
Digital Trauma

1
测试用例应在字符串文字中至少包含一个goto / gosub。
彼得·泰勒,

1
我发现有一个提及:ZX81允许像中的那样计算GOTO和GOSUBGOTO 100 + A*10,并且ZX Spectrum手册列表的附录CGO TO接受数字表达式(不限制常量)。这是在ZX80和ZX81 上计算的优点的讨论GOTO。顺便说一句,我不知道为什么在Spectrum版本中增加了空间。
Toby Speight

Answers:


5

JavaScript(ES6)177

编辑添加了(昂贵的)扫描以查找下一个有效行号

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

测试

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
看起来不错。我的+1站:)
Digital Trauma

2

Perl 6中,147个 145 144 142字节

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

这可能会打得更多。

展开式

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

没有理由使用该方法.min。使用{min %line-map.keys».Num.grep:*>=$1代替
法师

1

Visual Basic应用程序,288字节

我无法抗拒使用BASIC方言提供解决方案。可能与Visual Basic 6 / .NET或其他经过细微更改的现代变体一起使用。

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

为了简洁起见,我使用了很多一个字母的变量。另外,我禁止所有不必要的空格(VBE在导入时会自动扩展它们)。字节数用于最终的.BAS文件,其中CHR(10)作为换行符。

可以从VBE立即窗口调用的子例程打开Sinclair BASIC程序(第一个参数是ASCII文件的路径-带有该程序的CHR(10)作为换行符),对行重新编号,并将结果写入Variant变量(第二个参数)。

这个想法是对所有可能的源行号,升序进行迭代,并且对于每个源行号,立即替换所有匹配的行号以及GO TOGO SUBRUN用下一个可用的目标行号进行引用。使用这种方法,我们不需要任何类型的转换表。每次找到源行号匹配项,目标行号就会递增,因此“错误”的行引用会自动调整为下一个有效号。换行符用作行首和行尾的标记,而CHR(0)-暂时不可在程序中使用,因为它不可打印-用作临时标记,以避免多次对同一行重新编号。

一些说明:

  • 为简洁起见,我们使用较小的字符串与跳跃语句进行匹配。在我们的搜索字符串上使用行尾,不会冒包括引号出现或用户函数(在Sinclair中始终使用括号)的风险。GO TO由于FOR ... TO构造原因,需要更大的字符串(例如compare 50 FOR X=AGO TO 10050 GO TO 100

  • 该代码不支持格式GO TO200(无空格)的语句,尽管ZX手册暗示它在几个示例中都是有效的代码(处理该代码将花费更多的字节)。

  • 该代码在程序的开头和结尾添加了一个换行符。我可以在最后清理(十多个字节),但是要知道ZX可能会忽略空行。

下面是一个更具可读性的版本:

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

顺便说一句,一个QBasic解决方案会更长一些,因为据我所知,QBasic没有内置字符串替换功能。
DLosc

我认为你是对的...忘了那个
dnep

1

-rn,63字节

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

在线尝试!

说明

设定

-r标志读取所有stdin并将其存储为局部变量中的行列表g。全局变量t预初始化为10,全局变量s预初始化为" "

Yg

将行列表g插入全局变量y,以便在我们将要定义的函数中使用它。

行号转换功能

我们构造了一个函数,该函数将从原始编号方案中的任何行号(包括不存在的行号)映射到新编号方案中的相应行号。

假设我们有以下几行:

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

我们想要映射1到10、2-4到20和5-9到30。如果我们有原始行号([1; 4; 9])的列表,则可以使用过滤操作来找出这些行号中有多少个而不是我们要转换的行号。将该结果乘以10并加10,我们得到了所需的答案。

例如,转换9时,有两个小于9的行号(1和4)。2 * 10 + 10得到30。转换3时,有一个小于3的行号(1)。1 * 10 + 10给出20。

这是代码(稍作修改以便于阅读):

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

首先更换:GO TOGO SUB,和RUN

程序的其余部分是一个单一表达式,它接受g并执行几次正则表达式替换(向量化,应用于其中的每一行)。g)。

这是第一个替代品:

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

正则表达式匹配RUNGO SUB和中的任何一个,GO TO后跟一个数字,然后是行尾。这样可以确保它在字符串内不匹配,也不匹配RUN行号。

捕获组的顺序很重要。第一组捕获命令(之一RUNGO SUBGO TO)。如果使用第二组,则捕获SUBTO。我们不需要捕获这部分,但是不捕获的组将需要额外的字节。然后,第三组捕获行号。

我们使用回调函数进行替换。随着匹普回调函数,整场比赛是第一个参数a,而为了捕获组是后续参数bcd,和e。因此,我们在第一组中输入了命令,b在第三组中输入了行号d。我们唯一需要做的更改是通过我们的转换函数(称为Lisp样式:)传递行号(nd)。然后我们将其与b和并返回它。

第二次替换:行号

剩下要转换的是行首的行号。

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

正则表达式在行的开头匹配一系列数字。同样,我们使用回调函数;这次,转换函数n本身就足够了,因为整个匹配项(第一个参数a)就是我们要转换的数字。

由于这是程序中的最后一个表达式,因此Pip会自动打印结果。该-n标志用换行符分隔结果列表。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.