扩展Tiny BASIC解释器,添加完整的运算能力(比较、逻辑、内置函数)和IO系统(INPUT、键盘读取、VGA输出)。让BASIC真正成为8位电脑的"操作系统"——不仅能做数学计算,还能与用户交互、在屏幕上画图。
上一课的表达式求值器只支持算术运算。要让IF-THEN生效,我们需要比较运算符和逻辑运算符:
def _comparison(self, tokens, pos):
"""比较运算层"""
left, pos = self._expr(tokens, pos)
while pos < len(tokens) and \
tokens[pos][0] == 'OP' and \
tokens[pos][1] in ('=','<','>','<=','>=','<>'):
op = tokens[pos][1]
pos += 1
right, pos = self._expr(tokens, pos)
if op == '=': result = (left == right)
elif op == '<': result = (left < right)
elif op == '>': result = (left > right)
elif op == '<=': result = (left <= right)
elif op == '>=': result = (left >= right)
elif op == '<>': result = (left != right)
left = -1 if result else 0
return left, pos
def _and_op(self, tokens, pos):
"""AND运算层"""
left, pos = self._comparison(tokens, pos)
while pos < len(tokens) and \
tokens[pos] == ('KEYWORD', 'AND'):
pos += 1
right, pos = self._comparison(tokens, pos)
left = left & right # 位与
return left, pos
def _or_op(self, tokens, pos):
"""OR运算层(最顶层)"""
left, pos = self._and_op(tokens, pos)
while pos < len(tokens) and \
tokens[pos] == ('KEYWORD', 'OR'):
pos += 1
right, pos = self._and_op(tokens, pos)
left = left | right # 位或
return left, pos
Tiny BASIC提供几个关键的内置函数:
| 函数 | 语法 | 功能 | 实现说明 |
|---|---|---|---|
| RND | RND(X) | 返回0到X-1的随机整数 | 线性同余伪随机数生成器 |
| ABS | ABS(X) | 绝对值 | 负数取反 |
| SGN | SGN(X) | 符号函数(-1/0/1) | 判断正负零 |
| PEEK | PEEK(addr) | 读取内存地址 | 直接访问8位内存空间 |
| LEN | LEN(A$) | 字符串长度 | 未来扩展 |
PEEK和POKE是8位BASIC最强大的特性——它们让BASIC程序直接读写内存映射I/O,控制硬件:
; PEEK函数实现 - 读取内存地址
; 在8位汇编中的实现
PEEK_FUNC:
; 参数: 16位地址在$24/$25
; 返回: 8位值在A寄存器
LDX #$00
LDA ($24,X) ; 读取地址处的值
RTS
; POKE语句实现 - 写入内存地址
; POKE addr, value
POKE_STMT:
JSR PARSE_ADDR ; 解析地址到$24/$25
JSR PARSE_COMMA ; 跳过逗号
JSR EXPR ; 计算值 → A
LDX #$00
STA ($24,X) ; 写入内存
RTS
; 用POKE控制VGA屏幕颜色
10 REM 改变屏幕背景色
20 POKE &H7800, 65 : REM 第一个字符='A'
30 POKE &H7801, &HF1 : REM 属性=白字蓝底
40 END
; 用PEEK读取键盘
10 REM 等待按键
20 IF PEEK(&HFF03) AND 1 = 0 THEN 20
30 LET K = PEEK(&HFF02)
40 PRINT "KEY CODE: "; K
50 GOTO 20
; 声音输出 - 通过定时器产生频率
10 REM 播放音阶
20 FOR I = 1 TO 8
30 READ F
40 POKE &HFF04, F AND 255
50 POKE &HFF05, F / 256
60 POKE &HFF06, 11
70 FOR D = 1 TO 500 : NEXT D
80 NEXT I
90 DATA 262,294,330,349,392,440,494,523
INPUT让用户在程序运行时输入数据:
def _exec_input(self, tokens):
"""INPUT语句执行"""
i = 0
# 可选的提示字符串
if tokens[i][0] == 'STRING':
print(tokens[i][1], end='')
i += 1
if i < len(tokens) and tokens[i] == ('OP', ';'):
i += 1
else:
print("?", end=' ')
# 读取变量名列表
while i < len(tokens):
if tokens[i][0] == 'VAR':
var_name = tokens[i][1]
value = input() # 在8位上替换为键盘读取
try:
self.variables[var_name] = int(value)
except ValueError:
print("?REDO FROM START")
continue
i += 1
; INPUT语句 - 8位汇编实现
INPUT_STMT:
; 显示提示符"? "
LDA #'?'
JSR PUTCHAR
LDA #' '
JSR PUTCHAR
; 读取一行输入到缓冲区
JSR READ_LINE
; 解析输入为数字
JSR PARSE_NUMBER ; 结果在$24/$25 (16位)
; 存入变量
; 变量地址 = VAR_BASE + var_index * 2
LDA $24
STA $0200,X ; 存入变量表
LDA $25
STA $0201,X
RTS
10 REM 猜数字游戏
20 LET N = RND(100) + 1
30 PRINT "I'm thinking of a number 1-100"
40 INPUT "Your guess? "; G
50 IF G = N THEN 90
60 IF G < N THEN PRINT "Too low!"
70 IF G > N THEN PRINT "Too high!"
80 GOTO 40
90 PRINT "You got it!"
100 END
10 REM 9×9乘法口诀表
20 FOR I = 1 TO 9
30 FOR J = 1 TO I
40 LET P = I * J
50 IF P < 10 THEN PRINT " ";
60 PRINT P; " ";
70 NEXT J
80 PRINT
90 NEXT I
100 END
10 REM 在VGA屏幕上画对角线
20 FOR I = 0 TO 24
30 LET ADDR = &H7800 + I * 80 + I
40 POKE ADDR, 42 : REM '*' 的ASCII码
50 POKE ADDR+1, &HE4 : REM 黄色亮红底
60 NEXT I
70 END
10 REM 简易音乐播放器
20 READ N
30 IF N = 0 THEN END
40 POKE &HFF04, N AND 255
50 POKE &HFF05, N / 256
60 POKE &HFF06, 11 : REM 使能+循环+/8预分频
70 FOR D = 1 TO 300 : NEXT D
80 POKE &HFF06, 0 : REM 关闭定时器
90 GOTO 20
100 DATA 262,262,294,330,330,294,262,262
110 DATA 247,220,220,247,262,262,247,247
120 DATA 0
扩展BASIC支持字符串变量(A$)和字符串操作:字符串赋值(LET A$="HELLO")、连接(A$+B$)、比较(A$=B$)、LEN(A$)。在8位实现中,字符串存储在专用的字符串池中,变量表存储字符串指针和长度。
实现ON X GOTO 100,200,300语句:根据X的值跳转到不同的行号。这是BASIC的"switch"语句。在8位汇编中,行号列表存储为连续的16位地址,用X作为变址。
实现数据语句:DATA定义常量列表,READ逐个读取,RESTORE重置读取指针。这是BASIC特有的初始化机制——程序中内嵌数据,运行时读取。注意DATA语句不执行,只在程序区中占位。
为解释器添加完善的错误处理:语法错误(?SYNTAX ERROR)、除零错误(?DIVISION BY ZERO)、行号不存在(?UNDEF'D STATEMENT)、栈溢出(?OUT OF MEMORY)。每个错误显示错误类型和出错的行号。
你扩展了BASIC的运算和IO能力!这包括:
BASIC现在可以做真正的交互式程序了——游戏、工具、甚至简单的应用!