百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

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

liebian365 2025-03-07 20:35 7 浏览 0 评论

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

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

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

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

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

三、动力所在

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

四、为什么叫X语言

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

五、先写代码后补构思

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

六、初步架构设想

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

/**
 * X language interpreter entry
 * @Author Rizhong Li
 * @Date 2024-01-10
 */

#include 
#include 
#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
 * @Date 2024-01-10
 */

#include 
#include 
#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 isspaceps ps void lexer::trim_rightchar 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 isspaceps 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_leftps 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 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

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

相关推荐

C++零基础入门学习指南(中篇)

目标:像拼装乐高一样理解程序模块,掌握内存管理核心技能...

“5 分钟 CMake 使用指南,解决我的 C++ 打包问题!”

...

Linux下跨语言调用C++实践

不同的开发语言适合不同的领域,例如Python适合做数据分析,C++适合做系统的底层开发,假如它们需要用到相同功能的基础组件,组件使用多种语言分别开发的话,不仅增加了开发和维护成本,而且不能确保多种语...

输入格式控制:C++程序中的数据接收与处理技巧

在C++编程中,输入输出是非常基本且重要的操作。尤其是输入部分,程序员通常需要从用户那里获取数据,并根据不同的输入格式进行处理。然而,用户的输入往往是多样化的,如何有效地控制输入格式,确保程序正确接收...

常见读写excel文件的库/类

在C++语言中读写EXCEL表格,有这几种方法:COM方式、ODBC方式、OLE方式、纯底层格式分析方式。Basicexcel使用方法:https://www.cnblogs.com/paullam/...

C++文档识别接口如何实现 高效办公

  数字化信息爆炸时代,办公效率的提升成为企业和个人的迫切需求。人工智能技术的飞速发展,为我们带来了前所未有的便利,翔云文档识别接口便是其中之一。  与传统的人工手动录入相比,文档识别接口优势显著。人...

C++如何生成Microsoft Word文档

...

超实用C++学习指南:语法要点、经典书籍、实战案例全汇总!

以下是为您整理的C++学习指南,综合了语法要点、资源推荐及实战方向,结合搜索结果和经典知识体系,帮助您系统学习:一、C++基础语法学习指南1.核心概念oC++是静态类型、编译式语言,支持面向对象和...

掌握C++文件读写,让代码更灵动!

文章改写指令通常涉及对原有文本进行调整、重组或重新表达,以保持或增强信息的准确性和可读性,同时可能改变风格、语气或目标受众。以下是一些具体的文章改写指令示例:·2.简化语言:→指令:将文章中的复杂词汇...

闲置宽带能换钱?P2P CDN、无线宝、赚钱宝到底靠不靠谱

无线宝类产品其实由来已久,无线宝类产品即与支付宝、余额宝、余利宝等货币基金毫无干系,与区块链“挖币”更存在本质的不同,而是一种利用家庭中的闲置宽带,通过流量来换取佣金的产品。无线宝类产品其实在过去几年...

攻略什么?闲置宽带还可以赚钱?

现在很多朋友在使用10Mbps、50Mbps甚至100Mbps的高速宽带,不过普通用户并不是长时间都需要这么高速的宽带。比如对于100Mbps的宽带用户,在日常浏览网页时,基本上2Mbps左右的带宽即...

明日学业水平考试开始报名 详细步骤都在这里

点击上面蓝字关注我们哦~日前,山东省教育考试院发布了《山东省2019年夏季学业水平考试报名考生操作说明》(点击文末阅读原文查看),明天就到了报名的时候了,详细的报名步骤、网上缴费流程、追加报考科目等...

瞄准用户上传带宽:HiWiFi 极路由 联合 迅雷 推出 “极赚钱”套餐

上次总理谈到宽带降价问题时,很多网友除了吐槽网速慢费用贵,还反映宽带网络的上下行速度不对等。比如说以前ADSL2M的宽带只有512Kbps的上行速度,现在升级到光纤网络之后,按理说技术上实现上下行...

揭秘P2P平台刷数据:交易额从100万到1200万

(作者:峰岭、刘珺、周娜)从默默无闻到万众瞩目,从“零数据”到“大数据”,从小众投资到大众理财,从个人借贷到企业借款,从个人信用到车、房、资产抵押……近两年来P2P行业以迅雷之速快速爆发,P2P平台也...

运营商让我签这个宽带违规使用告知函,我懵逼了

特么的是爱奇艺迅雷自己上传的p2p数据,btpt也会上传,直播也会上传,监控也会,传文件也会,到底他么的运营商你要干个啥啊,我不仅没捞着一分好处,夹在中间两头受气!真特么晦气这特么是谁弄的函?完全没搞...

取消回复欢迎 发表评论: