- 编程语言
- 编译器
- 开发工具
【免费下载链接】grammars-v4
Grammars written for ANTLR v4; expectation that the grammars are free of actions.
Racket BSL(Beginner Student Language)是 How to Design Programs(HtDP)教材为初学者设计的受限函数式语言,本仓库的racket-bsl目录基于官方语言参考实现了一版无内嵌动作(free of actions)的 ANTLR4 文法。本文以 racket-bsl/README.md 为主线,结合 BSL.g4、示例程序与 Maven 构建配置,逐层拆解该文法的解析规则、词法设计、测试用例写法以及构建验证方式,帮助你快速理解如何用 ANTLR4 描述一个 Lisp 系教学语言,并为在此基础上扩展 BSL 变体(如 ISL、带 lambda 的中间语言)提供可直接照搬的骨架。
BSL 文法是什么:从 README 出发
racket-bsl目录下的 README 非常精简,核心信息就两点:这是一个针对 Racket BSL 的"简单 ANTLR4 文法"(A simple ANTLR4 grammar),并且与仓库的整体约定一致——文法不包含任何内嵌 action,纯描述语法结构。文法实现集中在一个文件 BSL.g4 中,词法规则与语法规则同文件共存,并配有一个真实可用的示例程序 temperature-conversion.rkt。
从项目根目录的 README.md 可以确认整个仓库的约定:根目录下的每个子目录名即为语言或文件格式的全小写名称,racket-bsl即 Racket BSL 教学语言的文法目录。目录结构如下:
racket-bsl/ ├── BSL.g4 # 唯一文法源文件(词法 + 语法) ├── README.md # 文法说明 ├── desc.xml # 声明可生成的目标语言 ├── pom.xml # Maven 构建与测试配置 └── examples/ └── temperature-conversion.rkt # 华氏/摄氏温度换算示例顶层结构:program 与 defOrExpr
BSL 程序由"定义、表达式、测试用例、库导入"四类顶层构件自由拼接而成。文法通过两个规则组织顶层结构:
program : defOrExpr+ EOF ; defOrExpr : definition | expr | testCase | libraryRequire ;其中program是整个文法的入口规则(entryPoint),EOF强制要求词法分析器消化全部输入,避免部分匹配导致的静默错误。这一设计与pom.xml中测试插件配置的entryPoint值一一对应:
<entryPoint>program</entryPoint> <grammarName>BSL</grammarName> <exampleFiles>examples/</exampleFiles>即 pom.xml 中的 antlr4test-maven-plugin 会以program为入口,用examples/目录下的全部示例文件作为测试输入,验证文法能否完整解析。
定义规则 definition:函数、变量与结构体
BSL 程序的核心之一是用define定义函数、变量与结构体。文法给出了四种定义形态:
definition : '(' 'define' '(' name NAME+ ')' expr ')' | '(' 'define' name expr ')' | '(' 'define' name '(' 'lambda' '(' NAME+ ')' expr ')' ')' | '(' 'define-struct' name '(' name* ')' ')' ;- 第一条对应多参函数定义,如
(define (fahrenheit->celsius f) ...),注意参数列表NAME+要求至少一个形参,且形参必须是NAME而非任意name(即不能是SYMBOL); - 第二条对应变量绑定,
(define x expr); - 第三条是
(define name (lambda (params) expr))的显式 lambda 写法,与第一条在语义上等价的两种语法形态; - 第四条对应define-struct 结构体定义,字段列表
name*允许为零个或多个字段。
示例程序 temperature-conversion.rkt 中的(define (fahrenheit->celsius f) (* 5/9 (- f 32)))正是第一条规则的实际应用:函数名fahrenheit->celsius中的-、>字符之所以能被词法器接受,依赖于NAME规则的字符集设计(详见后文)。
表达式规则 expr:cond、if、逻辑组合与字面量
表达式是 BSL 语法的主体,文法支持函数调用、条件分支、逻辑组合与四类原子字面量:
expr : '(' name expr+ ')' | '(' 'cond' ('[' expr expr ']')+ ')' | '(' 'cond' ('[' expr expr ']')* '[' 'else ' expr ']' ')' | '(' 'if' expr expr expr ')' | '(' 'and' expr expr+ ')' | '(' 'or' expr expr+ ')' | '’()' | name | NUMBER | BOOLEAN | STRING | CHARACTER ;逐条解读:
'(' name expr+ ')'是函数调用,expr+保证至少一个实参,因此无参调用(如(f))在本文法中不被接受——这与 BSL 教学语言的常见用法一致,但确实是使用时的边界约束;cond支持两种形态:无 else 分支(('[' expr expr ']')+)与带 else 分支(注意语法文本中'else '关键字后带一个空格字符,这与 Racket 实际关键字else存在细微差异,是阅读与复用本文法时需要留意的细节);if固定三参:条件、真分支、假分支;and/or要求expr expr+,即至少两个参数;'’()'表示空表字面量,注意这里使用的是弯引号字符 ’(U+2019),而非普通单引号,与下方SYMBOL词法规则保持一致;name、NUMBER、BOOLEAN、STRING、CHARACTER构成原子表达式。
测试用例 testCase:BSL 的单元测试语法
BSL 的一大特色是把测试写成与程序同级的顶层构件。文法完整覆盖了六种check-测试形态:
testCase : '(' 'check-expect' expr expr ')' | '(' 'check-random' expr expr ')' | '(' 'check-within' expr expr expr ')' | '(' 'check-member-of' expr expr+ ')' | '(' 'check-satisfied' expr name ')' | '(' 'check-error' expr expr? ')' ;check-expect断言两表达式求值结果相等,是最常用的测试形式;check-within多一个容差参数,适合浮点比较;check-member-of断言结果属于某个候选集合(expr+);check-satisfied断言结果满足某个谓词(谓词以name出现,不能是带引号的符号);check-error断言抛出错误,expr?可选表示可附带期望的错误信息。
示例文件中的两条断言即为check-expect的真实用例:
(check-expect (fahrenheit->celsius 212) 100) (check-expect (fahrenheit->celsius -40) -40)库导入 libraryRequire
BSL 程序常需导入教学库(如htdp/bsl)与测试库(如rackunit),文法给出了四种require形态:
libraryRequire : '(' 'require' STRING ')' | '(' 'require' name ')' | '(' 'require' '(' name STRING ('(' STRING+ ')')? ')' ')' | '(' 'require' '(' name STRING pkg ')' ')' ; pkg : '(' STRING STRING NUMBER NUMBER ')' ;- 带引号字符串形式
(require "lib.rkt"); - 裸名称形式
(require rackunit),示例文件第 3 行正是这种写法; - 带包路径的形式,如
(require (planet ...))一类的复合导入; - 最后一种结合
pkg规则,形如(require (planet "pkg" ("owner" "name" 1 0))),用四个字符串/数字元素描述包名、所有者、主版本与次版本。
词法设计:从 NAME 到字面量
BSL 文法的词法部分(BSL.g4)同样值得细读,它直接决定了 BSL 中"标识符到底能包含哪些字符"这一语言定义的核心问题。
SYMBOL 与 NAME:标识符字符集
SYMBOL : '’' NAME ; NAME : ([$%&!*+\\^_~] | [--:<-Za-z])+ ;name语法规则由SYMBOL(带弯引号前缀的符号值)与NAME二选一组成。NAME的字符集直接来自官方文档:"不包含空格以及" , ' \( ) [ ] { } | ; #` 的字符序列"。从正则表达式看,允许的字符分为两组:
- 显式列出的运算符字符:
$ % & ! * + ^ _ ~; - 区间
[--:<-Za-z]:即-(0x2D)到:(0x3A)之间的字符(含- . / 0-9 :),<(0x3C)到Z(0x5A)之间的字符(含< = > ? @ A-Z),再加上小写a-z。
这解释了为何示例中函数名fahrenheit->celsius、-40中的负号都能被合法解析:-、>都在允许区间内。
NUMBER 与 INT:数字字面量
NUMBER : INT | INT '.' [0-9]* [1-9] | INT '/' INT ; INT : [1-9] [0-9]* | '0' ;数字支持三种形态:整数、小数、分数。两个值得注意的约束:
INT不允许前导零(01不合法),0单独成规则;- 小数形式
INT '.' [0-9]* [1-9]强制要求小数点后以非零数字结尾,因此5.5合法而5.0不合法——这与 BSL 文档"数字示例如 123、3/2、5.5"的表述相符。
BOOLEAN、STRING 与 CHARACTER
BOOLEAN : '#true' | '#T' | '#t' | '#false' | '#F' | '#f' ; STRING : '"' ([ -~])* '"' ; CHARACTER : '#' '\u005C' [A-Za-z0-9] | '#' '\u005C' 'space' ;- 布尔值完整兼容大小写与缩写:
#true/#T/#t与#false/#F/#f; - 字符串用可打印 ASCII(
[ -~])描述,注意文法注释中提到了\"转义示例,但词法规则本身并未实现转义处理,因此含转义符的字符串(如"This is a string with \" inside")在实际解析中会被截断,这是本文法相对官方规范的一个简化点; - 字符以
#\开头('\u005C'即反斜杠),支持单个字母/数字字符或space。
隐藏通道:LANG、COMMENT 与 WS
LANG : '#lang' ~ ('\n' | '\r')* '\r'? '\n' -> channel (HIDDEN) ; COMMENT : ';' ~ ('\n' | '\r')* '\r'? '\n' -> channel (HIDDEN) ; WS : (' ' | '\r' | '\t' | '\u000C' | '\n') -> channel (HIDDEN) ;三条规则全部送入 HIDDEN 通道,对解析树透明:#lang行(BSL 源文件的第一行声明)、;分号注释、空白字符都不会出现在语法树中。示例文件第一行#lang htdp/bsl正是由LANG规则吞掉并隐藏的。
实战验证:解析示例程序
以 temperature-conversion.rkt 为例,对照文法走一遍解析流程:
#lang htdp/bsl (require rackunit) (define (fahrenheit->celsius f) (* 5/9 (- f 32))) (check-expect (fahrenheit->celsius 212) 100) (check-expect (fahrenheit->celsius -40) -40)- 第 1 行
#lang htdp/bsl由LANG规则匹配并隐藏; - 第 3 行
(require rackunit)匹配libraryRequire的'(' 'require' name ')'分支; - 第 5-6 行
(define (fahrenheit->celsius f) ...)匹配definition的函数定义分支;函数体(* 5/9 (- f 32))是嵌套调用:外层*调用实参为5/9(分数 NUMBER)与(- f 32)(减法调用,-为 NAME,-40中负号同理可解析为 NAME 或配合 NUMBER); - 第 8-9 行两条
check-expect匹配testCase。
验证方式与整个仓库一致:在仓库根目录执行 test.sh(本质是mvn test),antlr4-maven-plugin 先基于 BSL.g4 生成解析器代码,antlr4test-maven-plugin 再以program为入口遍历examples/目录完成解析断言。构建配置中还打开了visitor与listener生成开关(pom.xml),方便你在拿到解析树后直接挂载自定义遍历逻辑。根据 desc.xml 的声明,该文法面向 CSharp、Cpp、Dart、Go、Java、JavaScript、Python3、TypeScript、Antlr4ng 等多种目标语言生成。
已知边界与扩展思路
基于文法源码可以推断出本实现相对 Racket BSL 官方规范的几个简化点,复用时需注意:
- 无引号转义:
STRING规则不处理\",含转义内容的字符串会解析异常; - 弯引号符号:
SYMBOL与空表’()使用 U+2019 弯引号而非 ASCII 单引号,输入文件必须使用与之匹配的字符; - cond 的 else 分支:关键字写作
'else '(带尾部空格),与标准 Racket 的else存在细微差异; - 无参调用不被接受:函数调用要求
expr+,与 BSL 中"至少一参"的教学约定保持一致; - 顶层层级:
definition、expr、testCase、libraryRequire以defOrExpr+任意排列,未做"定义必须位于表达式之前"等语义级约束——语义检查需在上层语义分析阶段完成。
若需扩展至带 lambda 的中间语言(ISL),可参考同仓库 racket-isl/README.md 及其对应的文法目录,作为从 BSL 出发进一步学习 HtDP 语言家族文法设计的下一站。
- 编程语言
- 编译器
- 开发工具
【免费下载链接】grammars-v4
Grammars written for ANTLR v4; expectation that the grammars are free of actions.
相关推荐
使用 ANTLR4 语法解析 Geek Code:grammars-v4 中 geekcode 语法的完整解读
使用 ANTLR4 语法解析 Geek Code:grammars v4 中 geekcode 语法的完整解读 Geek Code 是 1990 年代互联网文化
编程语言编译器开发工具用 ANTLR4 解析 Bitcoin Miniscript 表达式:grammars-v4/miniscript 语法深度解析
用 ANTLR4 解析 Bitcoin Miniscript 表达式:grammars v4/miniscript 语法深度解析 本篇文章以 grammars
编程语言编译器开发工具P'' 语言文法解析:grammars-v4 中最小图灵完备语言的 ANTLR4 实现
P'' 语言文法解析:grammars v4 中最小图灵完备语言的 ANTLR4 实现 本篇文章围绕 grammars v4 仓库中的 p/p.g4 https
编程语言编译器开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考