有很多编程语言支持包含迷你语言。PHP 嵌入在 HTML 中。XML 可以嵌入到 JavaScript 中。Linq 可以嵌入到 C# 中。正则表达式可以嵌入到 Perl 中。
// JavaScript example
var a = <node><child/></node>
想一想,大多数编程语言都可以建模为不同的迷你语言。例如,Java 可以分解为至少四种不同的迷你语言:
- 类型声明语言(包指令、导入指令、类声明)
- 成员声明语言(访问修饰符、方法声明、成员变量)
- 语句语言(控制流、顺序执行)
- 一种表达式语言(文字、赋值、比较、算术)
能够将这四种概念语言实现为四种不同的语法肯定会减少我通常在复杂的解析器和编译器实现中看到的许多意大利面。
我之前已经为各种不同类型的语言实现了解析器(使用 ANTLR、JavaCC 和自定义递归下降解析器),当语言变得非常庞大和复杂时,您通常会得到一种 huuuuuuge 语法,并且解析器实现得到真的很丑真的很快。
理想情况下,当为其中一种语言编写解析器时,最好将其实现为可组合解析器的集合,在它们之间来回传递控制。
棘手的是,包含语言(例如,Perl)通常会为包含的语言(例如,正则表达式)定义自己的终点哨兵。这是一个很好的例子:
my $result ~= m|abc.*xyz|i;
在这段代码中,主要的 perl 代码定义了一个非标准的终点“|” 对于正则表达式。实现与 perl 解析器完全不同的正则表达式解析器将非常困难,因为正则表达式解析器不知道如何在不咨询父解析器的情况下找到表达式终点。
或者,假设我有一种语言允许包含 Linq 表达式,但不是以分号结尾(如 C# 那样),我想强制 Linq 表达式出现在方括号内:
var linq_expression = [from n in numbers where n < 5 select n]
如果我在父语言语法中定义了 Linq 语法,我可以轻松地为“LinqExpression”编写一个明确的产生式,使用语法前瞻来查找括号包围。但是我的父语法必须吸收整个 Linq 规范。这是一个拖累。另一方面,一个单独的子 Linq 解析器将很难确定在哪里停止,因为它需要为外来标记类型实现前瞻。
这几乎可以排除使用单独的词法分析/解析阶段,因为 Linq 解析器将定义一组与父解析器完全不同的标记化规则。如果您一次扫描一个标记,您怎么知道何时将控制权交还给母语的词法分析器?
你们有什么感想?当今可用于实现不同的、解耦的和可组合的语言语法以将迷你语言包含在更大的父语言中的最佳技术是什么?