用Python手写CNSH编译器:从词法分析到栈式虚拟机的完整实现
CNSH编译器 v1.0Python版是我最近抽了两个星期的晚上时间写完的一个小项目。它不算大但完整走通了“源码 - 词法分析 - 语法分析 - AST - 字节码 - 栈式虚拟机”这条编译链路最终能运行一门自己定义的迷你语言。我把这门语言取名为 CNSH内部代号叫“紧凑脚本”不打算解释全称纯粹是写到一半懒得改名的产物。文章很长但不是一个教科书式的项目复盘而是我踩过的坑、试过的办法以及一些“早知道会节省不少时间”的经验总结。如果你正打算用 Python 写一个自己的DSL、脚本语言、或者干脆就是想理解编译器到底怎么工作这篇应该对你有用。1. 起因我只是想给配置文件来点逻辑1.1 一套配置需求把我推到编译器边缘事情从一个很普通的诉求开始我当时在维护一个小的批处理工具它需要根据不同的输入文件跑不同的处理分支条件组合还不少。一开始我用纯 Python 写规则每加一个分支就改一次代码改多了人就容易烦躁。后来换成 JSON 配置条件是能表达一部分了但只要出现循环、递归、或者稍微复杂的计算JSON 就开始别扭——它只能描述数据不能描述行为。那段时间我甚至想过直接让用户填 Python 表达式又担心写错一行就把整个任务干掉没有任何隔离边界。后来我意识到我要的其实不是一个功能完整的通用语言而是一套“看起来像脚本、执行起来足够解释得清错误”的小型 DSL专门用来描述规则和轻量逻辑。于是就有了 CNSH v1.0 的立项理由用 Python 实现一个极简编译器把自定义源码编译成字节码再用自带虚拟机执行。这样规则被单独隔离在 CNSH 文件里主程序只需要调用一次执行入口错误信息也能限定在规则语言自身范围内。1.2 v1.0画下的边界不做通用语言只做一个可读可写的迷你脚本很多人一听“写编译器”就想到 C、Rust、指令集、优化器但我的目标非常明确不做优化不做复杂类型系统不做运行时的垃圾回收。CNSH v1.0 的全部代码包括词法分析、语法分析、编译和虚拟机总共只有一千多行 Python它更像一个“带自定义语法的解释器骨架”而不是生产级编译器。我把它定位成一种配置增强语言能够写变量、算术运算、条件、循环、函数递归和打印输出这就够了。我给自己画了很严格的范围把凡是可能无限膨胀的特性都拦在 v1.0 之外支持整数/浮点数、字符串、字符串插值但不支持 list 和 dict 字面量。支持自定义函数和递归调用但不支持嵌套函数。支持全局变量和函数局部变量但不支持闭包捕获。支持if/else、while、for循环不支持switch、异常处理。源码以换行或分号作为语句分隔块结构用end关键字结束不依赖缩进。这样做的直接好处是词法分析和语法分析都能保持简单。如果一开始就把闭包、类型推断、异常处理全部塞进去我大概要写三个月才能出一个能演示的版本而不是两周。第一版本可以“语言丑一点”但整套链路必须完整、可运行、可以加功能。这也是给所有想写语言的人一个建议先做最小闭环再做语法糖。2. 词法分析别急着上正则大法先把手写扫描器说清楚2.1 token的类型与源码规范CNSH 的词法分析器Lexer把字符串源码拆成一串 token。每个 token 有类型、值、行号、列号。行号和列号不是为了好看而是后面所有报错信息的地基。我见过不少初学者写 Lexer 时只用正则表达式从上到下匹配匹配完了才发现自己不知道某个错误发生在哪一行只能干瞪眼。所以我在设计第一个版本时就把 line 和 col 作为 Token 的必填字段。CNSH v1.0 支持的 token 类型大致如下token 类型示例数字123、3.14字符串hello标识符x、factorial、echo关键字let、if、else、end、func、return、while、for、echo、to运算符、-、*、/、、、、、、!符号(、)、、,、;EOF源码结束标记这里稍微提一个反直觉的设计我没有把echo当函数而是直接设计成关键字这样实现打印语句时语法分析器更省事对普通用户也更直观。类似的let被当作变量声明的关键字CNSH 不搞隐式变量声明所有变量必须先用let声明这样符号表管理更干净也不会发生“手滑拼错变量名却还能悄悄运行”的尴尬。词法分析时换行符本身不会被丢进 token 流但每个语句的结束由换行或者分号决定。这里有个实现细节源码里经常有连续多个空行如果每个换行都产生法一个 NL token语法分析器会很啰嗦。我的做法是只在遇到一个非空语句的结尾时才让 parser 认为语句结束了相当于在 parser 内部忽略多余空行只把 token 的行号列号保留下来作为报错位置。语言规范里我规定一行写多条语句时用分号分隔。2.2 Lexer核心实现与常见细节我写 Lexer 时没有使用一堆长正则来匹配整个语言而是采用逐字符扫描加小状态判断。原因很简单手写扫描器能精确控制每个 token 的边界也能在遇到不可识别字符时马上抛出带行列号的错误。正则大法确实可以在一行里把整数、浮点数全配出来但出了问题很难调试性能也未必好。核心结构长这样from dataclasses import dataclass dataclass class Token: kind: str value: object line: int col: int class Lexer: def __init__(self, text: str): self.text text self.pos 0 self.line 1 self.col 1 def peek(self): if self.pos len(self.text): return self.text[self.pos] return def advance(self): ch self.text[self.pos] self.pos 1 if ch \n: self.line 1 self.col 1 else: self.col 1 return ch每个 token 的解析函数都比较短比如数字读取时我会同时记录小数点和整数部分保证123变成整数3.14变成浮点数。字符串读取则要处理转义字符\n、\t、\否则用户写个带引号的字符串都费劲。标识符读取会顺手查关键字表命中就返回关键字 token否则返回标识符。这段代码初看很机械但它是整个编译器最常用的部分写完以后反而没什么 bug。真正需要花心思的是错误处理遇到、#这类不支持的字符时我不会悄悄跳过而是抛LexError(f无法识别的字符 {ch} at line {line}, col {col})。早期版本就是因为在 Lexer 阶段太宽容导致后面 parser 报了很奇怪的错查了半天才发现是一个非法字符混了进来。2.3 报错体验行列号比什么都值钱我项目里定义了一个基础异常类class CNSHError(Exception): def __init__(self, msg, lineNone, colNone): self.msg msg self.line line self.col col super().__init__(msg) class LexError(CNSHError): pass class ParseError(CNSHError): pass class CompileError(CNSHError): pass class VMError(CNSHError): pass包括后面的语法分析、编译和虚拟机执行所有错误都往这个基类上靠。实话说最初写 Lexer 时我只报“无法识别字符”没有带行列号结果有一次拿到一个几千行配置错误在中部我只能靠二分法手动删内容定位非常痛苦。后来我把行列号贯穿所有报错调试效率一下子上来了。CNSH 目前的报错格式是line 5, col 12: 说明这已经够在任意编辑器里快速定位了。如果你的 DSL 打算给别人用建议从一开始就把错误信息和位置当核心功能而不是等写完再补。3. 递归下降Parser为什么我用裸写而不是依赖依赖库3.1 AST节点与语句定义语法分析阶段要面对的经典选择是用 Pyparsing、LARK 之类的生成器还是直接手写递归下降。我选了后者理由有两层一是零依赖这个项目我希望任何环境下拿个 Python 解释器就能跑不想为了一个小语言引入一堆第三方包二是想保持每个报错信息都对用户友好手写解析器可以精确控制“什么时候报错、报什么内容”。CNSH 的 AST 节点我用dataclass定义每个节点代表一个语法结构。例如数字、二元运算、赋值语句、条件语句、函数定义from dataclasses import dataclass from typing import List, Optional dataclass class Program: stmts: List[object] dataclass class Number: value: float dataclass class VarRef: name: str dataclass class BinOp: op: str left: object right: object dataclass class Assign: name: str value: object dataclass class IfStmt: cond: object then_body: List[object] else_body: List[object] dataclass class WhileStmt: cond: object body: List[object] dataclass class ForStmt: var: str start: object end: object body: List[object] dataclass class FuncDef: name: str params: List[str] body: List[object] dataclass class CallExpr: func_name: str args: List[object] dataclass class ReturnStmt: value: Optional[object] dataclass class EchoStmt: value: objectAST 设计直接影响后面所有逻辑。我的原则是“节点尽可能贴近源码结构”不要过早把它拍平成某种中间表示。比如ForStmt在 AST 里保留变量名、起始值、结束值等到字节码生成阶段再去展开成条件跳转。这样方便我以后调试也方便做源码位置回显。3.2 优先级爬升一个函数解决二元运算手写表达式解析时最土的办法是把每个优先级写一层函数parse_expr - parse_add_sub - parse_mul_div - parse_primary这样没有错但层数多了以后代码非常重复加一个新的运算符得改好几处。CNSH v1.0 用了一个叫“优先级爬升”的写法把所有二元运算符的优先级放在一张表里用一个循环处理PRECEDENCE { or: 1, and: 2, : 3, : 3, : 3, : 3, : 3, !: 3, : 4, -: 4, *: 5, /: 5, } def parse_expr(self, min_prec0): left self.parse_primary() while True: if self.check_token(NL) or self.check_token(EOF): break op self.peek_value() if op not in PRECEDENCE: break prec PRECEDENCE[op] if prec min_prec: break self.advance() right self.parse_expr(prec 1 if op in (*, /, , !, , -) else prec) left BinOp(op, left, right) return left这段代码有一个细节左结合运算符在递归右操作数时传入的优先级会加一因此1 - 2 - 3会被解析成(1 - 2) - 3而不是1 - (2 - 3)。二元比较符号我允许连续出现但实际规约时还是按左结合处理v1.0 里不强制禁止a b c只是结果可能不符合直觉。如果要做得更严谨应该单独设计链式比较但这不在第一版范围内。3.3 错误恢复宁可错行不可崩盘递归下降最烦的问题是一旦遇到语法错误解析器可能一口气吐出几百个错误把用户淹没。更糟糕的是某些错误会导致无限循环。我设计了一套简单的“同步恢复”机制当 parse 一个语句失败时不是立刻抛错退出整个解析流程而是记录第一个错误的行列号然后不断丢弃 token直到遇到一行新的起始 token 再继续尝试。由于 CNSH 语句以换行或分号结束这在普通脚本语言里相对好处理。我实现的原则是“一次只报一个致命错误”语法错误信息格式为line x, col y: 预期是 end但实际是 xxx。如果恢复后继续报错也不会产生太离谱的海量错误。这个设计在 v1.0.1 的试用反馈里被点名表扬过因为很多用户第一次写脚本时容易丢end如果错误能准确告诉他们是行 8 缺了end他们很快就改对了。可见错误恢复不是锦上添花而是一个 DSL 能否被正常使用的门槛。4. AST到字节码从遍历树到生成指令4.1 作用域解析和符号表AST 构建好以后下一步是遍历它并生成字节码。这一步里最基础也最重要的数据结构是符号表。CNSH 没有把变量语法树里直接压栈因为如果不先完成“声明 vs 使用”的检查后续生成的指令很可能访问到不存在的变量。我设计了一套带父指针的作用域结构class SymbolTable: def __init__(self, parentNone): self.vars set() self.parent parent def declare(self, name): self.vars.add(name) def is_local(self, name): if name in self.vars: return True if self.parent: return self.parent.is_local(name) return False在编译器遍历时全局作用域在最外层函数定义会创建新的子作用域。所有变量在编译阶段就会被标成“局部”或“全局”。我不做复杂的静态类型检查也不区分整数和浮点数真正的类型错误留到虚拟机运行时判断。有人可能会觉得这样太不“编译器”了但对于一个配置型语言来说“编译期运行期分离”并不重要重要的是代码能快速跑起来并且错误信息能告诉你哪儿写错了。4.2 指令集设计20条指令足以撑起一个前端CNSH v1.0 使用栈式虚拟机所以指令集非常小主要分几组常量/变量加载PUSH、LOAD_VAR、STORE_VAR运算类ADD、SUB、MUL、DIV、LT、LE、GT、GE、EQ、NE控制流JUMP、JUMP_IF_FALSE函数CALL、MAKE_FUNCTION、RETURN输出ECHO整组指令大约二十条。有些人可能觉得“这也太少了”但实际跑下来条件分支、循环、递归、打印全都覆盖了。我特意把比较运算单独拆成不同的指令LT、LE等而不是像某些教程那样统一用一个COMPARE加参数这样虚拟机的执行主循环可以写得更直观每个算数操作符对应的处理函数一目了然。实现时码流我直接用(opname, arg)元组数组来表示。它虽然没有真正把操作码编码成二进制但逻辑上已经是编译器后端了。如果你想把它变成真正的字节码文件只需要给每个 opname 分配一个整数编号再把 arg 序列化即可这不是关键难点。4.3 一个阶乘示例的编译产物为了让上面的设计落地我用阶乘函数作为贯穿示例func factorial(n) if n 1 return 1 end return n * factorial(n - 1) end let result factorial(6) echo result这段代码经过词法、语法、语义分析后会生成类似下面的中间指令我在这里加了缩进方便观察MAKE_FUNCTION [n] LOAD_VAR n PUSH 1 LE JUMP_IF_FALSE false_label PUSH 1 RETURN false_label: LOAD_VAR n LOAD_VAR factorial LOAD_VAR n PUSH 1 SUB CALL 1 MUL RETURN STORE_VAR factorial PUSH factorial PUSH 6 CALL 1 STORE_VAR result ECHO result这段指令序列有两点值得说明。第一函数体在编译时已经生成了独立的指令片断MAKE_FUNCTION指令把参数名列表和指令片断打包成一个函数对象压入栈顶STORE_VAR factorial再把函数对象绑定到全局变量。第二递归调用其实只是普通的LOAD_VARCALL不需要特殊指令因为当前函数已经作为全局变量登记过了。符号表在这里的功劳是确保递归时n能正确判定为局部变量不会和全局的n混淆。从 AST 生成代码时最难的不是单个语句而是跳转目标的管理。我的编译器里维护了一个patch list遇到JUMP_IF_FALSE时先留一个占位后续把真正 label 的位置填充进去。这个写法和汇编器开发里的重定位很像。刚开始我嫌麻烦想直接用字符串标签跳转后来发现 Python 的标识符名称很容易冲突还是数值地址更可靠。5. 虚拟机的正确打开方式调试追踪帮我找回了一个Bugs5.1 执行循环与函数调用帧编译器做完后虚拟机反而是最让我头痛的一部分。它的核心是一个执行循环按顺序读取指令并修改栈和程序计数器。这个循环本身不难真正难的是函数调用语义。如果只是简单地把所有变量放到同一个字典里函数内部和外部就会互相污染。CNSH v1.0 的虚拟机使用了 Frame调用帧概念每次函数调用都会创建新的局部作用域class Frame: def __init__(self, code, locals_map, globals_map): self.code code self.pc 0 self.locals locals_map self.globals globals_map self.stack [] class VM: def __init__(self): self.frames [] self.globals {} self.funcs {} def call(self, func_obj, args): locals_map dict(zip(func_obj.params, args)) frame Frame(func_obj.code, locals_map, self.globals) self.frames.append(frame) while frame.pc len(frame.code): op, arg frame.code[frame.pc] frame.pc 1 self.execute(op, arg, frame) self.frames.pop()这里每个函数调用都会有一份独立的局部变量字典参数通过zip和 key 列表生成。栈式逻辑让递归成为可能factorial(5)会先压入factorial(4)的调用帧再压factorial(3)这些帧互不干扰。注意我在子函数里读取全局变量时是回到 Frame 里存的 globals 引用去查找而不是沿父作用域链查找。这个取舍让 CNSH 的闭包语义非常简单代价是不支持嵌套函数但从 v1.0 的定位看可以接受。5.2 踩坑函数参数被全局变量串掉我在第一版虚拟机里犯过一个典型错误创建 Frame 时局部变量表直接用了全局字典的引用而不是新建一个空字典。这样每个函数的第一件事就是把参数塞进全局命名空间整个程序的变量互相覆盖。当时我很难找到问题因为简单的单次函数调用能正常跑第二次调用参数变了结果还是第一次的值。定位这个 bug 花了我大概一个晚上。现象是call calc(2) - 4 call calc(3) - 4无论传什么参数结果都保持不变。我一开始怀疑是 CALL 指令的参数数量传错了反复打印参数列表结果发现参数在调用侧是对的但函数体里读到的值始终是第一次的值。直到我检查 VM 的全局字典才发现里面被塞了n、result等一大堆函数局部变量。根因就是我在调用时把参数直接写进了全局字典没有按帧隔离。修复办法就是我上面写的locals_map独立创建每个 Frame 持有自己的局部字典。这个教训让我真正理解了一句话虚拟机最重要的不是能在纸上画出漂亮的栈模型而是编码器里必须严格区分“当前执行环境”。如果没有调试追踪这种跨函数共享状态的问题不会自己暴露因为它往往是“偶发”的。5.3 追踪输出和定位方法调试这种 VM 问题最有效的手段是打印逐指令执行过程。我写了如下追踪逻辑def execute(self, op, arg, frame): if self.trace: print(f[trace] pc{frame.pc} op{op} arg{arg!r} stack{frame.stack} locals{frame.locals}) # execute...当traceTrue时程序会把每一步的指令、当前栈内容、当前局部变量全打出来。运行一段简单代码let x 2 echo x 3拿到的关键输出类似[trace] opSTORE_VAR argx stack[2] locals{} [trace] opLOAD_VAR argx stack[2] locals{x: 2} [trace] opPUSH arg3 stack[2, 3] locals{x: 2} [trace] opADD argNone stack[5] locals{x: 2} [trace] opECHO argx stack[5] locals{x: 2}如果栈顶出现一个不是期望类型的值、或者局部变量表里冒出一个不该存在的名字几乎一眼就能看出来。这个功能对我来说比单元测试还管用因为它能直观展示“语义错误发生的位置”。我建议任何写解释器或虚拟机的人都把 trace 开关内置进去它不复杂但能替你省下大量口算栈的精力。6. 实测体会它能用在哪不能用在哪6.1 拿它替换了一打if-else配置文件后CNSH v1.0 真正实际使用是在一个批处理规则场景我原来的 Python 程序里有几组规则分别判断输入文件的行数、大小、后缀再决定走哪个处理分支。这些规则用 if-else 写在主程序里烦人是每次更改都要重启主进程。后来我把它们剥离到 CNSH 脚本里主程序只提供一个执行入口传入一个全局参数对象CNSH 脚本读取参数后输出处理建议。简单示例let score get_score() if score 90 echo high else if score 60 echo mid else echo low end其中get_score在主程序里注册成内置函数。这个方案运行得很顺利修改规则不再需要碰主程序代码也让非开发人员能读懂规则到底写了什么。不过 CNSH 目前的内置函数扩展机制非常原始只是把 Python 函数按名字塞进全局字典运行前也没有参数类型检查所以真正面向外部用户前还需要加强 API 稳定性。6.2 性能量级与已知缺陷性能方面我做过粗略测试解析并编译一个 100 行的 CNSH 脚本一般在毫秒级执行纯循环加计算一百万次操作大概几十毫秒到几百毫秒。这个量级在配置和规则场景里完全够用你写一个 DSL 本来就不该去碰图像处理、大规模数值计算这类任务。CNSH v1.0 也确实有很明显的问题字符串没有内置拼接函数只靠手动运算符但不同类型不能混加这导致想输出“score90”这类信息非常麻烦函数定义时不检查return是否在所有分支都出现缺return的函数会隐式返回空值空值参与运算会直接报错错误定位又不那么直观。这些问题我认为不值得在 v1.0 里硬修因为一改“类型系统”味道就会变宁可留到 v1.1 认真设计。6.3 v1.1之前我想先把错误信息做好回看这个项目我最想优化的不是性能也不是语法糖而是错误提示。目前虚拟机运行时报错虽然带行列号但缺少完整的调用栈比如函数递归中途失败用户很难看出是哪一层调用导致的问题。如果下一步要给别人用我计划在 VM 执行遇到异常时打印当前函数调用链也就是 frame 列表里每个函数名和调用位置。词法分析阶段也打算支持//行注释和/* */块注释因为配置文件里写几行注释太常见了现在的# 行注释有时候不够用。还有一个小细节是内置input()交互和文件加载之间的边界没有做清楚。把 CNSH 当嵌入语言使用时不应该允许脚本随意执行文件读取或其他危险操作。v1.1 我会加一个“沙箱开关”默认拒绝对主机文件系统的访问只允许通过宿主程序注册的安全函数与外部交互。安全边界早设计远比事后打补丁好。写完这个编译器我最深的体会是造一门语言没那么玄但想造一门“用起来顺手”的语言难度全在细节里。忠于一个很小的语言核心先让变量、分支、循环、函数递归这四样东西完整跑通再考虑别的这是整个项目能坚持收尾的关键。下一步我大概率会在错误提示、字符串处理和沙箱上继续打磨让它成为真正能交付给同事使用的内部小工具而不是只躺在仓库里自嗨的玩具。

相关新闻

电子高科技行业国产管理软件选型:智石开连续三年领跑的底气

电子高科技行业国产管理软件选型:智石开连续三年领跑的底气

在制造企业圈子里摸爬滚布这些年,有一个直观感受:几年前聊国产化管理软件,大家第一反应是“能用就行”,而现在不少电子高科技企业的选型名单上,国产方案已经稳稳占据第一梯队。前段时间看到行业分析报告,有…

2026/10/17 9:08:30 阅读更多 →
物流系统微服务改造:从单体架构到分布式实战

物流系统微服务改造:从单体架构到分布式实战

有些技术决策,只有真正被业务逼过才想得明白。我最早接触物流系统时,它还是一个标准的单体应用:订单、运单、车辆、结算、用户体系全部打在一个包里,部署靠一台服务器扛着,数据库连接池经常被拖到极限。当时的业务量其…

2026/10/17 9:08:30 阅读更多 →
2026年AI编程工具大盘点:33款主流工具分类评测与避坑心得

2026年AI编程工具大盘点:33款主流工具分类评测与避坑心得

2026年了,AI编程工具这个赛道已经卷得不成样子。我自己的收藏夹里从2023年到现在,前前后后加进去又删掉的工具不下四十个,真正留下来值得长期用的,我整理完正好33个。这篇文章不打算给你罗列一堆官网链接,而是把这些工…

2026/10/17 9:07:30 阅读更多 →

最新新闻

储能电池被动均衡方案设计:MP2642GR-Z与PIC18F4550实战

储能电池被动均衡方案设计:MP2642GR-Z与PIC18F4550实战

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

2026/10/17 9:49:46 阅读更多 →
两节串联锂电池被动均衡方案:MP2642GR-Z与STM32F401RB实战

两节串联锂电池被动均衡方案:MP2642GR-Z与STM32F401RB实战

1. 为什么串联电池组的均衡不是"可选功能"而是"生存刚需"两节串联的锂电池放在储能柜或者备用电源机箱里,很多人第一反应是"才两节,能有多大差异"。我最早接触这类项目时也这么想,直到实测数据摆在面前&#x…

2026/10/17 9:49:46 阅读更多 →
蓝桥杯备赛练习3.3阶段:如何科学安排刷题节奏与知识点优先级

蓝桥杯备赛练习3.3阶段:如何科学安排刷题节奏与知识点优先级

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

2026/10/17 9:49:46 阅读更多 →
从博弈论到人际策略:纳什均衡、囚徒困境与以牙还牙的实战应用

从博弈论到人际策略:纳什均衡、囚徒困境与以牙还牙的实战应用

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

2026/10/17 9:49:46 阅读更多 →
开源代码审查方案:从机制到实践的完整落地指南

开源代码审查方案:从机制到实践的完整落地指南

做Code Review这些年,我越来越觉得,大多数团队不是不想做好代码审查,而是被一堆杂事拖垮了。拉分支、盯CI、等人review、处理冲突、口头说了又不记得……这一套流程走下来,真正花在“看代码”上的精力可能连三成都不到。所以当我看…

2026/10/17 9:49:46 阅读更多 →
Termexo v0.9.0体验:AI编程CLI的安装、状态监控与Diff审查实战

Termexo v0.9.0体验:AI编程CLI的安装、状态监控与Diff审查实战

我在过去半年里,陆陆续续在本地开发环境里接入了三款AI编程CLI,从最早折腾Codex安装到后来用上Claude Code,再到把Gemini CLI加进日常工作流。本来觉得差不多了,结果看到Termexo v0.9.0发布的时候,还是没忍住又试了一把…

2026/10/17 9:48:45 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →