<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>tuliang (涂亮)</title>
    <link>https://ruby-china.org/tuliang</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Redis Cluster 实践</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/redis/redis-rb" rel="nofollow" target="_blank" title=""&gt;redis&lt;/a&gt; 这个 gem 在最新的 4.1 版中开始支持 Redis Cluster。&lt;/p&gt;
&lt;h3 id="开发环境"&gt;开发环境&lt;/h3&gt;
&lt;p&gt;我们需要在开发环境搭建一个 Redis Cluster，最方便快捷的方法是使用 docker 和 docker-compose，并且使用配置好的 image。&lt;/p&gt;

&lt;p&gt;在 docker-compose.yml 中添加下列代码&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis-cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grokzen/redis-cluster&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IP=0.0.0.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7000-7007:7000-7007'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;docker-compose up -d&lt;/code&gt; 启动服务即可。&lt;/p&gt;
&lt;h3 id="更新代码"&gt;更新代码&lt;/h3&gt;
&lt;p&gt;在 Gemfile 中设置 redis gem 的版本为 4.1&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 4.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 application.rb 中修改 session_store 和 cache_store&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Redis Session Store (redis-rails Gem)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session_store&lt;/span&gt; &lt;span class="ss"&gt;:redis_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;servers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;month&lt;/span&gt;

  &lt;span class="c1"&gt;# Redis Cache Store (redis-rails Gem)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Redis Cache Store (ActiveSupport)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_cache_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在其他地方使用 Redis Cluster&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="分片"&gt;分片&lt;/h3&gt;
&lt;p&gt;从单机变成集群之后，数据分片了，分散到不同机器，但是实际需求要求相关联的 &lt;code&gt;key&lt;/code&gt; 分配到相同机器。&lt;/p&gt;

&lt;p&gt;因为业务或者性能问题，我们会使用 Redis 的 pipelined、multi 和 Rails.cache.read_multi，这些场景下都需要 &lt;code&gt;key&lt;/code&gt; 分配到相同机器。&lt;/p&gt;

&lt;p&gt;但是 Redis Cluster 的分片方案是服务端提供分片，规则不是客户端控制的。&lt;/p&gt;

&lt;p&gt;针对这些场景 Redis Cluster 提供了 &lt;a href="https://redis.io/topics/cluster-spec#keys-hash-tags" rel="nofollow" target="_blank" title=""&gt;Keys hash tags&lt;/a&gt; 的功能，简单来说是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;当一个 key 包含 {} 的时候，就不对整个 key 做 hash，而仅对 {} 包括的字符串做 hash。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'key2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)&lt;/span&gt;

&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{key}1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{key}2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; [nil, nil]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="总结"&gt;总结&lt;/h3&gt;
&lt;p&gt;本文只是一个简单的例子，不能在生产环境中直接使用。&lt;/p&gt;

&lt;p&gt;在生产环境需要考虑 Redis 的各个客户端，同时要考虑数据迁移和兼容的问题，升级到 Redis Cluster 要非常谨慎和细心。&lt;/p&gt;
&lt;h3 id="参考资料："&gt;参考资料：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/redis/redis-rb/wiki/Cluster-mode" rel="nofollow" target="_blank" title=""&gt;Cluster mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Grokzen/docker-redis-cluster" rel="nofollow" target="_blank" title=""&gt;Grokzen/docker-redis-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://made.livesense.co.jp/entry/2018/10/17/135245" rel="nofollow" target="_blank" title=""&gt;Ruby の Redis Client Library を Cluster Mode に対応させた話&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redis/redis-rb/pull/716" rel="nofollow" target="_blank" title=""&gt;Add Redis Cluster support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.text.wiki/2015/09/20/redis-hash-tag.html" rel="nofollow" target="_blank" title=""&gt;Redis 技巧：分片技术和 Hash Tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;原文转自博客 &lt;a href="https://www.tuliang.org/redis-cluster-shi-jian/" rel="nofollow" target="_blank" title=""&gt;Redis Cluster 实践&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Thu, 17 Jan 2019 20:26:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/38014</link>
      <guid>https://ruby-china.org/topics/38014</guid>
    </item>
    <item>
      <title>Nginx 进程和信号</title>
      <description>&lt;h2 id="Nginx 进程和信号"&gt;Nginx 进程和信号&lt;/h2&gt;&lt;h2 id="进程结构"&gt;进程结构&lt;/h2&gt;
&lt;p&gt;一般来说 Nginx 有一个父进程 Master，和一个或多个子进程。&lt;/p&gt;

&lt;p&gt;子进程分两类，一种是 Worker 进程，另一种是 Cache 相关的进程。&lt;/p&gt;

&lt;p&gt;Cache 在多个 Worker 间共享使用。同时被进程 Cache manager 和 Cache loader 使用。Cache loader 做缓存的载入，Cache manager 做缓存的管理。这些进程间的通讯都是使用共享内存来解决的。&lt;/p&gt;

&lt;p&gt;为什么不用多线程？因为线程之间是共享地址空间的，当某一个第三方模块引发了地址空间的段错误时，地址越界出现时，会导致整个进程挂掉。&lt;/p&gt;

&lt;p&gt;Nginx 希望每个 Worker 进程从头到尾占有一个 CPU，所以往往不止要把 Worker 进程的数量配置与服务器的 CPU 核数一致以外，还需要把每一个 Worker 进程与某一个 CPU 核绑定在一起。这样可以更好的使用每个 CPU 核上面的 CPU 缓存，提高缓存的命中率。&lt;/p&gt;

&lt;p&gt;不仅仅是缓存的原因，一个进程从头到尾占有一个 CPU，减少 CPU 上下文切换的次数和耗时，让 CPU 更多的时间用在运行进程上。&lt;/p&gt;
&lt;h3 id="Master 进程"&gt;Master 进程&lt;/h3&gt;
&lt;p&gt;监控 Worker 进程，接受信号&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CHLD，子进程终止的时候会向父进程发送 CHLD。Master 进程通过这个信号维持 Worker 进程的数量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;管理 Worker 进程，发送信号&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TERM/INT，立刻停止进程。&lt;/li&gt;
&lt;li&gt;QUIT，优雅的退出，等请求处理完才退出，用户无感知。&lt;/li&gt;
&lt;li&gt;HUP，重载配置文件。&lt;/li&gt;
&lt;li&gt;USR1，重新打开日志文件，做日志文件的切割。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面 2 个信号，Nginx 命令行中没有对应的操作，只能通过 kill 直接向 Master 进程发送&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;USR2，热升级第一阶段，启动新进程。旧的 Nginx 主进程 Master 将会把自己的进程文件改名为 .oldbin，然后执行新版 Nginx。此时新旧 Nginx 会同时运行，共同处理请求。&lt;/li&gt;
&lt;li&gt;WINCH，热升级第二阶段，停止老进程。逐步停止旧版 Nginx 的 Worker 进程就都会随着任务执行完毕而退出，新版的 Nginx 的 Worker 进程会逐渐取代旧版 Worker 进程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Worker 进程"&gt;Worker 进程&lt;/h3&gt;
&lt;p&gt;和上面对应，接受信号&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TERM/INT&lt;/li&gt;
&lt;li&gt;QUIT&lt;/li&gt;
&lt;li&gt;USR1&lt;/li&gt;
&lt;li&gt;WINCH&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Nginx 命令行"&gt;Nginx 命令行&lt;/h3&gt;
&lt;p&gt;启动 Nginx 以后，Nginx 会把 Master 进程的 pid 记录到一个文件中，一般是 Nginx 安装目录下 logs/nginx.pid。&lt;/p&gt;

&lt;p&gt;我们平时使用的命令行其实就是对这个 pid 发送对应的信号。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reload: HUP&lt;/li&gt;
&lt;li&gt;reopen: USR1&lt;/li&gt;
&lt;li&gt;stop: TERM&lt;/li&gt;
&lt;li&gt;quit: QUIT&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="reload 流程"&gt;reload 流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;向 Master 进程发送 HUP 信号（reload 命令）&lt;/li&gt;
&lt;li&gt;Master 进程校验配置语法是否正确&lt;/li&gt;
&lt;li&gt;Master 进程更新监听端口（比如新配置增加了监听 443 端口）&lt;/li&gt;
&lt;li&gt;Master 进程用新配置启动新的 Worker 子进程&lt;/li&gt;
&lt;li&gt;Master 进程向老 Worker 子进程发送 QUIT 信号&lt;/li&gt;
&lt;li&gt;老 Worker 子进程关闭监听句柄，处理完当前连接后结束进程&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在 1.11.11 的版本中，增加了 worker_shutdown_timeout 参数来设置优雅退出 Worker 进程的超时时间。&lt;/p&gt;
&lt;h3 id="热升级流程"&gt;热升级流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;将旧 Nginx 文件换成新 Nginx 文件（注意备份）&lt;/li&gt;
&lt;li&gt;向 Master 进程发送 USR2 信号&lt;/li&gt;
&lt;li&gt;Master 进程修改 pid 文件名，加后缀 .oldbin&lt;/li&gt;
&lt;li&gt;Master 进程用新 Nginx 文件启动新 Master 进程&lt;/li&gt;
&lt;li&gt;向老 Master 进程发送 WINCH 信号，老 Master 进程会优雅关闭老 Worker 进程&lt;/li&gt;
&lt;li&gt;此时老 Master 进程依然存活，如果出现问题需要回滚。向老 Master 进程发送 HUP，然后向新 Master 发送 QUIT 即可。（注意恢复备份文件）&lt;/li&gt;
&lt;li&gt;正常升级后，通过 kill -QUIT 信号关闭老 Master 进程&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;新老 Master 进程，同时存在，那么是怎么同时监听端口的？&lt;/p&gt;

&lt;p&gt;新老 Master 进程是父子进程，所以可以同时监听。&lt;/p&gt;
&lt;h3 id="优雅关闭 Worker 进程"&gt;优雅关闭 Worker 进程&lt;/h3&gt;
&lt;p&gt;优雅关闭是指 HTTP 请求，如果代理的是 WebSocket、TCP、UDP，这时候 Nginx 是无法进行优雅关闭的。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;设置定时器（这个功能默认不开启，配置文件有设置 worker_shutdown_timeout 才会生效）&lt;/li&gt;
&lt;li&gt;关闭监听句柄&lt;/li&gt;
&lt;li&gt;关闭空闲连接&lt;/li&gt;
&lt;li&gt;在循环中等待全部连接关闭（如果设置了 worker_shutdown_timeout，超时后会强制关闭全部连接）&lt;/li&gt;
&lt;li&gt;退出进程&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;更多信息可以参考官方文档 &lt;a href="https://www.nginx.com/resources/wiki/start/topics/tutorials/commandline/" rel="nofollow" target="_blank" title=""&gt;Starting, Stopping, and Restarting NGINX&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文转自博客 &lt;a href="https://www.tuliang.org/nginx-jin-cheng-he-xin-hao/" rel="nofollow" target="_blank" title=""&gt;Nginx 进程和信号&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Sun, 02 Dec 2018 10:49:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/37845</link>
      <guid>https://ruby-china.org/topics/37845</guid>
    </item>
    <item>
      <title>浅谈 Markdown 编译器</title>
      <description>&lt;h2 id="浅谈 Markdown 编译器"&gt;浅谈 Markdown 编译器&lt;/h2&gt;
&lt;p&gt;作者不是科班出身，基础比较差。一直在补习一些基础知识，也包括编译原理。最近和朋友交流的时候有提到 &lt;code&gt;AST(抽象语法树)&lt;/code&gt;，虽然以前也零散的看了一些文章，实际上还是比较模糊的。&lt;/p&gt;

&lt;p&gt;在这一篇文章中，我会尝试谈一下编译器。虽然参考了诸多资料，不过文中观点难免掺入主观想法，我也希望文中的错误与不足之处能被各位指出。&lt;/p&gt;
&lt;h2 id="基础知识"&gt;基础知识&lt;/h2&gt;
&lt;p&gt;编译原理是计算机专业的一门重要专业课，旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。&lt;/p&gt;
&lt;h3 id="解释器与编译器"&gt;解释器与编译器&lt;/h3&gt;&lt;h4 id="解释器"&gt;解释器&lt;/h4&gt;
&lt;p&gt;解释器根据程序中的算法执行运算。简单来讲，它是一种用于执行程序的软件。如果执行的程序由虚拟机器语言或类似于机器语言的程序设计语言写成，这种软件也能称为虚拟机。&lt;/p&gt;
&lt;h4 id="编译器"&gt;编译器&lt;/h4&gt;
&lt;p&gt;编译器能将某种语言写成的程序转换为另一种语言的程序。通常它会将原程序转换为机器语言。编译器转换程序的行为称为编译，转换前的程序称为源代码或源程序。如果编译器没有把源代码直接转换为机器语言，一般称为源代码转换器或源码转换器（source code translator）。&lt;/p&gt;

&lt;p&gt;过去人们提到编译器时，首先会联想到费时的编译过程。不过由于编译后实际执行的是机器语言，因此执行速度很快。而对于解释器，人们通常认为它会在程序输入的同时立即执行，执行速度较慢。这就是两者的基本区别。现代的解释器内部常采用各种类型的编译器，已经越来越没有必要将解释器和编译器区分看待。&lt;/p&gt;
&lt;h3 id="语言处理器的结构"&gt;语言处理器的结构&lt;/h3&gt;
&lt;p&gt;无论是解释器还是编译器，语言处理器前半部分的程序结构都大同小异。如下图所示&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www.tuliang.org/images/2018/QQ20181119-000312@2x.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h3 id="分割单词"&gt;分割单词&lt;/h3&gt;
&lt;p&gt;语言处理器的第一个组成部分是词法分析器（lexical analyzer、lexer 或 scanner）。程序的源代码最初只是一长串字符串。从内部来看，源代码中的换行也能用专门的（不可见）换行符表示，因此整个源代码是一种相连的长字符串。这样的长字符串很难处理，语言处理器通常会首先将字符串中的字符以单词为单位分组，切割成多个字符串。这就是词法分析。&lt;/p&gt;

&lt;p&gt;词法分析器将把程序源代码视作字符串，并把它分割为若干单词（token）。分割后得到的单词并不是简单地用 &lt;code&gt;String&lt;/code&gt; 对象表示，而是使用 &lt;code&gt;Token&lt;/code&gt; 对象。这种对象除了记录该单词对应的字符串，还会保存单词的类型、单词所处位置的行号等信息。&lt;/p&gt;
&lt;h3 id="用于表示程序的对象"&gt;用于表示程序的对象&lt;/h3&gt;
&lt;p&gt;语言处理器在词法分析阶段将程序分割为单词后，将开始构造抽象语法树（AST，Abstract Syntax Tree）。抽象语法树是一种用于表示程序结构的树形结构。&lt;/p&gt;

&lt;p&gt;构造抽象语法树的过程称为语法分析，依然属于语言处理器的前半阶段。经过词法分析后，程序已经被分解为一个个单词。语法分析的主要任务是分析单词之间的关系，如判断哪些单词属于同一个表达式或语句，以及处理左右括号（单词）的配对等问题。语法分析的结果能够通过抽象语法树来表示。这一阶段还会检查程序中是否含有语法错误。&lt;/p&gt;

&lt;p&gt;以 &lt;code&gt;13 + x * 2&lt;/code&gt; 为例&lt;/p&gt;
&lt;h4 id="单词序列"&gt;单词序列&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://www.tuliang.org/images/2018/QQ20181119-000259@2x.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h4 id="抽象语法树"&gt;抽象语法树&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://www.tuliang.org/images/2018/QQ20181119-000241@2x.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;抽象语法树仅用于表示语法分析的结果，因此通过语法分析得到的单词并不一定要与抽象语法树的节点一一对应。抽象语法树是一种去除了多余信息的抽象树形结构。例如&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(13 + x) * 2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这样一个表达式来说，它与之前的例子不同，包含了括号。乘法运算的左值不再是 &lt;code&gt;x&lt;/code&gt; 而是 &lt;code&gt;13 + x&lt;/code&gt;。那么它的抽象语法树是&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www.tuliang.org/images/2018/QQ20181119-000231@2x.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;程序中的括号等信息不必出现在抽象语法树中。除了括号，句尾的分号等无关紧要的单词通常也不会出现在抽象语法树中。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;抽象化原本指的就是去除多余的内容，抽取出事物的本质。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="实战"&gt;实战&lt;/h2&gt;&lt;h3 id="分割单词"&gt;分割单词&lt;/h3&gt;
&lt;p&gt;使用正则表达式来分割单词，支持&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;# ## ### ```&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;以下面为例&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 标题
\```
x = 1
y = x + 2
\`\`\`
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="单词序列"&gt;单词序列&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://www.tuliang.org/images/2018/QQ20181119-000609@2x.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;部分代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/(^###)|(^##)|(^#)|(^```)|(.+)/&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@filename&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# i 从 0 开始 需要先 + 1&lt;/span&gt;
    &lt;span class="vi"&gt;@line_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nb"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@regex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;add_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@line_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@line_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@line_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Code'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@line_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@line_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"bad token at line &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@line_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="vi"&gt;@queue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="构造抽象语法树"&gt;构造抽象语法树&lt;/h3&gt;
&lt;p&gt;抽象语法树使用栈转树的方式实现&lt;/p&gt;
&lt;h4 id="抽象语法树"&gt;抽象语法树&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://www.tuliang.org/images/2018/QQ20181119-000209@2x.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;部分代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ast_runner&lt;/span&gt;
  &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="c1"&gt;# 遍历队列&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="vi"&gt;@queue.length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'H1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'H2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'H3'&lt;/span&gt;
      &lt;span class="c1"&gt;# H1 H2 H3 规则&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ASTList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# index 向后移动 1 位 指向下个节点 &lt;/span&gt;
      &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="n"&gt;next_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="c1"&gt;# 去除左边的空格&lt;/span&gt;
      &lt;span class="n"&gt;next_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip!&lt;/span&gt;
      &lt;span class="c1"&gt;# 将节点附加到 children 末尾&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ASTLeaf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'Text'&lt;/span&gt;
      &lt;span class="c1"&gt;# Text 规则 &lt;/span&gt;
      &lt;span class="c1"&gt;# 直接生成叶子节点&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ASTLeaf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'Code'&lt;/span&gt;
      &lt;span class="c1"&gt;# Code 规则 &lt;/span&gt;

      &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ASTList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Code 未闭合前一直循环&lt;/span&gt;
      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="c1"&gt;# index 向后移动 1 位 指向下个节点 &lt;/span&gt;
        &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;next_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'Code'&lt;/span&gt;
          &lt;span class="c1"&gt;# 当前节点为闭合节点 退出循环&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="c1"&gt;# 将节点附加到 children 末尾&lt;/span&gt;
          &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ASTLeaf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"bad token type at line &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# 将节点附加到 ast 末尾&lt;/span&gt;
    &lt;span class="n"&gt;ast&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;

    &lt;span class="c1"&gt;# index 向后移动 1 位 进行下个迭代&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;ast&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="运行"&gt;运行&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ruby lexer.rb
H1
 └── Ruby Markdown

一个使用 Ruby 实现的 Markdown 编译器

H2
 └── 解释器与编译器

H3
 └── 解释器

解释器根据程序中的算法执行运算。简单来讲，它是一种用于执行程序的软件。如果执行的程序由虚拟机器语言或类似于机器语言的程序设计语言写成，这种软件也能称为虚拟机。

H3
 └── 编译器

编译器能将某种语言写成的程序转换为另一种语言的程序。通常它会将原程序转换为机器语言。编译器转换程序的行为称为编译，转换前的程序称为源代码或源程序。如果编译器没有把源代码直接转换为机器语言，一般称为源代码转换器或源码转换器（source code translator）。

过去人们提到编译器时，首先会联想到费时的编译过程。不过由于编译后实际执行的是机器语言，因此执行速度很快。而对于解释器，人们通常认为它会在程序输入的同时立即执行，执行速度较慢。这就是两者的基本区别。现代的解释器内部常采用各种类型的编译器，已经越来越没有必要将解释器和编译器区分看待。

H2
 └── 运行

Code
 └── ruby lexer.rb

Code
 ├── lexer &lt;span class="o"&gt;=&lt;/span&gt; Lexer.new&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"README.md"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
 ├── parser &lt;span class="o"&gt;=&lt;/span&gt; lexer.parser
 ├── parser.show
 └── puts parser.to_html
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Ruby Markdown&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;一个使用 Ruby 实现的 Markdown 编译器&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;h2&amp;gt;&lt;/span&gt;解释器与编译器&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;h3&amp;gt;&lt;/span&gt;解释器&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;解释器根据程序中的算法执行运算。简单来讲，它是一种用于执行程序的软件。如果执行的程序由虚拟机器语言或类似于机器语言的程序设计语言写成，这种软件也能称为虚拟机。&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;h3&amp;gt;&lt;/span&gt;编译器&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;编译器能将某种语言写成的程序转换为另一种语言的程序。通常它会将原程序转换为机器语言。编译器转换程序的行为称为编译，转换前的程序称为源代码或源程序。如果编译器没有把源代码直接转换为机器语言，一般称为源代码转换器或源码转换器（source code translator）。&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;过去人们提到编译器时，首先会联想到费时的编译过程。不过由于编译后实际执行的是机器语言，因此执行速度很快。而对于解释器，人们通常认为它会在程序输入的同时立即执行，执行速度较慢。这就是两者的基本区别。现代的解释器内部常采用各种类型的编译器，已经越来越没有必要将解释器和编译器区分看待。&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;h2&amp;gt;&lt;/span&gt;运行&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;ruby lexer.rb&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;lexer = Lexer.new("README.md")&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;parser = lexer.parser&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;parser.show&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;puts parser.to_html&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;pre&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;从 &lt;code&gt;分割单词&lt;/code&gt; 获得单词序列到 &lt;code&gt;构造抽象语法树&lt;/code&gt;，再到&lt;code&gt;生成代码&lt;/code&gt;，基本上实现了一个最简单的编译器。&lt;/p&gt;

&lt;p&gt;在做的过程中你会发现编译原理的确是大作业，细做的话每一步，有太多东西，另外解释器也不简单。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.tuliang.org/qian-tan-markdown-bian-yi-qi/" rel="nofollow" target="_blank" title=""&gt;博客地址 浅谈 Markdown 编译器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tuliang/ruby-markdown" rel="nofollow" target="_blank" title=""&gt;项目代码 ruby-markdown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://book.douban.com/subject/25908672/" rel="nofollow" target="_blank" title=""&gt;两周自制脚本语言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bilibili.com/video/av17649289" rel="nofollow" target="_blank" title=""&gt;编译原理（哈工大）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://rednaxelafx.iteye.com/blog/492667" rel="nofollow" target="_blank" title=""&gt;虚拟机随谈（一）：解释器，树遍历解释器，基于栈与基于寄存器，大杂烩&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>tuliang</author>
      <pubDate>Mon, 19 Nov 2018 00:35:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/37790</link>
      <guid>https://ruby-china.org/topics/37790</guid>
    </item>
    <item>
      <title>.bashrc、.bash_profile 和.zshrc</title>
      <description>&lt;p&gt;在 Mac 系统中，我们通过编辑&lt;code&gt;.bashrc&lt;/code&gt;和&lt;code&gt;.bash_profile&lt;/code&gt;来设置用户的工作环境，很多文章对它们进行修改。&lt;/p&gt;

&lt;p&gt;最近使用的时候发现编辑文件后，并不起效，查资料后发现&lt;code&gt;因为如果是 sh 或者其他 shell 显然不会运行 bashrc 的.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;突然记起来我用了&lt;a href="https://github.com/robbyrussell/oh-my-zsh" rel="nofollow" target="_blank" title=""&gt;oh-my-zsh&lt;/a&gt;，检查根目录，果然有一个文件叫&lt;code&gt;.zshrc&lt;/code&gt;，赶紧将设置纷纷移到里面，整个世界仿佛瞬间变得美好了。&lt;/p&gt;

&lt;p&gt;参考资料：&lt;a href="https://wido.me/sunteya/understand-bashrc-and-profile" rel="nofollow" target="_blank" title=""&gt;https://wido.me/sunteya/understand-bashrc-and-profile&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Tue, 08 Jul 2014 21:00:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/20381</link>
      <guid>https://ruby-china.org/topics/20381</guid>
    </item>
    <item>
      <title>Web 文件下载和查看</title>
      <description>&lt;p&gt;博文原文地址：&lt;a href="http://www.tuliang.org/blog/2014/06/11/file-download-or-view/" rel="nofollow" target="_blank" title=""&gt;Web 文件下载和查看&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在浏览器中点击一个文件链接，会被浏览器直接打开或者下载。其实浏览器的行为是可以人为控制的，最简单的方法是使用 HTML5 download Attribute。&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- will download as "expenses.pdf" --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/files/expenses.pdf"&lt;/span&gt; &lt;span class="na"&gt;download=&lt;/span&gt;&lt;span class="s"&gt;"expenses.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Download Your Expense Report&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方式虽然简单，但是兼容性不太好。查看&lt;a href="http://caniuse.com/download" rel="nofollow" target="_blank" title=""&gt;http://caniuse.com/download&lt;/a&gt;，我们可以发现 IE 和 Safari 全版本都不支持这个属性。&lt;/p&gt;

&lt;p&gt;根本原因在 HTTP Head 中，文件类型由&lt;code&gt;Content-Type&lt;/code&gt;控制。如果 pdf 文件是正确的&lt;code&gt;application/pdf&lt;/code&gt;，浏览器会打开 pdf，而不是去下载，jpg、png 这些文件类型同理。&lt;/p&gt;

&lt;p&gt;而下载是由&lt;code&gt;Content-Disposition&lt;/code&gt;来控制的，例如：&lt;code&gt;Content-Disposition: attachment; filename="fname.txt"&lt;/code&gt;。它的意思是将该文件作为附件，并且下载的文件名是 fname.txt.&lt;/p&gt;

&lt;p&gt;如果使用 S3，访问文件的 url，可以直接打开。
如果需要下载，使用 AWS 的 api 可以生成相应的 url：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;response_content_disposition: &lt;/span&gt;&lt;span class="s2"&gt;"attachment; filename=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相关文档：
&lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html" rel="nofollow" target="_blank"&gt;http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html&lt;/a&gt;
&lt;a href="http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html" rel="nofollow" target="_blank"&gt;http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Wed, 11 Jun 2014 15:12:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/19872</link>
      <guid>https://ruby-china.org/topics/19872</guid>
    </item>
    <item>
      <title>scss 文件名不写中间的 css 有什么好处吗？</title>
      <description>&lt;p&gt;刚刚在看 ruby-china 的代码&lt;a href="https://github.com/ruby-china/ruby-china" rel="nofollow" target="_blank"&gt;https://github.com/ruby-china/ruby-china&lt;/a&gt;
发现 CSS 都是&lt;code&gt;front.scss&lt;/code&gt;这种文件名，不是传统的&lt;code&gt;front.css.scss&lt;/code&gt;
这样写有什么好处？&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Thu, 20 Feb 2014 11:44:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/17368</link>
      <guid>https://ruby-china.org/topics/17368</guid>
    </item>
    <item>
      <title>可以不写 action？</title>
      <description>&lt;p&gt;routes &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'terms'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'product_pages#terms'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 views 里面有相应的模板&lt;/p&gt;

&lt;p&gt;在 ProductPagesController 中并没有写 terms action，就可以访问这个页面了。&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Thu, 16 Jan 2014 18:47:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/16836</link>
      <guid>https://ruby-china.org/topics/16836</guid>
    </item>
    <item>
      <title>Ruby 2.1: RGenGC</title>
      <description>&lt;p&gt;&lt;a href="http://tmm1.net/ruby21-rgengc/?utm_source=rubyweekly&amp;amp;utm_medium=email" rel="nofollow" target="_blank"&gt;http://tmm1.net/ruby21-rgengc/?utm_source=rubyweekly&amp;amp;utm_medium=email&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Sat, 04 Jan 2014 13:01:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/16617</link>
      <guid>https://ruby-china.org/topics/16617</guid>
    </item>
    <item>
      <title>有哪些将图片组合为 CSS 贴图定位的 gem?</title>
      <description>&lt;p&gt;如题，Asset Pipeline 合并了 js 和 css，有什么 gem 可以合并图片？&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Thu, 12 Dec 2013 16:24:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/16163</link>
      <guid>https://ruby-china.org/topics/16163</guid>
    </item>
    <item>
      <title>Heroku 改版了</title>
      <description>&lt;p&gt;&lt;a href="https://www.heroku.com/" rel="nofollow" target="_blank"&gt;https://www.heroku.com/&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Fri, 26 Jul 2013 12:11:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/12797</link>
      <guid>https://ruby-china.org/topics/12797</guid>
    </item>
    <item>
      <title>collection_select 如何给下面的 option 自定义属性</title>
      <description>&lt;p&gt;只能给 select 加属性，后来查文档发现在 model 写方法可以控制 option 的 text 和 value，一直没找到方法能给 option 加自定义属性。例如我要生成这样的 HTML&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"post[author_id]"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;D&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Heinemeier&lt;/span&gt; &lt;span class="no"&gt;Hansson&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/option&amp;gt;
  &amp;lt;option value="2" age="40"&amp;gt;D. Thomas&amp;lt;/o&lt;/span&gt;&lt;span class="n"&gt;ption&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"30"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Clark&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/option&amp;gt;
&amp;lt;/se&lt;/span&gt;&lt;span class="n"&gt;lect&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该怎么做呢？&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Tue, 14 May 2013 00:04:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/10968</link>
      <guid>https://ruby-china.org/topics/10968</guid>
    </item>
    <item>
      <title>关于 Git 的一些问题</title>
      <description>&lt;p&gt;开发环境和生产环境的配置文件不同，这个问题是很常见的。以前的解决方案是将它们设到.gitignore 中忽略。最近遇到一个项目，不仅是配置文件，项目中有许多文件都是有区别的，并且需要部署到多个生产环境，这个时候设忽略文件就不好控制了。这个时候 Git 有没有什么方法能够将文件纳入版本控制，并且不会互相覆盖。现在能想到的方法是开多个分支或者做各种补丁。&lt;/p&gt;

&lt;p&gt;另外一个问题，有一个 JS 的小项目 A，以这个为基础给多个不同的项目 X/Y/Z 之类，做模块或者是插件。如果项目 A 更新，项目 X/Y/Z 能够同步更新，最好能够带参数，比如说只更新 X 之类的。这个感觉很类似 rails 的 gem，但是这些项目不都是 rails 的可能是 PHP、JAVA。所以需要 git 来控制。这个问题现在能想到的方法也是开多个分支或者做各种补丁。&lt;/p&gt;

&lt;p&gt;这两个问题其实也可以说是同一个问题，区别就在于，一个允许覆盖，一个不允许覆盖。Git 有能不能设置一个权限列表，在某些项目中允许覆盖 a,b,c 这些文件，在另外一个项目就只允许覆盖 a,b 这些文件。&lt;/p&gt;

&lt;p&gt;大家遇到类似的问题是怎么解决的呢？&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Thu, 21 Mar 2013 14:49:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/9648</link>
      <guid>https://ruby-china.org/topics/9648</guid>
    </item>
  </channel>
</rss>
