- 编程语言
- 编译器
- 开发工具
【免费下载链接】grammars-v4
Grammars written for ANTLR v4; expectation that the grammars are free of actions.
本文基于 ANTLR v4 开源语法仓库 grammars-v4 中的vba/vba_cc子项目,系统讲解如何用 ANTLR4 语法解析 Visual Basic for Applications(VBA)文件中的条件编译代码段。读者将掌握 vba_cc 语法的整体结构与规则设计思路、#Const/#If/#ElseIf/#Else/#End If指令的完整文法定义、条件编译表达式的运算符体系与优先级建模方式,以及如何结合仓库内的示例与 Maven 测试配置快速验证该语法。
背景:为什么需要单独的 vba_cc 语法
在 vba/vba7_1/README.md 中明确说明:VBA 7.1 主语法(vbaLexer.g4/vbaParser.g4)会忽略条件编译语句("This grammar ignores conditional-compilation statements")。而 vba_cc 语法正是为此设计的补充:"The vba_cc grammar can be used against vba files to analyze that portion of the code."
也就是说,两条语法的分工是:
- vba7_1:解析 VBA 代码的主体(过程、语句、表达式、类型声明等);
- vba_cc:专门针对
.bas/.frm/.cls等 VBA 源文件,抽取并分析其中的条件编译代码段(conditional compilation),包括条件编译常量声明和条件编译分支指令。
vba_cc 语法声明"Derived from the Visual Basic 7.1 language reference",即其规则结构以微软 Visual Basic 语言规范(MS-VBAL)为蓝本实现,覆盖规范中编号为3.4 Conditional Compilation、3.4.1 Conditional Compilation Const Directive、3.4.2 Conditional Compilation If Directives以及5.6.16.2 Conditional Compilation Expressions等章节的内容。
vba_cc 语法整体结构
语法入口定义在 vba/vba_cc/vba_cc.g4,根规则startRule的组成如下:
startRule : (proceduralModuleHeader | classFileHeader) conditionalModuleBody NEWLINE* EOF ;规则读取顺序为:
- 可选的文件头:
.bas模块通常以Attribute VB_Name = "..."开头(proceduralModuleHeader),.cls/.frm类文件通常以VERSION x.x Class开头(classFileHeader); - 条件编译模块主体
conditionalModuleBody; - 结尾允许若干空行(
NEWLINE*)并必须以文件结束符EOF收束。
对应示例 vba/vba_cc/examples/cc.bas 的第一行正是:
Attribute VB_Name = "CCTEST"文件头的两条规则定义如下:
proceduralModuleHeader : 'ATTRIBUTE VB_NAME = ' STRINGLITERAL ; classFileHeader : 'VERSION' FLOATLITERAL 'CLASS'? ;proceduralModuleHeader:匹配Attribute VB_Name = "模块名",模块名必须是STRINGLITERAL(带引号字符串);classFileHeader:匹配VERSION <浮点数> CLASS,其中CLASS关键字可选——这覆盖了类模块与窗体文件两种头部形式。
条件编译模块主体与逻辑行
conditionalModuleBody与块结构的核心定义:
logicalLine : NEWLINE WS? (~(CONST | IF | ELSEIF | ELSE | NEWLINE) (~(NEWLINE))*)?; conditionalModuleBody: ccBlock; ccBlock: (ccConst | ccIfBlock | logicalLine)+;ccBlock是模块主体的骨架:由ccConst(条件编译常量声明)、ccIfBlock(条件编译 If 块)和普通logicalLine(普通代码行)三者的任意组合重复组成;logicalLine用于吞掉不属于条件编译指令的普通 VBA 代码行(例如示例中的Open pth for output as #ff、Print #ff, cont、Close #ff)。它的否定式字符类~(CONST | IF | ELSEIF | ELSE | NEWLINE)确保只有当一行不以#Const、#If、#ElseIf、#Else开头时,才会被当作普通逻辑行消费,从而把条件编译指令与普通代码精确区分开。
条件编译常量指令(#Const)
语法规则ccConst对应 MS-VBAL 规范的 3.4.1 节:
ccConst: NEWLINE CONST ccVarLhs '=' ccExpression COMMENT?; ccVarLhs: name;它要求一条#Const指令的形态为:
NEWLINE #Const 名字 = 条件编译表达式 [注释]?- 指令前必须有换行(
NEWLINE)作为语句边界; CONST词法符号定义为'#CONST',由于 grammar 选项caseInsensitive = true,#Const、#CONST、#const均可匹配;- 等号左侧是变量名
ccVarLhs,右侧是ccExpression条件编译表达式; - 行尾允许跟一个可选的
COMMENT(单引号注释)。
name规则区分未带类型后缀与带类型后缀的名字:
name : untypedName | typedName ; untypedName : IDENTIFIER | FOREIGN_NAME ; typedName : IDENTIFIER typeSuffix ; typeSuffix : '&' | '%' | '#' | '!' | '@' | '$' | '^' ;untypedName接受普通标识符IDENTIFIER([A-Z][A-Z0-9_]*)或方括号转义名FOREIGN_NAME('[' ~[...]* ']',用于包含空格或关键字的名字);typedName支持 VBA 的类型声明字符后缀:&(Long)、%(Integer)、#(Double)、!(Single)、@(Currency)、$(String)、^(LongLong,64 位)。
示例文件中的典型常量声明:
#Const conDebug = 1会被解析为ccConst:ccVarLhs = conDebug,ccExpression = 1(INTEGERLITERAL)。
条件编译分支指令(#If / #ElseIf / #Else / #End If)
规则ccIfBlock对应 MS-VBAL 规范的 3.4.2 节,它完整描述了条件编译分支块的嵌套结构:
ccIfBlock : ccIf ccBlock? ccElseifBlock* ccElseBlock? ccEndif; ccIf: NEWLINE+ IF ccExpression THEN COMMENT?; ccElseifBlock: ccElseif ccBlock?; ccElseif: NEWLINE+ ELSEIF ccExpression THEN COMMENT?; ccElseBlock: ccElse ccBlock?; ccElse: NEWLINE+ ELSE COMMENT?; ccEndif: NEWLINE+ ENDIF COMMENT?;块结构的语法语义:
ccIf:至少一个换行后跟IF('#IF')、一个条件表达式、THEN,可选尾随注释;- 每个分支主体
ccBlock?都是可选的(?表示零或一次),因此允许出现空分支块; ccElseifBlock*:#ElseIf分支可以出现任意多次;ccElseBlock?:#Else分支至多出现一次;ccEndif:以ENDIF结束。ENDIF词法符号同时接受两种写法:
ENDIF : '#END IF' | '#ENDIF' ;这覆盖了#End If(带空格)与#Endif(不带空格)两种常见写法。
对应示例 vba/vba_cc/examples/cc.bas 中的嵌套结构可以直观验证上述规则:
#If conDebuge = 1 Then 'Run debug code Debug.Print "oops" #Elseif conDebug > 1 Then Debug.Print "help" #Else #If Cdbl(Abs(conDebug)) < (1 Mod 3) Then foo = 2 #Endif #End If这里外层ccIfBlock内含#Else分支,而#Else分支体内又嵌套了一个完整的ccIfBlock,正好印证了ccBlock的递归组合能力。
示例文件还特意包含了两处"空分支块"用例,用于验证块可选规则:
' The blocks can each be empty. #if Win64 Then #Elseif Win32 Then #Else #Endif ' The blocks can each be empty. #if 1 / 2 = .5 Then foo = 4 #Endif第一段中#If Win64 Then、#Elseif Win32 Then、#Else、#Endif的各分支体均为空,语法仍能正确接受;第二段则演示了纯算术关系表达式1 / 2 = .5作为条件的场景。
条件编译表达式(ccExpression)
条件编译表达式的规则位于ccExpression,对应 MS-VBAL 规范 5.6.16.2 节。它采用 ANTLR4 的左递归 + 优先级分层方式建模,各备选分支按优先级从低到高排列:
ccExpression : literalExpression # LiteralExpress | reservedKeywords # IdentifierExpression | IDENTIFIER # IdentifierExpression | '(' ccExpression ')' # ParenthesizedExpression | ccExpression ('^') ccExpression # ArithmeticExpression | '-' ccExpression # UnaryMinusExpression | ccExpression ('*' | '/') ccExpression # ArithmeticExpression | ccExpression '\\' ccExpression # ArithmeticExpression | ccExpression 'MOD' ccExpression # ArithmeticExpression | ccExpression ('+' | '-') ccExpression # ArithmeticExpression | ccExpression '&' ccExpression # ConcatExpression | ccExpression (EQ | NEQ | GT | GEQ | LEQ | LT | LIKE) ccExpression # RelationExpression | ccFunc '(' ccExpression ')' # IndexExpression | 'NOT' ccExpression # NotOperatorExpression | ccExpression 'AND' ccExpression # booleanExpression | ccExpression 'OR' ccExpression # booleanExpression | ccExpression 'XOR' ccExpression # booleanExpression | ccExpression 'EQV' ccExpression # booleanExpression | ccExpression 'IMP' ccExpression # booleanExpression ;从规则顺序可以还原出 VBA 条件编译表达式的优先级层次(由高到低):
| 优先级 | 运算符 / 构造 | 标签(Alternative) |
|---|---|---|
| 最高 | 字面量、保留关键字、标识符 | LiteralExpress / IdentifierExpression |
括号( expr ) | ParenthesizedExpression | |
幂^ | ArithmeticExpression | |
一元负号-expr | UnaryMinusExpression | |
乘除*/,整除\,取模MOD | ArithmeticExpression | |
加减+- | ArithmeticExpression | |
字符串连接& | ConcatExpression | |
关系比较=<>>>=<<=LIKE | RelationExpression | |
函数调用ccFunc( expr ) | IndexExpression | |
逻辑非NOT expr | NotOperatorExpression | |
| 最低 | 逻辑与 / 或 / 异或 / 等值 / 蕴含:ANDORXOREQVIMP | booleanExpression |
值得注意的细节:
- 整除用反斜杠
\\表示,与 VBA 的\整除运算符对应; - 关系运算符同时兼容非标准变体写法,例如
GEQ同时接受>=与=>,LEQ接受<=与=<,NEQ接受<>与><,体现了对真实代码宽容度的考虑; &在此上下文中被定义为字符串连接运算符(ConcatExpression),而非十六进制前缀——十六进制字面量由词法层HEXLITERAL处理;MOD、AND、OR、XOR、EQV、IMP、LIKE、NOT均以关键字形式内联在文法中。
字面量类型
literalExpression支持 VBA 条件编译允许的六类字面量:
literalExpression : BOOLEANLITERAL | FLOATLITERAL | INTEGERLITERAL | STRINGLITERAL | DATELITERAL | EMPTY_ | NULL_ | NOTHING ;BOOLEANLITERAL:TRUE/FALSE;FLOATLITERAL:浮点字面量,支持尾缀!(Single)、#(Double)、@(Currency)以及D/E指数记法;INTEGERLITERAL:十进制、&H十六进制(HEXLITERAL)、&O/&八进制(OCTLITERAL)三种进制,支持%、&、^类型尾缀;STRINGLITERAL:双引号字符串,允许""转义表示内嵌引号;DATELITERAL:#日期/时间#形式的日期字面量,日期部分支持数字与英文月份名(含缩写,MAY被有意排除以避免歧义),时间部分支持AM/PM标记;- 特殊标识符
EMPTY_、NULL_、NOTHING分别对应EMPTY、NULL、NOTHING。
条件编译函数
ccFunc定义了条件编译表达式中允许调用的内建转换/数学函数集合:
ccFunc : 'INT' | 'FIX' | 'ABS' | 'SGN' | 'LEN' | 'LENB' | 'CBOOL' | 'CBYTE' | 'CCUR' | 'CDATE' | 'CDBL' | 'CINT' | 'CLNG' | 'CLNGLNG' | 'CLNGPTR' | 'CSNG' | 'CSTR' | 'CVAR' ;即:Int、Fix、Abs、Sgn、Len、LenB,以及类型转换函数CBool、CByte、CCur、CDate、CDbl、CInt、CLng、CLngLng、CLngPtr、CSng、CStr、CVar。这与 VBA 规范中对条件编译表达式允许使用的函数集合保持一致。示例中的Cdbl(Abs(conDebug))正是ccFunc与嵌套表达式的组合用法。
条件编译保留关键字
reservedKeywords定义了 VBA 条件编译中最常用的编译平台/版本判断变量:
reservedKeywords : WIN16_ | WIN32_ | WIN64_ | VBA6 | VBA7 | MAC ;对应词法符号分别为WIN16、WIN32、WIN64、VBA6、VBA7、MAC。这些是条件编译表达式里可直接使用的预定义编译常量标识符,例如示例中的#If Win64 Then/#Elseif Win32 Then。
词法设计要点(Lexer)
vba_cc 的词法规则在 vba/vba_cc/vba_cc.g4 后半部分,几个值得关注的设计点:
caseInsensitive = true:整个语法大小写不敏感,#IF、#if、#If均视为同一符号;- 指令记号均为
#前缀:CONST('#CONST')、IF('#IF')、ELSEIF('#ELSEIF')、ELSE('#ELSE')、ENDIF('#END IF'/'#ENDIF')——与 VBA 源文件中条件编译指令的#前缀写法严格对应; NEWLINE匹配[\r\n\u2028\u2029]+,并保留在语法规则中作为语句边界(VBA 规范要求指令必须位于行首,因此换行是结构性记号而非单纯空白);WS进入channel(HIDDEN)隐藏通道,不会出现在语法树中,但不会影响 NEWLINE 所承载的结构语义;COMMENT以单引号'起始直到行尾,允许出现在各指令行末尾;IDENTIFIER仅匹配以字母开头的[A-Z][A-Z0-9_]*,而FOREIGN_NAME匹配方括号括起的任意内容,用于处理[My Name]这类转义标识符。
用 Maven 与示例文件验证语法
vba_cc 是一个标准的 ANTLR4 Maven 模块,构建配置见 vba/vba_cc/pom.xml:
- 语法生成:使用
antlr4-maven-plugin,sourceDirectory指向模块根目录,只包含vba_cc.g4一个语法文件,并开启visitor与listener生成选项(分别生成vba_ccVisitor、vba_ccBaseVisitor、vba_ccListener、vba_ccBaseListener,便于用户用 Visitor 或 Listener 模式遍历条件编译语法树); - 测试驱动:使用
com.khubla.antlr:antlr4test-maven-plugin,入口规则为startRule,语法名vba_cc,测试样例目录为examples/。执行 Maven 测试即可自动对examples/下的每个示例文件进行解析验证; - 生成目标:desc.xml 声明支持
CSharp;Cpp;Dart;Go;Java;JavaScript;Python3;TypeScript等多语言 Target。
仓库内提供了一份完整的测试样例 vba/vba_cc/examples/cc.bas,它几乎覆盖了语法的全部特性:标准模块头Attribute VB_Name、#Const常量声明、带注释的#If ... Then、多个#ElseIf、带嵌套#If的#Else分支、#End If/#Endif两种结尾写法、空分支块、Win64/Win32平台关键字、函数调用(Cdbl(Abs(...)))、整除与取模(1 Mod 3)、浮点比较(1 / 2 = .5),以及普通代码行(Open/Print/Close语句)与条件编译指令的混合。
验证方式(在模块目录内执行):
# 生成解析器并运行示例测试 mvn testantlr4test-maven-plugin会依次解析examples/下的样例;若全部通过,说明语法规则与样例匹配,否则会在verbose输出中给出失败信息与期望语法树。
使用场景与局限
vba_cc 语法的典型使用场景包括:
- 条件编译代码静态分析:针对 VB6 / VBA 工程(
.bas、.frm、.cls)抽取#Const与#If分支结构,评估不同编译平台(Win16/Win32/Win64)、VBA 版本(VBA6/VBA7)与 Mac 环境下的代码分支覆盖情况; - 与 vba7_1 主语法配合:先用 vba7_1 分析 VBA 主体代码、再用 vba_cc 分析条件编译片段,从而获得完整的源代码结构视图;
- 作为实现参考:本语法以 MS-VBAL 规范的 3.4、5.6.16.2 等章节为蓝本,是学习"如何把规范中的条件编译文法翻译为 ANTLR4 左递归优先级文法"的现成范例。
需要注意的局限(README 与源码中均有体现):
- vba_cc 只分析条件编译相关的那部分代码结构,普通 VBA 语句在
logicalLine中被整体吞掉、不做细分解析,因此不能替代vba7_1 主语法完成完整语法分析; - vba7_1 主语法明确忽略条件编译语句,若直接用 vba7_1 解析含
#If的文件,这些指令会按普通内容处理或产生偏差,这正是需要两个语法配合的原因; - 语法基于 VBA 7.1 语言参考实现,覆盖的是规范定义的书写形式,实际工程代码中的异常排版需要依赖
NEWLINE、WS等规则的结构化处理来容错。
小结
vba_cc 是 grammars-v4 仓库中针对 VBA 条件编译代码段的专用 ANTLR4 语法:它以上下文无关文法完整覆盖了#Const常量声明、#If / #ElseIf / #Else / #End If分支块、条件编译表达式(算术、关系、字符串连接与逻辑运算)、内建函数与平台保留关键字,并通过caseInsensitive、NEWLINE结构化边界与隐藏通道 WS 的设计贴合 VBA 规范的真实书写规则。配合 cc.bas 示例与 pom.xml 中的 antlr4test 配置,开发者可以快速验证语法并在此基础上实现条件编译代码的解析、分析与版本/平台适配工具。
- 编程语言
- 编译器
- 开发工具
【免费下载链接】grammars-v4
Grammars written for ANTLR v4; expectation that the grammars are free of actions.
相关推荐
lo 库 MaxIndexBy 详解:基于自定义比较函数同时获取最大值与其下标
lo 库 MaxIndexBy 详解:基于自定义比较函数同时获取最大值与其下标 MaxIndexBy 是 lo(A Lodash style Go librar
编程语言编译器开发工具ANTLR4语法文件结构深度解析
ANTLR4语法文件结构深度解析 ANTLR4作为一款强大的语法分析器生成工具,其核心在于语法文件的编写。本文将全面解析ANTLR4语法文件的结构组成,帮助开发
开发工具编程语言编译器MASM 汇编语言 ANTLR4 语法解析:asmMASM 语法文件结构与实战示例详解
MASM 汇编语言 ANTLR4 语法解析:asmMASM 语法文件结构与实战示例详解 本文基于 grammars v4 仓库中 asm/asmMASM 目录下
编程语言编译器开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考