环境搭建与第一个程序
环境准备
- 安装 Java: ANTLR4 基于 Java,需要先安装 Java 运行时环境 (JRE) 或开发工具包 (JDK)。
- 获取 ANTLR4:
- 命令行方式: 从官网下载 ANTLR4 的完整 Jar 包。为了方便,可以在系统里配置好
antlr4和grun命令别名。 - IDE 插件方式 (推荐): 在 IntelliJ IDEA 或 VSCode 中搜索并安装 ANTLR4 插件。插件能提供语法高亮、代码生成、语法图预览等功能,对初学者非常友好。
- 命令行方式: 从官网下载 ANTLR4 的完整 Jar 包。为了方便,可以在系统里配置好
编写第一个语法文件:Hello.g4
创建一个名为 Hello.g4 的文件,这是的语法定义文件。输入以下内容:
// Define a grammar called Hello |
逐行解释:
- ①
grammar Hello;:定义语法的名字为Hello,文件名必须与此一致(Hello.g4)。 - ②
r : 'hello' ID ;:语法规则 (parser rule),描述语言的语法结构。它表示想匹配一个'hello'字符串,后面跟着一个标识符 (ID)。 - ③
ID : [a-z]+ ;:词法规则 (lexer rule),用正则表达式定义ID是什么。[a-z]+表示一个或多个小写字母。注意,词法规则名通常以大写字母开头。 - ④
WS : [ \t\r\n]+ -> skip ;:另一个词法规则,定义空白字符,并用-> skip指令告诉 ANTLR 忽略它。
生成解析器并测试
- 生成代码:在 IDEA 中,右键点击
Hello.g4文件,选择Generate ANTLR Recognizer。ANTLR4 会为生成一套 Java 代码(词法分析器、语法分析器等)。 - 测试语法:在
Hello.g4编辑器中,右键点击规则r的那一行,选择Test Rule r。在弹出窗口的Input框中输入hello world,会看到生成的语法分析树,直观展示了解析结果。
ANTLR4 的核心语法
掌握核心语法,就能定义更复杂的语言了。所有规则都写在 .g4 文件中。
语法文件结构
一个典型的语法文件结构如下:
/** 可选的 Javadoc 风格注释 */ |
词法规则 (Lexer Rules)
词法规则负责将字符流分解为词法符号 (Token),类似于“单词”。
- 规则名必须以大写字母开头。
- 规则体使用正则表达式风格定义。
- 示例:
NUMBER : [0-9]+ ; // 匹配数字
STRING : '"' .*? '"' ; // 匹配双引号字符串
COMMENT : '//' ~[\r\n]* ; // 匹配单行注释 - 片段规则 (fragment):可被其他词法规则复用的规则,本身不会成为独立的Token。
fragment DIGIT : [0-9] ;
NUMBER : DIGIT+ ; // 引用片段规则 DIGIT
语法规则 (Parser Rules)
语法规则描述语言的语法结构,由词法符号和子规则组成。
- 规则名必须以小写字母开头。
- 规则体可以包含:
- 词法符号:如
'+','if',ID。 - 子规则:如
expr。 - 操作符:
|: 备选分支,表示“或”。*,+,?: 分别表示“零次或多次”、“一次或多次”、“零次或一次”。(): 对子规则分组。
- 词法符号:如
- 示例 (简单算术表达式):这个规则通过递归和顺序定义了运算符的优先级。
expr : expr ('*'|'/') expr // 乘除
| expr ('+'|'-') expr // 加减
| NUMBER // 数字
;
高级特性
备选分支标签 (Alternative Labels):用
#给分支打标签,ANTLR4 会为每个标签生成独立的监听或访问方法,方便精细处理。expr: expr '*' expr # Multiply
| expr '+' expr # Add
| NUMBER # Number
;动作 (Actions):用
{...}在规则中嵌入目标语言(如 Java)的代码,用于执行自定义逻辑,如计算值。expr returns [int value]
: NUMBER { $value = Integer.parseInt($NUMBER.text); }
| e1=expr '+' e2=expr { $value = $e1.value + $e2.value; }
;@header
和@members:在生成的解析器类中,分别用于插入包声明和自定义成员变量/方法。
词法与语法
这个问题非常关键,是理解 ANTLR4 工作方式的核心。为了让直观理解,先打个比方:词法规则处理“单词”(字母组合),语法规则处理“句子”(单词顺序和结构)。两者分工明确,又协同完成解析任务。
它分别起什么作用?(核心职责)
词法规则(Lexer Rules):将字符流变成 Token 流
- 作用:读入源代码(字符串),根据规则将连续的字符切分并归类为有意义的“词法单元”(Token),并丢弃对语法分析无用的内容(如空格、注释)。
- 在
ASN.g4中的实例:ASSIGN : '::=' ;:将两个冒号和一个等号组成的字符序列识别为“赋值”Token。UCASE_ID : ('A'..'Z') ... ;:将“MyModule”这样的连续字母识别为“大写标识符”Token。NUMBER : '0' | ('1'..'9')('0'..'9')* ;:将“2026”识别为“数字”Token。WS : [ \t\r\n]+ -> skip ;:匹配空格、换行,并直接丢弃(不出现在后续解析中)。LINE_COMMENT : '--' ~[\r\n]* -> skip ;:匹配 ASN.1 的注释并丢弃。
- 一句话总结:词法规则负责 “拼写检查”,把原始文本拆成系统认识的单词(Token)。
语法规则(Parser Rules):将 Token 流变成语法树(Parse Tree)
- 作用:接收词法分析器产生的 Token 流,依据定义的语法结构(顺序、层次、嵌套、递归),将 Token 组合成更大的结构单元,最终生成一颗语法分析树。
- 在
ASN.g4中的实例:specification: moduleDefinition* ;:规定一个文件由“零个或多个模块定义”组成。moduleDefinition: moduleIdentifier DEFINITIONS_LITERAL ... ;:规定一个模块必须先是模块名,然后是DEFINITIONS关键字,再接着是其他成分。这里的DEFINITIONS_LITERAL就是引用词法规则定义好的 Token。sequenceType: SEQUENCE_LITERAL LCURLY componentTypeLists? RCURLY ;:规定SEQUENCE { ... }这种结构,内部的componentTypeLists递归定义了嵌套的组件列表。
- 一句话总结:语法规则负责 “语法检查”,把单词组合成符合语法的句子,并理清它的层级关系。
词法规则与语法规则的区别
| 对比维度 | 词法规则 (Lexer Rules) | 语法规则 (Parser Rules) |
|---|---|---|
| 命名规范 | 必须以大写字母开头(如 INTEGER_LITERAL, WS) |
必须以小写字母开头(如 moduleDefinition, type) |
| 输入数据 | 处理字符流(一个一个的字符) | 处理Token 流(词法分析器输出的结果) |
| 输出产物 | 输出 Token(给语法分析器用) | 输出 语法树(Parse Tree)(给后续应用用) |
| 描述能力 | 使用正则表达式风格(字符集 [a-z]、量词 +、*) |
使用上下文无关文法风格(引用其他规则、支持递归) |
| 递归支持 | 不支持左递归,一般只做简单拼接 | 支持递归(如 type 可以嵌套包含 type),这是解析嵌套结构的关键 |
| 处理内容 | 处理原子性细节:数字、标识符、字符串、注释、空白 | 处理结构性逻辑:语句块、函数定义、类型声明、表达式层级 |
| 丢弃能力 | 有 -> skip 命令,可直接丢弃匹配的文本(如空格、注释) |
无法丢弃 Token,所有匹配的 Token 都必须参与构建语法树 |
| 典型例子 (ASN.g4) | BSTRING : '\'' ('0'..'1')* '\'' 'B' ;(匹配二进制串字面量) |
typeAssignment: UCASE_ID ASSIGN type ;(规定类型赋值的结构) |
词法规则与语法规则的共同点
尽管分工不同,但它在很多方面是统一的:
- 写在同一个
.g4文件里:可以把词法规则和语法规则混合编写(通常语法规则在上,词法规则在下)。 - 使用相同的 EBNF 元语法:两者都使用 ANTLR 提供的操作符来描述重复或选择:
|:或(选择)*:零次或多次+:一次或多次?:零次或一次():分组- 例如:词法规则中的
('0'..'9')+和语法规则中的assignment*都使用了+和*。
- 都能包含动作(Actions):两者都可以嵌入目标语言代码(如
{ System.out.println("..."); })来实现副作用(虽然实践中极少在词法规则里写复杂动作)。 - 共同生成解析器类:运行
antlr4 ASN.g4后,ANTLR 会为词法规则生成ASNLexer.java,为语法规则生成ASNParser.java,它共同组成完整的解析器。
它在 ASN.g4 中是如何协作的?
以解析 MyType ::= INTEGER 这句 ASN.1 代码为例,两者的协作过程如下:
- 词法分析器出场(词法规则工作):
- 扫描字符串
MyType ::= INTEGER - 识别
MyType→ 匹配UCASE_ID规则,生成 TokenUCASE_ID。 - 识别
::=→ 匹配ASSIGN规则,生成 TokenASSIGN。 - 识别
INTEGER→ 匹配INTEGER_LITERAL规则,生成 TokenINTEGER_LITERAL。 - (空格被
WS规则匹配并跳过,不生成 Token)。
- 扫描字符串
- 语法分析器出场(语法规则工作):
- 接收 Token 流:
UCASE_ID→ASSIGN→INTEGER_LITERAL - 查找语法规则:
typeAssignment规则定义为( UCASE_ID | ... ) ASSIGN type - 发现
UCASE_ID匹配左边,ASSIGN匹配中间,type规则进而匹配INTEGER_LITERAL。 - 最终生成一颗以
typeAssignment为根的语法树。
- 接收 Token 流:
关键洞察:语法规则完全看不到原始字符,只能看到词法规则包装好的 Token。这种分层设计极大地降低了语法规则的复杂度。
为什么需要分开它
- 效率:词法分析采用正则引擎(状态机),处理字符级匹配非常高效;语法分析采用自适应 LL(*) 算法,处理 Token 流更关注结构。混在一起会拖慢速度。
- 分离关注点:词法规则负责“识词”的复杂性(如处理转义字符、十六进制),语法规则负责“识句”的复杂性(如处理优先级、嵌套)。分开让代码更清晰、更容易维护。
- 复用性:同一个 Token(如
ID)可以被多个语法规则引用,符合 DRY 原则。
词法规则把“字符”拼成“单词”,语法规则把“单词”组成“句子”。它各司其职,通过 Token 流衔接,共同完成从原始文本到结构化语法树的解析全过程。
理解了这一点,就能清晰地分辨出哪些是在定义 ASN.1 的“单词表”(那几百个 _LITERAL 和标识符规则),哪些是在定义 ASN.1 的“语法书”(moduleDefinition, type, value 等规则)。这种分层思维是学习所有编译原理工具的第一步,已经掌握了!
在程序中使用生成的解析器
生成代码后,需要在主程序中调用它。以下是 Java 的典型使用步骤:
import org.antlr.v4.runtime.*; |
ANTLR4 支持 Java、C#、Python、JavaScript、Go 等多种目标语言。
掌握 Listener (监听器) 和 Visitor (访问器) 两种遍历语法树的方式。Listener 是自动遍历的“推送”模型,而 Visitor 是可以手动控制遍历的“拉取”模型。
查阅文档与社区:遇到问题查阅 ANTLR4 官方文档和 GitHub 上的语法示例库,并阅读《The Definitive ANTLR 4 Reference》这本权威书籍。
- 遵循命名规范:语法规则小写开头,词法规则大写开头。
- 使用标签区分分支:为
#分支打标签,让代码更清晰。 - 处理所有输入:在起始规则末尾加上
EOF,确保解析器消费整个输入,避免静默忽略错误。 - 利用片段规则:用
fragment组织可复用的词法单元。
理解 ANTLR4 语法文件的结构
一个 .g4 文件由三部分组成:
grammar Name; // ① 语法名称(必须和文件名一致) |
- 词法规则:定义单词(Token),描述字符组合(例如标识符、数字、字符串)。使用正则表达式风格。
- 语法规则:定义句子(结构),描述 Token 如何组成更大的结构。使用类似 BNF 的语法(
|表示“或”,*表示零次或多次,+表示一次或多次,?表示零次或一次,括号用于分组)。
现在,打开 ASN.g4 文件,对照着看。
初识 ASN.g4 —— 一个真实的 ASN.1 语法
ASN.1(抽象语法标记一)是电信和网络协议中广泛使用的数据描述语言。这个语法文件非常完整,遵循国际标准(ITU-T X.680)。虽然看起来庞大,但只挑选核心部分来理解。
文件头部
grammar ASN; // 文件名必须为 ASN.g4 |
后面是版权注释(略)。这里定义了语法名称为 ASN。
词法规则示例
文件后半部分定义了所有 Token(关键字、标点、字面量等)。例如:
ASSIGN : '::=' ; // 赋值符号 |
注意:词法规则名通常大写开头,而语法规则名小写开头(这是 ANTLR 的强制约定)。
关键点:fragment 规则
fragment EscapeSequence : '\\' ('b'|'t'|'n'|'f'|'r'|'"'|'\''|'\\') ; |
fragment表示EscapeSequence本身不会成为独立的 Token,它只被CSTRING复用。这样避免重复定义。
语法规则示例
语法规则定义了 ASN.1 的结构。来看最顶层的规则:
specification: moduleDefinition* ; |
- 一个 ASN.1 规范文件由零个或多个模块定义组成(
*表示重复)。
模块定义:
moduleDefinition: |
- 它依次匹配:模块标识符 →
DEFINITIONS关键字 → 可选的标签默认值 → 可选的扩展默认值 →::=→BEGIN→ 可选的模块体(导出、导入、赋值语句) →END。 ?表示可选,*表示零个或多个。
嵌套结构举例:typeAssignment
typeAssignment: |
- 一个类型赋值,比如
MyType ::= INTEGER。左边可以是类型引用(大写开头)或内建字符串类型(如IA5String),右边是一个type规则。
type 规则(核心)
type: builtinType constraint? | referencedType constraint? ; |
- 一个类型可以是内建类型(如
INTEGER,SEQUENCE)或引用类型(如已经定义的类型名),后面可以跟一个可选的约束(用括号括起来)。
内建类型分支示例:
builtinType: |
每个类型又有自己的详细规则,例如:
sequenceType: |
这匹配 SEQUENCE { ... } 的形式,其中 componentTypeLists 定义了内部组件的列表,支持扩展(...)、可选、默认等。
动手生成解析器并测试
现在,利用 ASN.g4 生成一个解析器,并测试一个简单的 ASN.1 模块。
生成代码
在命令行进入 ASN.g4 所在目录,执行:
antlr4 ASN.g4 |
这会生成一堆 Java 文件:ASNLexer.java, ASNParser.java, ASNListener.java 等。如果要生成访问器(Visitor),加上 -visitor 选项:
antlr4 -visitor ASN.g4 |
编写一个测试输入文件 test.asn
创建一个文本文件,内容为一个最简单的 ASN.1 模块:
MyModule DEFINITIONS ::= BEGIN |
使用 TestRig(grun)可视化解析树
在命令行执行:
grun ASN specification -gui test.asn |
(如果使用 grun 别名无效,直接写 java org.antlr.v4.gui.TestRig ASN specification -gui test.asn)
这会弹出一个图形窗口,展示解析树(Parse Tree),可以直观看到每个节点是如何匹配的。
注意:
specification是入口规则,因为一个文件包含多个模块。也可以从moduleDefinition开始测试单个模块,但这里用specification更完整。
遍历解析树——监听器(Listener)与访问器(Visitor)
生成解析器后,通常需要遍历语法树来提取信息、验证语义或生成代码。ANTLR4 提供了两种模式:
监听器(Listener)
- 自动遍历树,在进入/离开每个节点时回调定义的方法。
- 使用
ParseTreeWalker驱动。
示例 Java 代码(处理的 test.asn):
import org.antlr.v4.runtime.*; |
运行后会打印 “发现类型赋值: MyType”。
访问器(Visitor)
- 可以显式控制遍历顺序,返回任意值。
- 需要生成时加
-visitor,然后继承ASNBaseVisitor<T>并重写方法。
示例:
public class MyVisitor extends ASNBaseVisitor<String> { |
然后在主程序调用 tree.accept(new MyVisitor())。
选择建议:如果只需被动响应节点,用 Listener;如果需要控制遍历顺序或返回值,用 Visitor。
高级技巧
备选分支标签(Alternative Labels)
在 value 规则中,看到:
value: CSTRING # value_CSTRING |
这样生成的监听器/访问器会有专门的方法,如 enterValue_CSTRING,避免在同一个 enterValue 中做类型判断,让代码更清晰。
使用 -> skip 忽略空白和注释
WS : [ \t\r\n]+ -> skip ; |
这些 Token 被丢弃,不会出现在解析树中,简化了语法规则。
处理歧义
在 componentTypeLists 中,有多个备选分支,顺序很重要。ANTLR4 按书写顺序尝试匹配,因此要把更具体的分支放前面,避免歧义。
实战示例
文件结构总览
grammar ASN; // 1. 语法名称,文件名必须为 ASN.g4 |
整体分为:
- 语法规则部分(小写开头):定义 ASN.1 的句法结构。
- 词法规则部分(大写开头):定义关键字、标点、字面量、标识符、注释等。
- 顺序上,语法规则在前,词法规则在后(ANTLR4 允许,但推荐将词法规则置于文件尾部)。
词法规则(Lexer Rules)解析
词法规则负责将字符流切分成 Token,是解析的基础。此文件定义了:
关键字(保留字)
约 50 多个,如 ANY_LITERAL: 'ANY';、INTEGER_LITERAL: 'INTEGER';、EXPLICIT_LITERAL: 'EXPLICIT'; 等。
命名习惯:在保留字后加 _LITERAL 后缀,避免与语法规则名冲突,且便于识别。
标点符号
ASSIGN: '::='、LCURLY: '{'、RCURLY: '}'、LBRACKET: '['、RBRACKET: ']'、DOUBLE_LBRACKET: '[['、ELLIPSIS: '...'、RANGE: '..'等,直接对应 ASN.1 的符号。
字面量(字符串/位串/十六进制串)
BSTRING: APOSTROPHE ('0'..'1')* APOSTROPHE 'B'—— 二进制串,如'1010'B。HSTRING: APOSTROPHE ('0'..'9'|'a'..'f'|'A'..'F')* APOSTROPHE 'H'—— 十六进制串。CSTRING: '"' ( EscapeSequence | ~('\\'|'"') )* '"'—— 加引号的字符串,支持转义(如\n),用于PATTERN或值中。
标识符(ASN.1 引用名称)
UCASE_ID:大写字母开头,后跟字母、数字或连字符(-),但连字符后必须跟字母数字(标准要求)。用于类型引用、模块名等。LCASE_ID:小写字母开头,规则相同。用于值引用、组件名等。- 这种定义允许 ASN.1 中的连字符名称(如
My-Type),但要避免与减号混淆(减号是MINUS单独 Token)。
数字
NUMBER:非负整数,不含前导零(除了单独的0)。NUMBER_WITH_DECIMALS:带小数点的实数(如1.23),用于 REAL 类型的值。
注释
LINE_COMMENT:--到行尾 →skip。BLOCK_COMMENT:/* ... */→skip,支持多行。- 注释完全忽略,不影响解析。
空白
WS: (' '|'\r'|'\t'|'\n') -> skip。
备用捕获(OTHER)
OTHER: .—— 捕获任意未匹配字符(通常用于错误恢复,但会匹配所有剩余,若放在最后可作为“万能”回退,但此处放在文件末尾,优先匹配其他规则,实际上不会匹配到,因为其他规则会覆盖;若无其他规则匹配,则会报错。此处可能多余,但不影响)。
语法规则(Parser Rules)核心剖析
语法规则定义了 ASN.1 的层次结构。按“模块→类型→值→约束”这条主线来梳理。
顶层:specification
specification: moduleDefinition* ; |
一个 ASN.1 文件可由多个模块定义组成(允许为空)。
模块定义:moduleDefinition
moduleDefinition: |
模拟标准中的模块语法:
moduleIdentifier:模块名 + 可选的 OID(如{iso(1) ...})。DEFINITIONS关键字。tagDefault:EXPLICIT TAGS/IMPLICIT TAGS/AUTOMATIC TAGS(可选)。extensionDefault:EXTENSIBILITY IMPLIED(可选)。::=和BEGIN。- 模块体:可选的
exports(导出)、imports(导入),然后是一个或多个assignment(类型或值赋值)。 END。
其中 assignment 又分为 typeAssignment 和 valueAssignment,省略了其他如对象类赋值(注释中有说明)。
类型定义(Type Assignment)
typeAssignment: |
允许为内建字符串类型起别名(如 MyString ::= IA5String)。
类型表达式:type
type: builtinType constraint? | referencedType constraint? ; |
一个类型可以是内建类型(如 INTEGER、SEQUENCE)、引用类型(如已定义的类型名、选择类型、有用类型),并可附加约束(如 INTEGER(0..10))。
内建类型 builtinType
包含所有 ASN.1 核心类型:
bitStringType:BIT STRING { a(0), b(1) }。BOOLEAN_LITERAL、NULL_LITERAL。characterStringType:各种字符串(IA5String、UTF8String等)。choiceType、enumeratedType、integerType、sequenceType、setType、sequenceOfType、setOfType、prefixedType(带标签)等。- 还有
anyType(ANY DEFINED BY)等扩展。
每个类型的具体规则都严格按照标准定义,例如:
SequenceType:
sequenceType:
SEQUENCE_LITERAL LCURLY componentTypeLists? RCURLY
;其中
componentTypeLists处理带有扩展标记(...)、异常、可选/默认组件的复杂情况。ChoiceType:
choiceType:
CHOICE_LITERAL LCURLY alternativeTypeList ( COMMA extensionAndException extensionAdditionAlternatives? extensionMarker? )? RCURLY
;允许在备选列表后跟扩展。
引用类型 referencedType
包括:
usefulType:GeneralizedTime、UTCTime、ObjectDescriptor。definedType:通过UCASE_ID或Module.Type引用。selectionType:如name < MyType(选择类型)。
标签类型 taggedType
taggedType: |
支持隐式/显式标签,以及上下文、应用、私有等标签类。
值定义(Value Assignment)
valueAssignment: |
值名称(小写开头) + 类型 + ::= + 值表达式。
值表达式 value 涵盖多种形式,用备选分支标签(#)区分:
value_CSTRING:带引号字符串。value_BSTRING/value_HSTRING:二进制/十六进制串。value_EmptyList:空列表{}。value_BOOLEAN:TRUE/FALSE。value_Choice:选择类型值,如name: value。value_NamedValueList:命名值列表(用于 SEQUENCE/SET)。value_ObjectIdentifier:OID 如{iso(1) 2 3}。value_ValueList:无名字的值列表(用于 SEQUENCE OF / SET OF)。value_Integer:整数或小数。value_NULL:NULL。value_ReferencedValue:引用已定义的值。
约束(Constraint)
constraint: |
约束总是括在括号中。subtypeConstraint 支持:
- 集合运算:
|(UNION)、^(INTERSECTION)、EXCEPT(排除)。 - 元素列表:值、值范围(
1..10)、SIZE、FROM、PATTERN、WITH COMPONENT等。 - 允许使用
MIN、MAX表示端点。 - 扩展标记
...用于表示未来扩展。
约束规则中使用了很多递归,如 unions → intersections → intersectionElements → elements,以正确解析运算符优先级。
关键设计模式与 ANTLR4 特性应用
备选分支标签(Alternative Labels)
在 value 规则中广泛使用,如:value: CSTRING # value_CSTRING
| BSTRING # value_BSTRING
...
这样在生成的监听器/访问器中,会为每个分支生成独立的 enterValue_CSTRING / exitValue_CSTRING 等方法,方便精细化处理,而不用在统一的 enterValue 里通过 instanceof 判断。
使用 fragment 组织词法片段
EscapeSequence 被定义为 fragment,供 CSTRING 复用,避免重复。
规则优先级与歧义处理
- 在
componentTypeLists中,通过多个备选分支(用#标记)和顺序排列,确保扩展标记...与普通组件组合时解析正确。 - 在
type中,builtinType和referencedType并列,ANTLR4 会按顺序尝试,但有些输入可能同时匹配两者(例如INTEGER既是内建类型也可能是引用?但INTEGER是关键字,词法上已被识别为INTEGER_LITERAL,因此不会冲突)。若存在歧义,ANTLR4 采用“优先第一条匹配”策略,此语法已谨慎处理。
大量使用 ?、*、+ 描述可选和重复
例如 moduleIdentifier 中 ( definitiveOID )?,componentTypeList 中 ( COMMA componentType )*,这都是 ANTLR4 的常用操作符。
异常处理和扩展标记(...、!)
ASN.1 支持扩展标记(...)和异常标识(!),本语法在 componentTypeLists、enumeration、extensionAddition 等规则中均有体现,允许解析带有扩展的模块定义。
对 ASN.1 标准的覆盖程度(及简化点)
注释中明确提到简化了以下部分:
- 参数化引用(Parameterized references)未实现(仅保留基础引用)。
- 对象类(ObjectClass)、对象(Object)、对象集(ObjectSet)等 X.681 相关的高级特性未实现。
- 编码控制段(Encoding Control Sections)忽略。
- XML 值等未包含。
但核心的类型、值、约束、模块、导入导出、标签、扩展等全部支持,足以解析绝大多数 ASN.1 规范(如 SNMP MIB、LDAP、3GPP 等标准)。
如何使用此语法生成解析器并测试
生成代码:
- 使用 ANTLR4 工具(或 IDE 插件)执行
antlr4 ASN.g4,生成词法分析器ASNLexer.java、语法分析器ASNParser.java以及监听器骨架等。 - 若需要访问器(Visitor),可加
-visitor选项。
- 使用 ANTLR4 工具(或 IDE 插件)执行
入口规则:从
specification开始解析(也可从moduleDefinition开始解析单个模块)。Java 示例:
CharStream input = CharStreams.fromFileName("myModule.asn");
ASNLexer lexer = new ASNLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ASNParser parser = new ASNParser(tokens);
ParseTree tree = parser.specification(); // 解析整个文件
// 然后可用监听器或访问器遍历 tree测试工具:可以用
grun(TestRig)图形化查看语法树,例如:grun ASN specification -gui myModule.asn
在 ASN.g4 这个语法文件中,ANTLR4 的原生关键字主要用于定义语法文件的结构和控制生成的解析器行为。它不是用来匹配输入文本(如 BEGIN 或 INTEGER)的,那些是在词法规则里定义的自定义Token。
以下是文件中出现的 ANTLR4 原生关键字及其详细说明。
核心结构关键字
这类关键字定义了 .g4 文件的骨架。
grammar: 必须出现在文件开头,用于声明语法名称。例如文件中的grammar ASN;,它要求文件名必须为ASN.g4。lexer: 用于声明一个纯词法分析器语法,其中只包含词法规则,没有解析器规则。parser: 用于声明一个纯语法分析器语法,其中只包含解析器规则,没有词法规则。fragment: 用于定义一个可复用的词法片段。fragment规则本身不会产生独立的Token,只能被其他词法规则引用。在ASN.g4中,EscapeSequence就是一个典型的fragment规则,它被用在CSTRING词法规则里。mode: 用于定义词法分析器的不同状态(模式)。在ASN.g4中没有使用,它通常在需要根据上下文切换词法规则的复杂场景下使用(比如解析HTML或XML)。
选项与配置关键字
这类关键字用于配置语法分析器的行为。
options: 用于在语法文件内部设置各种选项。例如,可以设置tokenVocab选项来指定词法Token的类型。在ASN.g4中没有使用options {}块。tokens: 用于声明虚构的Token类型。这些Token在语法规则中被引用,但实际上并没有对应的词法规则来生成它,通常用于在解析器生成时定义操作符优先级等。在ASN.g4中没有使用。
动作与代码注入关键字
这类关键字用于在生成的解析器中嵌入自定义代码。
returns: 用于为解析器规则定义返回值。例如,一个计算器语法中的expr returns [int value]。在ASN.g4中没有使用。locals: 用于为解析器规则定义局部变量。这些变量可以在规则内部使用。在ASN.g4中没有使用。throws: 用于声明解析器规则可能抛出的异常。在ASN.g4中没有使用。catch和finally: 用于在生成的解析器代码中添加异常处理和最终执行块。在ASN.g4中没有使用。import: 用于在当前语法文件中导入其他语法文件。被导入的语法中的规则和Token定义会被合并进来。在ASN.g4中没有使用。
一些重要的区分
- 关键字 vs. 自定义Token:
ASN.g4文件中定义了大量的Token,比如BEGIN_LITERAL、INTEGER_LITERAL等,它的值是'BEGIN'、'INTEGER'这样的字符串。这些是在为ASN.1语言本身定义的Token,它不是 ANTLR4 的原生关键字。 rule不是关键字:需要注意的是,尽管rule这个词很常用,但它不是 ANTLR4 的保留关键字。不过,官方建议不要将其用作规则名称,以免造成混淆。
在 ASN.g4 这个文件中,实际使用的 ANTLR4 原生关键字非常少,主要是:
grammar: 用于声明语法名称。fragment: 用于定义可复用的词法片段。
其他原生关键字(如 import, options, returns 等)虽然功能强大,但这个特定的语法文件并未采用。