矿石收音机论坛

 找回密码
 加入会员

QQ登录

只需一步,快速开始

搜索
查看: 2143|回复: 5

自制BASIC语言解释器

[复制链接]
     
发表于 2024-8-11 16:50:09 | 显示全部楼层 |阅读模式
之前看见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
该关键字作用是打印变量、常量或字符串(不能打印表达式的值)。被打印变量需要用空格隔开:
例子:pri  i  0  “1200”
1.2.2,var   
定义变量,变量默认为32位int型
例子:var  i
1.2.3,if   endif   
条件分支语句,使用方法如下:
if  表达式
语句
endif
例如,以下语句判断i的值,i>2则打印i的值
if  i  >  2
pri  i
endif

1.2.4,while endwh  循环语句
使用方法如下:
while  表达式
语句
endwh  
当表达式的值为1,则会进行循环,否则跳出。例如,您可以使用如下指令进入死循环
while  1
endwh

1.2.5,func call ret语句
您可以组合使用这三条语句定义子函数。call指令执行时,会自动将当前指令位置压栈,然后跳转到func位置,执行语句后遇到ret跳转回cal语句后的一条语句。子函数名必须为数字,函数内不能定义局部变量,您可以使用全局变量在主函数和子函数之间传递参数。
例如,您可以使用如下指令定义一个自加函数
var  i
call  1
pri   i
func  1
i  =  i  +  1
ret

程序执行时如果遇到func,则会自动跳转到ret后的语句,例如
var  i
i = 2
func  1
i = 1
ret
pri  i

执行结果是2,因为子函数只能使用call调用,不能顺序执行。因此,您可以使用func ret语句将代码注释掉。
1.2.6,read  write语句
PJ_BASIC中定义了一个大数组mem,您可以利用read  write读取这个数组。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
var  addr
addr  =  4
write  3  3
read  i  3
pri i  
write  addr  321
read  i  addr
pri  i  


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倍左右
     
发表于 2024-8-11 21:22:24 | 显示全部楼层
micropython 和 lua 不比 basic 强点嘛
STM32上很成熟
回复 支持 反对

使用道具 举报

     
 楼主| 发表于 2024-8-12 08:48:30 | 显示全部楼层
JuncoJet 发表于 2024-8-11 21:22
micropython 和 lua 不比 basic 强点嘛
STM32上很成熟

占用的空间大,小容量没法运行。
回复 支持 反对

使用道具 举报

     
 楼主| 发表于 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
回复 支持 反对

使用道具 举报

     
发表于 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的方言
回复 支持 反对

使用道具 举报

     
发表于 2024-8-27 11:53:06 | 显示全部楼层
过去很老的单片机固化了BASIC指令集,但只是听说过,没用过,basic写单片机很好用,我用过PIC的
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入会员

本版积分规则

小黑屋|手机版|矿石收音机 ( 蒙ICP备05000029号-1 )

蒙公网安备 15040402000005号

GMT+8, 2025-4-26 19:46

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表