├── chapter4 ├── 4.5.md ├── 4.2.md ├── 4.3.md ├── 4.4.md ├── Introduction.md └── 4.1.md ├── chapter2 ├── Introduction.md ├── 2-4.md ├── 2-3.md ├── 2-2.md ├── 2-1.md └── 2-5.md ├── chapter1 ├── Introduction.md ├── Install-ANTLR.md └── Run-ANTLR.md ├── images ├── basic-data-flow.png ├── array-parse-tree.png ├── translate-by-hand.png ├── visitor-interface.png ├── data-types-in-memory.png ├── parse-tree-listener.png ├── parse-tree-of-assign.png ├── subclass-of-rulenode.png ├── antlr-generated-files.png ├── expression-function-call.png └── parsetreewalker-call-sequence.png ├── code ├── install │ └── Hello.g4 └── array │ └── ArrayInit.g4 ├── Introduction.md ├── chapter3 ├── Introduction.md ├── 3.3.md ├── 3.2.md ├── 3.1.md └── 3.4.md ├── LICENSE ├── README.md └── SUMMARY.md /chapter4/4.5.md: -------------------------------------------------------------------------------- 1 | # 4.5 Cool Lexical Features 2 | 3 | -------------------------------------------------------------------------------- /chapter4/4.2.md: -------------------------------------------------------------------------------- 1 | # 4.2 Building a Calculator Using a Visitor 2 | 3 | -------------------------------------------------------------------------------- /chapter4/4.3.md: -------------------------------------------------------------------------------- 1 | # 4.3 Building a Translator Using a Visitor 2 | 3 | -------------------------------------------------------------------------------- /chapter2/Introduction.md: -------------------------------------------------------------------------------- 1 | # 第 2 章 纵观全局 2 | 3 | 本章介绍语言应用相关的重要过程、术语和数据结构。 -------------------------------------------------------------------------------- /chapter4/4.4.md: -------------------------------------------------------------------------------- 1 | # 4.4 Marking Things Happen During the Parse 2 | 3 | -------------------------------------------------------------------------------- /chapter1/Introduction.md: -------------------------------------------------------------------------------- 1 | # 第一章 初始 ANTLR 2 | 3 | 本章首先安装 ANTLR,然后用它编写一份简单的 "hello world" 语法。 -------------------------------------------------------------------------------- /images/basic-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/basic-data-flow.png -------------------------------------------------------------------------------- /images/array-parse-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/array-parse-tree.png -------------------------------------------------------------------------------- /images/translate-by-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/translate-by-hand.png -------------------------------------------------------------------------------- /images/visitor-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/visitor-interface.png -------------------------------------------------------------------------------- /images/data-types-in-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/data-types-in-memory.png -------------------------------------------------------------------------------- /images/parse-tree-listener.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/parse-tree-listener.png -------------------------------------------------------------------------------- /images/parse-tree-of-assign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/parse-tree-of-assign.png -------------------------------------------------------------------------------- /images/subclass-of-rulenode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/subclass-of-rulenode.png -------------------------------------------------------------------------------- /images/antlr-generated-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/antlr-generated-files.png -------------------------------------------------------------------------------- /images/expression-function-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/expression-function-call.png -------------------------------------------------------------------------------- /images/parsetreewalker-call-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kun-song/the-definitive-antlr4-reference/HEAD/images/parsetreewalker-call-sequence.png -------------------------------------------------------------------------------- /code/install/Hello.g4: -------------------------------------------------------------------------------- 1 | // 定义名为 Hello 的语法 2 | grammar Hello; 3 | 4 | // 匹配一个关键字 hello,和一个紧随其后的标识符 5 | r : 'hello' ID ; 6 | 7 | // 匹配小写字母组成的标识符 8 | ID : [a-z]+ ; 9 | 10 | // 忽略空格、Tab、换行以及 \r(windows) 11 | WS : [ \t\r\n]+ -> skip ; 12 | -------------------------------------------------------------------------------- /chapter4/Introduction.md: -------------------------------------------------------------------------------- 1 | # 4 A Quick Tour 2 | 3 | 本章通过 4 个主题快速介绍(不明白也别急,细节在后面章节会继续介绍) ANTLR 的不同特性: 4 | 5 | 1. 以简单的算术表达式语言为例,介绍: 6 | * 分析其语法 7 | * parser 的启动过程 8 | * 使用 `import` 拆分语法 9 | * parser 如何处理异常输入 10 | 2. 使用 visitor 模式构建计算器 11 | 3. 使用 listener 模式构建翻译器 12 | 4. 在 grammar 中嵌入 action 13 | * action 是指 **任意代码片段** 14 | * 一般用 vistor 或 listener 机制就足够了,但为了 **极端灵活性**,ANTLR 允许将任意代码 **注入** 自动生成的 parser 中,注入的代码将在 parsing 过程中执行 15 | * action 结合 semantic predicate,可以在运行时让部分语法消失,从而可处理 **多版本** 的语言 16 | -------------------------------------------------------------------------------- /Introduction.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | ANTLR 可自动生成编译器 **前端**,具体而言: 4 | 5 | * 根据用户定义的 **语法文件** 自动生成 **词法分析器** 和 **语法分析器** 6 | * 将 **输入文本** 处理为(可视化的)**语法分析树** 7 | * 处理 **结构化的文本** 和 **二进制文件** 8 | 9 | ANTLR 被广泛用于学术界、工业界: 10 | 11 | * twitter 搜索用 ANTLR 做语法分析; 12 | * Hadoop 中的 Hive 和 Pig 是基于 ANTLR 的; 13 | * Netbeas IDE 用 ANTLR 解析 C++; 14 | * Hibernate 用 ANTLR 处理 HQL 语言; 15 | 16 | 个人可以用 ANTLR 创建使用工具,例如: 17 | 18 | * 配置文件读取器 19 | * 遗留代码转换器 20 | * JSON 解析器 21 | * ... 22 | 23 | ANTLR 可根据语法自动创建语法分析器,大大降低了开发 **语言识别** 应用的难度。 24 | -------------------------------------------------------------------------------- /chapter1/Install-ANTLR.md: -------------------------------------------------------------------------------- 1 | # 1.1 安装 ANTLR 2 | 3 | 安装 ANTLR 本身仅需要下载其 jar 包([地址](http://www.antlr.org/download.html)),该 jar 包包含: 4 | 5 | * 运行 ANTLR 的工具 6 | * 编译、执行 ANTLR 生成的识别程序的所有依赖 7 | 8 | 官方网站提供了在 Mac Linux Windows 等系统中的设置步骤,例如在 Mac 中: 9 | 10 | ```Java 11 | OS X 12 | $ cd /usr/local/lib 13 | $ sudo curl -O https://www.antlr.org/download/antlr-4.7.1-complete.jar 14 | $ export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" 15 | $ alias antlr4='java -jar /usr/local/lib/antlr-4.7.1-complete.jar' 16 | $ alias grun='java org.antlr.v4.gui.TestRig' 17 | ``` 18 | 19 | 以后就可以直接使用 `antlr4` 和 `grun` 了。 20 | -------------------------------------------------------------------------------- /code/array/ArrayInit.g4: -------------------------------------------------------------------------------- 1 | /** 2 | * 1. Grammars always start with a grammar header. 3 | * 2. This grammar is called ArrayInit and must match the filename: ArrayInit.g4 4 | */ 5 | grammar ArrayInit; 6 | 7 | /** A rule called init that matches comma-separated values between {...}. */ 8 | init : '{' value (',' value)* '}' ; // must match at least one value 9 | 10 | /** A value can be either a nested array/struct or a simple integer (INT) */ 11 | value : init 12 | | INT 13 | ; 14 | 15 | // parser rules start with lowercase letters 16 | // lexer rules start with uppercase 17 | INT : [0-9]+ ; // Define token INT as one or more digits 18 | WS : [ \t\r\n]+ -> skip ; // Define whitespace rule, toss it out 19 | -------------------------------------------------------------------------------- /chapter3/Introduction.md: -------------------------------------------------------------------------------- 1 | # 3 A Starter ANTLR Project 2 | 3 | 本章为 C/Java 的一个很小语法子集构建 grammar,以识别 `{}` 中的整数,其中 `{}` 可以嵌套,例如 `{1, 2, }` 和 `{1, {2, 3}}`,形如 `{1, 2, 3}` 可以作为 `int` 数组的初始化器。 4 | 5 | 为这么简单的 syntax 构建 grammar 有什么用处呢,实际上该语法可以: 6 | 7 | * 将 `int` array 转换为 `byte` array(若数组中的整数都可以用 `byte` 存储) 8 | * 将 Java 的 `short` 数组的初始化器转换为 `String` 9 | 10 | 例如将: 11 | 12 | ```Java 13 | static short[] data = {1, 2, 3}; 14 | ``` 15 | 16 | 转换为: 17 | 18 | ```Java 19 | static short[] data = "\u0001\u0002\u0003"; 20 | ``` 21 | 22 | 因为 Java 中的 `char` 是 unsigned short,因此 1 可以用 `\u0001` 表示。 23 | 24 | 为什么要多此一举呢?因为 Java 的数组初始化器是通过显式赋值实现的,`static short[] data = {1, 2, 3};` 会被转换为: 25 | 26 | ```Java 27 | data[0] = 1; 28 | data[1] = 2; 29 | data[2] = 3; 30 | ``` 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Song Kun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter2/2-4.md: -------------------------------------------------------------------------------- 1 | # 2.4 使用语法分析树来构建语言类应用程序 2 | 3 | 要创建 language application,必须能够对 input phrase/subphrase 做合适的代码处理,最简单的方式是操作 parser 生成的 parse tree。 4 | 5 | 前面讲过: 6 | 7 | * lexer 处理输入的 character 流,解析 token,并将 token 传递给 parser 8 | * parser 根据 token 检查 sentence syntax 是否合法,并生成 parse tree 9 | 10 | 下面是 ANTLR 在识别语言、生成 parse tree 过程中涉及的数据结构 11 | 12 | ![img](../images/data-types-in-memory.png) 13 | 14 | * 连接 lexer 和 parser 的是 `TokenStream` 15 | * `RuleNode` 和 `TerminalNode` 是 `ParseTree` 的子类 16 | 17 | 为 **节省内存**,ANTLR 数据结构会尽量共享内存: 18 | 19 | * parse tree 叶子结点(`TernimalNode`)是 token 容器,仅持有指向 `TokenStream` 中对应 token 的“指针”; 20 | * `TokenStream` 中的 token 仅保存该 token 在 `CharStream` 中的起始字符索引; 21 | 22 | `ParseTree` 的子类 `RuleNode` 和 `TerminalNode` 分别是子树的根节点、叶子节点,ANTLR 会为 **每条规则** 生成一个 `RuleNode` 的子类(为更方便访问该树的元素): 23 | 24 | ![img](../images/subclass-of-rulenode.png) 25 | 26 | * `StatContext` `AssignContext` 和 `ExprContext` 是 `RuleNode` 的子类 27 | * 根节点叫 `XXXContext`,because they record everything we know about **the recognition of a phrase by a rule**. 28 | * 每个 context 对象记录它识别到的 phrase 的起始 token,并能访问该 phrase 的所有元素 29 | + `AssignContext` 提供 `ID()` 和 `expr()` 方法,用于访问 assign 语法识别到的 phrase 包含的元素 30 | 31 | 现在可以实现对 parse tree 的深度优先遍历了,遍历过程中可以对节点做各种操作,也可以用 ANTLR 自动生成的遍历机制,以避免重复手写。 32 | -------------------------------------------------------------------------------- /chapter3/3.3.md: -------------------------------------------------------------------------------- 1 | # 3.3 Integrating a Generated Parser into a Java Program 2 | 3 | 生成 lexer 和 parser 后,就可以开始将 parser 与 language application 集成了: 4 | 5 | ```Java 6 | // 导入 ANTLR runtime 7 | import org.antlr.v4.runtime.*; 8 | import org.antlr.v4.runtime.tree.*; 9 | 10 | public class Test { 11 | public static void main(String[] args) throws Exception { 12 | // 从标准输入创建 CharStream 13 | ANTLRInputStream input = new ANTLRInputStream(System.in); 14 | 15 | // 创建 lexer,处理输入的 CharStream 16 | ArrayInitLexer lexer = new ArrayInitLexer(input); 17 | 18 | // 创建 buffer of tokens,保存 lexer 生成的 token 19 | CommonTokenStream tokens = new CommonTokenStream(lexer); 20 | 21 | // 创建 parser,处理 token 缓冲区中的 token 22 | ArrayInitParser parser = new ArrayInitParser(tokens); 23 | 24 | // 从 init rule,开始语法分析 25 | ParseTree tree = parser.init(); 26 | 27 | // 输入 LISP 风格的 parse tree,效果类似 -trees 28 | System.out.println(tree.toStringTree(parser)); 29 | } 30 | } 31 | ``` 32 | 33 | 编译 parser 等和 `Test.java`: 34 | 35 | ```Java 36 | javac *.java 37 | ``` 38 | 39 | 运行 `Test`: 40 | 41 | ```Java 42 | array [master●] % java Test 43 | {1, 2, 3} 44 | (init { (value 1) , (value 2) , (value 3) }) 45 | ``` 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | * [简介](Introduction.md) 4 | * [1 Meet ANTLR](chapter1/Introduction.md) 5 | * [1.1 Installing ANTLR](chapter1/Install-ANTLR.md) 6 | * [1.2 Executing ANTLR and Testing Recognizers](chapter1/Run-ANTLR.md) 7 | * [2 The Big Picture](chapter2/Introduction.md) 8 | * [2.1 Let's Get Meta](chapter2/2-1.md) 9 | * [2.2 Implementing Parsers](chapter2/2-2.md) 10 | * [2.3 You Can't Put Too Much Water into a Nuclear Reactor](chapter2/2-3.md) 11 | * [2.4 Building Language Applications Using Parse Trees](chapter2/2-4.md) 12 | * [2.5 Parse-Tree Listeners and Visitors](chapter2/2-5.md) 13 | * [3 A Starter ANTLR Project](chapter3/Introduction.md) 14 | * [3.1 The ANTLR Tool, Runtime, and Generated Code](chapter3/3.1.md) 15 | * [3.2 Testing the Generated Parser](chapter3/3.2.md) 16 | * [3.3 Integrating a Generated Parser into a Java Program](chapter3/3.3.md) 17 | * [3.4 Building a Language Application](chapter3/3.4.md) 18 | * [4 A Quick Tour](chapter4/Introduction.md) 19 | * [4.1 Matching an Arithmetic Expression Language](chapter4/4.1.md) 20 | * [4.2 Building a Calculator Using a Visitor](chapter4/4.2.md) 21 | * [4.3 Building a Translator Using a Visitor](chapter4/4.3.md) 22 | * [4.4 Marking Things Happen During the Parse](chapter4/4.4.md) 23 | * [4.5 Cool Lexical Features](chapter4/4.5.md) -------------------------------------------------------------------------------- /chapter3/3.2.md: -------------------------------------------------------------------------------- 1 | # 3.2 Testing the Generated Parser 2 | 3 | 运行 ANTLR tool 后,下一步编译 ANTLR 自动生成的 Java 代码: 4 | 5 | ```Java 6 | javac *.java 7 | ``` 8 | 9 | 然后使用 `grun` 来测试一下: 10 | 11 | ```Java 12 | array [master●●] % grun ArrayInit init -tokens 13 | {1, 22, 333} 14 | [@0,0:0='{',<'{'>,1:0] 15 | [@1,1:1='1',,1:1] 16 | [@2,2:2=',',<','>,1:2] 17 | [@3,4:5='22',,1:4] 18 | [@4,6:6=',',<','>,1:6] 19 | [@5,8:10='333',,1:8] 20 | [@6,11:11='}',<'}'>,1:11] 21 | [@7,13:12='',,2:0] 22 | ``` 23 | 24 | 上面例子中,每一行都代表一个 token,且包含该 token 的 **全部信息**,例如倒数第 3 行 `[@5,8:10='333',,1:8]` 25 | 26 | * `@5` 是 token 的序号,表明该行代表的 token 是第 5 个(`{1, 22, 333, }` 中 `333` 是第 5 个 token); 27 | * `8:10` 该 token 由 8 9 10 位置的 3 个字符组成; 28 | * `='333'` 该 token 包含的文本是 `333`; 29 | * `` 该 token 类型为 `INT`; 30 | * `1:8` 该 token 位于输入文本的第 1 行(从 1 开始计数)的第 8 个字符(从 0 开始计数); 31 | 32 | 还可以用 `-tree` 生成 LISP 风格的 parse tree: 33 | 34 | ```Java 35 | array [master●●] % grun ArrayInit init -tree 36 | {1, 22, 333} 37 | (init { (value 1) , (value 22) , (value 333) }) 38 | ``` 39 | 40 | 还可以用 `-gui` 生成可视化的 parse tree: 41 | 42 | ```Java 43 | array [master●●] % grun ArrayInit init -gui 44 | {1, {2, 3}, 4} 45 | ``` 46 | 47 | ![img](../image/array-parse-tree.png) 48 | 49 | * 根节点 `init` 和 `value` 是 parser rule 50 | * 叶子结点是 lexer rule 51 | 52 | 可以用 parser rule 来表示该 rule 识别到的 **所有元素**,比较方便。 -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [简介](Introduction.md) 5 | * [1 Meet ANTLR](chapter1/Introduction.md) 6 | * [1.1 Installing ANTLR](chapter1/Install-ANTLR.md) 7 | * [1.2 Executing ANTLR and Testing Recognizers](chapter1/Run-ANTLR.md) 8 | * [2 The Big Picture](chapter2/Introduction.md) 9 | * [2.1 Let's Get Meta](chapter2/2-1.md) 10 | * [2.2 Implementing Parsers](chapter2/2-2.md) 11 | * [2.3 You Can't Put Too Much Water into a Nuclear Reactor](chapter2/2-3.md) 12 | * [2.4 Building Language Applications Using Parse Trees](chapter2/2-4.md) 13 | * [2.5 Parse-Tree Listeners and Visitors](chapter2/2-5.md) 14 | * [3 A Starter ANTLR Project](chapter3/Introduction.md) 15 | * [3.1 The ANTLR Tool, Runtime, and Generated Code](chapter3/3.1.md) 16 | * [3.2 Testing the Generated Parser](chapter3/3.2.md) 17 | * [3.3 Integrating a Generated Parser into a Java Program](chapter3/3.3.md) 18 | * [3.4 Building a Language Application](chapter3/3.4.md) 19 | * [4 A Quick Tour](chapter4/Introduction.md) 20 | * [4.1 Matching an Arithmetic Expression Language](chapter4/4.1.md) 21 | * [4.2 Building a Calculator Using a Visitor](chapter4/4.2.md) 22 | * [4.3 Building a Translator Using a Visitor](chapter4/4.3.md) 23 | * [4.4 Marking Things Happen During the Parse](chapter4/4.4.md) 24 | * [4.5 Cool Lexical Features](chapter4/4.5.md) 25 | 26 | -------------------------------------------------------------------------------- /chapter2/2-3.md: -------------------------------------------------------------------------------- 1 | # 2.3 你再也不能往核反应堆多加水了 2 | 3 | phrase 或 sentence 都可能匹配多个 grammatical structure,此时称它们有歧义。 4 | 5 | #### 最明显的歧义 6 | 7 | ```Java 8 | stat: ID '=' expr ';' 9 | | ID '=' expr ';' 10 | ; 11 | ``` 12 | * 重复的 alternative 13 | 14 | #### 更隐晦的歧义 15 | 16 | ```Java 17 | stat: expr ';' // expression statement 18 | | ID '(' ')' ';' // function call statement 19 | ; 20 | expr: ID '(' ')' ';' 21 | | INT 22 | ; 23 | ``` 24 | 25 | stat 规则有两个 alternative,第一个是表达式规则,第二个是函数调用规则,而文本 `f();` 既可以匹配表达式,又可以匹配函数调用,有两种不同解释: 26 | 27 | ![img](../images/expression-function-call.png) 28 | 29 | * 左侧 parse tree 是 `f();` 匹配第一个 alternative 的场景; 30 | * 右侧 parse tree 是 `f();` 匹配第二个 alternative 的场景; 31 | 32 | 若存在歧义,ANTLR 会选择 **第一个 alternative**,上面的例子中,parser 会将 `f();` 匹配为表达式。 33 | 34 | #### lexer 中的歧义 35 | 36 | 歧义不仅会发生在 parser 中,也会发生在 lexer 中,ANTLR 通过选择 gammar 中 **最靠前** 的 lexer rule 来解决 lexer 歧义。 37 | 38 | 编程语言中的 keyword rule 和 identifier rule 通常会存在歧义,例如: 39 | 40 | ```Java 41 | BEGIN : 'begin' ; 42 | ID : [a-z]+ ; 43 | ``` 44 | 45 | 文本 `begin` 既可以匹配 `BEGIN` 又可以匹配 `ID`,因此存在歧义。 46 | 47 | 但文本 `beginner` 却只会匹配 `ID`,而不会匹配成 `BEGIN` 后面跟着 `ID`,因为 lexer 会为每个 token 匹配 **longest** string possible。 48 | 49 | #### 无法避免的歧义 50 | 51 | 部分语法天生存在歧义,无法修正,例如表达式 `1 + 2 * 3`,既可以从左到右计算(Smalltalk),又可以按优先级计算。 52 | 53 | #### 通过 context 信息解决歧义 54 | 55 | 部分歧义可用 context information 解决,例如 `i * j`,若 `i` 是变量,则其为表达式;若 `i` 为类型,则其为指针声明。 56 | -------------------------------------------------------------------------------- /chapter4/4.1.md: -------------------------------------------------------------------------------- 1 | # 4.1 Matching an Arithmetic Expression Language 2 | 3 | 本节的算术表达式仅考虑: 4 | 5 | * 加减乘除 4 个操作符 6 | * `()` 包裹的表达式 7 | * 整数 8 | * 变量 9 | 10 | 下面的例子展示了所有该算术表达式的所有特性: 11 | 12 | ```Java 13 | 193 14 | a = 5 15 | b = 6 16 | a + b * 2 17 | (1 + 2) * 3 18 | ``` 19 | 20 | 我们的算术表达式语言写成的程序有一系列 statement 组成,statement 以 newline 结束,statement 可以是 expression, assignment, 或 blank line,下面是其语法: 21 | 22 | ```java 23 | grammar Expr; 24 | 25 | // the start rule, begin parsing here 26 | prog: stat+ ; 27 | 28 | stat: expr NEWLINE 29 | | ID '=' expr NEWLINE 30 | | NEWLINE 31 | ; 32 | 33 | expr: expr ('*' | '/') expr 34 | | expr ('+' | '-') expr 35 | | INT 36 | | ID 37 | | '(' expr ')' 38 | ; 39 | 40 | ID : [a-zA-Z]+ ; 41 | INT : [0-9]+ ; 42 | NEWLINE: '\r' ? '\n' ; 43 | WS : [ \t]+ -> skip ; 44 | ``` 45 | 46 | * grammar 是 rule 集合,rule 用来描述语言的 syntax,rule 分两类: 47 | + parser rule 48 | - 描述 syntactic structure 49 | - 小写字母开头,如 `prog` `stat` `expr` 50 |  + lexer rule 51 | - 描述 vocabulary symbols(token) 52 | - 大写字母开头,如 `ID` `INT` 53 | * subrule: `()` 54 | * alternative rule: `|` 55 | 56 | ANTLR v4 版重要特性为处理 left-recursive rule 的能力,left-recursive rule 是指在其 alternative rule 的 **开头** 递归调用自己的 rule,如 `expr`。传统的 top-down parser 处理左递归规则比较繁琐,ANTLR v4 非常容易。 57 | 58 | 注意 `-> skip` 指令,该指令告诉 lexer 匹配然后 **丢弃** whitespace,因为每个输入字符都必须被 **至少一个** lexer rule 匹配,因此只能先匹配,后丢弃。 59 | 60 | 下面运行 ANTLR tool,测试下该语法: 61 | 62 | ```Java 63 | >antlr4 Expr.g4 64 | >javac *.java 65 | >grun Expr prog -gui 66 | (1 + 2 + 3 * 2 / 2) 67 | EOF 68 | ``` 69 | -------------------------------------------------------------------------------- /chapter3/3.1.md: -------------------------------------------------------------------------------- 1 | # 3.1 The ANTLR Tool, Runtime, and Generated Code 2 | 3 | ANTLR 官网提供的 jar 由两部分组成: 4 | 5 | * ANTLR tool `org.antlr.v4.Tool` 6 |  + 说 run ANTLR on a grammar 时,指的是 ANTLR tool, 7 | * ANTLR runtime API 8 |  + 运行 ANTLR 生成的 lexer parser 时,需要 ANTLR runtime 的支持 9 |   10 | 因此使用 ANTLR 分为 3 步: 11 | 12 | 1. run **ANTLR tool** on a grammar 13 | 2. then compile the generated lexer and parser against **ANTLR runtime** 14 | 3. finally, run lexer/parser with **ANTLR runtime** 15 | 16 | 实现 language application 首先要写出它的语法: 17 | 18 | ```Java 19 | // ArrayInit.g4 20 | /** 21 | * 1. Grammars always start with a grammar header. 22 | * 2. This grammar is called ArrayInit and must match the filename: ArrayInit.g4 23 | */ 24 | grammar ArrayInit; 25 | 26 | /** A rule called init that matches comma-separated values between {...}. */ 27 | init : '{' value (',' value)* '}' ; // must match at least one value 28 | 29 | /** A value can be either a nested array/struct or a simple integer (INT) */ 30 | value : init 31 | | INT 32 | ; 33 | 34 | // parser rules start with lowercase letters 35 | // lexer rules start with uppercase 36 | INT : [0-9]+ ; // Define token INT as one or more digits 37 | WS : [ \t\r\n]+ -> skip ; // Define whitespace rule, toss it out 38 | ``` 39 | 40 | * 语法文件后缀 `.g4`,且语法的名字必须与文件名一致; 41 | * parser rule:小写字母 42 | * lexer rule:大写字母 43 | 44 | 然后运行 ANTLR tool 以生成 lexer 和 parser: 45 | 46 | ```Java 47 | antlr4 ArrayInit.g4 48 | ``` 49 | 50 | ANTLR 会为 `ArrayInit.g4` 语法生成很多文件(若没有 ANTLR,这些文件都是需要手写的): 51 | 52 | ![img](../images/antlr-generated-files.png) 53 | 54 | * `ArrayInitParser.java` 55 | + 语法分析器 56 | + **每条规则**,`ArrayInitParser.java` 都有对应的 **方法** 57 | * `ArrayInitLexer.java` 58 | + 词法分析器 59 | + 根据词法规则 `INT` `WS` 和字面值 '{' '}' ',' 生成 60 | * `ArrayInit.tokens` 61 | + ANTLR 为每个 token 指定一个 **数字形式的类型** 62 | + 该文件存储 token 与其类型的 **对应关系** 63 | * `ArrayInitListener.java` 和 `ArrayInitBaseListener.java` 64 | + 遍历 65 | 66 | >正则表达式无法完成该任务,因为它无法识别 **嵌套** 的 '{}'。 -------------------------------------------------------------------------------- /chapter2/2-2.md: -------------------------------------------------------------------------------- 1 | # 2.2 实现一个语法分析器 2 | 3 | ## recursive-descent parser 4 | 5 | ANTLR 根据 grammar rule 生成 recursive-descent parser: 6 | 7 | * recursive-descent parser 仅仅是一些 recursive method 的集合; 8 | * 每一条 rule 对应一个 recursive method; 9 | 10 | 首先被调用的 rule 会变成 parse tree 的根结点,descent 是指解析从 parse tree 的根节点开始,一路向下,直到叶子结点,这种解析方式为 top-down parsing,而 recursive-descnet parser 仅仅是其中一种而已。 11 | 12 | 下面是 ANTLR 为 `assign` 规则(`assign : ID '=' expr ';'`)生成的 recursive method: 13 | 14 | ```Java 15 | /** 16 | * assign 方法是根据 assign 规则生成的 17 | */ 18 | void assign() { 19 | match(ID); // compare ID to current input symbol then consume 20 | match('='); 21 | expr(); // match an expression by calling expr() 22 | match(';'); 23 | } 24 | ``` 25 | 26 | * `assign()` 仅仅验证所需的 token 全部存在,且顺序正确; 27 | 28 | `assign` 规则生成的 parse tree 如下: 29 | 30 | ![img](../images/parse-tree-of-assign.png) 31 | 32 | recursive-descent parser 的神奇之处在于: 33 | 34 | * 依次调用 `stat()` `assign()` 和 `expr()` 形成的 **路线** 对应 parse tree 的中间结点; 35 | * 调用 `match()` 对应 parse tree 的叶子结点; 36 | 37 | ## alternatives 38 | 39 | assign 规则只有一个 alternative,而下面的 stat 规则有多个 alternatives: 40 | 41 | ```Java 42 | stat: assign 43 | | ifstat 44 | | whilestat 45 | ... 46 | ``` 47 | 48 | stat 规则生成的函数类似 `switch` 语句: 49 | 50 | ```Java 51 | void stat() { 52 | switch (<>) { 53 | case ID : assign(); break; 54 | case IF : ifstat(); break; 55 | case WHILE : whilestat(); break; 56 | ... 57 | default: ... 58 | } 59 | } 60 | ``` 61 | 62 | ## parsing decision 63 | 64 | `stat()` 方法必须通过检查 **the next input token** 来做 parsing decision/prediction,parsing decision 决定使用哪个 alternative。 65 | 66 | 例如 `stat()` 中,看到 `WHILE` 关键字就选 stat 规则第 3 个 alternative,从而执行 `whilestat()` 方法。 67 | 68 | >lookahead token 69 | > 70 | >lookahead token 即 next input token,是指 parser 在实际 match 和 comsume 之前 sniff(嗅探)到的 **任意 token**。 71 | 72 | 有时 parser 需要 **很多** lookahead token 才能决定选用哪个 alternative,最夸张的情况下,甚至需要考虑从当前位置直到文件结束的所有 token! 73 | 74 | 幸运的是,ANTLR 会自动选择 **足够数量** 的 lookahead token,以做出正确的 parsing decision。 75 | -------------------------------------------------------------------------------- /chapter1/Run-ANTLR.md: -------------------------------------------------------------------------------- 1 | # 1.2 运行 ANTLR 并测试识别程序 2 | 3 | 下面是识别类似 hello world 和 hello xxx 词组的语法定义: 4 | 5 | ```Java 6 | // 定义名为 Hello 的语法 7 | grammar Hello; 8 | 9 | // 匹配一个关键字 hello,和一个紧随其后的标识符 10 | r : 'hello' ID ; 11 | 12 | // 匹配小写字母组成的标识符 13 | ID : [a-z]+ ; 14 | 15 | // 忽略空格、Tab、换行以及 \r(windows) 16 | WS : [ \t\r\n]+ -> skip ; 17 | ``` 18 | 19 | 将该语法保存在 `install/Hello.g4` 文件中,并运行 `antlr4 Hello.g4` 为其生成词法分析器和语法分析器: 20 | 21 | ```Java 22 | -rw-r--r-- 1 satansk staff 308B 4 1 23:12 Hello.interp 23 | -rw-r--r-- 1 satansk staff 27B 4 1 23:12 Hello.tokens 24 | -rw-r--r-- 1 satansk staff 1.3K 4 1 23:12 HelloBaseListener.java 25 | -rw-r--r-- 1 satansk staff 1.0K 4 1 23:12 HelloLexer.interp 26 | -rw-r--r-- 1 satansk staff 3.2K 4 1 23:12 HelloLexer.java 27 | -rw-r--r-- 1 satansk staff 27B 4 1 23:12 HelloLexer.tokens 28 | -rw-r--r-- 1 satansk staff 536B 4 1 23:12 HelloListener.java 29 | -rw-r--r-- 1 satansk staff 3.5K 4 1 23:12 HelloParser.java 30 | ``` 31 | 32 | 上面生成的词法分析器和语法分析器已经是 **可运行** 的了,但还缺少一个 `main` 函数还触发识别动作。 33 | 34 | ANTLR 提供名为 TestRig 的调试工具,TestRig 通过 **反射机制** 调用生成的分析器,1.1 节已经为其定义别名 `grun`。 35 | 36 | TestRig 接受一个 **语法名** 与一个 **起始规则名** 作为参数,另外跟一些指定 **输出内容** 的参数: 37 | 38 | ```Java 39 | ± % grun Hello r -tokens 40 | 41 | hello xxx 42 | 43 | eof 44 | [@0,0:4='hello',<'hello'>,1:0] 45 | [@1,6:8='xxx',,1:6] 46 | [@2,11:13='eof',,3:0] 47 | [@3,15:14='',,4:0] 48 | ``` 49 | 50 | * 使用 Hello 语法 + r 规则启动 TestRig 51 | * hello xxx 是要被识别的语句 52 | * 结束符需要手动输入,UNIX 为 Ctrl + D,Windows 为 Ctrl + Z 53 | 54 | 因为使用了 `-tokens` 参数,所以 TestRig 会打印全部 **词法符号** 的列表: 55 | 56 | ```Java 57 | [@0,0:4='hello',<'hello'>,1:0] 58 | [@1,6:8='xxx',,1:6] 59 | [@2,11:13='eof',,3:0] 60 | [@3,15:14='',,4:0] 61 | ``` 62 | 63 | 还可以指定其他打印风格,例如 LISP 风格、可视化风格等,下面是常用的选项: 64 | 65 | * `-tokens` 66 | + 打印词法符号流 67 | * `-tree` 68 | + 以 LISP 风格打印 **语法分析树** 69 | * `-gui` 70 | + 以可视化方式打印 **语法分析树** 71 | * `-ps file.ps` 72 | + 以 PostScript 格式生成可视化的 **语法分析树**,并将其存储在 `file.ps` 文件中 73 | * `-trace` 74 | + 打印规则名字,以及进入、离开该规则时的词法符号 75 | * `-diagnostics` 76 | + 输出调试信息 77 | * `-SLL` 78 | + 更快,但稍弱的解析策略 79 | -------------------------------------------------------------------------------- /chapter2/2-1.md: -------------------------------------------------------------------------------- 1 | # 2.1 从 ANTLR 元语言开始 2 | 3 | * language 是 valid sentences 的集合 4 | + sentence 由 phrase(词组)组成 5 | - phrase 由 subphrase 和 vocabulary symbol(词汇符号)组成。 6 | 7 | 因此要实现一门语言,需要有一个程序,它可以读取 sentences,并正确处理其解析到的 phrase 和 symbol,这样的程序可分为两类: 8 | 9 | 1. Interpreter: the application that **computes** or **executes** sentences 10 | * 计算器 11 | * 配置文件读取器 12 | * Python 解释器 13 | 2. Translator: **converting** sentences from one language to another 14 | * Java to C# 转换器 15 | * C 编译器 16 | 17 | 要达到各自目的,interpreter 和 translator 必须能 **识别** 出特定语言所有的 valid sentence, phrase 和 subphrase。 18 | 19 | 识别一个 phrase 是指可以将它从众多 component 中辨识出,并能将它与其他 phrase 区分出来。例如将 `sp = 100;` 识别到意味着: 20 | 21 | 1. 我们知道 `sp` 是赋值对象,而 `100` 是要赋的值(与识别主语、谓语、宾语类似); 22 | 2. language appliation 能将它与其他语句(例如 `import`)**区分** 开; 23 | 24 | 识别语言的程序被称为 parser(语法分析器)或 syntax analyzer(句法分析器): 25 | 26 | * syntax(句法)是指约束语言各组成部分关系的 **规则**,本书用 ANTLR grammar 来指定语言的句法; 27 | * grammar(语法)只是 **规则** 的集合 28 | + 每条规则表达一个 phrase 的结构(那岂不是 grammar 就是 syntax 的集合?) 29 | + ANTLR grammar 遵循 ANLTR meta-language 语法 30 | 31 | ANTLR 会将 grammar 转换为 parser,与专家手写的 parser 不相上下。 32 | 33 | ## 词法分析 -> 语法分析 34 | 35 | 我们读一个 sentence 时不会一个 char 一个 char 地读,而是将 sentence 视为 word 组成的流,整个阅读理解分为两步: 36 | 37 | 1. 通过潜意识将 char 序列聚合为 word,并在大脑查找每个 word 的意义; 38 | 2. 之后识别整个 sentence 的 grammatical structure; 39 | 40 | languange application **parseing** sentences 的过程与之类似,可以分为两个相似,但独立的阶段: 41 | 42 | 1. lexical analysis(词法分析)or tokenizing(词法符号化) 43 | * 将 chars 聚集为 word/symbol/token 44 | * 执行词法分析的应用为 lexer(词法分析器) 45 | + 因为语法分析器并 **不关心** 具体 token,而只关心 token type,因此 lexer 将相关 tokens 按 token class(token type) 分类 46 | + token 包含两个信息: 47 | - the token type(确定词法结构) 48 | - the text matched for that token by lexer 49 | 2. actual parsing 50 | * 输入 tokens,输出 sentence structure 51 | * ANTLR 生成的 parser 会构建 parse tree(语法分析树) 52 | 53 | 下图是语言识别器中的基本数据流动: 54 | 55 | ![img](../images/basic-data-flow.png) 56 | 57 | * chars -> token -> parse tree 58 | 59 | ## 语法分析树 60 | 61 | parse tree 各结点: 62 | 63 | * 内部结点为 phrase name 64 | * 根结点为最抽象的 phrase name(本例中为 `stat`) 65 | * 叶子结点为输入的 token 66 | 67 | parse tree 可被应用灵活处理,例如可以多次遍历 parse tree,而非多次 parsing,以提升效率(parse tree 包含了所有解析的过程信息)。 68 | 69 | 要使用、调试 ANTLR 语法,必须理解 ANTLR 是如何将 **语法规则** 转换成 **人类可读的代码** 的,因此下节介绍 parsing 的工作原理。 70 | -------------------------------------------------------------------------------- /chapter3/3.4.md: -------------------------------------------------------------------------------- 1 | # 3.4 Building a Language Application 2 | 3 | **识别** 到数组初始化语句后,下一步是 **转换** 初始化语句,将 `short` 值转换为对应的十六进制表示,例如将 `{99, 3, 451}` 转换为 `\u0063\u0003\u01c3`。 4 | 5 | 要转换 token,首先要从 parse tree 中提取 token,可以用默认的 parse-tree walker,当该 walker 执行深度优先遍历时,会触发一系列回调函数,我们只要继承 `XXXBaseListener` 并 override 特定方法即可。 6 | 7 | 先看几个手动转换的例子: 8 | 9 | ![img](../images/translate-by-hand.png) 10 | 11 | * 将 `{` 转换为 `"` 12 | * 将 `}` 转换为 `"` 13 | * 将 `short` 转换为 4 位十六进制表示的字符串,并前缀 `\u` 14 | 15 | 当 walker 进入、离开每个 phrase 时,都会在 `Listener` 中触发指定方法,可以在 `enterXXX` 方法中执行转换: 16 | 17 | ```Java 18 | /** 19 | * 将 {1, 2, 3} 之类的 short 数组转换为 "\u0001\u0002\u0003` 20 | */ 21 | public class ShortToString extends ArrayInitBaseListener { 22 | 23 | // { -> " 24 | @Override 25 | public void enterInit(ArrayInitParser.InitContext ctx) { 26 | System.out.print('"'); 27 | } 28 | 29 | // } -> " 30 | @Override 31 | public void exitInit(ArrayInitParser.InitContext ctx) { 32 | System.out.print('"'); 33 | } 34 | 35 | @Override 36 | public void enterValue(ArrayInitParser.ValueContext ctx) { 37 | // Assumes no nested array initializers 38 | int value = Integer.valueOf(ctx.INT().getText()); 39 | System.out.printf("\\u%04x", value); 40 | } 41 | } 42 | ``` 43 | 44 | * 不需要 override `BaseListener` 中的所有方法,按需覆盖; 45 | * `ctx.INT()` 从上下文对象中获取 `INT` token 的值; 46 | 47 | 实现转换: 48 | 49 | ```Java 50 | // 导入 ANTLR runtime 51 | import org.antlr.v4.runtime.*; 52 | import org.antlr.v4.runtime.tree.*; 53 | 54 | public class Translate { 55 | public static void main(String[] args) throws Exception { 56 | // 从标准输入创建 CharStream 57 | ANTLRInputStream input = new ANTLRInputStream(System.in); 58 | 59 | // 创建 lexer,处理输入的 CharStream 60 | ArrayInitLexer lexer = new ArrayInitLexer(input); 61 | 62 | // 创建 buffer of tokens,存储 lexer 生成的 token 63 | CommonTokenStream tokens = new CommonTokenStream(lexer); 64 | 65 | // 创建 parser,处理 token 缓冲区中的 token 66 | ArrayInitParser parser = new ArrayInitParser(tokens); 67 | 68 | // 从 init rule,开始语法分析 69 | ParseTree tree = parser.init(); 70 | 71 | /********************* Added ****************/ 72 | // 创建 walker 73 | ParseTreeWalker walker = new ParseTreeWalker(); 74 | 75 | // 遍历 parse tree,触发回调 76 | walker.walk(new ShortToString(), tree); 77 | 78 | System.out.println(); 79 | } 80 | } 81 | ``` 82 | 83 | * 相比 `Test.java`,只添加了最后 3 行! 84 | 85 | 编译、运行 `Translate`: 86 | 87 | ```Java 88 | javac *.java 89 | java Translate.java 90 | 91 | >{1, 2, 3} 92 | >EOF 93 | >"\u0001\u0002\u0003" 94 | ``` 95 | 96 | 通过 listener 机制,grammar 和 language application 完全解耦。 97 | -------------------------------------------------------------------------------- /chapter2/2-5.md: -------------------------------------------------------------------------------- 1 | # 2.5 Parse-Tree Listeners and Visitors 2 | 3 | ANTLR 运行时支持 2 种 parse tree 遍历方式: 4 | 5 | * 默认 built-in tree walker 6 | + 自动生成 parse-tree listener 接口,响应 walker 触发的事件 7 | * 使用 visitor 设计模式的 tree walker 8 | 9 | ## Parse-Tree Listeners 10 | 11 | * `ParseTreeWalker` 12 | * `ParseTreeListener` 13 | 14 | ANTLR 为 **each grammar** 生成 `ParseTreeListener` 子类,对 grammar 中的每条规则,生成对应的 `enter` 和 `exit` 方法: 15 | 16 | ![img](../images/parse-tree-listener.png) 17 | 18 | * ANTLR 为 assign 规则生成 `enterAssign()` 和 `exitAssign()` 方法 19 | * `AssignContext` 对象作为参数传递给 `enterAssign()` 和 `exitAssign()` 方法 20 | 21 | 在上面的深度优先遍历中,`ParseTreeWalker` 对监听器方法的完整调用如下: 22 | 23 | ![img](../images/parsetreewalker-call-sequence.png) 24 | 25 | * 根节点:`enterXXX` 和 `exitXXX` 26 | * 叶子结点:`visitTerminal` 27 | 28 | listener 方式优点: 29 | 30 | * 无需手动编写 **遍历** 代码; 31 | * 全自动 32 | 33 | ## Parse-Tree Visitors 34 | 35 | 有时需要控制 **遍历本身**,通过 **函数调用** 来访问子节点(listener 是通过 **回调函数**),此时可以通过 `-visitor` 生成 visitor 接口: 36 | 37 | * 每个 grammar 对应一个 visitor interface 38 | * **一条规则** 对应 visitor interface 中的一个 `visit` 方法 39 | 40 | 下面是 assign 语法生成的 visitor 访问模式: 41 | 42 | ![img](../images/visitor-interface.png) 43 | 44 | * 深度优先 45 | * 当遇到根节点时,ANTLR 会调用 `visitStat` 函数 46 | 47 | visitor 接口使用如下: 48 | 49 | ```Java 50 | ParseTree tree = ...; // parse tree is the result of parsing 51 | MyVisitor v = new MyVisitor(); 52 | v.visit(tree); 53 | ``` 54 | 55 | * 必须手动调用 `visit` 函数 56 | 57 | ## 术语 58 | 59 | * Language 60 | + A language is a set of **valid sentences** 61 | + Sentences are composed of **phrases**, which are composed of subphrases, and so on 62 | * Grammar 63 | + A grammar formally defines the **syntax rules** of a language 64 | + Each rule in a grammar expresses the **structure** of a subphrase 65 | * Syntax tree/parse tree 66 | + This represents the structure of the sentence where each subtree root gives an abstract name to the elements beneath it. 67 | + The **subtree roots** correspond to **grammar rule** names. 68 | + The **leaves** of the tree are symbols or **tokens**s of the sentence. 69 | * Token 70 | + A token is a **vocabulary symbol** in a language; 71 | + these can represent a category of symbols such as "identifier" or can represent s single operator or keyword. 72 | * Lexer or tokenizer 73 | + This breaks up an **input character stream** into **tokens**; 74 | + A lexer performs lexical analysis. 75 | * Parser 76 | + A parser checks sentences for membership in a specific language by checking the sentence's structure against the rules of a grammar. 77 | + ANTLR generates top-down parsers called **ALL(*)** that can **use all remaining input symbols to make decisions**. 78 | + Top-down parser are **goal-oriented** and start matching at the rule associated with the coarsest, such as *program* or *inputFile*. 79 | * Recursive-descent parser 80 | + This is a specific kind of top-down parser implemented with **a function** for **each rule** in the grammar. 81 | * Lookahead 82 | + Parsers use lookahead to **make decisions** by comparing the symbols that begin each alternatives. 83 | --------------------------------------------------------------------------------