制作一个简单的交互式解释器(REPL)
什么是交互式解释器(REPL)
简单解释就是:“读取值-求值-输出结果”循环(Read-Eval-Print Loop,简称REPL)1
创建简单的REPL程序
新建个db_tutorial目录,用vs code打开改目录,在目录下创建一个db.c文件。
完整代码如下:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
char* buffer;
size_t buffer_length;
ssize_t input_length;
} InputBuffer;
/*
* 功能:从指定文件流stream中读取一行数据,保存到lineptr指向的缓冲区中
* 参数:lineptr:指向缓冲区的指针,用于保存读取出来的一行数据
* n:指向缓冲区大小的指针(入参时为缓冲区大小,出参时为实际读取到的字节数)
* stream:指定的文件流
* 返回值:成功返回读取到的字节数,失败返回-1
*/
ssize_t getline(char **lineptr, size_t *n, FILE *stream)
{
size_t size; // 当前缓冲区大小
size_t index; // 当前缓冲区中已经读取的字节数
int c; // 从文件流中读取的字节
char *buf; // 缓冲区
if(!lineptr || !n || !stream) // 参数检查
return -1;
buf = *lineptr; // 获取缓冲区
size = *n; // 获取缓冲区大小
index = 0; // 缓冲区已经读取的字节数置0
while((c = fgetc(stream)) != EOF) // 逐个字节读取数据
{
/* 当前缓冲区已满,需要扩容 */
if(size <= index + 1)
{
size += 128; // 扩容128字节
buf = (char *)realloc(buf, size); // 重新分配内存
if(!buf) // 内存分配失败
return -1;
*lineptr = buf; // 更新缓冲区指针
*n = size; // 更新缓冲区大小
}
buf[index++] = c; // 将字节写入缓冲区
if(c == '\n') // 读取到换行符,一行数据读取完毕
break;
}
buf[index] = '\0'; // 写入字符串结束符
// 返回实际读取到的字节数
return index > 0 ? (ssize_t)index : -1;
}
InputBuffer* new_input_buffer()
{
InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer));
input_buffer->buffer = NULL;
input_buffer->buffer_length = 0;
input_buffer->input_length = 0;
return input_buffer;
}
void print_prompt()
{
printf("db > ");
}
void read_input(InputBuffer* input_buffer)
{
ssize_t bytes_read = getline(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin);
if(bytes_read <= 0) {
printf("Errorr reading input\n");
exit(EXIT_FAILURE);
}
input_buffer->input_length = bytes_read - 1;
input_buffer->buffer[bytes_read - 1] = 0;
}
void close_input_buffer(InputBuffer* input_buffer)
{
free(input_buffer->buffer);
free(input_buffer);
}
int main(int argc, char* argv[]) {
InputBuffer* input_buffer = new_input_buffer();
while (true)
{
print_prompt();
read_input(input_buffer);
if(strcmp(input_buffer->buffer, ".exit") == 0)
{
close_input_buffer(input_buffer);
exit(EXIT_SUCCESS);
}
else
{
printf("Unrecognized command '%s'.\n", input_buffer->buffer);
}
}
}
编译运行效果
代码解释
首先我们的需求是用户运行程序后在命令窗口输入命令,程序读取命令然后识别命令并做出相应的反馈。
缓存读取的输入
我们需要个连续内存,需要个字段记录这个内存大小,需要个字段记录内存中有用数据的长度。详见InputBuffer
结构体内容。
读取输入
详细代码见getline
函数。
第一个参数是用来存放输入内容,用指针的指针这个类型,是因为lineptr指向的指针可能为空或者lineptr指向的指针的内存长度不够,这两种情况都需要扩充内存。**lineptr
是通过&(input_buffer->buffer)
得到,input_buffer->buffer
是char*
类型。我们需要知道,buffer
里面存放的是指针,即内存地址。buffer
字段本身也是在内存中有个地址。当需要扩容时,我们需要改变的就是buffer
自身的地址。
第二个参数是表示第一个参数中*lineptr
指向的内存块的大小。这样在读取输入时,如果输入数据的长度大于内存块的长度,那么可以对改内存块进行扩容。
第三个参数表示输入流,这里传入的为stdin
。stdin
表示指向键盘的输入。这里配合fgetc
从stdin
中一个个读取字符。
命令识别
因为输入是字符,这里用strcmp
函数对输入进行简单的判断。在main函数中strcmp(input_buffer->buffer, ".exit") == 0
,只对.exit
命令进行了识别。
在实际中,用户不可能一次只输入一个单词,肯定是一连串的字符。这个会在后面的内容进行实现。