声明

为提高教学质量,我所在的学院正在筹划编写C语言教材。《用C语言写解释器》系列文章经整理后将收入书中“综合实验”一章。因此该系列的文章主要阅读对象定为刚学完C语言的学生(不要求有数据结构等其它知识),所以行文比較罗嗦,请勿见怪。本人水平有限,如有描写叙述不恰当或错误之处请指教!特此声明。

起因

近期,我们学院老师联系我,希望我能提供一段用 C 语言编写的 BASIC 解释器,用于 C 语言课程设计教学。我前段时间也正好着迷于“语言”本身,本就有打算写一个解释器,这下正中我下怀,于是欣然接受。

曾经在图书馆看过梁肇新的《编程高手箴言》,第四章“编程语言的运行机理”中就包括了一段 C 语言编写的 BASIC 解释器代码,但代码好像并不完整(我翻了好几遍,都没发现函数 get_token 的实现代码);再者,这次的代码还有其它用处,不宜牵涉版权问题;最后的原因是我有“想自己编码”的冲动 ^_^。综上所述,我要从零開始用 C 语言来编写一个 BASIC 解释器。

前置知识

1. 要编写解释器,首先就要明确什么是解释器(具体的解释请參看维基百科:http://zh.wikipedia.org/zh-cn/解释器)。盗用《编程高手箴言》里的话:解释程序就是一个字符串的解释器(P165 解释语言的原理)。所以,假设仅仅是为我个人编写的话,我宁可会借助 lex & yacc 甚至 perl,而不会纯粹用 C 语言来写。

2. 在起因中已经提过,这个程序会在学弟学妹们学完 C 语言后作为综合实验。因此须要你熟悉 C 语言的语法、单链表加入/删除节点等操作以及栈的概念(这些内容大部分都能在 C 语言的教材中找到),一些相对冷僻的技术(比如 setjmp/longjmp)则不会出如今程序中。

关于语言

我在《编程和语言之我见》一文中提过,编程是一个非常宽泛的概念。从某种意义上来说全部的软件都是一种特定的语言,但依据程序本身的灵活性能够分为“硬编码”、“可配置”、“可控制”和“可编程”四类(详见《四类程序》)。假设一个程序的灵活性达到了“可编程”,它的配置文件就能够被看作一种“编程语言”,而该程序本身也就是一个“解释器”。

要做到“可编程”,程序至少应该具备“输入/输出”、“表达式运算”、“内存管理”和“按条件跳转”四个功能(详见《用DOS批处理来做数字图像处理》)。这正好相应了冯·诺依曼计算机的结构:以运算器和控制器为中心,输入/输出设备与存储器之间的传输数据都要经过运算器。以下具体介绍各个部分。

我们的目标

我们要编写解释器,自然也逃不出上面的条条例例。语法就參考 BASIC,但由于是设计我们自己的语言,当然能够依据个人兴趣进行“添油加醋”(比方表达式里提供神往已久的阶乘运算 ^_^)。以下是一段 BASIC 的演示样例代码(example.bas):

0009 N = 0
0010 WHILE N < 1 OR N > 20
0011 PRINT "请输入一个1-20之间的数"
0012 INPUT N
0013 WEND
0020 FOR I = 1 TO N
0030 L = "*"
0040 FOR J = 1 TO N - I
0050 L = " " + L
0060 NEXT
0070 FOR J = 2 TO 2 * I - 1 STEP 2
0080 L = L + "**"
0090 NEXT
0100 PRINT L
0110 NEXT
0120 I = N - 1
0130 L = ""
0140 FOR J = 1 TO N - I
0150 L = L + " "
0160 NEXT
0170 FOR J = 1 TO ((2*I) - 1)
0180 L = L + "*"
0190 NEXT
0200 PRINT L
0210 I = I - 1
0220 IF I > 0 THEN
0230 GOTO 130
0240 ELSE
0250 PRINT "By redraiment"
0260 END IF

BASIC 语法要求行首提供一个 1->9999 之间的数字作为该行的行号(当前行的行号不小于上一行的行号),供 GOTO 语句跳转时调用。BASIC 的语法比 C 严格,这不仅能够减少代码的复杂性还使语言本身更易学。上面的代码差点儿相同涵盖了我们须要实现的全部功能,假设能被正确解析,你将看到以下的运行效果:

以下来依次讨论要实现的功能。

输入/输出(IO)

通过输入/输出来和外部程序或人交互,这是脱离“硬编码”的最基本要求。输入/输出也是非常抽象的概念,它并不局限于标准输入输出端(键盘、显示器等),也能够通过文件、互联网等方式获得数据(因此 C 语言中除了 scanf、printf 等,事实上 #include 指令也算是一种 IO 操作)。我们这个程序并不强调 IO,因此仅仅要求实现 INPUT 和 PRINT 两条指令,分别用于从键盘输入数据和打印到屏幕。指令的格式例如以下:

INPUT var[, var ...]
  当中 var 代表变量名(下同),变量之间用逗号隔开。
  作用:从键盘获得一个或多个值,并赋值到相应的变量。同一时候输入多个变量时,输入的每一个数之间用空格、回车或制表符隔开。
  比如:INPUT A, B, C
PRINT expression[, expression ...]
  当中 expression 为表达式(下同),表达式之间用逗号隔开。
  作用:对表达式求值,将结果输出到屏幕并换行。假设有多个表达式,表达式之间用制表符(/t)隔开。
  比如:PRINT I * 3 + 1, (A + B)*(C + D)

表达式运算

在《DOS》中我称呼它为“算术运算”。但对于计算机来说,“算术运算”不仅包括诸如“四则运算”等算术运算,还包括“关系运算”和“逻辑运算”。为了避免歧义,在此就改称它为“表达式运算”。“表达式运算”是整个程序的核心,地位相当于计算机的运算器。在我们的程序中,须要实现以下几种运算符:

符号 名称 优先级 结合性
( 左括号 17 left2right
) 右边 17 left2right
+ 12 left2right
- 12 left2right
* 13 left2right
/ 13 left2right
% 取模 13 left2right
^ 求幂 14 left2right
+ 正号 16 right2left
- 负号 16 right2left
! 阶乘 16 left2right
> 大于 10 left2right
< 小于 10 left2right
= 等于 9 left2right
<> 不等于 9 left2right
<= 不大于 10 left2right
>= 不小于 10 left2right
AND 逻辑与 5 left2right
OR 逻辑或 4 left2right
NOT 逻辑非 15 right2left

内存管理

在我们这个迷你型的解释器中,能够不用考虑内存空间动态分配的问题,仅仅要实现简单的变量管理。我们默认提供 A-Z 26个可用的弱类型变量(能够任意赋值为整数、浮点数或字符串)。变量要求先赋值才干使用,否则就会提示变量不可用(因此演示样例代码中第一行就是给 N 赋值为 0)。赋值语句的格式为

[LET] var = expression
  当中 LET 是可选的keyword。BASIC 中不同意出现 var1 = var2 = expression 这种赋值语句,
  由于在表达式中“=”被翻译为“等于”,所以赋值符合没有出如今上面的表格中。
  作用:计算表达式的值,并将结果赋值给变量 var。
  比如:I = (123 + 456) * 0.09

按条件跳转

假设设计一门最简洁的语言,那它的控制语句就仅仅需提供像汇编中的 JMP、JNZ 等依据条件跳转的语句就可以,通过它们的组合就可以模拟出 IF、WHILE、FOR、GOTO 等控制语句。但 BASIC 作为一门高级语言,须要提供更高层、更抽象的语句。我们将会实现以下四条语句:

1)
GOTO expression
  当中 expression 是一个数值表达式,计算结果必须为可用的行号。由于它是一个表达式,通过动态计算就能模拟子程序调用。
  作用:无条件跳转到指定行。
  比如:GOTO 120+10
2)
IF expression THEN
sentence1
[ELSE
sentence2]
END IF
  当中 sentence 是语句块(下同),包括一条或多条可运行语句。ELSE 为可选部分。
  作用:分支结构。但表达式值为真(数字不等于0或者字符串不为空)时运行语句块1;否则,有 ELSE 语句块时运行 ELSE 语句块。
  比如:
IF 1=1 THEN
PRINT "TRUE"
ELSE
PRINT "FALSE"
END IF
3)
FOR var = expression TO expression [STEP expression]
sentence
NEXT
  全部表达式均为数值表达式。STEP 为可选部分,为迭代器的步长。步长表达式的值不同意为 0。
  作用:循环迭代结构
  比如:
FOR I = 1 TO 10 STEP 3
PRINT I
NEXT
4)
WHILE expression
sentence
WEND
  作用:迭代运行语句块,直到表达式的值为假。
  比如:
WHILE N < 10
N = N + 1
WEND

很多其它细节

  1. BASIC 的源代码不区分大写和小写;
  2. 本程序在实现中没有处理字符转义,因此无法无法输出双引號。在介绍全然部源代码后,假设你有兴趣能够尝试自行完好;
  3. 本程序相同没有考虑凝视(REM keyword)。事实上这非常easy,但这个问题相同留给你来处理 ^_^;
  4. 或许你也会有兴趣加入 GOSUB 和 RETURN keyword,让子程序功能从 GOTO 中解放出来。

总结

这一篇主要介绍了我们编写的解释器要实现的功能,接下来会有一系列文章来逐步具体介绍解释器的实现。在下一篇中会首先介绍解释器的核心部分——表达式求值。请关注《用C语言写解释器(二)》。


版权声明

请尊重原创作品。转载请保持文章完整性,并以超链接形式注明原始作者“redraiment”和主网站地址,方便其它朋友提问和指正。

联系方式

我的邮箱,欢迎来信(redraiment@gmail.com
我的Blogger(子清行):http://redraiment.blogspot.com/
我的Google Sites(子清行):https://sites.google.com/site/redraiment
我的CSDN博客(梦婷轩):http://blog.csdn.net/redraiment
我的百度空间(梦婷轩):http://hi.baidu.com/redraiment

用C语言写解释器(一)——我们的目标的更多相关文章

  1. 用VC编译lua源码,生成lua语言的解释器和编译器

    用VC编译lua源码,生成lua语言的解释器和编译器 1.去网址下载源码 http://www.lua.org/download.html 2.装一个VC++,我用的是VC6.0 3.接下来我们开始编 ...

  2. 对SNL语言的解释器实现尾递归优化

    对于SNL语言解释器的内容可以参考我的前一篇文章<使用antlr4及java实现snl语言的解释器>.此文只讲一下"尾递归优化"是如何实现的--"尾递归优化& ...

  3. python为什么是高级语言和解释型编程语言?它是如何粘合其它语言写的代码的?

    学习python之初,不知道大家对于python有没有疑惑,应当是有的.这里我整理出来了自己的一些疑惑,供大家参考. 为什么python是高级程序设计语言 ​ Java,C,C++这些语言是高级语言, ...

  4. 自己用C语言写dsPIC / PIC24 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). HyperBootlo ...

  5. 自己用C语言写单片机PIC18 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). HyperBootlo ...

  6. 自己用C语言写单片机PIC16 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 为什么自己写bootl ...

  7. C语言写的流氓关机程序及破解

    记得大二刚开始接触电脑的那个时候,偶尔会弹出一个强制关机的窗口,当时没有办法,如下: 现在看来只是一个小程序而已,用C语言编写的: #include<windows.h> int main ...

  8. php调用一个c语言写的接口问题

    用php调用一个c语言写的soap接口时,遇到一个问题:不管提交的数据正确与否,都无法请求到接口 1.用php标准的soap接口去请求 2.拼接xml数据去请求 以上两种方式都不正确 解决办法:php ...

  9. PIC12F629帮我用C语言写个程序,控制三个LED亮灭

    http://power.baidu.com/question/240873584599025684.html?entry=browse_difficult PIC12F629帮我用C语言写个程序,控 ...

随机推荐

  1. 托管到GitHub

    如何把项目托管到GitHub iOS开发拓展篇——如何把项目托管到GitHub 说明:本文主要介绍如何把一个OC项目托管到Github,重操作轻理论. 第一步:先注册一个Github的账号,这是必须的 ...

  2. hdu5336 Walk Out

    hdu5336 Walk Out 题意是:入口:地图的左上角,出口,地图的右上角,求所经过的路径的二进制数最小 照着题解敲了一遍 思路是:首先 二进制 的 位数 越小 越好,其次 二进制的前缀零越多 ...

  3. Android ListView条目全选功能,不用checkbox实现!

    大家好,翻了翻曾经的笔记,发现了一个我特别标记的功能,那就是ListView全选功能,顿时想起了我那个时候苦逼的生涯,因为我大学机械出身,大学毕业了都不知道什么叫代码,在58干了一段销售.实在是干不下 ...

  4. perl lwp编码

    $var= $response->content; $var= $response->decoded_content;

  5. UVA 10718 Bit Mask 贪心+位运算

    题意:给出一个数N,下限L上限U,在[L,U]里面找一个整数,使得N|M最大,且让M最小. 很明显用贪心,用位运算搞了半天,样例过了后还是WA,没考虑清楚... 然后网上翻到了一个人家位运算一句话解决 ...

  6. UVA 10201 Adventures in Moving - Part IV(dp)

    Problem A: Adventures in Moving - Part IV To help you move from Waterloo to the big city, you are co ...

  7. 【UML九种图系列】之如何利用三层来绘制类图、时序图?

    UML并发视图:实体之间行为的交互,是动态.分为:时序图.协作图.状态图.活动图 一.时序图简述: 时序图(Sequence Diagram):描述对象之间的交互行为,按照时间顺序排列. 元素: 角色 ...

  8. HDU 1030 Delta-wave 数学题解

    给出一个数字塔,然后求沿着数字之间的边走,给出两个数字,问其路径最短的长度是多少. 看似一条搜索题目,只是有一定做题经验的人都知道,这个不是搜索题,直接搜索肯定超时. 这个是依据规律计算的数学题目. ...

  9. 简化ui文件转换写法

    在命令行敲一串长的命令.枯燥麻烦. #coding:utf-8 import sys import os import subprocess if len(sys.argv) == 2: #节省输入, ...

  10. Android插件化开发---执行未安装apk中的Service

    欢迎各位增加我的Android开发群[257053751​] 假设你还不知道什么叫插件化开发.那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从总体角度分析了一下 ...