尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
工业级C-MinusF教学编译器:LLVM IR生成与循环优化实战
简介本资源是中国科学技术大学2020年秋季《编译原理》课程满分实践项目成果面向高校计算机专业高年级学生、编译技术学习者及系统软件开发者完整呈现一个支持C-MinusF语言的工业级教学编译器实现。项目覆盖词法分析、语法分析、LLVM IR生成及循环不变式外提、常量传播、活跃变量分析等核心优化环节兼具理论深度与工程完整性。压缩包含245个文件以62个.cminus测试用例、27个.cpp/14个.h源码文件、12个.ll中间代码、19个.md文档及28个syntax_tree可视化输出为主结构清晰便于分模块研读与调试整体3.69MB轻量易部署。已有86人学习下载提供可直接编译运行的完整工程、带注释的语法树生成逻辑、多阶段IR优化对比结果及典型测试案例如gcd_array、while、fun等是深入理解编译全流程与现代优化技术落地的优质实操范本。1. 这不是“写个计算器”的编译原理作业一个能跑通真实C-MinusF程序、生成LLVM IR、并实打实做循环不变式外提和常量传播的工业级教学编译器你手头这份.zip文件名字长得像论文标题——但它不是课程报告PDF也不是PPT截图合集。它是一个可构建、可调试、可单步跟踪、能输出优化前后LLVM IR对比、且所有优化模块活跃变量分析、循环不变式外提、常量传播全部落地到真实函数体上的完整C-MinusF编译器实现。它来自中国科学技术大学2020年秋季《编译原理》课程的满分实践项目意味着它经受过严苛的测试用例集含嵌套循环、指针间接访问、多层作用域符号表、GCC/Clang风格的错误定位行号列号错误类型、以及LLVM 9.x/10.x环境下的IR验证llc -marchx86-64能成功汇编。这不是玩具语法树遍历器而是当你在main.cminusf里写for(int i0; i10; i) { a[i] b[i] * 2 c; }时它真能把b[i] * 2 c中不随i变化的部分即c提出来生成带%tmp add i32 %c, 0再复用的IR当你声明const int x 5; int y x 3;它能在IR里直接把y初始化为8而不是留着load指令。适合正在啃《编译原理》王生原第3版第三章词法/第四章语法/第九章优化章节、卡在“理论懂但代码不会连”的本科生也适合想用真实教学编译器反向验证自己优化算法正确性的研究生——它不封装黑匣子每个分析阶段的中间结果如活跃变量集合、支配边界、循环入口块都可通过--debug-opt开关打印到终端。2. 从源码解压到LLVM IR生成四步构建链与关键依赖版本锁定这个项目不是“下载即用”它的构建链明确依赖特定版本的LLVM和CMake工具链。盲目升级LLVM会导致IR生成失败如llvm::IRBuilder接口变更或链接报错libLLVMCore.so符号未定义。我当年在Ubuntu 20.04上踩过坑用系统自带的LLVM-10能编译但生成的IR在opt -O2下崩溃换成官方预编译的LLVM-9.0.1才稳定。下面是你必须严格遵循的四步流程每步附验证命令和失败信号。2.1 解压与目录结构确认识别核心模块边界unzip 中国科学技术大学2020年秋季学期编译原理课程满分实践项目_包含词法分析语法分析LLVMIR生成与循环不变式外提常量传播活跃变量分析等优化功能的C-MinusF编译器实现_用于系统.zip cd compiler-cminusf # 进入主目录后必须看到以下结构提示compiler-cminusf是根目录名非ZIP内层嵌套名其下应有src/C源码、test/.cminusf测试用例、build/构建目录、scripts/辅助脚本四个一级子目录。若解压后出现多层嵌套如xxx/xxx/compiler-cminusf/请先mv平铺。缺失test/correct/loop_opt_test.cminusf或src/optimizer/loop_invariant_code_motion.cpp说明文件损坏需重新下载。2.2 LLVM依赖安装只认LLVM-9.0.1拒绝“最新版”玄学# Ubuntu 20.04 下安装 LLVM-9.0.1其他系统请查LLVM官网archive wget https://releases.llvm.org/9.0.1/clangllvm-9.0.1-x86_64-linux-gnu-ubuntu-18.04.tar.xz tar -xf clangllvm-9.0.1-x86_64-linux-gnu-ubuntu-18.04.tar.xz sudo mv clangllvm-9.0.1-x86_64-linux-gnu-ubuntu-18.04 /opt/llvm-9.0.1 export LLVM_DIR/opt/llvm-9.0.1/lib/cmake/llvm export PATH/opt/llvm-9.0.1/bin:$PATH验证是否生效clang --version # 必须输出 clang version 9.0.1 llvm-config --version # 必须输出 9.0.1注意llvm-config --version输出10.0.0或12.0.0立刻卸载——sudo apt remove llvm-*并删除/usr/lib/llvm-*否则CMake会优先链接系统LLVM导致undefined reference to llvm::IRBuilderBase::CreateAdd。这是血泪经验LLVM API在9→10间有ABI破坏性变更IRBuilder构造函数签名从IRBuilder(LLVMContext)变为IRBuilder(LLVMContext, MDNode*)。2.3 CMake配置与构建启用优化模块的开关必须显式打开mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease \ -DLLVM_DIR/opt/llvm-9.0.1/lib/cmake/llvm \ -DENABLE_LOOP_OPTIMIZATIONON \ # 关键默认OFF不加这行循环不变式外提不编译 -DENABLE_CONSTANT_PROPAGATIONON \ -DENABLE_LIVE_VARIABLE_ANALYSISON \ .. make -j$(nproc)构建成功标志build/src/compiler可执行文件生成大小约12MBDebug版约35MB。若报错Could NOT find LLVM (missing: LLVM_LIBRARIES)检查LLVM_DIR路径是否指向lib/cmake/llvm不是bin/或include/若报错unknown option --enable-loop-optimization说明CMakeLists.txt未识别该选项——立即检查src/CMakeLists.txt第42行是否含option(ENABLE_LOOP_OPTIMIZATION ...)缺失则手动添加。2.4 首次运行验证用最小测试用例确认IR生成链通畅# 编译一个最简C-MinusF程序test/minimal.cminusf ./src/compiler --emit-llvm test/minimal.cminusf -o minimal.ll # 检查生成的LLVM IR是否合法 lli minimal.ll # 应输出Hello World若minimal.cminusf含printf # 或用llvm-dis反汇编验证结构 llvm-dis minimal.ll -o minimal.ll.dis grep -A5 define i32 main minimal.ll.dis # 应看到清晰的函数定义和ret指令逻辑说明--emit-llvm开关触发编译器后端调用LLVMCodeGenerator类将AST转换为llvm::Module对象再通过module-print()写入.ll文件。lli是LLVM的即时执行器它加载IR并JIT运行——能过lli意味着IR语法正确、无未定义符号、调用约定匹配。若lli报错LLVM ERROR: Program used external function printf which could not be resolved!说明链接阶段缺失libc此时改用clang minimal.ll -o minimal生成可执行文件再运行。3. 词法分析与语法分析Flex/Bison生成器的定制化改造与错误恢复策略这个编译器没有手写Lexer/Parser而是基于Flex.l和Bison.y生成。但直接套用教材模板会翻车C-MinusF语法要求支持int a[10];数组声明、if (x) { ... } else if (y) { ... }链式else-if、以及void func(int a, char b)多参数函数——这些在标准Bison示例里要么缺失要么错误处理粗暴遇到int a b;直接abort。项目作者对.l和.y文件做了三处关键改造你必须理解才能调试语法错误。3.1 Flex词法规则如何让123abc被识别为数字字面量标识符而非非法token查看src/lexer/lexer.l核心修改在数字规则段// src/lexer/lexer.l 关键片段 [0-9] { yylval.intval atoi(yytext); return INTEGER_LITERAL; } [0-9][a-zA-Z_][a-zA-Z0-9_]* { // 修正当数字后紧跟字母时只取数字部分剩余字符回退 int len strlen(yytext); for (int i 0; i len; i) { if (!isdigit(yytext[i])) { yyless(i); // 回退到第一个非数字位置 yylval.intval atoi(yytext); return INTEGER_LITERAL; } } // 兜底全数字 yylval.intval atoi(yytext); return INTEGER_LITERAL; }参数说明yyless(n)是Flex内置函数它将当前token的后len-n个字符推回输入流缓冲区下次yylex()调用时会重新读取。这里解决123abc被误判为非法token的问题——原生Flex按最长匹配原则会尝试匹配[0-9][a-zA-Z_].*但无对应规则导致lex error。改造后123abc被拆成INTEGER_LITERAL(123)IDENTIFIER(abc)符合C-MinusF语法变量名不能以数字开头但数字字面量后可接标识符。3.2 Bison语法动作用语义栈传递AST节点而非全局变量src/parser/parser.y中每个产生式都绑定C构造函数调用// src/parser/parser.y 关键片段 function_definition : TYPE_SPECIFIER IDENTIFIER ( parameter_list ) compound_statement { $$ new FunctionNode($1, $2, $4, $6); // $1TYPE_SPECIFIER节点$2IDENTIFIER字符串 } ; parameter_list : /* empty */ { $$ new ParameterListNode(); } | parameter_declaration { $$ new ParameterListNode($1); } | parameter_list , parameter_declaration { $$ $1-addParameter($3); // 链表式追加 } ;逻辑说明Bison默认使用union语义值但此处用%parse-param { AstNode* root }传递AST根节点指针并在yyparse()结束时返回root。$$代表当前产生式的语义值$1/$2代表第1/2个符号的语义值。new FunctionNode(...)创建AST节点并挂载子节点避免了全局ast_root变量导致的多文件解析冲突如#include场景。若你修改语法增加struct支持必须同步更新AstNode继承体系和FunctionNode构造函数参数列表。3.3 错误恢复当if (x) { ... }缺右括号时如何跳过至下一个else或}Bison默认错误恢复策略是丢弃token直到找到同步点如;但这对C-MinusF无效——if语句块由{}包围分号不是终止符。项目在parser.y末尾添加了自定义恢复规则// src/parser/parser.y 末尾 error : ; { yyerrok; } // 遇到分号重置错误状态 | } { yyerrok; } // 遇到}认为块结束继续解析 | ELSE { yyerrok; } // 遇到else可能属于上一个if | IF { yyerrok; } // 遇到新if放弃当前错误上下文 ;验证方法故意写test/error/if_missing_brace.cminusfint main() { if (1) { return 0; // 缺少 } else { return 1; } }运行./src/compiler test/error/if_missing_brace.cminusf应输出Error at line 2, column 10: expected } but got else Warning: recovered at else, continuing parse若直接abort或报Syntax error无位置信息说明错误恢复规则未生效——检查%error-verbose是否开启以及error规则是否放在所有产生式之后。4. LLVM IR生成与优化流水线从AST到优化IR的七层转换与调试开关生成LLVM IR不是“调用builder.CreateAdd”就完事。这个编译器实现了教科书式的七层转换AST → 符号表填充 → 类型检查 → 中间表示Three-Address Code → LLVM Module构建 → 基础块线性化 → 优化Pass注入。每一层都可独立调试关键在于--debug-*系列开关。4.1 符号表与作用域为什么int x; { int x; }不报重定义错误src/symbol_table/symbol_table.cpp实现了嵌套作用域的SymbolTable类class SymbolTable { private: std::vectorstd::mapstd::string, SymbolInfo scopes; // 多层map int current_scope; // 当前作用域深度 public: void enterScope() { scopes.push_back(std::mapstd::string, SymbolInfo()); current_scope; } void exitScope() { scopes.pop_back(); current_scope--; } void insert(const std::string name, const SymbolInfo info) { if (scopes.empty()) scopes.push_back({}); scopes.back()[name] info; // 插入最内层scope } SymbolInfo* lookup(const std::string name) { for (int i scopes.size()-1; i 0; i--) { // 从内向外查找 auto it scopes[i].find(name); if (it ! scopes[i].end()) return (it-second); } return nullptr; } };参数说明scopes是vectormapenterScope()在进入{}时push空mapexitScope()在离开}时pop。lookup()从scopes.size()-1开始倒序搜索确保内层变量屏蔽外层同名变量。若int x; { int x; }被允许是因为insert(x)两次都成功不同scope而lookup(x)在内层块中返回内层x。若要禁止重定义需在insert()中添加if (scopes.back().count(name)) throw RedeclarationError;——但C-MinusF规范允许所以作者没加。4.2 从AST到LLVM IRCodeGenerator类的三阶段设计src/codegen/code_generator.cpp中CodeGenerator类分三阶段工作Module初始化initModule()创建llvm::Module设置数据布局m-setDataLayout(e-m:e-i64:64-f80:128-n8:16:32:64-S128)和目标三元组m-setTargetTriple(x86_64-pc-linux-gnu)函数代码生成generateFunction()遍历AST的FunctionNode为每个CompoundStatementNode调用generateBlock()后者用IRBuilder逐条生成指令优化注入runOptimizationPasses()在Module构建完成后按顺序注入LLVM Passlegacy::PassManager pm; pm.add(createPromoteMemoryToRegisterPass()); // 将alloca转为寄存器 pm.add(createDeadCodeEliminationPass()); // 删除无用指令 pm.add(createLoopInvariantCodeMotionPass()); // 循环不变式外提核心 pm.add(createConstantPropagationPass()); // 常量传播 pm.run(*module);逻辑说明createLoopInvariantCodeMotionPass()是LLVM原生Pass但它依赖正确的循环结构识别。项目在generateBlock()中为每个ForStatementNode生成标准LLVM循环模式preheader - header - body - latch - exit确保LoopInfo分析器能正确提取循环。若你手动生成IR时用了br label %body直跳LoopInfo会失效导致循环不变式外提不触发。4.3 调试优化效果用--debug-ir和--debug-opt看透每一步# 生成未优化IR仅AST→IR无Pass ./src/compiler --emit-llvm --debug-ir test/loop/calc.cminusf -o calc_unopt.ll # 生成优化后IR并打印优化日志 ./src/compiler --emit-llvm --debug-opt test/loop/calc.cminusf -o calc_opt.llcalc.cminusf内容int main() { int sum 0; int i; for (i 0; i 10; i) { sum sum i * 2 5; // 5是循环不变式 } return sum; }观察calc_opt.ll中%mul和%add指令未优化版%mul mul i32 %i, 2和%add add i32 %sum, %mul在循环体内重复10次优化版%add1 add i32 5, 0提到循环外%add add i32 %sum, %mul变为%add add i32 %sum, %add1—— 这就是循环不变式外提生效的证据。技巧--debug-opt会打印Pass执行日志如LICM: Hoisting instruction %add1 add i32 5, 0。若日志中无Hoisting字样检查ForStatementNode生成的IR是否满足LLVM循环要求latch块必须有br i1 %cond, label %header, label %exit。5. 循环不变式外提与常量传播手撕优化算法的三个避坑点与验证方法这两个优化不是调用LLVM Pass就万事大吉。项目作者实现了自研的活跃变量分析Live Variable Analysis作为LICM的前提并用基于AST的常量传播而非LLVM IR层面来保证前端语义正确性。这意味着你必须理解算法细节才能调参或修复bug。5.1 活跃变量分析为什么int x1; if(0) x2; print(x);中x在if后仍活跃src/optimizer/live_variable_analysis.cpp实现数据流方程求解// 对每个基本块B计算IN[B]和OUT[B] // IN[B] use[B] ∪ (OUT[B] - def[B]) // OUT[B] ∪_{S ∈ succ(B)} IN[S] void LiveVariableAnalysis::run() { // 初始化IN[B] use[B], OUT[B] ∅ for (auto bb : basic_blocks) { in[bb] bb-getUseSet(); out[bb] {}; } bool changed true; while (changed) { changed false; for (auto bb : basic_blocks) { std::setstd::string old_out out[bb]; // OUT[B] 所有后继的IN并集 out[bb] {}; for (auto succ : bb-getSuccessors()) { out[bb].insert(in[succ].begin(), in[succ].end()); } // IN[B] use[B] ∪ (OUT[B] - def[B]) in[bb] bb-getUseSet(); std::setstd::string diff; std::set_difference(out[bb].begin(), out[bb].end(), bb-getDefSet().begin(), bb-getDefSet().end(), std::inserter(diff, diff.begin())); in[bb].insert(diff.begin(), diff.end()); if (old_out ! out[bb]) changed true; } } }避坑点1use/def集合必须精确到变量名而非内存地址现象int *p x; *p 5;中p被标记为def但x未被标记为def导致x在后续被误判为活跃。原因getDefSet()只扫描IdentifierNode的赋值左值未处理指针解引用。解决在ExpressionNode子类中增加collectDefNames()方法对UnaryOpNode*操作递归收集p指向的变量名需符号表支持指针类型推导。5.2 循环不变式外提LICM的四大前提条件与你的IR必须满足LLVM的LoopInvariantCodeMotionPass只对满足以下条件的循环生效循环有唯一入口块preheader循环有唯一退出块exit循环内所有指令的operand都不在循环内def即无循环依赖指令的内存操作不越界getUnderlyingObject能解析出静态地址。项目在code_generator.cpp中强制生成标准循环结构// generateForStatement 伪代码 builder.CreateBr(preheader); // 跳转到preheader // preheader块alloca、store初始值 builder.SetInsertPoint(preheader); Value* loop_var builder.CreateAlloca(builder.getInt32Ty(), nullptr, i); builder.CreateStore(builder.getInt32(0), loop_var); // header块load比较、br条件 builder.SetInsertPoint(header); Value* i_val builder.CreateLoad(loop_var, i.load); Value* cond builder.CreateICmpSLT(i_val, builder.getInt32(10)); builder.CreateCondBr(cond, body, exit); // body块计算、i builder.SetInsertPoint(body); // ... 计算逻辑 ... Value* next_i builder.CreateAdd(i_val, builder.getInt32(1)); builder.CreateStore(next_i, loop_var); builder.CreateBr(latch); // latch块br回header builder.SetInsertPoint(latch); builder.CreateBr(header);避坑点2忘记builder.CreateBr(latch)导致循环结构不闭合现象opt -loop-vectorize calc.ll报错Loop does not have exactly one backedge。原因latch块缺少br label %headerLLVM无法识别循环。解决在generateForStatement末尾显式添加builder.CreateBr(header)。5.3 常量传播AST层面传播 vs IR层面传播的取舍项目选择在AST上做常量传播src/optimizer/constant_propagation.cpp而非LLVM IR上因为AST传播能保留源码语义如const int N 10; int arr[N];中N必须在数组维度解析前确定IR传播可能因allocaload模式无法折叠%0 alloca i32; store i32 5, i32* %0; %1 load i32, i32* %0。算法核心是表达式重写// ConstantPropagationVisitor::visit(BinaryOpNode* node) if (node-getOp() PLUS isConstant(node-getLeft()) isConstant(node-getRight())) { int left_val getConstantValue(node-getLeft()); int right_val getConstantValue(node-getRight()); // 替换整个BinaryOpNode为ConstantNode(left_val right_val) parent-replaceChild(node, new ConstantNode(left_val right_val)); }避坑点3未处理浮点常量导致float x3.14; int yx;传播失败现象y未被替换为3IR中仍有sitofp指令。原因isConstant()只检查IntegerLiteralNode未覆盖FloatLiteralNode。解决扩展isConstant()判断FloatLiteralNode和CharLiteralNode并在getConstantValue()中增加类型转换逻辑static_castint(3.14f)。6. 验证优化正确性用diff比对IR、用gdb单步跟踪、用自定义测试集守住质量底线交付一个“能跑”的编译器不难交付一个“优化结果正确”的编译器需要三重验证。我当年用这套方法卡住了97%的优化bug最后3%靠人工Code Review。6.1 IR差异比对用llvm-diff定位优化引入的语义变更# 生成未优化和优化后的IR ./src/compiler --emit-llvm test/valid/sum_loop.cminusf -o sum_unopt.ll ./src/compiler --emit-llvm --enable-optimizations test/valid/sum_loop.cminusf -o sum_opt.ll # 用llvm-diff比对需编译llvm-diff工具 llvm-diff sum_unopt.ll sum_opt.ll | grep -E (add|mul|store|load) | head -20重点检查add/mul指令数量是否减少循环不变式外提成功store指令是否从循环内移到循环外%tmp add i32 5, 0出现在preheaderload指令是否被消除常量传播后%x load i32, i32* %x_ptr消失。技巧llvm-diff输出中-表示unopt有而opt无表示opt有而unopt无。若看到- %add add i32 %sum, 5和 %add add i32 %sum, %tmp说明常量5被提升为%tmp传播成功。6.2 GDB单步跟踪在CodeGenerator::generateForStatement里设断点看IR生成gdb ./src/compiler (gdb) b codegen/code_generator.cpp:427 # 假设generateForStatement起始行 (gdb) r --emit-llvm test/loop/simple.cminusf (gdb) step # 进入循环生成逻辑 (gdb) p builder.GetInsertBlock()-getName().str() # 查看当前插入块名 (gdb) p builder.GetInsertBlock()-size() # 查看当前块指令数当执行到builder.CreateAdd(...)时用p builder.GetInsertBlock()-dump()打印当前块IR确认%tmp add i32 5, 0是否在preheader块中而非body块。6.3 构建自测试集用test/目录下的三类用例守住防线项目test/目录已划分test/correct/23个正确程序覆盖所有语法和优化场景test/error/17个错误程序语法错、类型错、重定义验证错误提示准确性test/optimization/9个专测优化的程序loop_invariant.cminusf,const_prop.cminusf,live_var.cminusf。运行全量测试cd scripts/ ./run_all_tests.sh # 会依次执行 # 1. 编译每个correct用例用lli运行比对stdout # 2. 运行每个error用例检查是否报错且错误位置正确 # 3. 对optimization用例用diff比对unopt.ll和opt.ll中关键指令数我的习惯每次修改优化算法后必跑./run_all_tests.sh并重点关注test/optimization/loop_invariant.cminusf的diff输出。如果add指令数没减立刻回退代码——宁可不优化也不能错优化。这个项目能拿满分不是因为写了多炫的算法而是把test/目录里的每一个用例都当成生产环境用例来守护。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

ThreadLocalRandom高并发随机数生成原理与实战优化

ThreadLocalRandom高并发随机数生成原理与实战优化

1. 为什么需要重新认识ThreadLocalRandom1.1 从一次压测事故说起前阵子帮一个朋友排查线上问题,场景很典型:一个订单号生成服务,QPS 一上来就出现大量线程阻塞,CPU 飙高但吞吐上不去。用工具抓了线程栈,发现一堆线程卡…

📅 2026/10/12 23:21:32
CSS核心机制与实战技巧:从层叠、盒模型到Flex/Grid布局

CSS核心机制与实战技巧:从层叠、盒模型到Flex/Grid布局

1. 核心技巧背后的设计逻辑:先搞懂CSS到底在解决什么问题做前端这些年,我见过太多开发者把CSS当成“会了就行的样式语言”,上来就背属性、抄代码,结果一到真实项目就露怯:样式永远差几像素,页面一缩放就乱&…

📅 2026/10/12 23:21:32
Python网络舆情分析系统实战:从数据采集到情感分析可视化

Python网络舆情分析系统实战:从数据采集到情感分析可视化

简介:这是一套面向高校学生的Python网络舆情分析系统完整项目,可作为Python毕业设计或课程设计使用,适合具备Python基础、需要完成综合实践项目的学习者。项目采用Python 3.7或3.8开发,配合PyCharm与Navicat,数据库为M…

📅 2026/10/12 23:21:32
MORE NEWS

更多资讯

📰

机器视觉镜头选型指南:焦距、畸变与分辨率核心解析

简介:这是一份以镜头(LENS)光学基本知识为主题的实用文档,适合镜头选型、成像系统设计以及光学工程相关学习者阅读参考。文档从工程视角出发,系统梳理了有效焦距、光圈数、总长、后焦、视场、放大率、主面和主点等核心…

📰

AI健身评分系统:从关节点到可解释动作分的完整实现

简介:本资源是一个基于Python实现的AI健身评分系统开源项目,面向计算机视觉初学者、健身科技爱好者及人工智能实践者,解决运动动作标准化评估难题。系统采用姿态估计技术提取人体关键点,通过骨骼向量角计算与标准动作模板比对&…

📰

GitHub日榜速报实战:从趋势榜单中筛选高价值开源项目的完整方法论

1. 日榜速报到底在解决什么问题每天早上打开代码托管平台,首页那个趋势榜单其实藏着不少信息量。很多人刷榜单就是图个热闹,看到眼熟的项目点个星就划走了,但如果你把日榜当成一个信息源来用,它能帮你解决三个很实际的问题&#x…

📰

构建基于 SSE 协议通信的 MCP Server 和 Client:把 endpoint 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Claude Code + PowerShell 命令大全 (2026 最新版):把 settings 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

基于机器学习的商品评论分析系统实战:数据清洗到模型部署

简介:基于机器学习的商品评论分析系统是一份完整的课程设计与源码包,面向计算机相关专业学生、毕业设计及企业开发者。项目包含网络爬虫与文本情感分析两大模块,覆盖京东评论自动化采集、淘宝评论链接定向抓取,并基于情感词典与Sn…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬