<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Parser on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/parser/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 17 Aug 2020 09:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/parser/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>编译原理学习总结：新人也能看懂的编译器流水线与避坑指南</title>
      <link>https://ftmao.com/blog/posts/compiler-principles-study-notes/</link>
      <pubDate>Mon, 17 Aug 2020 09:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/compiler-principles-study-notes/</guid>
      <description>&lt;h1 id=&#34;编译原理学习总结新人也能看懂的编译器流水线与避坑指南&#34;&gt;编译原理学习总结：新人也能看懂的编译器流水线与避坑指南&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考教材：王生原、董渊、张素琴、吕映芝、蒋维杜《编译原理（第 3 版）》，清华大学出版社。
目标读者：刚开编译原理课、被「NFA/DFA/LL(1)/LR」一堆缩写吓到的同学；或者工作中想手写个 DSL、规则引擎、配置解析器，发现需要补编译基础的工程师。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;编译原理这门课的劝退点不在「难」，而在&lt;strong&gt;概念链条太长&lt;/strong&gt;：正则表达式→NFA→DFA→最小化，文法→FIRST→FOLLOW→分析表，每一环都建立在前一环上，掉队一步后面全懵。这篇总结帮你做三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;建立主干认知：编译器就是一条「翻译流水线」，六个阶段各干一件事；&lt;/li&gt;
&lt;li&gt;给出最小可运行的实现：词法分析器、递归下降 Parser、一个 60 行的计算器；&lt;/li&gt;
&lt;li&gt;把新人必踩的坑（左递归、二义性、优先级结合性、FIRST/FOLLOW 算错）提前标出来。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一先建立心智模型编译器--翻译流水线&#34;&gt;一、先建立心智模型：编译器 = 翻译流水线&lt;/h2&gt;
&lt;p&gt;编译器做的事，和人工翻译一篇文章一模一样：先认单词，再分析句子结构，再理解含义，最后用另一种语言重新表达。对应六个阶段：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    SRC[&amp;#34;源程序&amp;lt;br/&amp;gt;a = b + 2 * c&amp;#34;] --&amp;gt; LEX[&amp;#34;词法分析&amp;lt;br/&amp;gt;切成单词&amp;#34;]
    LEX --&amp;gt; SYN[&amp;#34;语法分析&amp;lt;br/&amp;gt;搭出句子结构树&amp;#34;]
    SYN --&amp;gt; SEM[&amp;#34;语义分析&amp;lt;br/&amp;gt;检查含义是否合理&amp;#34;]
    SEM --&amp;gt; ICG[&amp;#34;中间代码生成&amp;lt;br/&amp;gt;三地址码&amp;#34;]
    ICG --&amp;gt; OPT[&amp;#34;代码优化&amp;#34;]
    OPT --&amp;gt; CG[&amp;#34;目标代码生成&amp;lt;br/&amp;gt;汇编/机器码&amp;#34;]

    SYM([&amp;#34;符号表&amp;lt;br/&amp;gt;记录每个名字是什么&amp;#34;]) -.贯穿.- LEX
    SYM -.贯穿.- SYN
    SYM -.贯穿.- SEM
    ERR([&amp;#34;错误处理&amp;lt;br/&amp;gt;报错并恢复继续&amp;#34;]) -.贯穿.- LEX
    ERR -.贯穿.- SYN
    ERR -.贯穿.- SEM&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;记三个要点，后面全部内容都挂在这上面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;符号表和错误处理不是独立阶段，而是贯穿全程的两条辅助线&lt;/strong&gt;。符号表回答「这个名字是什么（类型、作用域、地址）」；错误处理保证发现一个错不崩溃，继续分析把后面的错也找出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前三个阶段是「理解原文」（分析/前端），后三个阶段是「重新表达」（综合/后端）&lt;/strong&gt;。中间代码是分界线——这也是为什么 GCC/LLVM 能「N 种语言 × M 种CPU」：每种语言写个前端，每种 CPU 写个后端，中间用统一的 IR 对接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个阶段的输出是下一个阶段的输入&lt;/strong&gt;，所以学的时候务必搞清每个阶段的「输入是什么、输出是什么」，概念就不容易混。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;日常最常见的对照例子：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;阶段&lt;/th&gt;
					&lt;th&gt;干的事&lt;/th&gt;
					&lt;th&gt;日常类比&lt;/th&gt;
					&lt;th&gt;典型报错&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;词法分析&lt;/td&gt;
					&lt;td&gt;字符流 → Token 流&lt;/td&gt;
					&lt;td&gt;认出单词&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;invalid character&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;语法分析&lt;/td&gt;
					&lt;td&gt;Token 流 → 语法树&lt;/td&gt;
					&lt;td&gt;分析句子结构&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;unexpected token&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;语义分析&lt;/td&gt;
					&lt;td&gt;检查类型、声明&lt;/td&gt;
					&lt;td&gt;理解句子含义&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;undefined variable&lt;/code&gt; / 类型不匹配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;中间代码&lt;/td&gt;
					&lt;td&gt;语法树 → 三地址码&lt;/td&gt;
					&lt;td&gt;列翻译提纲&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;优化&lt;/td&gt;
					&lt;td&gt;等价变换让代码更快&lt;/td&gt;
					&lt;td&gt;润色&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;目标代码&lt;/td&gt;
					&lt;td&gt;IR → 汇编/机器码&lt;/td&gt;
					&lt;td&gt;正式成文&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二词法分析把字符流切成单词&#34;&gt;二、词法分析：把字符流切成单词&lt;/h2&gt;
&lt;h3 id=&#34;21-任务与产出&#34;&gt;2.1 任务与产出&lt;/h3&gt;
&lt;p&gt;输入 &lt;code&gt;a = b + 2 * c&lt;/code&gt;，输出 Token 序列：&lt;code&gt;ID(a) ASSIGN PLUS...&lt;/code&gt;。每个 Token 带「种别 + 属性值」。&lt;/p&gt;
&lt;h3 id=&#34;22-理论主线正则表达式--nfa--dfa--最小化&#34;&gt;2.2 理论主线：正则表达式 → NFA → DFA → 最小化&lt;/h3&gt;
&lt;p&gt;这是教材第 3 章的核心链条，也是新人最容易断掉的地方。一张图记住四个概念的关系：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    RE[&amp;#34;正则表达式&amp;lt;br/&amp;gt;letter(letter|digit)*&amp;#34;] --&amp;gt;|Thompson 构造| NFA[&amp;#34;NFA&amp;lt;br/&amp;gt;一个字符可走多条路&amp;#34;]
    NFA --&amp;gt;|子集构造法| DFA[&amp;#34;DFA&amp;lt;br/&amp;gt;每个状态+字符&amp;lt;br/&amp;gt;只有唯一去处&amp;#34;]
    DFA --&amp;gt;|状态划分| MINDFA[&amp;#34;最小化 DFA&amp;lt;br/&amp;gt;合并等价状态&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个构造算法各自一句话记住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Thompson 构造&lt;/strong&gt;：把正则的每种运算（连接、&lt;code&gt;|&lt;/code&gt;、&lt;code&gt;*&lt;/code&gt;）各对应一个固定的 NFA 拼图，像搭积木一样拼出整图。因为允许 ε 边（不读字符就能跳的边），所以拼起来毫无难度——这就是引入 NFA 的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子集构造法&lt;/strong&gt;：DFA 的每个状态 = NFA 的「一组状态的集合」（从起点出发、经过任意条 ε 边能到达的所有状态，即 ε-闭包）。NFA 的不确定性被「打包进集合」消掉了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小化&lt;/strong&gt;：不断把「行为一样」（对每个字符都跳到同一组）的状态合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：ε-闭包是子集构造里最容易漏算的一步——每读一个字符到达新状态后，&lt;strong&gt;必须再沿 ε 边补一轮闭包&lt;/strong&gt;，漏了就会丢边，识别结果出错。&lt;/p&gt;
&lt;h3 id=&#34;23-例识别标识符与整数的-dfa&#34;&gt;2.3 例：识别标识符与整数的 DFA&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;stateDiagram-v2
    [*] --&amp;gt; S0
    S0 --&amp;gt; S1 : 字母
    S1 --&amp;gt; S1 : 字母或数字
    S0 --&amp;gt; S2 : 数字
    S2 --&amp;gt; S2 : 数字
    S1 --&amp;gt; [*] : 其他字符，接受 ID
    S2 --&amp;gt; [*] : 其他字符，接受 NUM&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;24-手写实现实践中没人先画-nfa&#34;&gt;2.4 手写实现：实践中没人先画 NFA&lt;/h3&gt;
&lt;p&gt;工程上写词法分析器，直接用「&lt;strong&gt;最长匹配 + 逐个分派&lt;/strong&gt;」的循环，十几行搞定，理论留在脑子里：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TOKEN_SPEC &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;,   &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+&amp;#34;&lt;/span&gt;),            &lt;span style=&#34;color:#75715e&#34;&gt;# 数字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ID&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[A-Za-z_]\w*&amp;#34;&lt;/span&gt;),   &lt;span style=&#34;color:#75715e&#34;&gt;# 标识符&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[+\-*/=]&amp;#34;&lt;/span&gt;),       &lt;span style=&#34;color:#75715e&#34;&gt;# 运算符&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;,  &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[ \t]+&amp;#34;&lt;/span&gt;),         &lt;span style=&#34;color:#75715e&#34;&gt;# 空白，跳过&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;,   &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;),              &lt;span style=&#34;color:#75715e&#34;&gt;# 兜底：非法字符&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tokenize&lt;/span&gt;(code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(?P&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;v&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; k, v &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; TOKEN_SPEC)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; m &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;finditer(pattern, code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        kind, val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup, m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; kind &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; kind &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;非法字符: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;val&lt;span style=&#34;color:#e6db74&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)   &lt;span style=&#34;color:#75715e&#34;&gt;# 词法错误在这里报&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt; (kind, val)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(list(tokenize(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a = b + 2 * c&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# [(&amp;#39;ID&amp;#39;,&amp;#39;a&amp;#39;), (&amp;#39;OP&amp;#39;,&amp;#39;=&amp;#39;), (&amp;#39;ID&amp;#39;,&amp;#39;b&amp;#39;), (&amp;#39;OP&amp;#39;,&amp;#39;+&amp;#39;), (&amp;#39;NUM&amp;#39;,&amp;#39;2&amp;#39;), (&amp;#39;OP&amp;#39;,&amp;#39;*&amp;#39;), (&amp;#39;ID&amp;#39;,&amp;#39;c&amp;#39;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：正则的排列顺序就是优先级（&lt;code&gt;=&lt;/code&gt; 要排在 &lt;code&gt;==&lt;/code&gt; 后面之类），以及永远留一个兜底规则报「非法字符」，否则遇到乱字符直接静默跳过，后面语法分析报错位置会莫名其妙。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三语法分析把单词流搭成结构树&#34;&gt;三、语法分析：把单词流搭成结构树&lt;/h2&gt;
&lt;h3 id=&#34;31-上下文无关文法cfg与推导&#34;&gt;3.1 上下文无关文法（CFG）与推导&lt;/h3&gt;
&lt;p&gt;语法规则用 CFG 描述，例如表达式文法：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;E → E + T | T
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T → T * F | F
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;F → ( E ) | id | num&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;2 + 3 * 4&lt;/code&gt; 的语法树（注意 &lt;code&gt;*&lt;/code&gt; 比 &lt;code&gt;+&lt;/code&gt; 离根更远——&lt;strong&gt;离根越远越先算，优先级就藏在这里&lt;/strong&gt;）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    E1[&amp;#34;E&amp;#34;] --&amp;gt; E2[&amp;#34;E&amp;#34;] &amp;amp; PLUS[&amp;#34;+&amp;#34;] &amp;amp; T1[&amp;#34;T&amp;#34;]
    E2 --&amp;gt; T2[&amp;#34;T&amp;#34;] --&amp;gt; F1[&amp;#34;F&amp;#34;] --&amp;gt; N2[&amp;#34;num 2&amp;#34;]
    T1 --&amp;gt; T3[&amp;#34;T&amp;#34;] &amp;amp; MUL[&amp;#34;*&amp;#34;] &amp;amp; F2[&amp;#34;F&amp;#34;]
    T3 --&amp;gt; F3[&amp;#34;F&amp;#34;] --&amp;gt; N3[&amp;#34;num 3&amp;#34;]
    F2 --&amp;gt; N4[&amp;#34;num 4&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;32-两大流派自顶向下-vs-自底向上&#34;&gt;3.2 两大流派：自顶向下 vs 自底向上&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph TD[&amp;#34;自顶向下（LL 家族）&amp;#34;]
        direction LR
        A1[&amp;#34;从根开始&amp;lt;br/&amp;gt;预测该展开哪条规则&amp;#34;] --&amp;gt; A2[&amp;#34;代表：递归下降&amp;lt;br/&amp;gt;LL(1) 表驱动&amp;#34;]
        A2 --&amp;gt; A3[&amp;#34;优点：好写好懂&amp;lt;br/&amp;gt;缺点：怕左递归/回溯&amp;#34;]
    end
    subgraph BU[&amp;#34;自底向上（LR 家族）&amp;#34;]
        direction LR
        B1[&amp;#34;从叶子开始&amp;lt;br/&amp;gt;移进-归约&amp;#34;] --&amp;gt; B2[&amp;#34;代表：LR(0)/SLR/&amp;lt;br/&amp;gt;LALR(1)/LR(1)&amp;#34;]
        B2 --&amp;gt; B3[&amp;#34;优点：能力强&amp;lt;br/&amp;gt;缺点：表难手写&amp;lt;br/&amp;gt;用 Yacc/Bison 生成&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;手写 Parser 用递归下降&lt;/strong&gt;（每个非终结符一个函数），工具生成用 &lt;strong&gt;Yacc/Bison（LALR）&lt;/strong&gt;。工业界主流语言（GCC 的 C++、Go、Rust 前端）大量采用手写递归下降——错误信息友好、调试容易。&lt;/li&gt;
&lt;li&gt;LR 家族记一个不等式：&lt;strong&gt;LR(1) ⊃ LALR(1) ⊃ SLR ⊃ LR(0)&lt;/strong&gt;，能力依次减弱、表依次变小。Yacc 生成的是 LALR(1)——能力和表大小的折中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-ll1-的三道手续左递归左公因子firstfollow&#34;&gt;3.3 LL(1) 的三道手续：左递归、左公因子、FIRST/FOLLOW&lt;/h3&gt;
&lt;p&gt;把文法改造成 LL(1) 可用，是教材第 4 章的主线，也是作业/考试重灾区：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坑 1：左递归会让递归下降死循环。&lt;/strong&gt; &lt;code&gt;E → E + T&lt;/code&gt; 翻译成函数就是「解析 E 先调自己」，无限递归。&lt;strong&gt;必须改写成右递归/迭代形式&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;E  → T E&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;E&amp;#39; → + T E&amp;#39; | ε      （原来是左递归 E → E + T | T）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T  → F T&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T&amp;#39; → * F T&amp;#39; | ε
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;F  → ( E ) | id | num&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;坑 2：左公因子导致「选哪条规则」看不准。&lt;/strong&gt; &lt;code&gt;S → if E then S | if E then S else S&lt;/code&gt; 两条规则开头一样，提取公因子：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;S  → if E then S S&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;S&amp;#39; → else S | ε&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;坑 3：FIRST/FOLLOW 算错，分析表全错。&lt;/strong&gt; 记住用途就不容易错：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FIRST(A)：A 推出的串&lt;strong&gt;开头&lt;/strong&gt;可能是什么终结符——决定「看到某个 token 该选哪条产生式」；&lt;/li&gt;
&lt;li&gt;FOLLOW(A)：A &lt;strong&gt;后面&lt;/strong&gt;可能跟什么终结符——只在「考虑是否选 ε 产生式」时用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;算 FOLLOW 的三条规则（教材原文浓缩）：&lt;code&gt;$&lt;/code&gt; 进 FOLLOW(S)；&lt;code&gt;A → αBβ&lt;/code&gt; 则 FIRST(β)−ε 进 FOLLOW(B)；&lt;code&gt;A → αB&lt;/code&gt; 或 β 可空时 FOLLOW(A) 进 FOLLOW(B)。&lt;strong&gt;最常犯的错就是漏掉第三条&lt;/strong&gt;，考试拿分点也在这里。&lt;/p&gt;
&lt;h3 id=&#34;34-手写实现递归下降表达式解析器&#34;&gt;3.4 手写实现：递归下降表达式解析器&lt;/h3&gt;
&lt;p&gt;改造后的文法直接翻译成人话：&lt;strong&gt;每个非终结符一个函数，看到什么 token 走哪条分支&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Parser&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, tokens):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokens &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(tokens)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peek&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokens[self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos] &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokens) &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;eat&lt;/span&gt;(self, kind):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        k, v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; k &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; kind:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;期望 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;kind&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;，实际 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;v&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)  &lt;span style=&#34;color:#75715e&#34;&gt;# 语法错误在这里报&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# E → T E&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_E&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_T()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); val &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_T()   &lt;span style=&#34;color:#75715e&#34;&gt;# E&amp;#39; → + T E&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; val
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# T → F T&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_T&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_F()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); val &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_F()   &lt;span style=&#34;color:#75715e&#34;&gt;# T&amp;#39; → * F T&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; val
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# F → num | ( E )&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_F&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; int(self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LP&amp;#34;&lt;/span&gt;); val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_E(); self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RP&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; val&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;三个层次各管一件事：&lt;strong&gt;E 管加减、T 管乘除、F 管括号与原子&lt;/strong&gt;——优先级靠「调用层级」天然保证，结合性靠 &lt;code&gt;while&lt;/code&gt; 循环方向保证（左结合）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四语义分析与中间代码&#34;&gt;四、语义分析与中间代码&lt;/h2&gt;
&lt;h3 id=&#34;41-语义分析语法对了意思对吗&#34;&gt;4.1 语义分析：语法对了，意思对吗&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;int x = &amp;quot;abc&amp;quot;;&lt;/code&gt; 语法完全合法，但语义不通。语义分析干三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;查符号表&lt;/strong&gt;：变量声明过吗？作用域对吗？重复定义了吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型检查&lt;/strong&gt;：运算两边类型兼容吗？函数调用参数个数/类型对吗？&lt;/li&gt;
&lt;li&gt;理论上用**属性文法/语法制导翻译（SDD/SDT）**描述：给语法树节点挂「综合属性」（从下往上传，如类型）和「继承属性」（从上往下传，如声明的类型传给变量表项）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：语义错误往往是「前面埋雷后面炸」——声明处没检查，使用处才报。写编译器时把检查尽量前移到「信息刚产生的地方」。&lt;/p&gt;
&lt;h3 id=&#34;42-中间代码三地址码&#34;&gt;4.2 中间代码：三地址码&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;a = b + 2 * c&lt;/code&gt; 翻成三地址码（每条指令至多一个运算符）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t1 = 2 * c
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t2 = b + t1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a  = t2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;作用：&lt;strong&gt;把前端和后端解耦&lt;/strong&gt;。LLVM IR、Java 字节码都是这个思想——前端不管什么 CPU，后端不管什么语言。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五优化与目标代码&#34;&gt;五、优化与目标代码&lt;/h2&gt;
&lt;h3 id=&#34;51-优化等价变换不改结果改速度&#34;&gt;5.1 优化：等价变换，不改结果改速度&lt;/h3&gt;
&lt;p&gt;新人只需记住四类常见优化，看到汇编里的「怪异写法」能认出是哪种：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;优化&lt;/th&gt;
					&lt;th&gt;例子&lt;/th&gt;
					&lt;th&gt;效果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;常量折叠/传播&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;2*3&lt;/code&gt; → &lt;code&gt;6&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;编译期算掉&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;公共子表达式消除&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;a+b&lt;/code&gt; 算两次 → 算一次存临时量&lt;/td&gt;
					&lt;td&gt;少算&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;死代码消除&lt;/td&gt;
					&lt;td&gt;删掉结果没人用的语句&lt;/td&gt;
					&lt;td&gt;代码变小&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;循环优化&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;i*4&lt;/code&gt; → 强度削弱为加法；循环不变量外提&lt;/td&gt;
					&lt;td&gt;热点提速&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：优化的铁律是「&lt;strong&gt;语义等价&lt;/strong&gt;」。浮点运算&lt;strong&gt;不满足结合律&lt;/strong&gt;，&lt;code&gt;(a+b)+c&lt;/code&gt; 和 &lt;code&gt;a+(b+c)&lt;/code&gt; 结果可能不同——所以激进优化可能改变浮点结果（GCC 的 &lt;code&gt;-ffast-math&lt;/code&gt; 就会），数值程序测试对不上时先查这个。&lt;/p&gt;
&lt;h3 id=&#34;52-目标代码核心是寄存器分配&#34;&gt;5.2 目标代码：核心是寄存器分配&lt;/h3&gt;
&lt;p&gt;指令选择、指令调度了解一下即可，真正值得记住的是&lt;strong&gt;寄存器分配&lt;/strong&gt;：寄存器快但数量有限，谁住寄存器谁住内存？经典解法是&lt;strong&gt;图着色&lt;/strong&gt;——变量当顶点、同时活跃的变量连边，寄存器当颜色，给图染色。这个「把分配问题建模成图着色」的思想，比算法细节本身更有迁移价值。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六动手实战60-行写一个四则运算计算器&#34;&gt;六、动手实战：60 行写一个四则运算计算器&lt;/h2&gt;
&lt;p&gt;把第二、三章的东西拼起来：词法用正则，语法用递归下降，语义直接求值（跳过中间代码），支持 &lt;code&gt;+ - * / ( )&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tokenize&lt;/span&gt;(code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    spec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[+\-*/]&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\(&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\)&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\s+&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(?P&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;v&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; k, v &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; spec)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; m &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;finditer(pattern, code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;非法字符 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group()&lt;span style=&#34;color:#e6db74&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt; (m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup, m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calc&lt;/span&gt;(code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ts, pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(tokenize(code)), [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]          &lt;span style=&#34;color:#75715e&#34;&gt;# pos 用列表包一层方便改&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peek&lt;/span&gt;(): &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ts[pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]] &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(ts) &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;eat&lt;/span&gt;(k):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; peek()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; k: &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;期望 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;, 得到 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;peek()&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ts[pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;][&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;expr&lt;/span&gt;():                                  &lt;span style=&#34;color:#75715e&#34;&gt;# expr → term ((+|-) term)*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; term()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; peek() &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; ((&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-&amp;#34;&lt;/span&gt;)):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            op &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; term()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; r &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; op &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; r
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;term&lt;/span&gt;():                                  &lt;span style=&#34;color:#75715e&#34;&gt;# term → factor ((*|/) factor)*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; factor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; peek() &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; ((&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            op &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; factor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; r &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; op &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; r
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;factor&lt;/span&gt;():                                &lt;span style=&#34;color:#75715e&#34;&gt;# factor → NUM | ( expr )&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; peek()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; int(eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LP&amp;#34;&lt;/span&gt;); v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; expr(); eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RP&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; expr()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; len(ts): &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;表达式未结束，多了内容&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(calc(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2 + 3 * (4 - 1)&amp;#34;&lt;/span&gt;))   &lt;span style=&#34;color:#75715e&#34;&gt;# 11&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;写完这个，你就走完了一个迷你编译器的完整前端：&lt;strong&gt;字符 → Token → 语法树（隐含在调用栈里）→ 结果&lt;/strong&gt;。想再进一步，把 &lt;code&gt;expr/term/factor&lt;/code&gt; 里的「直接求值」换成「返回 AST 节点」，就有了语法树；遍历 AST 输出三地址码，就有了中间代码层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七典型问题速查新人必踩的坑&#34;&gt;七、典型问题速查（新人必踩的坑）&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;根因&lt;/th&gt;
					&lt;th&gt;解法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;递归下降死循环/栈溢出&lt;/td&gt;
					&lt;td&gt;文法有左递归 &lt;code&gt;E → E + T&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;改写为右递归形式（&lt;code&gt;E → T E&#39;&lt;/code&gt;）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;同一个输入两种语法树&lt;/td&gt;
					&lt;td&gt;文法二义性（如 &lt;code&gt;E → E+E&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;分层文法（E/T/F）或显式声明优先级结合性&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;if-else 挂错位置（dangling else）&lt;/td&gt;
					&lt;td&gt;二义性经典案例&lt;/td&gt;
					&lt;td&gt;规定 else 匹配最近的未配对 then（Yacc 默认移进优先即可）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;1+2*3&lt;/code&gt; 算成 9&lt;/td&gt;
					&lt;td&gt;优先级没分层&lt;/td&gt;
					&lt;td&gt;运算符按优先级各分一层非终结符&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;8/4/2&lt;/code&gt; 算成 4&lt;/td&gt;
					&lt;td&gt;结合性错了（写成右结合）&lt;/td&gt;
					&lt;td&gt;左结合用 &lt;code&gt;while&lt;/code&gt; 循环收集，右结合（如 &lt;code&gt;^&lt;/code&gt;、赋值）用递归&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;LL(1) 分析表一格填了两条&lt;/td&gt;
					&lt;td&gt;FIRST 集相交，或 ε 规则的 FIRST∩FOLLOW 非空&lt;/td&gt;
					&lt;td&gt;提左公因子；重算 FIRST/FOLLOW&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;遇到一个错就整个编译中断&lt;/td&gt;
					&lt;td&gt;没做错误恢复&lt;/td&gt;
					&lt;td&gt;恐慌模式：跳过 token 直到同步符号（&lt;code&gt;;&lt;/code&gt;、&lt;code&gt;}&lt;/code&gt;）再继续&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;子集构造结果丢边&lt;/td&gt;
					&lt;td&gt;读完字符后忘了补 ε-闭包&lt;/td&gt;
					&lt;td&gt;每步「先 move 再 closure」写成固定套路&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;优化后浮点结果对不上&lt;/td&gt;
					&lt;td&gt;浮点不满足结合律，优化重排了运算&lt;/td&gt;
					&lt;td&gt;关掉激进浮点优化，或接受精度差异&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八学习路线建议&#34;&gt;八、学习路线建议&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一周&lt;/strong&gt;：看懂六阶段流水线图，用正则手写一个词法分析器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二周&lt;/strong&gt;：攻形式语言——正则→NFA→DFA→最小化，动手做一遍子集构造的纸面推导（这步走一遍比看十遍有用）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三周&lt;/strong&gt;：递归下降写表达式 Parser，亲手把左递归文法改写成 LL(1)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第四周&lt;/strong&gt;：跑通本文的计算器，然后加变量赋值（需要符号表）——这一步会让你真正理解符号表为什么贯穿始终。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进阶&lt;/strong&gt;：读 LR 分析表构造（SLR→LALR），用 Yacc/Bison 或 ANTLR 生成一个 Parser 对比手写的差别；再往后可以啃「龙书」的优化章节。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后说一句教材反复强调的心法：&lt;strong&gt;编译器的每个阶段都是「输入一种表示，输出另一种表示」&lt;/strong&gt;。学不懂某个概念时，先问两个问题——它输入什么、输出什么？答案往往就是理解本身。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;王生原、董渊、张素琴、吕映芝、蒋维杜《编译原理（第 3 版）》，清华大学出版社&lt;/li&gt;
&lt;li&gt;Aho 等《编译原理》（龙书, Compilers: Principles, Techniques, and Tools）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.antlr.org/&#34;&gt;ANTLR 官方文档&lt;/a&gt;（工具生成 Parser 的现代选择）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://llvm.org/docs/&#34;&gt;LLVM 官方文档&lt;/a&gt;（中间代码与后端思想的工业界范本）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
