专业编程基础技术教程

网站首页 > 基础教程 正文

X语言解析器C++实现(模糊探索篇)

ccvgpt 2025-01-18 17:50:33 基础教程 10 ℃

一、为什么要写X语言解析器

因为之前在《数学表达式计算器》中提到过,我最想尝试的挑战就是写一个C语言解析器,所以我今天抽空开始写了一点点,但是写的不是C语言的解析器。

X语言解析器C++实现(模糊探索篇)

二、为什么要不写C语言解析器

,为什么呢?因为一方面我觉得我不一定知道C语言中的所有语言特性,就算勉强写出来也不见得能称为C语言解析器;另外一方面是C语言中可能有一些特性比较复杂,现在很多现代语言为了更高效的实现解析工作,也摒弃了一些古老特性,或者说这些新语言强制我们按照一种新习惯来处理,比如必须换行无须分号;分割,比如函数{}左右耳朵也可以不用有。

不管怎么说,我想抛弃一些比较复杂的特性,用更简单的方式达到相同目的即可,比如一行内不允许多条语句或表达式。

三、动力所在

重起炉灶,按照自己的想法来,随意发挥,完全按自己想法来实现,自己发现开发过程中的难点,自己为这个难题提出自己的解决方案,所有的东西都应是自己发现、自己想出来解决方案。尽管实现可能不够完整、性能不够好,目标还是达成的。所以,重起炉灶造X语言解析器。

四、为什么叫X语言

为什么叫X语言,因为我不知道叫什么名称好,不好叫高大尚的,也不想叫低矮戳的,所以选择一个X,我喜欢把X当作未知来理解,因为未知,所以充满无数变数,这就是我选择X的原因。

五、先写代码后补构思

在今天之前,我稍微构思过整个语言的实现思路,不过我发现,有很多细节我也想不到的。之前我还想把我的构思梳理出来,但是心里确实没底,还是不要丢出来了,所以我今天马上抽空先写,写得很挫也没关系,主要我想快速验证我会遇到哪些问题,等我把问题弄清楚了,解决了,我再回来补充即可。

六、初步架构设想

先通过代码看看我的初步架构设想:

/**
 * X language interpreter entry
 * @Author Rizhong Li<lirizhong97@163.com>
 * @Date 2024-01-10
 */

#include <iostream>
#include <memory>
#include <xlang/Interpreter.h>


int main(int argc, const char* argv[]) {
    int nr;
    try {
        auto *ir = new xlang::Interpreter();
        if ((nr=ir->parse_cmd_line(argc, argv)) == 0) {
            if ((nr=ir->initialize()) == 0) {
                nr = ir->start();
            }
        }
    } catch (const std::exception& e) {
        nr = -1;
        std::cerr << "Main exception: " << e.what() << ".\n";
    } catch (...) {
        nr = -1;
        std::cerr << "Main unknown error" << ".\n";
    }

    std::cout << "Interpreter finished with exit code " << nr << ".\n";
    return nr;
}

Interpreter就整个程序最外层的数据结构,通过它来解析命令行参数(parse_cmd_line),解析完命令行参数后,再通过它来初始化(initialize),我的意图是可以初始化X语言解析器自己要使用的配置参数(比如php解析器也有自己的配置文件),还有就是脚本程序的命令行参数,还有就是我把X设计成支持模块形式,所以类似php/go/python,也有一个模块元数据文件也要解析。

比如如下运行xlang解析器的命令行:

xlang -c /Users/codebook/xlang.toml /Users/codebook/CLionProjects/xtest/main.x 1 2 3 4 5 6 7 8 9 10

1、xlang是我写代码编译生成的xlang解析器可执行文件;

2、-c /Users/codebook/xlang.toml这个意图是做xlang自己需要的配置文件,目前我没去解析这个文件,留了空函数对应数据结构存在,目前也没设计依赖什么参数,如果有,也会在代码里面写死一套,等有空回过头再回去解析xlang.toml配置文件;

3、/Users/codebook/CLionProjects/xtest/main.x 这个就是xlang语言写的程序代码,类似python main.py或者php main.php或者node index.js一样,就是启动这个xlang脚本程序;

4、1 2 3 4 5 6 7 8 9 10 这个就是传给脚本程序main.x的命令参数;

目前,我只有到/Users/codebook/CLionProjects/xtest/main.x,其他的是计划实现,设计好框架和数据结构,解析配置文件工作没写,优先级最低,尽快写代码发现问题、解决问题后再补充回来。下面当前的代码目录结构:

main.cpp就不用说,Interpreter就是外层的数据结构,内部包含Parser作为成员变量,而Parser内部又包含Lexer作为成员变量。

Lexcer主要做的工作就是词法分析,生成token给到Parser,后面可能增加一个Grammar来做语法分析处理,也有可能由Parser直接做了。反正就是很多东西要写了之后才更清晰。

目前写的代码核心代码主要在Lexcer,即词法分析工作,写得很乱,不过也贴出来看看吧,后面肯定会修改的。

/**
 * xlang lexer
 * @Author Rizhong Li<lirizhong97@163.com>
 * @Date 2024-01-10
 */

#include <iostream>
#include <string>
#include "xlang/Lexer.h"

namespace xlang {

    Lexer::Lexer() : _fin() {

    }

    int Lexer::load(std::filesystem::path &filepath) {
        reset();
        _fin.open(filepath.c_str());
        if (!_fin.is_open()) {
            std::cerr << "Failed to open source file:" << filepath << "\n";
            return -1;
        }
        return 0;
    }

    void Lexer::reset() {
        _fin.clear();
        _fin.close();
    }

    int Lexer::is_new_line(char c) {
        return (c == '\n' || c == '\r');
    }

    int Lexer::is_space(char c) {
        return (c == '\t' || c == ' ' || c == '\v' || c == '\f');//``\t''``\n''``\v''``\f''``\r''`` ''
    }
    void Lexer::trim_left(char *&ps, char *&pe) {
        while (ps <= pe && isspace(*ps)) ++ps;
    }

    void Lexer::trim_right(char *&ps, char *&pe) {
        while (pe >= ps && isspace(*pe)) --pe;
    }

    int Lexer::get_line(std::string &line, size_t &line_no, char *&ps, char *&pe) {
        while (true) {
            std::getline(_fin, line);
            if (_fin.bad()) {
                //TODO:
                return -1;/* Failed to read file */
            }

            if (line.empty()) {
                if (_fin.eof()) {
                    return 1;/* End of file */
                }
                ++line_no;
            } else {
                ++line_no;
                ps = line.data();
                pe = line.data() + line.length() - 1;
                trim_left(ps, pe);
                trim_right(ps, pe);
                if (ps > pe) {
                    continue;
                }
                break;
            }
        }

        return 0;
    }

    int Lexer::generate_token() {
        int nr;
        bool read_new = false; /* Ignore \ to concat next line, so read_new is always true now */
        char *ts = nullptr;
        char *te = nullptr;
        char *ps = nullptr;
        char *pe = nullptr;
        size_t line_no = 0;
        size_t mlc_beg_line_no = 0;
        size_t mlc_end_line_no = 0;
        std::string line = "";
        std::string token = "";
        ESentenceType sen_state = SEN_NONE;
        ETokenType token_state = TOKEN_NONE;

        /* Read one line from file to process */
        nr = get_line(line, line_no, ps, pe);
        if (nr != 0) {
            return nr;
        }

        while (true) {
            read_new = false;
            while (ps <= pe) {
                if (sen_state == SEN_NONE) {
                    if (isspace(*ps)) {/* Here ignore the space */
                        break;
                    }
                    if (*ps == '/') {
                        token_state = TOKEN_SLASH;
                        sen_state = SEN_COMMENT;
                    } else if (*ps == '+') {
                        token_state = TOKEN_PLUS;
                        sen_state = SEN_PRE_INCR_P;
                    } else if (*ps == '-') {
                        token_state = TOKEN_MINUS;
                        sen_state = SEN_PRE_DECR_OP;
                    } else if (*ps == '_') {
                        token_state = TOKEN_UNDERSCORE;
                        sen_state = SEN_VARIABLE;
                    } else if ((*ps >= 'a' && *ps <= 'z') || (*ps >= 'A' && *ps <= 'Z')) {
                        token_state = TOKEN_LETTER;
                        sen_state = SEN_VARIABLE;
                    }
                } else if (sen_state == SEN_COMMENT) {
                    if (*ps != '/' && *ps != '*') {
                        std::cout << "Unrecognized line:" << line << std::endl;
                        return -1;//TODO:
                    }

                    if (*ps == '/') {
                        std::cout << "Found single line comment [" << line_no << "]:" << line << std::endl;
                        token_state = TOKEN_NONE;
                        sen_state = SEN_NONE;
                        read_new = true;
                        break;
                    } else {
                        mlc_beg_line_no = line_no;
                        token_state = TOKEN_MLC_SLASH_STAR;
                        sen_state = SEN_ML_COMMENT;
                    }
                } else if (sen_state == SEN_ML_COMMENT) {
                    if (*ps == '/') {
                        if (token_state == TOKEN_MLC_SLASH_STAR_STAR) {
                            mlc_end_line_no = line_no;
                            std::cout << "Found multi line comment [" << mlc_beg_line_no << "~" << mlc_end_line_no << "]:" << line << std::endl;
                            token_state = TOKEN_NONE;
                            sen_state = SEN_NONE;
//                            read_new = true;//Maybe wo can do better, now simplify the process
//                            break;
                            //Do better on this way
                            ps++;
                            trim_left(ps, pe);
                            continue;
                        } else {
                            token_state == TOKEN_MLC_SLASH_STAR;
                        }
                    } else if (*ps == '*') {
                        token_state = TOKEN_MLC_SLASH_STAR_STAR;
                    } else {
                        token_state == TOKEN_MLC_SLASH_STAR;
                    }
                } else if (sen_state == SEN_PRE_INCR_P) {
                    if (*ps != '+') {
                        std::cerr << "Unrecognized line:" << line << std::endl;
                    } else {
                        token_state = TOKEN_PRE_INCR_PP_VAR;
                        sen_state = SEN_PRE_INCR_PP;
                        token.clear();
                        ps++;
                        trim_left(ps, pe);
                        if (ps > pe) {
                            std::cout << "Unrecognized ++var [" << line_no << "]:" << line << std::endl;
                            return -1;
                        }
                        continue;
                    }
                } else if (sen_state == SEN_PRE_INCR_PP) {
                    if (isspace(*ps)) {
                        token_state = TOKEN_NONE;
                        sen_state = SEN_NONE;
                        std::cout << "Found ++var1 [" << line_no << "]:" << line << std::endl;
                    } else {
                        if (token.empty()) {
                            if (!(*ps == '_' || (*ps >= 'A' && *ps <= 'Z') || (*ps >= 'a' && *ps <= 'z'))) {
                                std::cout << "Unrecognized ++var [" << line_no << "]:" << line << std::endl;
                                return -1;
                            } else {
                                token += *ps;
                            }
                        } else {
                            if (!(*ps == '_' || (*ps >= '0' && *ps <= '9') || (*ps >= 'A' && *ps <= 'Z') || (*ps >= 'a' && *ps <= 'z'))) {
                                std::cout << "Unrecognized ++var [" << line_no << "]:" << line << std::endl;
                                return -1;
                            } else {
                                token += *ps;
                                if (ps == pe) {
                                    token_state = TOKEN_NONE;
                                    sen_state = SEN_NONE;
                                    std::cout << "Found ++var2 [" << line_no << "]:" << line << std::endl;
                                }
                            }
                        }

                    }
                }

                ps++;
            }

            /* Read one line from file to process */
            nr = get_line(line, line_no, ps, pe);
            if (nr != 0) {
                return nr;
            }
        }

        return 0;
    }
}

七、词法分析整体思路

整体思路也是只遍历一遍,由状态机推进,目前写了两个状态进行维护,后续优化一下可能只使用一个状态就可以。目前的代码主要是为了能够解析注释,包括单行注释和多行注释,还有就是变量前置自增。

测试的main.x代码如下:


// this is an single line comment
//this is also an single line comment
/**/ /**/ //
 /**
  * this is comment
  * ok
  * llll
  */

  ++ abc

目前上面的代码,都能正常解析,后面会增加加新代码,一个功能点一个功能点的往里面接,正常已有代码不会影响目前解析注释的代码。

Tags:

最近发表
标签列表