Aline744 发表于 2024-8-11 16:50:09

自制BASIC语言解释器

之前看见PC1500、VIC20等等电脑,对于里面的BASIC语言很感兴趣,于是写了这个简易解释器。现在是靠串口输入程序等串口屏回来了再开发图形界面。https://github.com/PJSDDL/PJ_BASIC
GITHUB不稳定,如果打不开可以喝一杯茶,然后再打开。:(:(:(
下面是README内容:
使用<2000行C代码,开发了一个简洁的BASIC解释器。该解释器具有如下优点:只使用C标准库、能产生报错信息、适合移植嵌入式设备、不用写行号。在STM32上该解释器占用rom<20K(包含CUBEMX标准库占用空间),使用ram<20K,绝大多数STM32芯片都可以轻松运行。下面详细介绍该语言。

(该代码存在不同版本,请以示例代码及注释为准!)

一、语法
PJ_BASIC语句有两种:赋值语句和关键字语句。
1.1,赋值语句
格式如下:
变量=表达式
例如:
i = i + 1
需要注意的是,每个符号之间都要由空格分开,表达式不能以运算符开头:
i = -1-1 (错误,没有空格)
i = -1-1(错误,表达式以运算符开头)
i = 0-1-1(正确)
变量被赋值前,要使用var语句声明,否则会报错。
PJ_BASIC支持以下几种运算符:+-*/><==,其中比较运算符的运算优先级最低,值为0或1,例如如下表达式
2 > 1
1 > 2
前者等于1,后者等于0

1.2,关键字语句
关键字语句由关键字开头,有以下几种:
1.2.1,pri
该关键字作用是打印变量、常量或字符串(不能打印表达式的值)。被打印变量需要用空格隔开:
例子:prii0“1200”
1.2.2,var   
定义变量,变量默认为32位int型
例子:vari
1.2.3,if   endif   
条件分支语句,使用方法如下:
if表达式
语句
endif
例如,以下语句判断i的值,i>2则打印i的值
ifi>2
prii
endif
1.2.4,while endwh循环语句
使用方法如下:
while表达式
语句
endwh
当表达式的值为1,则会进行循环,否则跳出。例如,您可以使用如下指令进入死循环
while1
endwh
1.2.5,func call ret语句
您可以组合使用这三条语句定义子函数。call指令执行时,会自动将当前指令位置压栈,然后跳转到func位置,执行语句后遇到ret跳转回cal语句后的一条语句。子函数名必须为数字,函数内不能定义局部变量,您可以使用全局变量在主函数和子函数之间传递参数。
例如,您可以使用如下指令定义一个自加函数
vari
call1
pri   i
func1
i=i+1
ret
程序执行时如果遇到func,则会自动跳转到ret后的语句,例如
vari
i = 2
func1
i = 1
ret
prii
执行结果是2,因为子函数只能使用call调用,不能顺序执行。因此,您可以使用func ret语句将代码注释掉。
1.2.6,readwrite语句
PJ_BASIC中定义了一个大数组mem,您可以利用readwrite读取这个数组。read命令格式如下:
read常数1(或变量1)常数2(或变量2)
意为将常数2(或变量2)视为数组索引,读取数组mem,结果保存在常数1(或变量1)中
write 命令格式如下:
write常数1(或变量1)常数2(或变量2)
意为将常数2(或变量2)的值,保存在常数1(或变量1)对应的地址中
下面的示例代码展示了两种指令的用法:
var i
i=100
varaddr
addr=4
write33
readi3
pri i
writeaddr321
readiaddr
prii

1.3,关于代码书写
若代码以字符串的形式保存在char basic_prog[]中,字符串中的换行应使用\n表示,双引号"使用转义字符\"表示,字符串换行时用\连接两行代码。一个示例字符串数组basic_prog形式如下:
char basic_prog[] = "\
var flag \n\
flag = 11 \n\
while flag > 1 \n\
    pri flag \n\
endwh \n\
";
每行代码后都要留一个空格,然后以\n\结尾。
若您使用STM32串口输入代码,代码结尾不应有任何符号,代码最后应加一个\0,以提示代码结尾。
var flag
flag = 11
while flag > 1
    pri flag
endwh \0

二、解释器宏定义介绍
在BASIC.h中,有一些宏定义,您可以修改这些定义以节约硬件资源
LIST_LEN:预编译生成代码列表的最大长度
MAX_EXPR_LEN:表达式最大长度
MAX_VAR_NUM:最多可以定义的变量数量
MAX_VAR_LEN:变量最大长度(字符串长度)
MAX_FUN_STACK:函数嵌套最大数量
MEM_SIZE:mem数组尺寸

三、解释器运行原理简介
代码首先经过分词器parser分词,形成预编译代码,然后利用索引计算器index_match计算关键字跳转位置,例如计算if对应的endif的位置,最后使用运行器basic_run执行代码,代码中的表达式由表达式计算器expr计算。
该代码没有优化,如果您对代码进行修改,可以节约更多的空间,代价是牺牲一定的速度。

四、性能测试
运行以下质数运算代码:
pri \"prim\"\n\
var b \n\
var div \n\
var flag \n\
b = 2 \n\
div = 2 \n\
flag = 0 \n\
while b < 10000 \n\
    flag = 1 \n\
    div = 2 \n\
    while div < b / 2 + 1 \n\
      if ( b / div ) * div == b \n\
      flag = 0 \n\
      endif \n\
      div = div + 1\n\
    endwh \n\
    if flag \n\
    pri b \n\
    endif \n\
    b = b + 1 \n\
endwh \n\
pri \"prim_end\"\n\
笔记本电脑上,C语言耗时0.3秒,PJ_BASIC耗时15.6秒,耗时相差40倍左右,STM32上,C语言耗时6.5秒,PJ_BASIC耗时2397秒,耗时相差400倍左右

JuncoJet 发表于 2024-8-11 21:22:24

micropython 和 lua 不比 basic 强点嘛
STM32上很成熟

Aline744 发表于 2024-8-12 08:48:30

JuncoJet 发表于 2024-8-11 21:22
micropython 和 lua 不比 basic 强点嘛
STM32上很成熟

占用的空间大,小容量没法运行。

Aline744 发表于 2024-8-12 09:06:12

JuncoJet 发表于 2024-8-11 21:22
micropython 和 lua 不比 basic 强点嘛
STM32上很成熟

刚才又在51上编译了一遍,占用Program Size: data=15.1 xdata=7586 code=15827

JuncoJet 发表于 2024-8-12 09:16:13

Aline744 发表于 2024-8-12 09:06
刚才又在51上编译了一遍,占用Program Size: data=15.1 xdata=7586 code=15827

哦那估计比 lua 还小一个等级
试试移植lisp哈哈,这个估计特别特别小
毕竟一切函数编程语言都是lsp的方言

mcu51c51 发表于 2024-8-27 11:53:06

过去很老的单片机固化了BASIC指令集,但只是听说过,没用过,basic写单片机很好用,我用过PIC的
页: [1]
查看完整版本: 自制BASIC语言解释器