目录
文章目录
前言
通过开发一门类 Lisp 的编程语言来理解编程语言的设计思想,本实践来自著名的《Build Your Own Lisp》。
环境
- 操作系统:CentOS7
- 编辑器:VIM
- C 语言标准:C99
实现交互式 Shell
交互式 Shell,这种系统也被叫做 REPL(Read-Evaluate-Print Loop,读取-求值-输出-循环),这种技术被广泛地应用在各种编程语言的解释器中,例如 Python。我们称这种模式为交互提示。
在编写一个完整的 REPL 之前,我们先实现一个简单的程序:读取用户的输入,在程序内部进行处理,然后返回一些信息给用户。
- 使用一个永循环等待用户输入并打印信息。
- 使用 stdio.h 中的 fgets 函数获取用户输入的内容,这个函数可以一直读取直到遇到换行符为止。
- 声明一个固定大小的数组缓冲区来存储用户的输入。
- 一旦获取到用户输入的字符串,就可以使用 printf 将它打印到命令行中。
创建一个 parsing.c 源文件,意为解析器:
#include <stdio.h>
/* Declare a buffer for user input of size 2048.
* 定义了一个拥有 2048 个字符长度的全局数组。这个数组中存储的数据可以在程序的任何地方获取到。
* 我们会把用户在命令中输入的语句保存到这里面来。
* static 关键字标明这个数组仅在本文件中可见。
*/
static char input[2048]; // 2Kb
int main(int argc, char *argv[]) {
/* 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。 */
puts("Lispy Version 0.1");
puts("Press Ctrl+c to Exit\n");
/* In a never ending loop */
while(1) {
/* Output our prompt.
* 把字符串写入到指定的 stream 中,但不包括空字符。
* 与 puts 函数区别是 fputs 不会在末尾自动加换行符。
*/
fputs("lispy> ", stdout);
/* Read a line of user input of maximum size 2048.
* 使用 fgets 函数来获取用户在命令行中输入的字符串。
* 这两个函数都需要指定写入或读取的文件。在这里,我们使用 stdin 和 stdout 作为输入和输出。
* 这两个变量都是在 <stdio.h> 中定义的,用来表示向命令行进行输入和输出。
* 当我们把 stdin 传给 fgets 后,它就会等待用户输入一串字符,并按下回车键。
* 如果得到了字串,就会把字串连同换行符存放到 input 数组中。
* 为了不让获取到的数据太大数组装不下,我们还要指定一下可以获取的最大长度为 2048。
*/
fgets(input, 2048, stdin);
/* Echo input back to user */
printf("You're a %s", input);
}
return 0;
}
运行:
$ ./parsing
Lispy Version 0.1
Press Ctrl+c to Exit
lispy> fanguiju
You're a fanguiju
lispy> ^C
使用 GNU Readline 函数库
上述完成了最基本的交互式功能(输入、输出),如果你用的是 Mac 或 Linux,当你用左右箭头键编辑在程序中的输入时,你会遇到一个奇怪的问题:使用箭头键不会前后移动输入的光标,而是会产生像 ^[[D
或 ^[[C
这种奇怪的字符。如下所示。
Lispy Version 0.0.0.0.3
Press Ctrl+c to Exit
lispy> hel^[[D^[[C
注:在 Windows 上则不会有这个现象。
所以,我们还需要完成 Shell 具有的一些特性,例如:使用左右箭头对指令行进行编辑、使用上下箭头来获取历史输入。这里,我们使用 GNU Readline 库来进行改造,把 fputs 和 fgets 替换为这个库提供的相同功能的函数。
安装 GNU Readline 函数库:
yum install readline-devel -y
查看:
$ ll /usr/include | grep readline
drwxr-xr-x 2 root root 143 4月 7 18:06 readline
GNU Readline 函数库提供的两个函数:readline 和 add_history。
- readline 函数:和 fgets 一样,从命令行读取一行输入,并且允许用户使用左右箭头对指令行进行编辑。
- add_history 函数:可以纪录下我们之前输入过的命令,并运行用户使用上下箭头来获取历史输入。
#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>
int main(int argc, char *argv[]) {
puts("Lispy Version 0.1");
puts("Press Ctrl+c to Exit\n");
while(1) {
char *input = NULL;
/* Output our prompt and get input.
* 使用 readline 读取用户输入。
* 与 fgets 不同的是,readline 并不在结尾添加换行符。
* 所以我们在 printf 函数中添加了一个换行符。
*/
input = readline("lispy> ");
/* Add input to history.
* 使用 add_history 将该输入添加到历史纪录当中。
*/
add_history(input);
printf("You're a %s\n", input);
/* Free retrieved input.
* 还需要使用 free 函数手动释放 readline 函数返回给我们的缓冲区 input。
* 因为 readline 不同于 fgets 函数,后者使用已经存在的空间,而前者会申请一块新的内存,所以需要手动释放。
*/
free(input);
}
return 0;
}
编译:
gcc -std=c99 -Wall parsing.c -o parsing -lreadline
-
-l<lib_name>
:指定程序要链接的库。 - -std:指定 C 语言标准。
运行:
$ ./parsing
Lispy Version 0.1
Press Ctrl+c to Exit
lispy> fanguiju
You're a fanguiju
lispy> fanguiju # 使用上箭头获取历史输入记录
You're a fanguiju
lispy>