一个简单的解释器(1)

参考:Let’s Build A Simple Interpreter.
源代码:github

前言

日常使用python写一些自动化脚本,用到多线程的时候才发现,python居然没有真正的多线程?
这一切都得感谢GIL(Global Interpreter Lock),最常用的CPython解释器使用GIL来保证线程安全,也就是说python线程运行的时候是被GIL锁死的,其它线程并不能并发执行,python只是使用轮询来模拟多线程,想要在python上利用多核CPU,只能用多进程,然后通过PIPE或者别的一些恶心玩意来进行进程间通信。
当然也有一些没用GIL的python解释器,但是由于几乎所有python库都默认存在GIL,它们不会对线程安全进行任何处理,所以会使得多线程运行起来非常危险。
当然这是历史原因,在最初的python解释器实现的年代,多核CPU还不是主流,现如今已经是尾大不掉,不好修改了。
当然我并不想自己写一个线程安全的没有GIL的python解释器,这工程未免太大,只是由于以上原因,研究一下解释器的实现原理,实现一个简单的解释器,也许发明一个自己的编程语言,实现语言方面选择C++,足够灵活,C++17标准下也基本可以保证内存安全,不过std::string居然还是没有split,这方面只好效率低一点了。

Let's go

先忘记那些词法分析、语法分析、语法图、有限状态机等等麻烦的玩意,实现一个最简单的语法:

输入两个个位数字相加的表达式,输出结果,其余输入抛出错误,如:输入1+1,输出2

OK,我们先声明一个Token类在Token.h里:

#pragma once
#include <variant>
#include <string>
#include <memory>

enum class TokenType
{
    INTEGER,
    PLUS,
    EOF,
};

typedef std::variant<int,std::string> TokenValue;

class Token
{
private:
    TokenType tokenType;
    TokenValue tokenValue;
public:
    Token(TokenType t, TokenValue v);
    TokenType getType();
    TokenValue getValue();
};

typedef std::shared_ptr<Token> TokenPtr;

感谢C++17,感谢std::varaint,我可以用接近动态类型语言的方式实现TokenValue,而不需要自己去写template或者union,这里可以使用std::shared_ptr来更灵活和安全地使用Token对象。
实现上比较简单,Token.cpp:

#include "Token.h"

Token::Token(TokenType t, TokenValue v)
{
    tokenType = t;
    tokenValue = v;
}

TokenType Token::getType()
{
    return tokenType;
}

TokenValue Token::getValue()
{
    return tokenValue;
}

好了,接下来我们只要实现一个Interpreter,声明如下:

#prama once
#include <string>
#include "Token.h"

class Interpreter
{
private:
    TokenPtr currentTokenPtr;
    std::string inputText;
    size_t currentPos;
private:
    void eat(TokenType type);
    void raiseError();
    TokenPtr getNextTokenPtr();
public:
    Interpreter(std::string text);
    int expr();
};

接下来的功能是核心,我们一步一步来,首先实现构造函数:

Interpreter::Interpreter(std::string text)
{
    inputText = text;
    currentPos = 0;
}

没有任何难度,接下来是真正干活的expr:

int Interpreter::expr()
{
    currentTokenPtr = getNextTokenPtr();
    TokenPtr left = currentTokenPtr;
    eat(TokenType::INTEGER);

    TokenPtr op = currentTokenPtr ;
    eat(TokenType::PLUS);

    TokenPtr right = currentTokenPtr ;
    eat(TokenType::INTEGER);

    return std::get<int>(left->getValue()) + std::get<int>(right->getValue());
}

思路上也很简单,首先获取第一个Token,由于我们的语法是定死的:个位数、加号、个位数,所以第一个Token必须是TokenType::INTEGER的类型,然后我们调用eat,把它吃进去,检测合法性,同时把下一个Token赋予currentTokenPtr,如是三遍,读取所有Token以后,返回结果。
然后实现getNextTokenPtr:

TokenPtr Interpreter::getNextTokenPtr()
{
    const char * textStr = text.c_str();
    if( currentPos >= strlen(textStr) )
    {
        return std::make_shared<Token>(TokenType::EOF,NULL);
    }

    const char currentChar = textStr[currentPos];
    if( currentChar >= '0' && currentChar <= '9' )
    {
        currentPos++;
        return std::make_shared<Token>(TokenType::INTEGER,currentChar - '0');
    }

    if( strcmp( currentChar,'+' ) == 0 )
    {
        currentPos++;
        return std::make_shared<Token>(TokenType::PLUS,"+");
    }

    raiseError();
}

逻辑也非常简单,判断当前的字符,返回对应的TokenPtr,如果条件都不符合,则抛出error。
然后实现一个eat,就大功告成啦:

void Interpreter::eat(TokenType type)
{
    if( type != currentTokenPtr->getType() )
    {
        raiseError();
    }
    currentTokenPtr = getNextTokenPtr();
}

至于raiseError,简单点可以直接throw "Interpreter error.";,或者自己实现一个exception类:

#prama once
#include <exception>
#include <string>

class InterpreterError:public std::exception
{
};

这样raiseError的实现就是:

void Interpreter::raiseError()
{
    throw InterpreterError();
}

这样可以在catch的时候规定更明确的exception类型。
最后实现一个main函数来进行测试:

#include "Interpreter.h"
#include <iostream>
#include <string>

int main()
{
    std::string text;
    while(true)
    {
        try
        {
            std::in >> text;
            Interpreter interpreter(text);
            std::cout << interpreter.expr() << std::endl;
        }
        catch(InterpreterError e)
        {
            return -1;
        }
    }
    return 0;
}

当然,上面的解释器过于原始,不支持多位整数,不支持负数,不支持空格等等等,但这至少是一个开始,接下来,我们将在这个基础上慢慢改进。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容