Deep Dive | src/utils/bash/bashParser.ts 4436 行 — 纯 TS 手写 Bash 解析器¶
重要性:⭐⭐⭐⭐(自研 bash 解析器——Bash 工具权限/安全/补全/解析的基石) 真实位置:
src/utils/bash/bashParser.ts(4436 行) 角色:纯 TypeScript 手写的 bash 解析器,输出与 tree-sitter-bash 兼容的 AST,供 parser.ts / ast.ts / prefix.ts / ParsedCommand.ts 遍历 关联:topics/deep-dive-bash-ast.md(AST 工具)、topics/deep-dive-bash-security.md(安全规则)、topics/deep-dive-bash-permissions.md(权限引擎)
1. 文件全景¶
bashParser.ts (4436 行, 79 个函数)
│
├── 行 1-50 :模块头注释 + 类型定义 (TsNode, ParserModule)
├── 行 29-44 :2 个常量 (PARSE_TIMEOUT_MS, MAX_NODES) + 2 个 init 函数
│
├── Tokenizer / Lexer 段(行 50-600, ~550 行)
│ ├── 行 50-77 :TokenType + Token 类型
│ ├── 行 77-110 :特殊常量 (SPECIAL_VARS, DECL_KEYWORDS, SHELL_KEYWORDS)
│ ├── 行 110-132 :Lexer + HeredocPending 类型
│ ├── 行 134-300 :makeLexer + advance + peek + byteAt + is*Char + skipBlanks
│ └── 行 302-595 :nextToken — **核心 tokenizer 295 行**
│
├── Parser 段(行 600-4400, ~3800 行)
│ ├── 行 600-650 :parseSource 入口 + 超时 + 预算
│ ├── 行 650-705 :mk / sliceBytes / leaf 辅助
│ ├── 行 706-878 :parseProgram + saveLex/restoreLex + parseStatements
│ ├── 行 879-1140:parseAndOr + parsePipeline + parseCommand
│ ├── 行 1141-1406:parseSimpleCommand
│ ├── 行 1406-1623:redirect + tryParseAssignment
│ ├── 行 1623-1860:tryParseRedirect (重定向)
│ ├── 行 1861-2000:parseProcessSub + scanHeredocBodies + parseHeredocBodyContent
│ ├── 行 2008-2215:parseWord + parseBareWord
│ ├── 行 2215-2340:tryParseBraceExpr + tryParseBraceLikeCat
│ ├── 行 2339-2430:parseDoubleQuoted
│ ├── 行 2429-3100:parseDollarLike + parseExpansionBody + parseExpansionRest + parseExpansionRegexSegmented + parseBacktick
│ ├── 行 3101-3700:parseIf / parseWhile / parseFor / parseDoGroup / parseCase / parseCaseItem / parseCasePattern / parseCasePatternSegmented / parseFunction / parseDeclaration / parseUnset
│ ├── 行 3700-4120:parseTestExpr ( [[ ... ]] 完整表达式)
│ ├── 行 4120-4436:parseArithExpr ( (( ... )) 算术)
│ └── 行 4426 :isArithStop
总览: - Tokenizer ~550 行(13%) - Parser ~3800 行(85%) - 辅助/类型/常量 ~150 行(2%)
2. 模块头注释 — 3 个关键不变量¶
/**
* Pure-TypeScript bash parser producing tree-sitter-bash-compatible ASTs.
*
* Downstream code in parser.ts, ast.ts, prefix.ts, ParsedCommand.ts walks this
* by field name. startIndex/endIndex are UTF-8 BYTE offsets (not JS string
* indices).
*
* Grammar reference: tree-sitter-bash. Validated against a 3449-input golden
* corpus generated from the WASM parser.
*/
3 个不变量:
- API 兼容 tree-sitter-bash —— 字段名一致
startIndex/endIndex是 UTF-8 字节偏移(不是 JS 字符串索引)- 黄金语料库 3449 个输入——和 WASM 解析器对比验证
为什么用纯 TS 而不是 tree-sitter WASM:
- 不需要 WASM runtime(启动更快,体积更小)
- 可控的超时(50ms PARSE_TIMEOUT_MS)
- 可控的预算(50K MAX_NODES 节点上限)
- byte offset 精确(WASM 可能精度损失)
3. 2 个安全护栏¶
/** 50ms wall-clock cap — bails out on pathological/adversarial input. */
const PARSE_TIMEOUT_MS = 50
/** Node budget cap — bails out before OOM on deeply nested input. */
const MAX_NODES = 50_000
2 个护栏:
| 护栏 | 值 | 作用 |
|---|---|---|
PARSE_TIMEOUT_MS |
50ms | 病态/对抗输入超时返回 null |
MAX_NODES |
50,000 | 深层嵌套防 OOM |
2 个 API:
注释强调:CI 抖动会触发误判 → 测试用 Infinity 关闭。
4. TsNode — 与 tree-sitter 一致的 AST 节点¶
export type TsNode = {
type: string // 节点类型 ('command', 'word', 'string', ...)
text: string // 原始文本片段
startIndex: number // UTF-8 字节偏移
endIndex: number // UTF-8 字节偏移
children: TsNode[] // 子节点
}
5 字段:
- type —— string 节点类型
- text —— 原始文本
- startIndex / endIndex —— UTF-8 字节偏移(不是 JS 字符串索引!)
- children —— 递归子节点
与 tree-sitter-bash 兼容:
- parser.ts / ast.ts / prefix.ts / ParsedCommand.ts 都按字段名遍历
- 不需要写适配层
5. 2 个公开 API¶
export function ensureParserInitialized(): Promise<void> {
return READY // Promise.resolve()
}
export function getParserModule(): ParserModule | null {
return MODULE
}
const MODULE: ParserModule = { parse: parseSource }
API 设计:
- ensureParserInitialized() —— 异步初始化(纯 TS 无需初始化)—— 保持 API 兼容
- getParserModule() —— 返回 parse 函数
- parse(src, timeoutMs?) —— 主入口
Promise.resolve() —— 不需要 await,但返回 Promise 保持向后兼容。
6. Tokenizer(Lexer)— 行 50-600¶
6.1 TokenType + Token¶
type TokenType = 'word' | 'operator' | 'newline' | 'eof' | ...;
type Token = {
type: TokenType
text: string
startByte: number // 字节偏移
endByte: number
// ... 一些额外字段
}
const SPECIAL_VARS = new Set(['?', '$', '@', '*', '#', '-', '!', '_'])
const DECL_KEYWORDS = new Set(['declare', 'typeset', 'export', 'readonly', 'local'])
export const SHELL_KEYWORDS = new Set(['if', 'then', 'else', 'elif', 'fi', 'while', ...])
3 个常量集合:
- SPECIAL_VARS —— bash 特殊变量($?, $$, $@ 等)
- DECL_KEYWORDS —— 变量声明(declare, export 等)
- SHELL_KEYWORDS —— shell 关键字(if, then, while 等)
6.2 Lexer 状态¶
type Lexer = {
src: string
byteLen: number
charIdx: number // 当前字符索引
byteIdx: number // 当前字节偏移
// ... 内部状态
}
type HeredocPending = {
delim: string
// ... heredoc 元数据
}
双索引:charIdx(JS 字符串)+ byteIdx(UTF-8 字节)—— 用于精确的位置信息。
6.3 nextToken — 核心 295 行¶
function nextToken(L: Lexer, ctx: 'cmd' | 'arg' = 'arg'): Token {
// 行 302-595
// 处理 30+ 种 token 类型
// 包含 heredoc 状态机
}
295 行——一个函数吃掉了整个 token 识别。
ctx 参数('cmd' | 'arg')控制是否识别关键字。
7. Parser 入口¶
7.1 parseSource — 超时 + 预算¶
function parseSource(source: string, timeoutMs?: number): TsNode | null {
// 行 610-650
// 1. 设置超时
// 2. 初始化 ParseState
// 3. parseProgram
// 4. 检查预算
// 5. 返回根节点或 null
}
返回 null 的两种情况: - 超时(> 50ms) - 节点超预算(> 50K)
7.2 ParseState¶
type ParseState = {
lex: Lexer
startTime: number
timeoutMs: number
nodeCount: number
// ... 派生 state
}
4 字段:
- lex —— 当前 lexer
- startTime / timeoutMs —— 超时检查
- nodeCount —— 预算跟踪
7.3 checkBudget — 双重保护¶
双重检查:每次 mk() 调用前都 check。
7.4 mk / sliceBytes / leaf — 3 个核心辅助¶
function mk(...): TsNode {
// 创建节点 + 计数 + checkBudget
}
function sliceBytes(P: ParseState, startByte: number, endByte: number): string {
// UTF-8 字节切片
}
function leaf(P: ParseState, type: string, tok: Token): TsNode {
// 叶子节点(无 children)
}
mk 是"分配"操作——每次创建节点都 +1 计数 + check 预算。
8. 顶层解析¶
8.1 parseProgram + parseStatements¶
function parseProgram(P: ParseState): TsNode {
// 行 706
// 程序 = 多个语句(用 \n 或 ; 分隔)
}
function parseStatements(P: ParseState, terminator: string | null): TsNode[] {
// 行 769
// 解析语句直到 terminator
}
terminator 决定停止条件:
- null —— 顶层(直到 EOF)
- '}' —— { ... } 块
- 'do' —— for/while 体
- 'done' —— 循环体
- 'then' —— if 体
- 'fi' —— if 块结束
8.2 parseAndOr + parsePipeline¶
function parseAndOr(P: ParseState): TsNode | null {
// 行 879
// cmd1 && cmd2 || cmd3
}
function parsePipeline(P: ParseState): TsNode | null {
// 行 938
// cmd1 | cmd2 | cmd3
}
bash 操作符优先级:
list = pipeline ( ('&&' | '||') pipeline )*
pipeline = command ( '|' command )*
command = simple_command | compound | ...
8.3 parseCommand — 分发¶
function parseCommand(P: ParseState): TsNode | null {
// 行 995
// 看到 keyword → 分发
// - if/while/for/case/function/declare/unset → 对应 parseXxx
// - 否则 → parseSimpleCommand
}
dispatch 表:
- if → parseIf
- while / until → parseWhile
- for → parseFor
- case → parseCase
- function → parseFunction
- declare / typeset / export / readonly / local → parseDeclaration
- unset → parseUnset
- 其他 → parseSimpleCommand
8.4 parseSimpleCommand — 146 行¶
function parseSimpleCommand(P: ParseState): TsNode | null {
// 行 1141
// command_name (redirect | assignment | arg)*
}
支持:
- 命令名
- 重定向(>, <, >>, <<<, >&, <&)
- 赋值(FOO=bar)
- 参数(任意数量)
- 内置(time, [, [[, (()
9. 重定向 + 赋值¶
9.1 maybeRedirect + tryParseRedirect — 200+ 行¶
function maybeRedirect(P: ParseState): TsNode | null {
// 行 1406
// 看一眼当前位置是不是 redirect
}
function tryParseRedirect(P: ParseState, greedy = false): TsNode | null {
// 行 1623
// 解析完整 redirect
}
支持:
- > file —— 输出重定向
- < file —— 输入
- >> file —— 追加
- >& fd —— fd 复制
- 2>&1 —— stderr → stdout
- <<< word —— here-string
- > file 中的 fd 前缀(2>)
- {fd}> 语法
greedy 参数控制是否"贪心"——parseSimpleCommand 中 greedy=true(不允许中断),其他上下文 greedy=false(遇到 redirect 可以回退)。
9.2 tryParseAssignment¶
function tryParseAssignment(P: ParseState): TsNode | null {
// 行 1431
// FOO=bar / FOO[1]=bar / FOO+=bar
}
支持:
- 简单赋值 FOO=bar
- 数组下标 FOO[1]=bar / FOO[1][2]=bar
- 追加 FOO+=bar
10. 词法解析¶
10.1 parseWord + parseBareWord¶
function parseWord(P: ParseState, _ctx: 'cmd' | 'arg'): TsNode | null {
// 行 2008
// 解析一个 word
// word = (literal | expansion | quote | escape)+
}
function parseBareWord(P: ParseState): TsNode | null {
// 行 2163
// 简单 word(无扩展)
}
word 内部组成:
- literal(普通字符)
- $VAR(变量)
- ${VAR}(带花括号)
- $(cmd)(命令替换)
- `cmd`(反引号)
- $((expr))(算术)
- "..."(双引号)
- \$, \\, \"(转义)
- ~(波浪号扩展)
10.2 tryParseBraceExpr / tryParseBraceLikeCat¶
function tryParseBraceExpr(P: ParseState): TsNode | null {
// 行 2215
// ${VAR:-default}, ${VAR:=default}, ${VAR:offset:len}, ...
}
function tryParseBraceLikeCat(P: ParseState): TsNode[] | null {
// 行 2268
// ${VAR} 或 ${#VAR} 或 ${!VAR}
}
10+ 种花括号扩展:
- ${VAR} —— 简单
- ${#VAR} —— 长度
- ${VAR:-default} —— 默认值
- ${VAR:=default} —— 赋值
- ${VAR:+alt} —— 替代
- ${VAR:?error} —— 错误
- ${VAR:offset} —— 子串
- ${VAR:offset:len} —— 子串
- ${VAR#pattern} —— 前缀删除
- ${VAR##pattern} —— 最长前缀
- ${VAR%pattern} —— 后缀删除
- ${VAR%%pattern} —— 最长后缀
- ${VAR/pat/rep} —— 替换
- ${VAR//pat/rep} —— 全替换
- ${VAR^} / ${VAR^^} —— 大写
- ${VAR,} / ${VAR,,} —— 小写
- ${!VAR} —— 间接引用
- ${VAR@ops} —— 参数转换
10.3 parseDoubleQuoted — 90 行¶
双引号内允许:变量、命令替换、转义、$, `, \, "。
10.4 parseDollarLike + parseExpansionBody + parseExpansionRest — 700+ 行¶
function parseDollarLike(P: ParseState): TsNode | null {
// 行 2429
// $VAR / ${VAR} / $(cmd) / $((expr)) / $[expr] 统一入口
}
function parseExpansionBody(P: ParseState): TsNode[] {
// 行 2555
// 解析扩展的"内容"
}
function parseExpansionRest(P: ParseState): ... {
// 行 2807
// ${VAR} 之后剩余(操作符 + 参数)
}
function parseExpansionRegexSegmented(P: ParseState): TsNode[] {
// 行 3036
// 模式参数(如 ${VAR/pat/rep} 的 pat)
}
700+ 行——bash 扩展语法是最复杂的部分。
10.5 parseBacktick¶
反引号 —— 反引号内 \ 仅 \\, \$, \`, \", \newline 有意义。其他 \ 字面保留。
11. 复合语句¶
11.1 parseIf¶
function parseIf(P: ParseState, ifTok: Token): TsNode {
// 行 3152
// if cond; then ... elif cond; then ... else ... fi
}
11.2 parseWhile¶
function parseWhile(P: ParseState, kwTok: Token): TsNode {
// 行 3189
// while cond; do ... done
// until cond; do ... done
}
11.3 parseFor — 106 行¶
function parseFor(P: ParseState, forTok: Token): TsNode {
// 行 3200
// for VAR in items; do ... done
// for ((init; cond; step)); do ... done
}
两种形式:
- for x in 1 2 3; do ... done
- C-style for ((i=0; i<10; i++)); do ... done
11.4 parseDoGroup¶
11.5 parseCase — 28 行¶
function parseCase(P: ParseState, caseTok: Token): TsNode {
// 行 3322
// case WORD in pat1) ... ;; pat2) ... ;; esac
}
11.6 parseCaseItem + parseCasePattern + parseCasePatternSegmented — 175+ 行¶
function parseCaseItem(P: ParseState): TsNode | null {
// 行 3350
// pat1|pat2) commands ;;
}
function parseCasePattern(P: ParseState): TsNode[] {
// 行 3441
// 单个 pattern(支持 glob 扩展)
}
function parseCasePatternSegmented(P: ParseState): TsNode[] {
// 行 3525
// 切分 pat1|pat2|pat3
}
case pattern 是 glob 表达式——?, *, [abc], \ 都有意义。
11.7 parseFunction + parseDeclaration + parseUnset¶
function parseFunction(P: ParseState, fnTok: Token): TsNode {
// 行 3565
// function name { ... } or name() { ... }
}
function parseDeclaration(P: ParseState, kwTok: Token): TsNode {
// 行 3598
// declare/typeset/export/readonly/local [options] VAR[=value]
}
function parseUnset(P: ParseState, kwTok: Token): TsNode {
// 行 3650
// unset VAR
}
12. Test 表达式 — [[ ... ]]¶
function parseTestExpr(P: ParseState, closer: string): TsNode | null {
// 行 3701
// 入口
function parseTestOr(P, closer) {...} // ||
function parseTestAnd(P, closer) {...} // &&
function parseTestUnary(P, closer) {...} // ! expr
function parseTestNegatablePrimary(P) {...}
function parseTestBinary(P, closer) {...} // expr OP expr
function parseTestRegexRhs(P) {...} // =~
function parseTestExtglobRhs(P) {...} // ==, !=, = (extglob)
function parseTestPrimary(P, closer) {...}
}
[[ ... ]] 优先级(从低到高):
支持的运算符:
- 文件测试:-e, -f, -d, -r, -w, -x, -s, -L, ...
- 字符串测试:-z, -n, ==, !=, =~, <, >
- 数值测试:-eq, -ne, -lt, -le, -gt, -ge
- 布尔:-a (deprecated), -o (deprecated), &&, ||, !
- extglob:==(pat), !=(pat)
13. Arithmetic 表达式 — (( ... ))¶
function parseArithExpr(P: ParseState): TsNode {
// 行 4129
// (( expr ))
}
function parseArithCommaList(P): ... // ,
function parseArithTernary(P): ... // ? :
function scanArithOp(P): ... // 扫描运算符
function parseArithBinary(P): ... // 二元
function parseArithUnary(P): ... // 一元
function parseArithPostfix(P): ... // ++ --
function parseArithPrimary(P): ... // 字面量 + 变量
function isArithStop(P, stop): ... // 停止判断
算术运算符(C 风格):
- 基础:+, -, *, /, %
- 位运算:<<, >>, &, |, ^, ~
- 比较:<, >, <=, >=, ==, !=
- 逻辑:&&, ||, !
- 三元:?:
- 赋值:=, +=, -=, *=, ...
- 自增:++, --
- 逗号:,
14. 关键设计模式¶
14.1 递归下降¶
每个语法结构对应一个 parseXxx 函数,互相递归调用:
- parseProgram → parseStatements → parseAndOr → parsePipeline → parseCommand → parseSimpleCommand / parseIf / ...
- parseWord → parseDollarLike → parseExpansionBody → ...
没有 parser generator(不是 yacc/bison)——纯手写。
14.2 字节 vs 字符¶
type Lexer = {
byteLen: number
charIdx: number
byteIdx: number
// ...
}
function byteAt(L: Lexer, charIdx: number): number {
// charIdx → byte offset
}
双索引:保持 JS 字符串索引(charIdx)和 UTF-8 字节偏移(byteIdx)双向同步。
为什么用字节: - Bash 工具的输出(如错误信息)用字节偏移 - 与 tree-sitter 一致 - 多字节字符(中文 emoji)正确处理
14.3 黄金语料库验证¶
注释说:
Validated against a 3449-input golden corpus generated from the WASM parser.
测试策略: - 用 tree-sitter WASM 生成 3449 个输入的预期 AST - 纯 TS 解析器跑同样输入,对比 AST - 任何不一致 → 修复
这保证了与 tree-sitter-bash 兼容。
14.4 saveLex / restoreLex — 试探性解析¶
function saveLex(L: Lexer): LexSave {
// 保存 lexer 状态
}
function restoreLex(L: Lexer, s: LexSave): void {
// 恢复 lexer 状态
}
试探性解析: - 看到一个 token 不确定是 X 还是 Y - save → 尝试解析 X → 失败 → restore → 尝试解析 Y - 常见于:assignment vs command, redirect vs operator, [[ vs [(
14.5 timeoutMs 参数化¶
默认 50ms —— 生产环境。
Infinity —— 测试环境(关超时)。
14.6 Heredoc 多遍扫描¶
function scanHeredocBodies(P: ParseState): void {
// 1. 找到所有 << EOF / <<-EOF / <<< "word" 标记
// 2. 扫描每个 heredoc 的 body
// 3. 把 body 附加到命令的 children
}
两遍扫描:
- 第一遍:找到所有 << EOF 标记,记录位置
- 第二遍:实际解析时,知道每个 heredoc 的 body 在哪
<<- 形式:heredoc body 剥离前导 tab(不是空格!)
15. 性能特征¶
15.1 时间复杂度¶
最坏 O(n) —— 几乎所有 token 都扫一次。
最坏 O(n²) —— 试探性回溯(如 parseWord)。
15.2 空间复杂度¶
O(MAX_NODES) = 50K —— 节点上限。
15.3 实际性能¶
- 普通命令(< 100 tokens):< 1ms
- 大脚本(1K 行):~5-10ms
- 病态输入(如 100K 个
():50ms 超时返回 null
15.4 与 tree-sitter WASM 对比¶
| 维度 | 纯 TS | WASM |
|---|---|---|
| 启动 | 0(无 runtime) | ~5-10ms(WASM init) |
| 解析速度 | 接近 | 略快(编译代码) |
| 体积 | 0 | ~200KB |
| 跨平台 | 100% | 100% |
| 调试 | 易(TS) | 难(WASM) |
| 扩展 | 直接改 TS | 重编译 WASM |
Claude Code 选纯 TS——启动时间更重要。
16. 与 tree-sitter-bash 的兼容性¶
16.1 字段名一致¶
| 概念 | 字段 |
|---|---|
| 节点类型 | type |
| 文本 | text |
| 范围 | startIndex, endIndex |
| 子节点 | children |
下游代码(parser.ts, ast.ts, prefix.ts, ParsedCommand.ts)按字段名遍历——无需适配。
16.2 节点类型对照¶
| 节点类型 | 含义 |
|---|---|
program |
整个脚本 |
command |
单条命令 |
simple_expansion |
$VAR |
expansion |
${VAR} |
command_substitution |
$(cmd) |
process_substitution |
<(cmd) / >(cmd) |
string |
"..." 或 '...' |
word |
词 |
variable_name |
变量名 |
function_definition |
function name { ... } |
if_statement / elif_clause / else_clause |
if |
while_statement / for_statement / do_group |
循环 |
case_statement / case_item |
case |
binary_operator / unary_operator |
运算符 |
redirected_statement |
带重定向的语句 |
file_redirect / heredoc_redirect / herestring_redirect |
重定向 |
variable_assignment |
赋值 |
subscript |
数组下标 |
test_operator |
test 运算符 |
arithmetic_expansion |
((expr)) |
30+ 种节点类型——与 tree-sitter-bash 一致。
17. 关键设计权衡¶
17.1 纯 TS vs WASM¶
选纯 TS: - ✅ 无 WASM 启动开销 - ✅ 字节偏移精确 - ✅ 易于调试 - ✅ 可控的超时/预算 - ❌ 解析速度略慢 - ❌ 不能"复用" tree-sitter 生态
17.2 50ms 超时 vs 长解析¶
50ms 是商业产品选择: - 用户体验:长脚本解析 > 50ms 视为"可疑" - 攻击面:拒绝深度对抗输入 - 性能预算:保护主线程
17.3 50K 节点 vs 内存¶
节点上限防 OOM: - 50K 节点 × ~200 bytes = 10MB(最坏) - 远小于 Bun/Node 默认 heap
17.4 不实现执行¶
只解析,不执行——不实现命令运行、变量绑定、函数调用。
上游(bashSecurity.ts, bashPermissions.ts, prefix.ts)决定怎么用 AST(做权限检查、做补全、做风险评分)。
18. 阅读建议¶
- 看模块头注释(行 1-10)—— 理解不变量
- 看 2 个护栏(行 29-44)—— 安全设计
- 看 295 行 nextToken(行 302-595)—— 词法分析的核心
- 看 parseProgram(行 706)—— 顶层入口
- 看 parseWord(行 2008)—— 词法的复杂
- 看 parseTestExpr(行 3701)—— test 表达式
- 看 parseArithExpr(行 4129)—— 算术
19. 关键洞察¶
19.1 商业产品选择纯 TS¶
不是技术不能 WASM——是启动时间更重要。
19.2 50ms 是 UX 选择¶
不是 100ms,不是 200ms——50ms 是人感知不到延迟的阈值。
19.3 字节偏移贯穿全文¶
不是字符索引——和 tree-sitter 兼容、bash 工具输出兼容、UTF-8 安全。
19.4 黄金语料库 3449 个¶
这是工程严谨性**——不是"我觉得对了"。
19.5 试探性回溯(save/restore)¶
这是手写 parser 的标准技巧——bison/yacc 自动做,手写要自己写。
19.6 bash 扩展是"最复杂的"段¶
${VAR:-default} 等 700+ 行——bash 的真正难度都在这里。
20. 与其他文件的关系¶
bashParser.ts
├──→ parser.ts (遍历 AST)
├──→ ast.ts (AST 工具)
├──→ prefix.ts (补全 / 路径分析)
├──→ ParsedCommand.ts (ParsedCommand 包装)
├──→ bashSecurity.ts (安全规则)
└──→ bashPermissions.ts (权限引擎)
bashParser 是"地基"——所有 bash 相关功能都依赖它。
21. 阅读清单¶
- ✅ 看模块头注释(行 1-10)
- ✅ 看 2 个护栏(行 29-44)
- ✅ 看 nextToken 295 行(行 302-595)
- ✅ 看 parseProgram 入口(行 706)
- ✅ 看 parseWord + parseDollarLike(行 2008-2429)
- ✅ 看 parseExpansionBody(行 2555)
- ✅ 看 parseTestExpr(行 3701)
- 📌 对照 topics/deep-dive-bash-ast.md 看 AST 怎么遍历
- 📌 对照 topics/deep-dive-bash-security.md 看 AST 怎么用
22. 练习任务¶
- 数 parseXxx 函数(grep)—— 79 个
- 画递归下降调用图 —— parseProgram → parseStatements → parseAndOr → ...
- 找 7 种 token 分类(word/operator/newline/eof/...)
- 写一个迷你 bash parser(~200 行)—— 只支持
cmd arg | cmd arg - 思考:如果改成 PEG.js / chevrotain 写,会怎样?
- 跑测试:
practice-tests/bash-parser验证 3449 个黄金语料