{"id":51121868,"url":"https://github.com/xianjianlf2/mini-compiler","last_synced_at":"2026-06-25T03:30:44.247Z","repository":{"id":107737322,"uuid":"527904031","full_name":"xianjianlf2/mini-compiler","owner":"xianjianlf2","description":"手写 the-super-tiny-compiler：词法 / 语法 / 转换 / 代码生成（mini-* 源码学习系列，按 Git 提交历史一步步搭建）","archived":false,"fork":false,"pushed_at":"2026-06-24T11:44:37.000Z","size":11,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-24T12:09:52.474Z","etag":null,"topics":["ast","compiler","learning","parser","tokenizer","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xianjianlf2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-08-23T08:42:45.000Z","updated_at":"2026-06-24T11:44:42.000Z","dependencies_parsed_at":"2023-04-23T16:43:23.756Z","dependency_job_id":null,"html_url":"https://github.com/xianjianlf2/mini-compiler","commit_stats":null,"previous_names":["xianjianlf2/mini-compiler"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xianjianlf2/mini-compiler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xianjianlf2%2Fmini-compiler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xianjianlf2%2Fmini-compiler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xianjianlf2%2Fmini-compiler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xianjianlf2%2Fmini-compiler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xianjianlf2","download_url":"https://codeload.github.com/xianjianlf2/mini-compiler/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xianjianlf2%2Fmini-compiler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34758773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","compiler","learning","parser","tokenizer","typescript"],"created_at":"2026-06-25T03:30:43.768Z","updated_at":"2026-06-25T03:30:44.242Z","avatar_url":"https://github.com/xianjianlf2.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mini-compiler\n\n这个仓库会通过 Git 提交历史，一步步把 mini-compiler 搭出来。\n\n目标不是一次写完一个复杂编译器，而是按最适合理解编译流程的顺序，让同一个小编译器慢慢长出来。\n\n换句话说，这个仓库更在意“为什么下一步应该是它”，而不是“还能不能再多加一个功能”。\n\n## 怎么看这条历史\n\n每一条提交只做一件事，所以这条历史本身就是学习路线。\n\n你可以先看完整代码，再按顺序回看提交，观察同一条编译链路是怎么一步步长出来的。\n\n常用方式：\n\n```bash\ngit log --oneline --reverse\n```\n\n如果你想看某一步的具体变化，可以继续用：\n\n```bash\ngit show \u003ccommit-id\u003e\n```\n\n如果你想按顺序体验每一步，可以从最早的提交开始，一步步切过去看。\n\n## 为什么要按这个顺序\n\n如果目标是学编译器的核心，而不是尽快做一个真正的语言，那顺序非常重要。\n\n最容易走偏的方式，是太早去做这些东西：\n\n- 先做变量作用域\n- 先做类型检查\n- 先做优化\n- 先做复杂语法\n\n这样虽然看起来“功能更多”，但会把编译器最核心的那条主线打散。\n\n这个项目真正最值得先弄明白的，是这条链：\n\n1. 源码字符串怎么拆开\n2. 拆开的片段怎么变成树\n3. 树怎么被稳定遍历\n4. 源语言的树怎么变成目标语言的树\n5. 目标语言的树怎么打印成代码\n6. 这些步骤怎么串成一个 compiler\n\n只有这条主线通了，后面的作用域、类型系统、优化、报错提示，才知道该挂在哪、为什么能挂上去。\n\n## 推荐学习顺序\n\n| 步骤 | 当前问题 | 新增能力 | 为什么重要 |\n| --- | --- | --- | --- |\n| 1 | 源码是一整段字符串 | 分词 | 先得到最小片段 |\n| 2 | tokens 还是平铺的 | 解析成语法树 | 保存嵌套关系 |\n| 3 | 树需要被稳定访问 | 遍历器 | 把“走树”和“处理节点”分开 |\n| 4 | 源语言和目标语言结构不同 | 转换语法树 | 让结构靠近目标代码 |\n| 5 | 树不能直接运行 | 生成代码 | 得到最终字符串 |\n| 6 | 步骤太散 | compiler 总入口 | 串起完整流程 |\n\n## 整体流程\n\n```mermaid\nflowchart LR\n  A[\"源码字符串\"] --\u003e B[\"tokenizer\"]\n  B --\u003e C[\"parser\"]\n  C --\u003e D[\"traverser\"]\n  D --\u003e E[\"transformer\"]\n  E --\u003e F[\"codegen\"]\n  F --\u003e G[\"目标代码\"]\n```\n\n## 第一步\n\n先把源码拆成一串 tokens。\n\n这一步只回答一个问题：编译器拿到字符串以后，第一眼应该怎么把它切成更小、更容易理解的片段。\n\n\u003e **为什么下一步不是直接生成代码？**\n\n反例是：如果连 `add`、`2`、`(`、`)` 这些最小片段都没分清楚，后面就没法判断谁是谁的参数。\n\n## 第二步\n\n把 tokens 组织成语法树。\n\n这一版解决的是“括号嵌套关系怎么保存”的问题。\n\n`(add 2 (subtract 4 2))` 不是一串平铺的词，而是 `add` 里面又包含了一个 `subtract`。语法树就是用来保存这种层级关系的。\n\n\u003e **为什么下一步不是直接改成目标代码？**\n\n反例是：如果每次改代码都手写一堆针对某种节点的判断，逻辑会散在很多地方。先有统一遍历方式，后面转换会更清楚。\n\n## 第三步\n\n给语法树加一个统一遍历器。\n\n这一版解决的是“怎么稳定走完整棵树”的问题。\n\n遍历器会先进入外层节点，再进入里面的节点，最后再从里面退出来。这样后面想对某种节点做处理，只需要告诉遍历器“遇到它时做什么”。\n\n\u003e **为什么下一步才开始转换？**\n\n反例是：如果没有遍历器，转换逻辑既要负责走树，又要负责改树，两件事混在一起会很难读。\n\n## 第四步\n\n把源语言语法树转换成目标语法树。\n\n这一版解决的是“源语言结构和目标语言结构不一样”的问题。\n\n源语言是 `(add 2 4)`，目标语言更像 `add(2, 4)`。它们表达的是同一件事，但树的形状不同，所以需要先转换结构。\n\n\u003e **为什么下一步还不能直接结束？**\n\n反例是：转换后的树仍然只是对象结构，人和机器都不能直接把它当成最终代码运行。还需要把树重新打印成字符串。\n\n## 第五步\n\n把目标语法树生成 JavaScript 代码。\n\n这一版解决的是“树怎么变回字符串”的问题。\n\n转换后的树已经很接近 JavaScript，但还不是最终代码。代码生成器会按节点类型，把函数调用、数字、字符串这些节点打印出来。\n\n\u003e **为什么最后还要有 compiler？**\n\n反例是：如果使用者每次都要手动调用分词、解析、转换、生成代码四个步骤，使用体验会很碎，也不利于看清完整流程。\n\n## 第六步\n\n把所有阶段串成一个 compiler。\n\n这一版解决的是“怎么从源码一步到最终代码”的问题。\n\n最终入口只做一件事：把源码依次交给分词、解析、转换和代码生成，最后返回目标代码。\n\n\u003e **为什么到这里先停住？**\n\n反例是：如果刚跑通主线就加入变量、作用域、类型检查，学习者会分不清哪些是编译器主流程，哪些是更高级的语言能力。\n\n## 最终能做什么\n\n现在这个 mini-compiler 可以把这种 Lisp 风格的调用：\n\n```text\n(add 2 (subtract 4 2))\n```\n\n编译成 JavaScript 风格的调用：\n\n```text\nadd(2, subtract(4, 2));\n```\n\n它也支持字符串参数：\n\n```text\n(print \"hello\")\n```\n\n会变成：\n\n```text\nprint(\"hello\");\n```\n\n## 怎么验证\n\n运行：\n\n```bash\nnpm test\n```\n\n它会检查每一个阶段，也会检查完整 compiler 的最终输出。\n\n---\n\n## `mini-*` 源码学习系列\n\n手写主流框架 / 工具的最小可运行实现，每个仓库只追「核心主线」，不堆功能。\n\n| 仓库 | 内容 |\n| --- | --- |\n| [mini-vue](https://github.com/xianjianlf2/mini-vue) | 手写 Vue3：响应式 / runtime / 编译器 |\n| [mini-react](https://github.com/xianjianlf2/mini-react) | 手写 React：Fiber / reconciliation / Hooks |\n| [mini-koa](https://github.com/xianjianlf2/mini-koa) | 手写 Koa：中间件洋葱模型 / context |\n| [mini-webpack](https://github.com/xianjianlf2/mini-webpack) | 手写 webpack：依赖图 / loader / plugin |\n| **mini-compiler**（本仓库） | 手写 the-super-tiny-compiler：词法 / 语法 / 转换 / 生成 |\n| [ts-axios](https://github.com/xianjianlf2/ts-axios) | 手写 axios（TypeScript 版） |\n\n\u003e Talk is cheap. Read the code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxianjianlf2%2Fmini-compiler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxianjianlf2%2Fmini-compiler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxianjianlf2%2Fmini-compiler/lists"}