ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ANTLR 4 C++ Target 完全指南:从代码生成、运行时构建到自定义 Listener 与高级定制

ANTLR 4 C++ Target 完全指南:从代码生成、运行时构建到自定义 Listener 与高级定制 开发工具编程语言编译器【免费下载链接】antlr4ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files.项目地址https://gitcode.com/gh_mirrors/an/antlr4点击查看免费下载导读本篇技术指南以 ANTLR 4 的 C target 为主线讲解如何在 C 项目中用 ANTLR 生成词法分析器Lexer与语法分析器Parser获取并构建 C 运行时以及通过 Listener 机制驱动语法树遍历。你将掌握-DlanguageCpp的完整代码生成流程、-package/exportMacro/-DANTLR4_USE_THREAD_LOCAL_CACHE等 C 特有的定制参数理解 C target 的内存管理与 Unicode 处理模型并学会使用 C 独有的 named actions 对生成代码做精细化定制。全文以仓库 doc/cpp-target.md 为核心骨架结合 runtime/Cpp 下的构建脚本、演示工程与语法文件进行源码级佐证。平台支持与构建前提C target 支持所有能够运行以下任一工具链的平台MS Visual Studio 2017 或更新版本WindowsXCode 7 或更新版本macOS / iOSCMake需要C17支持其他平台所有构建工具均可生成静态库或动态库且同时支持64 位与 32 位架构此外 XCode 还可以额外产出iOS 库。从仓库 runtime/Cpp/CMakeLists.txt 可以看到CMake 构建默认启用 C17CMAKE_CXX_STANDARD 17CMAKE_CXX_STANDARD_REQUIRED ON并且cmake_minimum_required (VERSION 3.15)如果你希望显式指定标准可在配置阶段传入-DCMAKE_CXX_STANDARD17之类的选项覆盖默认值。构建类型未指定时会回退到Release并给出警告runtime/Cpp/CMakeLists.txt。说明仓库根目录下 runtime/Cpp/README.md 的 Build Usage Notes 中仍写有 minimum cmake 2.8 的旧表述实际以 runtime/Cpp/CMakeLists.txt 中的3.15为准——这是仓库内文档与代码之间的一处不一致配置时请以代码为准。如何生成 C 词法分析器 / 语法分析器创建 C 的 Lexer/Parser 与创建 Java 版本几乎完全一致唯一区别是必须指定语言目标为 Cpp$ antlr4 -DlanguageCpp MyGrammar.g4执行这条命令后会生成一批文件。在未显式抑制默认开启Listener 或 Visitor 的情况下你会得到MyGrammarLexer.hMyGrammarLexer.cppMyGrammarParser.hMyGrammarParser.cppMyGrammarVisitor.hMyGrammarVisitor.cppMyGrammarBaseVisitor.hMyGrammarBaseVisitor.cppMyGrammarListener.hMyGrammarListener.cppMyGrammarBaseListener.hMyGrammarBaseListener.cpp仓库中的演示工程给出了这一生成过程的真实调用示例。runtime/Cpp/demo/CMakeLists.txt 中通过自定义 CMake 命令调用 ANTLR jarjava -jar antlr4.jar -Werror -DlanguageCpp -listener -visitor -o 输出目录 -package antlrcpptest TLexer.g4 TParser.g4而 runtime/Cpp/demo/generate.sh 则展示了从 classpath 直接运行工具的方式其参数与上面一致-DlanguageCpp -listener -visitor -o generated/ -package antlrcpptest。生成的源文件被放在generated/子目录中供 demo 程序编译使用。从哪里获取 C 运行时生成 Lexer/Parser 代码之后还需要下载或自行构建 ANTLR C 运行时runtime。获取途径有三类预编译二进制ANTLR 官网提供 WindowsVisual Studio、OSX/macOS 与 iOS 的预编译 C 运行时二进制。仓库中 runtime/Cpp 目录本身就是这份运行时在仓库内的源码形态其项目状态声明支持 macOS、Windows、Android 与 Linux 构建见 runtime/Cpp/README.md。CMake 构建使用 CMake 构建 Linux 库同样适用于 OSX但不能用于生成 iOS 库。IDE 工程构建在 OSX 或 Windows 上直接使用仓库提供的 XCode 工程或 Visual Studio 工程构建自己的库开箱即用、无需额外依赖。用 CMake 构建运行时Linux / macOS参考 runtime/Cpp/README.md 中的完整步骤cd antlr4-dir/runtime/Cpp # 本 README 所在目录 mkdir build mkdir run cd build cmake .. -DANTLR_JAR_LOCATIONfull/path/to/antlr4-version-complete.jar -DWITH_DEMOTrue make DESTDIRantlr4-dir/runtime/Cpp/run make install关键参数说明-DWITH_DEMOTrue同时构建演示程序。开启后 CMake 会强制要求提供 Java 运行时与-DANTLR_JAR_LOCATION缺失会直接FATAL_ERROR因为 demo 需要调用 ANTLR 工具生成代码runtime/Cpp/CMakeLists.txt。若不需要 demo去掉该参数直接cmake ..即可。-DANTLR4_INSTALLON安装阶段会同时生成并安装CMake Package对外暴露antlr4_runtime与antlr4_generator两个包便于其他工程引用runtime/Cpp/CMakeLists.txt。此外 runtime/Cpp/CMakeLists.txt 还提供了-DWITH_LIBCXXLinux 下配合 clang 使用 libc、-DWITH_STATIC_CRTVC 静态链接 CRT默认开启与-DDISABLE_WARNINGS等选项。通过 CMake Package 在应用工程中集成如果安装了 CMake Package可以在你的应用工程中这样使用详见 runtime/Cpp/cmake/Antlr4Package.mdfind_package(antlr4-runtime REQUIRED) find_package(antlr4-generator REQUIRED) set(ANTLR4_JAR_LOCATION ${PROJECT_SOURCE_DIR}/thirdparty/antlr/antlr-4.13.2-complete.jar) antlr4_generate( antlrcpptest_lexer ${CMAKE_CURRENT_SOURCE_DIR}/TLexer.g4 LEXER FALSE FALSE antlrcpptest ) include_directories( ${ANTLR4_INCLUDE_DIR} ) add_dependencies( Parsertest antlr4_shared ) target_link_libraries( Parsertest PRIVATE antlr4_shared )antlr4-runtime包提供antlr4_shared共享库与antlr4_static静态库两个 target并导出ANTLR4_INCLUDE_DIR、ANTLR4_LIB_DIR变量。antlr4-generator包提供antlr4_generate函数参数依次为唯一 target 名、输入语法文件、规则类型LEXER/PARSER/BOTH、是否生成 listener、是否生成 visitor、C namespace、附加依赖文件、生成期库路径调用后会导出ANTLR4_INCLUDE_DIR_目标名、ANTLR4_SRC_FILES_目标名、ANTLR4_TOKEN_FILES_目标名、ANTLR4_TOKEN_DIRECTORY_目标名等变量。在其他平台构建WindowsVisual Studio直接用 VS 打开 runtime 目录下的 VS 工程VS 2019构建或用 VS 的 “Open Folder” 功能打开 runtime/Cpp自动生成 CMake 解决方案runtime/Cpp/README.md。macOS打开 runtime/Cpp/runtime/antlrcpp.xcodeproj 构建或用上述 CMake 流程runtime/Cpp/README.md。Android使用 NDK 与 Ninja 构建例如runtime/Cpp/README.mdcmake -DCMAKE_ANDROID_NDK/folder/of/android_ndkr17_and_above \ -DCMAKE_SYSTEM_NAMEAndroid -DCMAKE_ANDROID_API14 \ -DCMAKE_ANDROID_ARCH_ABIx86 -DCMAKE_ANDROID_STL_TYPEc_shared \ -DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSIONclang \ -DCMAKE_BUILD_TYPERelease /folder/antlr4_src_dir -G Ninja如何运行生成的 Lexer / Parser把各环节串起来得到一个可工作的 Parser 非常简单。仓库的 runtime/Cpp/demo 目录提供了一个可直接参考的最小示例其 README 描述了在 OSX、Windows、Linux 上的构建运行步骤下载 ANTLR jar 放入 demo 目录打开平台对应的生成脚本Windows 用 generate.cmd*nix/OSX 用 generate.sh把其中的 jar 名称更新为实际文件名运行生成脚本会在generated/子目录产出测试 Parser Lexer 及 Listener/Visitor 类打开与系统匹配的工程Windows 下有 antlr4cpp-vs2022.slnmacOS 下有 antlrcpp-demo.xcodeproj另有 Linux/main.cpp 配合 CMake编译并运行。Linux/main.cpp 给出了最典型的驱动流程——先构造ANTLRInputStream再依次组装 Lexer、CommonTokenStream与 Parser最后以入口规则parser.main()得到语法树并打印ANTLRInputStream input(u8 \\;(((x * π))) * µ ∰; a (x * (y ? 0 : 1) z);); TLexer lexer(input); CommonTokenStream tokens(lexer); tokens.fill(); for (auto token : tokens.getTokens()) { std::cout token-toString() std::endl; } TParser parser(tokens); tree::ParseTree* tree parser.main(); std::cout tree-toStringTree(parser) std::endl std::endl;注意 demo 使用-package antlrcpptest因此源码中通过using namespace antlrcpptest;引入生成的类若未指定 package则生成类位于全局命名空间。如何创建并运行自定义 Listener前面的生成步骤已经为你创建了 Listener 与 BaseListener 类Listener 类是抽象接口为每一条 parser 规则声明enterXxx/exitXxx方法BaseListener 类将所有这些抽象方法以空实现的方式实现因此当你只想实现个别方法时不必自己补齐全部接口——直接继承 BaseListener即可。一个完整的自定义 Listener 示例原文代码可直接复制使用#include iostream #include antlr4-runtime.h #include MyGrammarLexer.h #include MyGrammarParser.h #include MyGrammarBaseListener.h using namespace antlr4; class TreeShapeListener : public MyGrammarBaseListener { public: void enterKey(ParserRuleContext *ctx) override { // Do something when entering the key rule. } }; int main(int argc, const char* argv[]) { std::ifstream stream; stream.open(argv[1]); ANTLRInputStream input(stream); MyGrammarLexer lexer(input); CommonTokenStream tokens(lexer); MyGrammarParser parser(tokens); tree::ParseTree *tree parser.key(); TreeShapeListener listener; tree::ParseTreeWalker::DEFAULT.walk(listener, tree); return 0; }该示例假设你的语法中包含名为key的 parser 规则从而生成了对应的enterKey函数。整个遍历由tree::ParseTreeWalker::DEFAULT.walk(listener, tree)驱动这是 ANTLR 4 中标准的监听-遍历模式ParseTreeWalker实现在 runtime/Cpp/runtime/src/tree 目录。本 Target 的特殊之处C target 有一些其他语言 target 不需要关心的独特问题下面分四类说明。代码生成层面Code Generation Aspects通过 ANTLR4 jar 生成代码时有两个可选参数有助于将生成文件更好地集成进你的应用1. 命名空间-package参数$ antlr4 -DlanguageCpp -package MyNamespace MyGrammar.g4生成类的所有代码将放进指定的 C namespace 中。demo 工程即用-package antlrcpptest把生成的类放进antlrcpptest命名空间。2. 导出宏export macro-DexportMacro...尤其在 VC 下从 DLL 导出类需要额外工作通常用一个宏来区分正在生成 DLL还是导入 DLL。ANTLR4 运行时自身也使用了这样一个宏#ifdef ANTLR4CPP_EXPORTS #define ANTLR4CPP_PUBLIC __declspec(dllexport) #else #ifdef ANTLR4CPP_STATIC #define ANTLR4CPP_PUBLIC #else #define ANTLR4CPP_PUBLIC __declspec(dllimport) #endif #endif与ANTLR4CPP_PUBLIC类似你可以为自己的生成类指定同样的宏方式有两种命令行参数-DexportMacro...语法文件中的 grammar optionoptions {exportMacro...;}相关补充在 Visual Studio 中构建静态库时除了工程要设置为静态库还需要定义ANTLR4CPP_STATIC宏如果你自己编译运行时的话。对于 gcc 与 clang可以使用-fvisibilityhidden隐藏所有符号只保留显式设为 default 可见的符号运行时中所有公共类已做过此设置。编译层面Compile Aspects编译生成文件时可以根据需要配置一个编译选项Thread local DFA 宏-DANTLR4_USE_THREAD_LOCAL_CACHE1在编译选项中加上该宏即可启用 thread local DFA 缓存默认关闭。开启后每个线程使用自己独立的 DFA。代价是内存占用上升要为每个线程存储各自的 DFA会有少量重复计算每个线程都要构建自己的 DFA。收益是提升多线程并发场景下的解析性能。换句话说当你发现并发吞吐不够高时就应该考虑打开这个选项。内存管理Memory ManagementC 没有内建的内存管理机制因此需要额外小心。ANTLR C 运行时主要依赖智能指针但如果使用不当会带来时间开销或内存副作用例如循环引用。目前运行时的内存管理整体上非常稳定。有一条核心准则在代码中看到**裸指针raw pointer**时应视为它由他处管理绝不要自己去 delete、或把它交给智能指针等。相应地对象生命周期构成一条依赖链ANTLRInputStream或其等价物 → TokenStream如 CommonTokenStream → Parser → ParseTree语法树语法树仅在它的 parser 存活期内有效parser 又仅在它的 token stream 存活期内有效依此类推回溯到最初的ANTLRInputStream。若想在函数调用之间保留语法树你需要创建并持有这条链上的所有对象并且在不再需要时delete除语法树以外的全部对象。Unicode 支持编码问题基本是输入侧的问题——即 Lexer 把文本输入转换为 token 的过程Parser 对编码完全无感知。C target始终要求 UTF-8 输入无论是字符串还是流输入会被转换为UTF-32一个char32_t数组再送入 Lexer 处理。这解释了为什么 Linux/main.cpp 中的演示输入使用u8...字符串字面量其中包含、π、µ、∰等非 ASCII 字符仍能正确分词。相应地TLexer.g4 中的 fragment 规则LETTER : [a-zA-Z\u0080-\u{10FFFF}]也体现了对扩展 Unicode 码点的支持。Named Actions深度定制生成代码为了便于定制生成文件ANTLR 提供了一系列named actions。这些 action 绑定到生成代码中的特定区域允许你注入自定义target 相关的代码。所有 target 都支持以下基础 actionparser::headerparser::memberslexer::headerlexer::members以及它们不带 scope 的等价写法header和members。这里需要澄清一个易混淆点header 并不指 C/C 头文件而是指代码文件的顶部。headeraction 的内容会出现在所有生成文件的第一行因此非常适合放置版权/许可license/copyright信息。membersaction 的内容则被放到 lexer 或 parser 类声明的public 区可用于声明公共变量或供 grammar predicate 使用的谓词函数。由于headermembers被所有 target 支持跨语言可移植的公共内容放在这里最合适。C 特有的扩展 action在此基础上C target 还支持更多 named actions。受限于无法自定义新的 scope例如在parser之外再加listener这些 action 只能并入现有 scopelexer或parser。demo 语法文件 TLexer.g4 与 TParser.g4 中实际使用了下面全部 action可作为完整参考。Lexer 的扩展 actionAction放置位置lexer::preinclude紧接第一个#include之前适合必须最先出现的头、系统头文件等出现在 lexer 的 h 与 cpp 两个文件中lexer::postinclude紧跟最后一个#include之后、任何类代码之前例如额外的命名空间出现在 h 与 cpp 两个文件中lexer::context紧接 lexer 类声明之前例如额外的类型、别名、前向声明出现在 lexer 的 h 文件中lexer::declarationslexer 声明的private 区生成类的各区严格遵循自上而下的 public、protected、private 顺序用于私有变量等lexer::definitionscpp 文件中其他实现之前但在postinclude之后用于实现私有类型等Parser 的扩展 action与上面 lexer 所列完全同名对应parser::preinclude、parser::postinclude、parser::context、parser::declarations、parser::definitions。Listener / Visitor 相关的扩展 action在 parser 的 scope 下还有专门针对 listener 与 visitor 类族的 actionparser::listenerpreinclude、parser::listenerpostinclude、parser::listenerdeclarations、parser::listenermembers、parser::listenerdefinitionsparser::baselistenerpreinclude、parser::baselistenerpostinclude、parser::baselistenerdeclarations、parser::baselistenermembers、parser::baselistenerdefinitionsparser::visitorpreinclude、parser::visitorpostinclude、parser::visitordeclarations、parser::visitormembers、parser::visitordefinitionsparser::basevisitorpreinclude、parser::basevisitorpostinclude、parser::basevisitordeclarations、parser::basevisitormembers、parser::basevisitordefinitions这些 action 的语义与 lexer/parser 的同名 action 一一对应preinclude 在最前、postinclude 在 include 之后、declarations/members 进类声明区、definitions 进 cpp 实现区可以自行类推。注意listener 和 visitor 没有contextaction——因为它的使用频率比其它 action 更低而 action 的数量已经很多了。以 TParser.g4 为例parser::members中定义了myAction()、doesItBlend()等成员函数这些函数随后直接在语法规则的谓词中被调用如divide : ID (and_ GreaterThan)? {doesItBlend()}?;展示了 members action 与 grammar predicate 配合的典型用法。小结完整工作流一览把本文内容串起来一个完整的 ANTLR 4 C 项目工作流是编写语法文件.g4可在其中通过-package/exportMacro语法选项或 named actions 声明定制需求生成代码antlr4 -DlanguageCpp MyGrammar.g4必要时附加-package、-DexportMacro、-listener/-visitor等参数构建/获取运行时下载预编译二进制或用 CMakeLinux/macOS、VS 工程Windows、XCode 工程macOS/iOS构建 runtime/Cpp编写驱动代码构造ANTLRInputStream→ Lexer →CommonTokenStream→ Parser → 得到语法树处理语法树继承 BaseListener / BaseVisitor 实现自定义逻辑用ParseTreeWalker遍历按需优化多线程场景开启-DANTLR4_USE_THREAD_LOCAL_CACHE1注意对象生命周期与 UTF-8 输入约定。进一步阅读仓库内参考doc/cpp-target.md本文的原始依据文档runtime/Cpp/README.mdC 运行时构建与使用说明runtime/Cpp/demo/README.md演示工程构建步骤runtime/Cpp/demo/TLexer.g4 与 runtime/Cpp/demo/TParser.g4全部 named actions 的完整用法示例runtime/Cpp/demo/Linux/main.cpp最小的运行时驱动示例runtime/Cpp/CMakeLists.txt 与 runtime/Cpp/cmake/Antlr4Package.mdCMake 构建与 Package 集成细节runtime/Cpp/runtime/src/antlr4-runtime.h运行时伞形头文件umbrella header在应用中包含它即可获得使用库所需的全部声明。赞分享开发工具编程语言编译器【免费下载链接】antlr4ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files.项目地址https://gitcode.com/gh_mirrors/an/antlr4点击查看免费下载相关推荐为 ANTLR 4 创建自定义语言 Target从 Target 类到运行时与测试套件的完整指南为 ANTLR 4 创建自定义语言 Target从 Target 类到运行时与测试套件的完整指南 导读 ANTLR 4 是一个以 Java 实现的解析器生成器开发工具编程语言编译器AutoAgent 自定义 Docker 沙箱完全指南从定制镜像构建到运行时配置AutoAgent 自定义 Docker 沙箱完全指南从定制镜像构建到运行时配置 本指南以 AutoAgent 仓库内沿袭自 OpenHands 的自定义沙箱人工智能大模型AI AgentAgent 框架工具调用自主智能体RAGradare2 基于 libFuzzer 的模糊测试实践构建、运行与自定义 Fuzz Target 完全指南radare2 基于 libFuzzer 的模糊测试实践构建、运行与自定义 Fuzz Target 完全指南 导读 本文面向想要为 radare2 https逆向工程网络安全创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表