KRL文法规则及其编译原理分析
KRL文法规则及其编译原理分析
摘要
KUKA 工业机器人广泛应用于焊接、搬运、涂胶等自动化场景,其控制程序通常以 KRL 语言编写,并随机器人备份文件一并保存。随着车型种类增多、车型数量增加,依靠现场专业人员逐个翻阅示教器或备份文件来判断机器人程序结构和调用关系的方式,已经难以满足效率与准确性的双重要求。基于这一现实背景,本文从编译原理角度出发,讨论对 KRL 程序进行自动化静态分析的可行性,并围绕 ANTLR4 的 LL(*) 分析策略,对 KRL 的主要语法结构进行整理与建模。
本文不以“实现一个完整编译器”为目标,而是面向静态程序理解任务,重点研究如何通过词法分析与语法分析,从 KRL 文本中提取稳定、可结构化的语法信息。围绕 krl.g4 文法文件,本文系统说明了 KRL 中注释、控制头、标识符、字面量、程序定义、控制流语句、运动语句、表达式与调用表达式等内容如何被整理为适合自动分析的形式,并解释了若干关键改写策略,例如大小写不敏感处理、关键字与标识符冲突处理、表达式优先级组织,以及 .src 与 .dat 双入口统一建模等。
研究表明,KRL 程序虽然具有工业控制语言特有的业务语义和项目约定性,但其文本形式仍然具有较强的结构化特征,完全可以借助编译原理方法建立自动化分析基础。特别是,当 KRL 语言特征被整理为满足 ANTLR4 要求的文法规则后,词法分析器与语法分析器便能够稳定地产生结构化结果,为后续的调用关系识别、程序理解和可视化分析提供统一入口。本文最终的主要理论产出,是一份能够描述 KRL 核心语法结构、并能支撑后续静态分析的 krl.g4 文法规则体系。
关键词: KRL;ANTLR4;词法分析;语法分析;LL(*);工业机器人程序;静态分析
说明:本篇报告对kuka工业机器人所使用的编程语言KRL进行梳理,并编写其所对应的满足LL(*)要求的文法规则。随后基于分析Kuka机器人程序的需求,结合编译原理的相关理论,给出解析KRL程序的方案,并做可行性分析。
1. 背景介绍
- 目前对于kuka工业机器人设备中运行程序的梳理依赖于人力。只有经过培训的相关人员才能够"读懂"程序。
- 项目调试与生产中存在大量场景依赖于"了解"工业机器人所运行的程序。
- 数量众多、车型繁杂导致仅依靠人力来获取当前生产线体的情况困难重重。
- 由于近年来标准化的有序推进、调试规范的充分应用,自动化分析程序具备了条件。
在当前工业现场中,机器人程序往往由调试工程师和设备维护人员分阶段维护。对于熟悉某条产线历史的人来说,理解某个程序为何调用另一个程序,某个车型分支为何对应某套轨迹,往往依靠经验积累即可完成;但一旦人员流动、项目交接或车型快速切换,仅靠人工梳理机器人程序就会变得十分困难。尤其是在 KUKA 机器人项目中,程序数量多、命名跨度大、调用层级深,人工排查不仅耗时,还容易受个人经验影响而产生遗漏。
过去难以自动化,主要原因并不是 KRL 完全不可分析,而是项目现场的程序组织不够规范,命名习惯和调用方式差异较大。近年来,随着标准化命名、模板化程序框架、固定备份结构和统一调试规范逐步普及,工业机器人程序已经具备了较稳定的结构特征。这意味着,基于程序文本本身开展静态分析,从“人工经验驱动”转向“规则与语法驱动”,已经具备现实条件。
图片标题:图 1 KRL 程序静态分析总体思路图
图片内容描述: 图中从“机器人备份文件”出发,依次经过“提取 KRL 源文本”“词法分析”“语法分析”“结构化理解”“后续进一步分析与组合”几个阶段,突出本文关注的是前半部分的理论基础。
2. 问题定义与研究目标
- 明确本文的核心议题:通过对KRL语法规则的分析,对编译原理的研究,为自动化分析程序提供理论基础。
- 说明研究目标不是执行程序,而是静态分析程序结构,因此并不需要完整制作出一个编译器。
- 界定分析范围、输入对象(程序字符串)与预期输出结果(所需相关数据)。
本文研究的问题可以概括为:面对 KUKA 工业机器人备份中的 KRL 程序文本,是否能够通过编译原理中的词法分析与语法分析方法,将其转化为适合后续静态分析的结构化表示,从而为调用关系识别奠定基础。
这里需要强调的是,本文并不追求实现一个完整的 KRL 编译器。完整编译器的目标是将源程序转为目标程序,或驱动解释执行;而本项目面对的任务是程序理解,其核心目标是“读懂程序结构”,而不是“运行程序语义”。换言之,本文讨论的是一种面向静态分析的语言处理过程,其重点在于识别程序的形式结构,例如程序定义、语句块、控制流、调用表达式等。
从输入角度看,本文的分析对象是 KRL 程序字符串,主要来自机器人备份中的 .src 与 .dat 文件;从输出角度看,本文不直接输出最终的调用图,而是输出能够支撑后续调用关系识别的结构化语法基础。这一基础最直接的体现,就是 krl.g4 所描述的文法体系,以及据此能够生成的词法分析器与语法分析器。
表 1 对本文的研究对象和预期产出做了概括。
| 项目 | 内容 |
|---|---|
| 输入对象 | 机器人备份中的 KRL 源程序文本(主要为 .src、.dat) |
| 研究目标 | 建立适合自动分析的 KRL 文法规则与语法解释体系 |
| 不做的内容 | 程序执行、控制器仿真、完整编译、在线控制 |
| 直接产出 | krl.g4 文法文件与可解释的词法/语法分析框架 |
| 间接价值 | 为后续调用关系识别、结构提取、可视化分析提供基础 |
3. 工业机器人程序调用关系识别的可行性分析
- 从原理上论证 KRL 程序是一种结构化工业控制语言,存在清晰的语法边界和调用语句模式,因此具备静态分析基础。
- 从数据来源上论证机器人备份文件中包含足够的源代码、配置和元信息,可支撑调用关系恢复。
- 从成本上分析使用自动化语法分析替代人工阅读的必要性与经济性。
- 从编译原理角度,对于字符串进行一系列操作是可以得到该文本所蕴含的"信息"。
- 从工程实现上说明已有工具链可以进行辅助,如利用Antlr4来进行词法分析和语法分析。
3.1 原理上的可行性
KRL 并不是自然语言,也不是完全自由的脚本语言,它具有明显的结构性:程序由关键字定义边界,语句具有固定的组合模式,控制流依赖 IF、SWITCH、FOR、LOOP 等显式结构,过程和函数调用通常也以明确的调用语句形式出现。这意味着,只要能够正确识别关键字、标识符、字面量和语句边界,就有机会把原本连续的文本整理成可分析的结构。
从编译原理角度看,程序文本中的“信息”并不是天然不可见的。所谓静态分析,本质上就是先把字符流切分为 token,再把 token 组合成语法结构,最后基于这些结构进行进一步的语义判断。因此,只要 KRL 程序具有稳定的词法和语法模式,就可以通过语言处理方法把隐藏在文本中的程序结构提取出来。
3.2 数据来源上的可行性
KUKA 机器人备份文件一般保留了源代码、配置文件和一定的元信息。对于本文关注的静态分析任务来说,最关键的是 .src 和 .dat 文件中包含了程序主体、变量声明、过程/函数定义以及调用相关文本。这些内容并非黑盒二进制,而是可读取、可解析的文本资源。
此外,备份文件通常保留原始文件路径、文件名和程序分布方式,使得“模块归属”和“文件组织方式”也能被纳入分析。这一点对于后续理解程序结构非常关键,因为工业机器人程序并不只是单文件语言,它往往依赖文件层级和项目命名习惯共同组织语义。
3.3 成本上的可行性
人工阅读 KRL 程序的主要问题,不是“完全做不到”,而是“规模上不经济”。一个或几个程序靠人工理解尚可完成,但当同一线体下机器人数量上升、车型分支增多、项目版本累积后,人工方式的重复劳动迅速增加。自动化分析并不要求替代全部人工判断,只要能把大部分结构关系快速梳理出来,就足以显著降低现场人员的理解成本。
从研发成本看,直接使用正则表达式进行大量特征匹配虽然上手快,但后续维护困难;完全手写解析器则实现复杂、验证成本高。相比之下,使用成熟的语法分析工具来承接 KRL 语言整理工作,在成本与收益之间更平衡。
3.4 工具链上的可行性
ANTLR4 已经是成熟的语法分析工具。它能够根据文法文件自动生成词法分析器与语法分析器,并提供解析树访问机制,这使得研究重点可以从“如何手写解析器”转向“如何准确表达 KRL 文法”。对本文而言,这种工具链的存在极大降低了工程门槛,使得问题的核心转化为:如何为 KRL 设计出一份可被 LL(*) 分析器接受、又足够贴合工业现场实际的文法。
表 2 给出了三种常见方案的对比。
| 方案 | 优点 | 局限 | 适合程度 |
|---|---|---|---|
| 正则匹配 | 上手快,适合局部模式识别 | 难以稳定处理嵌套结构、优先级和复杂语句 | 低 |
| 手写递归解析 | 可高度定制 | 开发与维护成本高,正确性验证困难 | 中 |
| ANTLR4 文法驱动解析 | 结构清晰,便于维护与扩展,工具链成熟 | 前期需要认真整理文法 | 高 |
4. KRL 语言特征与分析难点
- 概述 KRL 的文件组织方式、程序声明方式和典型调用写法。
- 说明
.src、.dat分离、内联注释、系统指令、宏折叠语句等语言特征对语法分析带来的影响。 - 分析调用识别中的主要难点,如大小写不敏感、语法片段不完全标准化、备份文件结构复杂、业务语义强依赖项目约定等。
KRL 是一种典型的工业控制语言。它既有类似传统编程语言的程序定义、条件判断、循环和函数调用,也有大量与机器人控制密切相关的运动语句、模拟量控制语句、中断语句和工艺专用表达方式。因此,它并不是“语法极其简单的配置语言”,也不是可以完全照搬通用语言分析思路的高级语言。
KRL 的第一个显著特点,是 .src 与 .dat 的分离。.src 更偏向可执行程序主体,.dat 则承担变量、常量、枚举、结构体等数据定义。二者共同组成一个模块。这种文件组织方式要求分析器不能只看单个文件,而必须理解“一个模块可由两个文件共同构成”这一事实。
第二个特点是,KRL 文件头部常出现若干以 & 开头的控制行,例如版本、模板、磁盘路径和程序注释等。这些控制行不是普通可执行语句,但又真实存在于源文件文本中。如果忽略不当,就会干扰正常语法分析;如果完全丢弃,又会失去部分有价值的元信息。因此,如何对这类内容进行单独建模,是 KRL 文法设计的一个重要问题。
第三个特点是,KRL 在工业现场中具有较强的“项目约定性”。例如某些程序名承担特定角色,某些 SWITCH 结构被约定用来按程序号或车型号进行分派,某些轨迹调用遵循特定命名形式。也就是说,KRL 的形式语法虽然能描述“什么写法是合法的”,但程序真正的业务含义往往还依赖行业约定和项目规范。这也是本文后续强调“语法分析是基础,而不是全部”的原因。
第四个特点是大小写不敏感。对于人来说,这通常只是书写习惯问题;但对于自动分析器来说,这意味着词法规则不能简单按大小写严格匹配,否则同样的关键字会被错误地视为不同 token。与之类似的还有“某些关键字在某些上下文中又能当作标识符使用”的现象,这也是 KRL 在文法设计上比表面更复杂的原因。
图片标题:图 2 .src 与 .dat 文件组织关系示意图
图片内容描述: 图中展示一个模块由同名 .src 和 .dat 文件共同组成,.src 保存程序体,.dat 保存数据定义,并通过模块名与文件名建立对应关系。
5. 编译原理基础与本文方法选择
- 说明词法分析、语法分析、语义分析在静态程序理解中的作用。
- 解释为什么本项目采用“词法分析 + 语法分析 + 语义遍历”的经典编译技术路线。
- 对比正则匹配、手写递归解析和 ANTLR4 自动生成分析器的优劣,论证采用 ANTLR4 的合理性。
编译原理通常被理解为“编译器如何把高级语言变成机器代码”的理论,但它的核心价值其实更广。对于本文关心的静态分析问题而言,编译原理提供的是一套把源程序文本转为结构化表示的可靠方法。即便最终并不生成目标代码,词法分析、语法分析和语义分析仍然是理解程序结构的自然路径。
词法分析解决的是“切词”问题,即把连续字符流切分成有意义的最小单位,例如关键字、标识符、字面量、操作符和分隔符。语法分析解决的是“组句”问题,即判断这些 token 如何组成更高层次的结构,例如过程定义、条件语句、循环语句和表达式。语义分析则进一步讨论这些结构在具体任务中的含义,例如某个调用表达式是否表示一次真正的程序调用,某个分支结构是否承担车型分派作用。
本文之所以采用“词法分析 + 语法分析 + 后续语义理解”的路线,是因为这种路线与问题本身高度匹配。当前任务不是简单从文本里搜若干关键字,而是要稳定识别程序结构。如果跳过词法和语法阶段,直接用字符串或正则处理,很快就会遇到嵌套结构、优先级、分支语句和多义表达的问题。
ANTLR4 的优势在于,它把“语言规则的描述”与“分析器的生成”清晰分离。开发者不需要手工编写大量状态转移逻辑,只需把语言结构准确写成文法规则,即可生成对应的 Lexer 和 Parser。对本文来说,这意味着工作重心不是“实现一个分析器框架”,而是“把 KRL 语言整理成可以被形式化描述的文法”。
图片标题:图 3 ANTLR4 的词法分析与语法分析链路图
图片内容描述: 图中依次展示字符流进入词法分析器形成 token 序列,再进入语法分析器形成语法树,最后为后续静态分析任务提供结构化输入。
6. 面向 LL(*) 的 KRL 文法建模思路
- 说明 ANTLR4 基于 LL(*) 分析策略,对文法左递归、歧义性和上下文依赖有一定要求。
- 介绍如何将 KRL 的主要语法结构抽象为可被 LL(*) 接受的文法规则。
- 重点说明对程序定义、调用语句、条件分支、循环语句、参数列表等结构的规则改写思路。
- 说明如何通过文法拆分、优先级控制、可选项约束等方式降低歧义。
ANTLR4 采用的是 LL(*) 风格的自顶向下分析策略。对实际工程来说,这意味着文法设计要尽量清晰地体现“从前往后读、逐层做选择”的结构,而不能把大量含糊或高度依赖上下文的写法直接堆在一起。如果文法规则之间区分不清,分析器就容易出现歧义、回溯复杂或难以维护的问题。
因此,KRL 文法整理的关键,并不是机械照抄语言手册,而是把工业现场中的真实写法转换为更适合形式描述的结构。以程序入口为例,KRL 文件并非只有一种形式,.src 与 .dat 对应两种不同内容;以表达式为例,不同运算之间存在优先级和结合性关系;以调用表达式为例,变量访问、结构体成员访问和函数调用在文本上相似,但在语法角色上并不相同。如果这些层次不加区分,后续分析就会变得混乱。
本文在建模时采用了几项基本原则。第一,优先按语言层次拆分规则,例如将“文件入口”“程序定义”“语句”“表达式”“字面量”分层组织。第二,优先把差异大的结构分开建模,例如过程与函数、数据文件与代码文件、变量访问与调用表达式。第三,对存在可选项或多种变体的结构,尽量通过规则拆分和局部约束降低歧义,而不是在单条规则中无序堆叠。
这种整理思路最终体现在 krl.g4 中。它不是对 KRL 语言的百科全书式收录,而是一份面向静态分析任务的核心文法。换言之,文法设计的目标不是覆盖所有极少见的历史写法,而是在满足主要工业场景的前提下,提供稳定、可读、可维护的解析基础。
7. 词法分析器设计(重点篇章,需结合已编写的krl.g4进行举例展示)
- 说明需要识别的基本记号类别,如关键字(分类整理好列出)、标识符、字面量(数字、字符串)、注释、系统指令与分隔符。
- 介绍大小写不敏感处理策略以及对 KRL 特殊词法现象的兼容方式。
- 说明词法分析阶段如何为后续语法分析提供稳定的 token 序列。
7.1 词法分析器的任务
如果把 KRL 源程序看作一整段连续文本,那么词法分析器的工作,就是把这段文本切分成一个个“有名字的片段”。例如 DEF 应识别为过程定义关键字,P13 应识别为标识符,123 应识别为整数字面量,;注释 应识别为注释,
应识别为换行。
只有完成这一步,语法分析器才能继续判断:“这些 token 究竟组成了一个过程定义、一个循环语句,还是一次调用表达式”。因此,词法层的目标不是理解程序含义,而是给后续语法层提供稳定而一致的原材料。
7.2 krl.g4 中词法规则的整体组织方式
在 krl.g4 中,词法规则位于文件后半部分,采用大写命名。该文件首先定义了特殊文本单元,例如注释与控制行;随后定义各种关键字 token;再定义空白与换行;最后定义字面量、标识符和一组用于大小写不敏感匹配的 fragment 规则。这样的组织方式符合 KRL 的实际需要:先处理高优先级、容易与普通标识符冲突的特殊结构,再处理通用规则。
读者在阅读 krl.g4 的 lexer 部分时,可以按以下顺序理解:
- 先看“哪些内容被单独切出来”,如注释与控制行;
- 再看“哪些单词被定义为关键字”,如
DEF、IF、SWITCH; - 再看“哪些内容属于字面量”,如整数、实数、字符串、字符;
- 最后看“剩余普通名字如何识别”,即
IDENTIFIER以及大小写不敏感片段。
7.3 注释与控制行:为什么必须单独建模
KRL 中以分号 ; 开头的是行内注释,而以 & 开头的内容则是控制行,例如版本号、模板路径、磁盘路径等。它们都真实存在于源程序文件里,但作用不同。
在 krl.g4 中,注释规则写成如下形式:
Comment
: ';' (~('\n' | '\r'))* -> channel(HIDDEN)
;这里最关键的不是“分号后面跟随到行尾”,而是 -> channel(HIDDEN)。这表示:注释会被词法分析器识别出来,但不会进入语法分析器默认消费的 token 通道。这样设计有两个好处。第一,注释不会干扰正常语法匹配;第二,注释并没有被彻底丢弃,后续如果有需要,仍然可以从隐藏通道中取回。对静态分析而言,这是一种很实用的折中方式。
控制行则采用另一种处理方式:
KrlControlLine
: '&' (~('\n' | '\r'))*
;控制行没有被直接跳过,而是保留为一种独立 token。这是因为控制行虽然不属于普通可执行语句,但它在文件中往往承担元信息作用,例如记录程序模板或访问权限。把它单独建模,既避免它与普通语句混淆,也为后续保留了解释空间。
7.4 关键字:不是简单罗列,而是功能分组
KRL 关键字很多,但若只是把它们逐个列出来,读者很难建立整体认识。因此,更合理的方式是按功能分组理解。
表 3 给出了 krl.g4 中主要词法记号的分类方式。
| 记号类别 | 代表规则 | 作用说明 |
|---|---|---|
| 程序定义类 | DEF、DEFFCT、DEFDAT、END、ENDFCT、ENDDAT | 定义过程、函数、数据文件及其边界 |
| 控制流类 | IF、ELSE、ENDIF、FOR、LOOP、SWITCH、CASE、DEFAULT | 描述程序流程结构 |
| 调用与中断类 | EXT、EXTFCT、INTERRUPT、TRIGGER、RETURN | 描述调用声明、中断和触发机制 |
| 运动指令类 | PTP、LIN、CIRC、LIN_REL、SPTP、SLIN | 描述机器人运动行为 |
| 输入输出类 | SIGNAL、ANIN、ANOUT、WAIT、ON、OFF | 描述信号与等待、模拟量控制 |
| 数据定义类 | DECL、ENUM、STRUC、GLOBAL、CONST、PUBLIC | 描述变量、枚举、结构体和修饰符 |
| 字面量类 | INTLITERAL、REALLITERAL、STRINGLITERAL、CHARLITERAL | 承载基础数值和文本 |
| 标识符类 | IDENTIFIER、krlIdentifier | 表示模块名、变量名、程序名等 |
这样分组后,读者就能明白:词法分析器不是无差别切词,而是在尽量把不同语言角色分离出来,为后续语法分析创造条件。
7.5 大小写不敏感:为什么在词法层解决
KRL 的一个典型特征是大小写不敏感。也就是说,DEF、def、Def 在语言语义上通常等价。如果词法规则只按某一种大小写形式定义,那么现实中的大量程序都无法被稳定识别。
krl.g4 采用了一种直接而清晰的方案:为字母 A 到 Z 分别定义 fragment 规则,例如:
fragment A : ('a' | 'A');
fragment D : ('d' | 'D');
fragment E : ('e' | 'E');
fragment F : ('f' | 'F');在此基础上,关键字规则写成:
DEF : D E F;这样一来,无论源程序写成 DEF、def 还是 DeF,都能被识别为同一个 token。把大小写兼容放在词法层解决,有两个优点。第一,语法层无需反复考虑大小写问题;第二,关键字与标识符的识别规则能保持一致性。
7.6 为什么有些关键字又被纳入 krlIdentifier
KRL 的一个特殊现象在于,有些词在某些上下文中是关键字,在另一些上下文中又可能出现在标识符位置。例如 SEC 既能出现在 WAIT SEC 10 中表示时间单位,也可能在结构体成员访问中作为普通名字出现。类似地,WITH、DELAY、DISTANCE 等词也可能同时扮演不同角色。
因此,krl.g4 没有简单地把“关键字”和“标识符”完全对立,而是额外定义了 krlIdentifier 这一语法层概念,将 IDENTIFIER 与若干特定关键字共同纳入:
krlIdentifier
: IDENTIFIER
| DISTANCE
| STEP
| SEC
| WITH
| ASYCANCEL
| BRAKE
| DELAY
| DO
| MAXIMUM
| MINIMUM
...这并不是词法层的混乱,而是一种对 KRL 语言现实的适配。换句话说,词法分析器先把这些词识别为关键字 token,而语法层再根据上下文决定:它们在此处究竟承担关键字功能,还是可以视为标识符的一部分。这样处理,既保留了词法分类的清晰性,又兼顾了 KRL 写法的灵活性。
7.7 字面量与普通标识符:让源程序中的值“有类型地进入分析器”
krl.g4 中对数字、字符、字符串的定义较完整。例如 REALLITERAL 支持科学计数法,INTLITERAL 不仅支持十进制,还支持 KRL 常见的十六进制与二进制写法;CHARLITERAL 与 STRINGLITERAL 则通过转义序列规则处理特殊字符。
这类规则的重要性在于,它们让源程序中的“值”能够带着形式信息进入语法层。例如,程序里写的是普通整数,还是带指数的实数,还是枚举字面量,这些差异在后续结构理解中都有价值。即使本文不深入讨论完整语义求值,词法阶段仍然需要把这些不同形式的文本准确区分开来。
普通名字则由 IDENTIFIER 负责识别:
IDENTIFIER
: [a-zA-Z_$][a-zA-Z_$0-9]*
;这个规则说明,KRL 中的变量、模块、函数、过程名称不能以数字开头,但可以包含字母、数字、下划线和 $。这与工业现场中大量系统变量、信号变量的书写方式是相吻合的。
7.8 空白、换行与词法顺序的重要性
在普通高级语言中,换行有时只是排版问题;但在 KRL 中,很多规则以“换行结束一条语句”为隐含边界。因此,NEWLINE 并没有被简单忽略,而是作为一种明确的 token 保留:
WS : (' ' | '\t' | '\u000C') -> skip;
NEWLINE : '\r'? '\n';这里的设计含义非常明确:空格和制表符对大多数语法结构没有决定性作用,因此可直接跳过;而换行会影响语句边界和程序结构,因此必须保留到语法层。
同时,阅读 lexer 规则时还要注意一个原则:词法规则的定义顺序会影响匹配结果。这也是为什么 krl.g4 先定义大量具体关键字,再定义较宽泛的 IDENTIFIER。如果把通用标识符规则放在前面,许多本应识别为关键字的文本就可能先被吃掉,导致后续语法分析混乱。
7.9 本章小结:如何阅读 krl.g4 的 lexer 部分
读者在阅读 krl.g4 的词法层时,可以按以下顺序把握其含义:
- 先找特殊文本单元:注释、控制行、换行、空白;
- 再找被明确列出的关键字;
- 再看各种字面量如何定义;
- 最后看普通标识符与大小写不敏感片段。
一旦理解了这一组织方式,读者就会发现,krl.g4 的 lexer 部分并不是机械罗列单词,而是在为后续语法结构搭建稳定、可控的 token 基础。
8. 语法分析器设计(重点篇章,需结合已编写的krl.g4进行举例展示)
- 说明如何围绕程序声明、过程(Process)/函数(Function)的调用、条件流程和语句块等构建语法树。
- 介绍程序主体、声明区、执行语句区在语法树中的组织方式。
- 说明语法树生成后,为什么可以为后续调用关系提取提供清晰的结构化基础。
8.1 语法分析器的任务
如果说词法分析器负责切分 token,那么语法分析器负责的就是“把这些 token 组织成层次结构”。在 KRL 程序中,读者关心的并不是单个 DEF 或 IF 本身,而是 DEF ... END 之间构成了一个过程定义,某个 SWITCH ... ENDSWITCH 构成了一个分支结构,某个 targetName(...) 则可能表示一次调用表达式。
因此,语法分析器的目标并不是记录“源码里出现过哪些词”,而是回答“这些词按什么结构组合在一起”。这一步完成后,程序文本就从“行和字符的集合”变成了“有层次的语言结构”,也就具备了后续静态分析的基础。
8.2 从入口规则开始:为什么 start 同时覆盖 .dat 与 .src
krl.g4 的入口规则如下:
start
: NEWLINE* krlControlHead moduleData EOF # dataFile
| NEWLINE* krlControlHead moduleSource EOF # sourceFile
;这条规则非常关键,因为它首先回答了“这个文法究竟在解析什么文件”。在 KRL 中,数据文件和代码文件是两种不同的文本形态,但它们又共享一些共同成分,例如控制行和换行。因此,入口规则采用双分支建模:一条分支面向 .dat 文件,另一条分支面向 .src 文件。
这种写法有三个好处。第一,读者能够清晰看到文法覆盖的文件边界。第二,控制头被统一放在入口前部,不必在两个分支中重复书写。第三,后续所有规则都可以围绕“数据文件”和“代码文件”两类内容分别组织,而不至于混成一个巨大的不透明入口。
在阅读整个 krl.g4 时,入口规则就是最先要看的部分。它告诉读者:这不是一份只处理单一脚本形式的文法,而是一份同时覆盖两种文件类型的完整语言入口。
8.3 控制头与数据文件部分:语法层如何承接词法层的特殊内容
控制头通过 krlControlHead 建模:
krlControlHead
: (KrlControlLine NEWLINE+)*
;这说明:文件头可以有零个或多个控制行,每个控制行后面跟一个或多个换行。这种写法延续了词法层对 KrlControlLine 的单独建模,也说明控制头虽然不属于普通程序体,但仍然是完整文件结构的一部分。
数据文件部分则以 moduleData 为核心:
moduleData
: DEFDAT moduleName PUBLIC? NEWLINE+ dataList ENDDAT NEWLINE*
;读者从这条规则中可以直接看到 KRL 数据文件的整体轮廓:先有 DEFDAT,后跟模块名,可选 PUBLIC,然后是若干数据行,最后以 ENDDAT 结束。这种写法把数据文件当作一个完整语法单元,而不是若干声明的散乱集合。
进一步往下看,dataList 与 dataLine 将枚举定义、结构体定义、变量声明、赋值表达式、旧式导入语句等内容统一纳入数据文件内部。这种组织方式说明,数据文件虽然主要服务于数据定义,但它内部仍然包含多种子结构,因此需要继续分层处理。
8.4 代码文件部分:为什么 moduleSource 要区分主程序与子程序
在 .src 文件部分,moduleSource 规则如下:
moduleSource
: mainRoutine NEWLINE? (subRoutine | NEWLINE)*
;这表明,一个 KRL 代码文件并不是简单的一串语句,而是由“主程序”以及若干“子程序”组成。这里的 mainRoutine 与 subRoutine 最终都可以落到过程或函数上,但把它们在模块层面区分开,有助于表达 KRL 文件的真实组织方式:文件通常有一个核心入口,其后还可能附带多个辅助过程或函数。
这一层设计的重要意义在于,它把“文件”与“程序单元”区分开了。文件是物理容器,过程和函数是逻辑单元。只有先在语法层把这两层分开,后续才能清晰理解:一个模块内部究竟定义了哪些可调用单元。
8.5 过程与函数:为什么要分开建模
KRL 中的过程和函数虽然都属于可调用单元,但二者并不完全等价。过程通常对应无返回值的子程序,函数则带有返回类型,因此在文法中分别写成:
procedureDefinition
: GLOBAL? DEF procedureName '(' parameterList ')' NEWLINE routineBody END
;
functionDefinition
: GLOBAL? DEFFCT typeName functionName '(' parameterList ')' NEWLINE routineBody ENDFCT
;这种区分看似细节,实际上非常关键。它不仅体现了 KRL 语言的原生分类,也避免在语法层把两种结构混成一个含糊规则。对于 ANTLR4 而言,这种明确分流可以降低歧义;对于读者而言,这也让 krl.g4 的结构更清楚:看到 DEF 与 END 就想到过程,看到 DEFFCT 与 ENDFCT 就想到函数。
同时,两种定义规则都把参数列表和 routineBody 纳入统一框架。这说明:虽然过程和函数在入口和结束关键字上不同,但它们内部结构仍然高度相似,因此可以共享后续的程序体规则。
8.6 程序体:声明区与实现区的划分
routineBody 在文法中被继续拆成两个部分:
routineBody
: routineDataSection routineImplementationSection
;这条规则反映了 KRL 程序组织的一个重要特点:程序内部通常既有声明区,也有执行语句区。声明区可能包含前置声明、变量定义、枚举定义、结构体定义等内容;实现区则由实际语句组成。
将二者分开,有助于解决两个问题。第一,读者可以清晰理解程序体内部结构,而不把“声明”和“执行”混在一起。第二,后续静态分析在寻找调用语句时,也能更明确地把注意力放在实现区,而不是对整个程序体一视同仁。
8.7 statement:为什么它是语法层最重要的规则之一
在 KRL 中,真正决定程序运行流程的核心结构,大多汇聚在 statement 规则中。krl.g4 将空语句、IF、FOR、LOOP、REPEAT、WHILE、SWITCH、WAIT、RETURN、运动语句、触发语句、中断语句以及“表达式作为语句”等内容统一纳入 statement:
statement
: NEWLINE
| CONTINUE NEWLINE
| EXIT NEWLINE
| IF expression THEN NEWLINE statementList ...
| FOR krlIdentifier '=' expression TO expression ...
| LOOP NEWLINE statementList ENDLOOP NEWLINE
| SWITCH expression NEWLINE+ switchBlockStatementGroups ENDSWITCH NEWLINE
| expression NEWLINE
| ...这条规则之所以重要,是因为它承接了 KRL 程序中几乎所有“能发生在执行区里的语言行为”。把这些写法统一归入 statement,可以让语法树的结构保持一致:无论是分支、循环还是运动指令,本质上都是“程序体中的一条语句”。
同时,这种设计也适合 ANTLR4 的分层解析思路。语法分析器不需要一开始就判断某段文本是否具有业务意义,而只需先判断:它在语言结构上属于哪类语句。业务含义可以留到后续层面处理。
特别值得注意的是 SWITCH、CASE、LOOP 等结构。在工业机器人程序中,这些结构往往承担程序号分派、车型分支选择、工艺流程组织等重要职责,因此它们不仅是一般语法结构,也是后续静态分析时最值得关注的结构性节点。
8.8 表达式系统:为什么 expression 要分层递归
表达式规则是 krl.g4 中另一个核心部分。其典型形式如下:
expression
: primary
| op=(NOT | B_NOT) expression
| op=('+' | '-') expression
| expression op=':' expression
| expression op=('*' | '/') expression
| expression op=('+' | '-') expression
| expression op=(AND | B_AND) expression
| expression op=(OR | B_OR) expression
| expression op=('==' | '<>' | '<=' | '>=' | '<' | '>') expression
| expression op='=' expression
;这条规则的目的,不只是“允许出现表达式”,更重要的是在文法中明确不同运算的层次关系。KRL 中既有普通算术运算,也有逻辑运算、关系运算、赋值运算,甚至还有用于几何变换的 : 运算。如果不把这些内容纳入统一表达式系统,许多语句就无法被稳定描述。
之所以采用分层递归写法,是为了把运算优先级和表达式组合方式显式化。对于读者来说,理解这条规则的关键,不是记住每个分支,而是明白它表达了一个思想:复杂表达式并不是平铺展开的,而是从最基础的 primary 开始,一层层向上扩展。
这也是为什么表达式规则通常是阅读文法时最需要耐心的部分。它看起来比程序定义更抽象,但正因为如此,它也是语法分析器能够正确处理赋值、判断、调用和运动参数的基础。
8.9 primary:为什么变量、成员访问与函数调用必须分开
primary 规则定义了表达式体系中最基础的单位:
primary
: '(' expression ')'
| literal
| variableName
| variableName ('.' variableName)+
| callableName=variableName '(' ... ')'
;从这条规则可以看出,KRL 中最基础的表达式至少包括五类:括号表达式、字面量、普通变量、结构体成员访问以及调用表达式。它们在源程序文本中看起来都很常见,但在语法角色上并不相同。
例如,varName 只是一个变量引用,pos.X 则表示结构体成员访问,而 ProgramName() 则已经进入调用语义。若不在语法层把这些结构区分开,后续分析就很难稳定判断:某一段文本究竟是在取值、在访问成员,还是在发起调用。
因此,primary 的重要性在于,它把“看起来相似的基本表达式”分成了不同种类。这种拆分既有利于降低歧义,也为后续静态分析保留了更准确的结构线索。
8.10 字面量、结构体与枚举:为什么补充规则不能忽略
除了程序定义、语句和表达式之外,krl.g4 还补充了 literal、structLiteral、enumLiteral、typeName、primitiveType、userType 等规则。它们看似不像 statement 那样显眼,但实际上共同决定了文法是否完整。
例如,KRL 中结构体字面量经常用于位置点、姿态点或工艺参数描述;枚举值则用 #VALUE 的形式表示离散语义。如果文法忽略这些内容,那么很多工业现场中常见的合法写法都会被误判为非法。换句话说,这些补充规则并不是边角料,而是在保证文法可用性。
8.11 关键规则与语言结构对照
为了帮助读者建立整体认识,表 4 将若干关键规则与其对应的 KRL 语言结构做了整理。
| 文法规则 | 对应语言结构 | 设计目的 |
|---|---|---|
start | 文件入口 | 同时兼容 .src 与 .dat |
krlControlHead | 文件头控制行 | 单独承接元信息,不干扰程序体 |
moduleData | 数据文件主体 | 组织变量、枚举、结构体等内容 |
moduleSource | 代码文件主体 | 组织主程序与子程序 |
procedureDefinition | 过程定义 | 描述无返回值程序单元 |
functionDefinition | 函数定义 | 描述带返回值程序单元 |
statement | 执行语句 | 统一承接控制流、运动、调用等结构 |
expression | 表达式 | 组织优先级与运算结构 |
primary | 基础表达式 | 区分变量、成员访问、调用等基本形式 |
8.12 本章小结:如何整体阅读 krl.g4
如果读者想真正读懂 krl.g4,最有效的方式不是从中间随意挑一条规则看,而是按层次顺序阅读:
- 先看
start,明确文件入口与整体分支; - 再看
moduleData与moduleSource,理解文件级组织; - 然后看过程、函数和程序体规则,理解程序单元结构;
- 接着看
statement,理解执行区主要语言现象; - 再看
expression与primary,理解复杂语句内部如何组织; - 最后回到 lexer 规则,理解这些结构所依赖的 token 基础。
图片标题:图 4 从 KRL 程序片段到语法树层次的示意图
图片内容描述: 图中以一个简化的 KRL 程序片段为例,展示其如何从“文件入口”逐步落到“程序定义—语句—表达式—基本表达式”的层次结构中,帮助读者建立阅读文法的顺序感。
9. 从语法树到调用关系的理论转换路径规划(重点篇章,需结合项目中的pojo包里面相关代码进行举例展示)
- 说明语法分析结果不能直接等同于调用图,还需要进行语义层遍历与规则映射。
- 阐述如何从调用语句中提取被调程序名,并结合项目中的P程序、车型编号、车型程序、轨迹程序的语义进行层级映射
- 设计出对应的数据结构,指出数据的流动汇总过程。
- 解释为什么在工业机器人业务场景下,“语法识别 + 规则约束”的联合方法比单纯语法分析更可靠。
需要说明的是,语法树并不直接等于调用关系图。词法分析与语法分析的任务,是把 KRL 程序还原为结构化的语言形式,例如“这里是一个 SWITCH 语句”“这里是一次调用表达式”“这里是一个过程定义”。而调用关系识别则还要继续回答:“这次调用在业务上属于哪一层程序关系”“某个分支标签在当前项目中代表什么含义”。
因此,从语法树到调用关系的转换,本质上是一条“语法识别 + 领域规则映射”的路径。前者负责保证文本被正确理解,后者负责把这些语言结构转化为工业现场真正关心的层级关系。没有语法基础,后续规则映射会变成脆弱的文本猜测;但仅有语法树,又不足以完全解释工业项目中的业务角色。
从理论上看,调用关系提取通常至少包括三个步骤。第一,识别可调用单元与调用表达式;第二,识别分支结构、程序号或标签等中间语义节点;第三,结合项目命名规范与结构约定,把这些语言结构组织成业务可理解的层级关系。由此可见,语法分析是后续工作的前提,但并不是全部。
10. 自动化调用关系识别方法的正确性与局限性
- 论证该方法在结构化 KRL 程序上的适用性与正确性来源。
- 说明其对规范命名、固定项目组织结构和配置辅助规则的依赖。
- 分析其局限性,如动态行为、间接语义、项目非标准写法等难以完全通过静态分析覆盖。
本文方法的正确性,主要来自两个方面。其一,KRL 本身具有较强的形式结构,关键字边界、程序定义、控制流与调用表达式都可以通过文法规则稳定识别。其二,工业现场的 KRL 项目往往遵循一定模板和命名规范,使得语法分析结果能够进一步被组织成更有意义的程序结构。
但这并不意味着自动化分析可以无条件覆盖所有情况。首先,静态分析无法完全还原运行时动态行为。例如某些条件分支是否真正执行,某些参数如何影响调用路径,并不能仅靠语法树完全判断。其次,若项目中存在大量非规范命名、历史遗留写法、极少见的控制器版本差异,文法与规则就可能需要额外适配。再次,工业程序中有些语义并不直接写在语法结构里,而是隐含于工艺约定或设备配置中,这超出了纯语法分析的能力边界。
因此,更准确的结论是:基于 KRL 文法和编译原理的自动化分析方法,对于结构化、规范化的工业机器人程序具有较高适用性,是构建程序理解系统的合理基础;但在面对复杂现场时,仍应保留对领域规则、项目约定和人工复核的依赖。
11. 结论
- 总结本文对“自动识别工业机器人程序"在kuka语言、编译理论方面的分析论证结果。
- 强调 ANTLR4 词法与语法分析在本项目中的核心基础作用。
- 为后续工程实现报告做好理论承接。
本文围绕 KRL 语言和编译原理方法,对工业机器人程序自动识别问题进行了理论层面的分析与论证。结论可以概括为三点。第一,KRL 程序虽然服务于工业控制场景,但其文本形式具有清晰的结构边界,因此适合采用词法分析与语法分析方法进行静态处理。第二,自动化识别程序结构在数据来源、工程成本和工具链成熟度方面都具备现实可行性。第三,通过对 KRL 主要语法结构进行整理,并按照 ANTLR4 LL(*) 的要求进行文法建模,可以形成一套兼具可读性、可维护性和可扩展性的 krl.g4 规则体系。
从全文结构来看,第 7 章与第 8 章构成了本文的核心:前者回答“字符如何被切成有意义的词法单元”,后者回答“这些词法单元如何组成层次化语法结构”。当这两部分建立起来以后,KRL 程序就不再只是难以直接阅读的工业文本,而成为可以被系统解释的形式语言对象。
因此,krl.g4 不只是一个工程文件,更是本文理论分析的集中体现。它把 KRL 语言从现场经验层面的“会看程序”,转化为可被形式化描述、可被自动分析处理的对象。后续工程实践报告将在此基础上继续展开,说明这些文法规则如何支撑模块识别、调用关系提取、可视化展示与 Excel 导出等实际功能。
参考资料
- KUKA Robot Language(KRL)相关语法资料与控制器编程说明。
- ANTLR4 官方文法设计与
LL(*)分析机制相关文档。 - 项目文法文件:
/Users/liuke/IdeaProjects/KRLParser/krl-core/src/main/resources/krl.g4。