如何基于C++结合Flex、Bison实现Markdown转HTML自定义编译器?
用C++ + Flex + Bison实现Markdown转HTML编译器的完整流程
1. 先确定核心支持的Markdown子集
先从基础特性入手,避免一开始复杂度太高,建议先实现:
- 1-6级标题(
#至######开头的行) - 粗体(
**文本**)、斜体(*文本*) - 无序列表(
-开头的行) - 普通段落文本
2. 搭建项目文件结构
创建以下核心文件:
md.l:Flex词法分析器规则定义md.y:Bison语法分析器规则定义codegen.h/codegen.cpp:C++实现的HTML代码生成模块main.cpp:程序入口,负责文件读写与分析器调用
3. 实现Flex词法分析器(md.l)
把Markdown文本拆分为可识别的token,传递给Bison处理:
%{ #include "md.tab.h" #include <string> using namespace std; #define YY_DECL int yylex() %} %option noyywrap %option c++ %% ^#{1,6}[ \t]+.*\n { yylval.str = new string(yytext); return TITLE; } \*\*[^\*]+\*\* { yylval.str = new string(yytext); return BOLD; } \*[^\*]+\* { yylval.str = new string(yytext); return ITALIC; } ^-[ \t]+.*\n { yylval.str = new string(yytext); return LIST_ITEM; } [^#\*\-\n]+ { yylval.str = new string(yytext); return TEXT; } \n { return NEWLINE; } [ \t]+ { /* 忽略空白字符 */ } %%
4. 实现Bison语法分析器(md.y)
定义语法规则,将token组合为语义单元,触发HTML代码生成动作:
%{ #include <iostream> #include <string> #include "codegen.h" using namespace std; extern int yylex(); extern void yyerror(const char* s); CodeGenerator codegen; %} %union { string* str; } %token <str> TITLE BOLD ITALIC LIST_ITEM TEXT %token NEWLINE %% markdown_file: /* 空 */ | markdown_file element ; element: title_element | list_element | paragraph_element | inline_formatting ; title_element: TITLE NEWLINE { string& title_str = *$1; int level = 0; while (level < title_str.size() && title_str[level] == '#') level++; string content = title_str.substr(level + 1); content = content.substr(0, content.find_last_not_of("\n") + 1); codegen.generateTitle(level, content); delete $1; } list_element: list_item+ { codegen.endList(); } list_item: LIST_ITEM NEWLINE { string& item_str = *$1; string content = item_str.substr(item_str.find_first_not_of("- \t")); content = content.substr(0, content.find_last_not_of("\n") + 1); codegen.generateListItem(content); delete $1; } paragraph_element: TEXT NEWLINE { string& para_str = *$1; para_str = para_str.substr(0, para_str.find_last_not_of("\n") + 1); codegen.generateParagraph(para_str); delete $1; } inline_formatting: BOLD { string& bold_str = *$1; string content = bold_str.substr(2, bold_str.size() - 4); codegen.generateBold(content); delete $1; } | ITALIC { string& italic_str = *$1; string content = italic_str.substr(1, italic_str.size() - 2); codegen.generateItalic(content); delete $1; } %% void yyerror(const char* s) { cerr << "语法错误: " << s << endl; }
5. 实现C++代码生成模块
codegen.h
#ifndef CODEGEN_H #define CODEGEN_H #include <string> #include <iostream> class CodeGenerator { public: CodeGenerator(); void generateTitle(int level, const std::string& content); void generateParagraph(const std::string& content); void generateListItem(const std::string& content); void generateBold(const std::string& content); void generateItalic(const std::string& content); void endList(); void generateHTMLHeader(const std::string& title = "Markdown转换结果"); void generateHTMLFooter(); private: bool in_list_ = false; }; #endif
codegen.cpp
#include "codegen.h" CodeGenerator::CodeGenerator() {} void CodeGenerator::generateHTMLHeader(const std::string& title) { std::cout << "<!DOCTYPE html>\n<html>\n<head>\n<title>" << title << "</title>\n</head>\n<body>\n"; } void CodeGenerator::generateHTMLFooter() { if (in_list_) { std::cout << "</ul>\n"; in_list_ = false; } std::cout << "</body>\n</html>\n"; } void CodeGenerator::generateTitle(int level, const std::string& content) { if (in_list_) { std::cout << "</ul>\n"; in_list_ = false; } std::cout << "<h" << level << ">" << content << "</h" << level << ">\n"; } void CodeGenerator::generateParagraph(const std::string& content) { if (in_list_) { std::cout << "</ul>\n"; in_list_ = false; } std::cout << "<p>" << content << "</p>\n"; } void CodeGenerator::generateListItem(const std::string& content) { if (!in_list_) { std::cout << "<ul>\n"; in_list_ = true; } std::cout << "<li>" << content << "</li>\n"; } void CodeGenerator::endList() { if (in_list_) { std::cout << "</ul>\n"; in_list_ = false; } } void CodeGenerator::generateBold(const std::string& content) { std::cout << "<strong>" << content << "</strong>"; } void CodeGenerator::generateItalic(const std::string& content) { std::cout << "<em>" << content << "</em>"; }
6. 实现主程序入口(main.cpp)
负责文件读取、分析器启动与HTML头尾生成:
#include <iostream> #include <fstream> #include "md.tab.h" #include "codegen.h" extern FILE* yyin; extern CodeGenerator codegen; int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "用法: " << argv[0] << " <输入Markdown文件>\n"; return 1; } yyin = fopen(argv[1], "r"); if (!yyin) { std::cerr << "无法打开文件: " << argv[1] << "\n"; return 1; } codegen.generateHTMLHeader(); yyparse(); codegen.generateHTMLFooter(); fclose(yyin); return 0; }
7. 编译与运行流程
- 生成Flex词法分析器代码:
flex -o md.yy.cpp md.l
- 生成Bison语法分析器代码:
bison -d -o md.tab.cpp md.y
- 编译所有源文件:
g++ -o md2html main.cpp md.yy.cpp md.tab.cpp codegen.cpp
- 测试转换:
./md2html input.md > output.html
8. 调试与扩展建议
- 调试:在Flex规则中添加
cout << "Token: " << yytext << endl;打印识别的token,在Bison动作中添加调试输出确认语义处理逻辑。 - 扩展:逐步添加链接、图片、块引用等特性,只需在Flex中新增token规则,Bison中补充语法规则,代码生成模块新增对应HTML标签生成方法。
- 注意:处理HTML特殊字符(如
<、>、&)时,需转义为对应实体(如<、>、&)避免解析错误。
内容的提问来源于stack exchange,提问作者Manish Kumar
相关产品推荐
相关产品推荐

