用c++写一个代码解析器需要向哪方面学习?

news2024/12/24 2:07:08

我以前在中小游戏公司工作的时候,其中一项比较琐碎的工作就是为游戏项目建库建表,主要是为了做数据分析。作为一个职能部门的打杂PHP,对游戏业务并没有什么发言权,但是每次建库建表,却是苦不堪言。

同时部门的基础设施也不怎么完善,比如大数据集群刚开始使用、git 也刚开始使用、各种mysql建库建表工具分布在各个屎山代码、告警系统也分布在各个屎山代码中

所以,一边熟悉工作业务,一边提炼工作需求。最终初步完成下图中的工具流程,勉强把零散的工具代码重新整理,并适配我总结的工作流程

在这里插入图片描述
大数据方面,那时对hadoop集群 什么的无能为力,主要是大数据组那边负责,毕竟我只会做数据分析

同时游戏项目组那边也无能为了,只能要求,对接的项目组能够给予我的Record上增加一些注释和特殊的数据类型,这样我就可以写一个脚本进行Record的解析,并生成对应表结构

举个例子: record 格式

%% role_id;用户ID;bigint
%% role_name;用户名;
%% activation_key;激活码;
%% activity_id;活动ID;int
%% platform;平台ID;int
%% award_list;奖励道具;
%% total_pay_gold;消费元宝;int
%% mtime;激活时间;
%% 激活码激活日志
-record(log_activation_code, {role_id=0, role_name="", activation_key="", activity_id=0, platform=0, award_list="", total_pay_gold=0, mtime=0}).

同时也添加了一些增强数据类型

# 常量定义
-define( ROLE_ID, '角色ID;BIGINT:20')
-define( ITEM_JSON, 'JSON,结构[{"type": xx, "type_id": xx, "num":xx}]type1.元宝;2;铜钱;3;经验;4.二级货币;5.道具;type_id为道具或积分ID,其他情况为0num为数量;text')
-define( GAINS, '获得奖励,ITEM_JSON')
-define( CONSUMES, '消耗,ITEM_JSON' )
# 使用默认字段解释,之后表结构如果存在以下字段,并且没有给出字段说明,将使用以下的默认说明
# 这样一些常用的字段的解释就可以不用重复写,直接定义默认的字段解释即可
-field(agent_id, '代理ID;int:10')
-field(role_id, 'ROLE_ID')
-field(server_id, '区服ID;int')
-field(account_name, '帐号;varchar:100')
-field(role_name, '角色名;varchar:100')
-field(upf, 'United pf')
-field(pf, '渠道; int')
-field(is_internal, '是否内部号')
-field(mtime, '时间')

通过以上的数据注释/类型补全就可以实现,对应的解析脚本。主要实现的功能包括:

  • record 校验 () 代表一个完整的 record
  • 获取默认定义信息(define、field)
  • 获取文本注释(%%、#)信息
  • 对record 内的字段进行处理 && 处理record类型和备注类型
  • 检查 record 字段类型(类型检测)
  • 错误信息提示:
    • 默认类型缺失: lackOfDataValueTypes
    • 默认类型与定义类型不一致: typeInconsistency
    • record 字段重复定义: fieldRepetitionDefinition

如果有兴趣可以点击以下的脚本链接

  • record2sql

经过这一次事情,也对文本解析这一过程越发感兴趣。但是由于个人基础不够,也不知道什么是编译原理,更直观的则是 对 字符串匹配算法 的研究

比如学习什么 BF 算法、RK算法、BM算法、KMP算法、AC自动机

  • 如何在字符串中找到特定的字符?

当然作为一个实战为主的人,我也把对应的字符串匹配算法,加入到某个web项目中

  • Web服务项目

作为模板解析引擎,使用是主要是BM算法

/**
 * 
 * BM字符匹配
 *  1. 把字符串转换成unicode编码
 *  2. 然后循环检测'{{' 或 '}}'的存在
 *  3. 通过BM字符串算法,进行替换
 * 
 * 时间复杂度: O(n ^ 2)
 */
string TemplateReplace::matchByBm(string text)
{
  ....
}

在这里插入图片描述
为什么没有使用多模式串匹配。因为当时对字符编码还不够熟悉,在文本匹配出现了乱码。当然这算我做的第一个C++项目,性能自然是不咋滴

学习的知识越多,不知道的东西也更多,更疑惑的是,编程语言和操作系统在我眼中成为了黑箱一样的存在,特别想拆开分析一番。也是这时接触到了编译原理

  • 为什么要把编译技术的知识与实践相结合?

下面是编译技术的整体流程
在这里插入图片描述

  1. 词法分析: 词法分析是把一个一个的字符变成一个一个的单词(Token)
  2. 语法分析: 通过递归下降和上下文无关生文法成抽象语法树(AST)
  3. 语义分析: 在抽象语法树的基础上,保证语义的正确
  4. 中间码生成: 现在的编译器都不是直接生成机器代码,而是先生成一种中间语言的代码.为了能够适配不同的架构(x86、mips、risc-v)
  5. 中间码优化:代数优化、常数折叠、删除不可达的基本块、删除公共子表达式、拷贝传播、常数传播、 死代码删除、代码移动、部分冗余删除
  6. 机器码生成: 源码翻译成最终能在机器上执行运行的程序,所以编译器最后要把中间码转换为机器码

简单点,可以通过有限自动机实现简单的词法分析器

  • 词法分析器例子
SimpleTokenReader* SimpleLexer::tokensize(string code)
{
    ...
    try {
        for (int i = 0; i < codeLen; i++) {
            ch = (char) code[i];

            switch (state) {
                case Initial:
                    state = initToken(ch);  //  重新确定状态
                    break;
                
                case Id:
                    if (isAlpha(ch) || isDigit(ch)) {
                        tokenText.push_back(ch);    // 保持标识符状态
                    } else {
                        state = initToken(ch);      // 退出标识符状态,并保存Token
                    }
                    break;
                
                case GT:
                    if (ch == '=') {
                        token->type = TokenType::GE;  // 状态成GE
                        state = DfaState::GE;
                        tokenText.push_back(ch);
                    } else {
                        state = initToken(ch);        // 推出GT状态,并保存Token
                    }
                    break;
                
                case GE:
                case Assignment:
                case Plus:
                case Minus:
                case Star:
                case Slash:
                case SemiColon:
                case LeftParen:
                case RightParen:
                    state = initToken(ch);         // 退出当前状态,并保存Token
                    break;
                
                case IntLiteral:
                    if (isDigit(ch)) {
                        tokenText.push_back(ch);   // 继续保持在数字字面量状态
                    } else {
                        state = initToken(ch);      // 退出当前状态,并保存Token
                    }
                    break;
                
                case Id_int1:
                    if (ch == 'n') {
                        state = DfaState::Id_int2;
                        tokenText.push_back(ch);

                    } else if (isDigit(ch) || isAlpha(ch)) {
                        state = DfaState::Id;    // 切换回ID状态
                        tokenText.push_back(ch);
                    } else {
                        state = initToken(ch);
                    }
                    break;
                
                case Id_int2:
                    if (ch == 't') {
                        state = DfaState::Id_int3;
                        tokenText.push_back(ch);
                    } else if (isDigit(ch) || isAlpha(ch)) {
                        state = DfaState::Id;   // 切换回id状态
                        tokenText.push_back(ch);
                    } else {
                        state = initToken(ch);
                    }
                    break;
                
                case Id_int3:
                    if (isBlank(ch)) {
                        token->type = TokenType::Int;
                        state = initToken(ch);
                    } else {
                        state = DfaState::Id;   // 切换回Id状态
                        tokenText.push_back(ch);
                    }
                    break;
                default:
                    break;
            }
        }

        // 把最后一个token送进去
        if (tokenText.length() > 0) {
            initToken(ch);
        }

    } catch (exception& e) {
        cout << "exception: " << e.what() << endl;
    }

    return new SimpleTokenReader(tokens);
}

接下来的语法分析,主要通过上下文无关文法 + 递归下降生成AST

  • 编译原理中的右递归文法不会死循环吗?
  • 词法分析器例子

通过以上一些知识和代码的积累,就可以完成一个简单的脚本语言

  • 简单脚本例子

递归下降算法这个算法很常用,但会有回溯的现象,在性能上会有损失

所以要把算法升级一下,实现带有预测能力的自顶向下分析算法,避免回溯。而要做到这一点,就需要对自顶向下算法有更全面的了解

自顶向下分析的算法是一大类算法。总体来说,它是从一个非终结符出发,逐步推导出跟被解析的程序相同的 Token 串

根据搜索的策略,有深度优先(Depth First)和广度优先(Breadth First)两种,这两种策略的推导过程是不同的

深度优先(Depth First): 沿着一条分支把所有可能性探索完

广度优先(Breadth First): 也叫层级遍历算法,把兄弟节点写遍历,再构建下一层的兄弟节点,使得我们遍历树或图非常便利

  • dfs && bfs 例子

但是使用广度优先遍历,需要探索的路径数量会迅速爆炸,成指数级上升。这个时候就出现了我们需要的LL(1) 算法

  • LL(1)算法

但是对算法的探究是无止境的,出现了诸如LL(k)算法,还有更高效的LR系列算法

  • LR算法-移进和归约
  • LR算法例子

手写文本解析算法自然是很酷,但是当了要快速实现功能的时候,就很难受。所以这个时候我就会使用Antrl 。Antlr 是一个开源的工具,支持根据规则文件生成词法分析器和语法分析器,它自身是用 Java 实现的,但也提供对应C++ API

这里使用《Antlr4权威指南》的12.4 的 xml 解析例子,说明 antrl的使用。 当然也可以查看 参考资料[1] 进行antrl的学习

词法规则: XMLLexer.g4

lexer grammar XMLLexer;

// 默认模式: 标签外
COMMENT: '<!--' .*? '-->' ;
CDATA: '<![CDATA[' .*? ']]>';
/**
 * 包含所有的DTD、类似<!ENTiTY ...> 的实体定义以及记号声明<!NOTATION>
 */
DTD: '<!' .*? '>' -> skip;
EntityRef: '&' Name ';' ;
CharRef: '&#' DIGIT+ ';' | '&#x' HEXDIGIT+ ';' ;

SEA_WS : (' ' | '\t' | '\r'? '\n');

OPEN : '<' -> pushMode(INSIDE) ;
XMLDeclOpen: '<?xml' S -> pushMode(INSIDE) ;
SPECIAL_OPEN: '<?' Name -> more,pushMode(PROC_INSTR);

TEXT: ~[<&]+;   // 匹配任意除<和&之外的16位字符

mode PROC_INSTR;
PI: '?>' -> popMode ; // 关闭<?...?>
IGNORE: . -> more;

mode INSIDE;
CLOSE: '>' -> popMode;
SPECIAL_CLOSE: '?>' -> popMode; // 关闭<?xml...?>
SLASH_CLOSE: '/>' -> popMode;
SLASH: '/';
EQUALS: '=';
STRING: '"' ~[<"]* '"' 
      | '\'' ~[<']* '\'' 
      ;

Name : NameStartChar NameChar* ;

S: [\t\r\n] -> skip;

// fragment 用于提取输入字符流中的一个子集,这个子集可以被包含在其他更复杂的规则中
fragment
HEXDIGIT: [a-fA-F0-9] ;

fragment
DIGIT: [0-9] ;


fragment
NameChar    :   NameStartChar
            |   '_' | '.' | DIGIT
            |   '\u00B7'
            |   '\u0300'..'\u036F'
            |   '\u203F'..'\u2040'
            ;

fragment
NameStartChar
            :   'A'..'Z' | 'a'..'z'
            |   '\u00C0'..'\u00D6'
            |   '\u00D8'..'\u00F6'
            |   '\u00F8'..'\u02FF'
            |   '\u0370'..'\u037D'
            |   '\u037F'..'\u1FFF'
            |   '\u200C'..'\u200D'
            |   '\u2070'..'\u218F'
            |   '\u2C00'..'\u2FEF'
            |   '\u3001'..'\uD7FF'
            |   '\uF900'..'\uFDCF'
            |   '\uFDF0'..'\uFFFF' // implicitly includes ['\u10000-'\uEFFFF]
            ;
语法规则: XMLParser.g4

parser grammar XMLParser;
options { tokenVocab=XMLLexer; } // 指定词法规则文件

document: prolog? misc* element misc*;

prolog: XMLDeclOpen attribute* SPECIAL_CLOSE;

content: chardata? ((element | reference | CDATA | PI | COMMENT) chardata?)* ;

element: '<' Name attribute* '>' content '<' '/' Name '>' 
       | '<' Name attribute* '/>'
       ;

reference: EntityRef | CharRef ;

attribute: Name '=' STRING ; // 我们的 STRING 就是规范里的AttValue

/**
 * 其余所有未标记的文本构成了文档中的字符数据
 */
chardata: TEXT | SEA_WS;

misc: COMMENT | PI | SEA_WS ;

待解析的xml: book.xml

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
    <book id="1">
        <name>冰与火之歌</name>
        <author>乔治马丁</author>
        <year>2014</year>
        <price>89</price>
    </book>
    <book id="2">
        <name>安徒生童话</name>
        <year>2004</year>
        <price>77</price>
        <language>English</language>
    </book>    
</bookstore>

XML AST 结果
在这里插入图片描述
具体的代码和规则,可以在GitHub 中查看。通过定义好的解析规则生成AST。但是接下来改怎么对AST的各个节点进行操作,比如最常规的增删改查?

这个时候就要使用 antlr 的 Vistor 模式,为每个 AST 节点实现一个 visit 方法

antlr -visitor PlayScript.g4

-visitor 参数告诉 Antlr 生成下面两个接口和类

public interface PlayScriptVisitor<T> extends ParseTreeVisitor<T> {...}

public class PlayScriptBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements PlayScriptVisitor<T> {...}

在 PlayScriptBaseVisitor 中,可以看到很多 visitXXX() 这样的方法,每一种 AST 节点都对应一个方法

@Override public T visitPrimitiveType(PlayScriptParser.PrimitiveTypeContext ctx) {...}

比如,我以前做过的 playscript-cpp 项目,使用Vistor 模式 并通过 C++ 编写栈机解释器,实现代码的基本功能

类java 代码: test.play

double foo(double x) { 
    return (1.0+2.0+x)*(x+(1.0+2.0));
}
foo(3.0);

对应的AST:
在这里插入图片描述
具体的栈机的实现代码不在此展示。但是通过C++编写栈机确实能够很好的屏蔽底层中间码的生成或中间码优化

这个时候就该LLVM登场了,具体LLVM怎么编写一门语言可以查看参考资料[3]

  • LLVM IR的对象模型
  • LLVM 案例分析
  • 代码优化-目标、对象、范围

参考资料

  1. Antlr4系列(一):语法分析器学习
  2. Antlr4系列(二):实现一个计算器
  3. My First Language Frontend with LLVM Tutorial
  4. llvm入门教程-Kaleidoscope前端-3-代码生成
  5. 《Antlr4 权威指南》
  6. 《编译器设计》
  7. 《编译原理之美》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1601608.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EPP和EDR是什么,如何提高端点安全性

端点保护平台&#xff08;EPP&#xff09;和端点检测和响应&#xff08;EDR&#xff09;工具是两种常用于保护端点系统免受威胁的安全产品。EPP 是一种全面的安全解决方案&#xff0c;提供一系列功能来检测和防止对端点设备的威胁。同时&#xff0c;EDR专门用于实时监控、检测和…

CC254X 8051芯片手册介绍

1 8051CPU 8051是一种8位元的单芯片微控制器&#xff0c;属于MCS-51单芯片的一种&#xff0c;由英特尔(Intel)公司于1981年制造。Intel公司将MCS51的核心技术授权给了很多其它公司&#xff0c;所以有很多公司在做以8051为核心的单片机&#xff0c;如Atmel、飞利浦、深联华等公…

AI讲师大模型培训老师叶梓:大模型应用的方向探讨

大模型应用的关键方向及其落地案例可以从多个角度进行探讨&#xff0c;结合最新的研究和实际应用案例&#xff0c;我们可以更全面地理解这些技术如何推动社会和经济的发展。 Agent&#xff08;数字代理&#xff09;: 方向说明:Agent方向的AI技术旨在创建能够独立执行任务、做出…

redis_watchDog机制

文章目录 介绍机制介绍任务开始任务释放 介绍 redis的watchDog机制实现了超时续约的功能&#xff0c;简单来说就是在获取锁成功以后&#xff0c;开启一个定时任务&#xff0c;这个任务每隔一段时间(relaseTime / 3)&#xff0c;重置超时时间&#xff0c;避免的因业务阻塞导致锁…

比特币L2项目主网密集上线:新业态背后的挑战与机遇

随着加密货币行业的快速发展&#xff0c;比特币Layer 2&#xff08;以下简称L2&#xff09;项目的主网密集上线成为了近期的热点话题。这一潮流不仅是对比特币网络扩展的重要里程碑&#xff0c;也为新的商业模式和生态系统带来了无限可能。然而&#xff0c;随之而来的是各种挑战…

支付宝支付之SpringBoot整合支付宝入门

支付宝支付 对接流程 申请阿里支付官方企业账号配置应用签约产品获取RSAKey&#xff08;非对称加密&#xff09;必须获得两个加密串&#xff1a;一个公钥&#xff0c;一个密钥SDK功能开发业务对接支付回调支付组件 核心所需的参数 APPID商家私钥支付宝公钥支付回调地址网关…

实战纪实 | 学工平台平行越权

一.账号密码可爆破&#xff08;无验证码&#xff09; 1.学校学工平台用于请假跟每日上报健康信息&#xff0c;登录框如下&#xff1a; 2.经过测试发现这里不存在验证码验证&#xff0c;并且存在初始密码&#xff0c;可以尝试使用默认密码爆破账号&#xff1a; 3.经测试&#x…

【无标题】PHP-parse_str变量覆盖

[题目信息]&#xff1a; 题目名称题目难度PHP-parse_str变量覆盖1 [题目考点]&#xff1a; 变量覆盖指的是用我们自定义的参数值替换程序原有的变量值&#xff0c;一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。 经常导致变量覆盖漏洞场景有&#xff1a;$$&…

自动化测试Selenium(3)

目录 WebDriver相关API 打印信息 打印title 打印url 浏览器的操作 浏览器最大化 设置浏览器的宽,高 操作浏览器的前进, 后退, 刷新 控制浏览器滚动条 键盘事件 键盘单键用法 键盘组合按键用法 鼠标事件 WebDriver相关API 打印信息 打印title 即打印该网址的标题.…

护网行动 | 蓝队应急响应流程概述

了解蓝队应急响应的流程 应急响应通常是指为了应对各种意外事件发生前所做的准备&#xff0c;以及在意外事件发生后所采取的措施。 网络安全应急响应是指对已经发生或可能发送的安全事件进行监控、分析、协调、处理、保护资产安全。 网络安全应急响应主要是为了让人们对网络安全…

idea2023专业版安装破解+maven配置教程

前言 上一篇文章已经介绍了maven在Win10系统的安装配置教程。基于Win10的maven配置环境&#xff0c;本篇文章将介绍idea2023的安装破解教程及maven在idea2023的配置教程&#xff08;同时会将maven在idea2023的配置教程内容补充至上一篇文章&#xff09;。 一、idea2023下载安…

【学习】jemter中如何高效使用正则表达式

在Jemter的世界里&#xff0c;正则表达式无疑是一把锐利的剑&#xff0c;它可以帮助我们轻松地解决许多问题。在Jemter的性能测试过程中&#xff0c;我们常常需要提取响应中的某些数据&#xff0c;以便在后续的请求中使用。这时&#xff0c;正则表达式就派上用场了。通过学习如…

Linux进阶篇:Centos7搭建smb服务

Centos7搭建smb服务 1 smb介绍 Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件&#xff0c;由服务器及客户端程序构成。SMB&#xff08;Server Messages Block&#xff0c;信息服务块&#xff09;是一种在局域网上共享文件和打印机的一种通信协议&#xff0c;它为局域…

C语言学习笔记之指针(二)

指针基础知识&#xff1a;C语言学习笔记之指针&#xff08;一&#xff09;-CSDN博客 目录 字符指针 代码分析 指针数组 数组指针 函数指针 代码分析&#xff08;出自《C陷阱和缺陷》&#xff09; 函数指针数组 指向函数指针数组的指针 回调函数 qsort() 字符指针 一…

内网隧道技术总结

隧道技术解决的是网络通信问题&#xff0c;因为在内网环境下&#xff0c;我们不同的内网主机管理员会进行不同的网络配置&#xff0c;我们就需要使用不同的方式去控制我们的内网主机。隧道技术是一个后渗透的过程&#xff0c;是可以是我们已经取得了一定的权限&#xff0c;在这…

元数据管理Atlas

文章目录 一、Atlas概述1、Atlas入门2、Atlas架构原理 二、Atlas安装1、安装环境准备1.1 安装Solr-7.7.31.2 Atlas2.1.0安装 2、Atlas配置2.1 Atlas集成Hbase2.2 Atlas集成Solr2.3 Atlas集成Kafka2.4 Atlas Server配置2.5 Kerberos相关配置2.6 Atlas集成Hive 3、Atlas启动 三、…

一次配置Docker环境的完整记录

一次配置Docker环境的完整记录 Docker环境搭建报错与解决报错一报错二报错三 Docker环境搭建 本节介绍了一次配置docker环境的完整记录&#xff1a; 编写Dockerfile文件&#xff1a; FROM pytorch/pytorch:1.10.0-cuda11.3-cudnn8-develRUN rm /etc/apt/sources.list.d/cuda.l…

【每日力扣】15. 三数之和与11. 盛最多水的容器

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害 15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k…

安全认证Kerberos详解

文章目录 一、Kerberos入门与使用1、Kerberos概述1.1 什么是Kerberos1.2 Kerberos术语1.3 Kerberos认证原理 2、Kerberos安装2.1 安装Kerberos相关服务2.2 修改配置文件2.3 其他配置与启动 3、Kerberos使用概述3.1 Kerberos数据库操作3.2 Kerberos认证操作 二、Hadoop Kerberos…

10个常用的损失函数及Python代码实现

本文深入理解并详细介绍了10个常用的损失函数及Python代码实现。 什么是损失函数&#xff1f; 损失函数是一种衡量模型与数据吻合程度的算法。损失函数测量实际测量值和预测值之间差距的一种方式。损失函数的值越高预测就越错误&#xff0c;损失函数值越低则预测越接近真实值…