28
2
真正糟糕的是,就像我跟大家讨论的模板系统,来自输入的代码跟生成的代码混杂
在一起,该情况下调错是一个非常棘手的问题。对编译器调错,只要定位错误,调
错并不难。与之不同的是,模板中的每处输入错误,都可能导致怪异、缺乏上下文
的异常,它们将耗费你一整天的时间去调错。对于要求达到生产强度的系统,你可
能想严格检查自己的模板。如今有多种优秀的
JavaScript
句法解析器(用
JavaScript
实现),编译时你可以用其解析模板中的表达式或语句,以可靠的方式判断它们是
否可以被解析(前面代码中的 #if $in.type == "#" # 这样的指令将无法解析,因
为它无法理解第
2
个被引起来的
#
号),遇到无法理解的句法,可给出有意义的错
误信息(包括模板名称和位置)。
二进制模式匹配
我要展示的第
2
个示例,其模式在很大程度上与第
1
个相同为了提升速度和表现力,
我们将一门领域特定语言编译为
JavaScript
Erlang
编程语言有一个功能,即支持通过指定一个字段序列的方式来匹配符合模式
的二进制数据,每个字段为变量名或一个常数。变量被绑定到字段的内容上,常数
则要与字段的内容相比较,以确定字段跟二进制数据是否匹配。该方法非常适合用
来检查代码和从二进制数据块中抽取数据。
假如我们想用
JavaScript
实现类似功能。理论上讲,它大致可以表示为:
There is a feature in the Erlang programming language that allows you to pattern-
match against binary data by specifying a sequence of fields and, for each field, a vari-
able name or constant. Variables will be bound to the content of the field, and con-
stants will be compared to the content of the field in order to determine whether the
pattern matches. This provides a very convenient way of checking and extracting data
from binary blobs.
Let’s say we want something like this in JavaScript. Ideally, it’d look like this:
function gifSize(bytes) {
binswitch (bytes) {
case <<"GIF89a" width:uint16 height:uint16>>:
return {width: width, height: height};
default:
throw new Error("not a GIF file");
}
}
where binswitch is like switch, except that it matches a series of fields in the given
chunk of binary data (a typed array, presumably). This pattern would mean “first the
bytes corresponding to the string
"GIF89a", then a two-byte unsigned integer, which is
bound to
width, and finally another unsigned integer bound to height.” Patterns that
bind variables like that are found in many modern programming languages, and are a
very pleasant feature.
If you’re willing to do heavyweight full-file preprocessing, you could write your own
JavaScript dialect in which this code is valid. But in this chapter, we’re looking for
lightweight tricks, not alternative languages. We need to find some kind of operator
that gets us close enough to this goal, but can be expressed in the existing syntax of
the language.
Here’s what I came up with:
var pngHead = binMatch("'\x89PNG\\r\\n\x1a\\n':str8 _:uint4 'IHDR':str4 " +
"width:uint4 height:uint4 depth:uint1");
function pngSize(bytes) {
var match;
if (match = pngHead(bytes, 0))
return {width: match.width, height: match.height};
else
throw new Error("Not a PNG file.");
}
Patterns are precompiled from strings to functions, much like in the template example.
The pattern string contains any number of
binding:type pairs, where type is a word
like
str or uint followed by a byte size, and binding can be _ (an underscore) to ignore
a field, a literal (in which case the pattern matches only when the value is equal to the
literal), or a field name in which to store the value.
CHAPTER TWO: EVAL AND DOMAIN-SPECIFIC LANGUAGES
22
其中,binswitch类似于 switch,只不过它匹配的是给定二进制数据块(假定是
某一数据类型的数组)中的几个字段。该模式表示“开头的几个字节对应字符串
"GIF89a",然后是绑定到 width 变量、长度为两个字节的无符号整数,最后是绑定
到另一变量 height 的无符号整数。”这种绑定内容到变量的方法,在现代编程语言
中很常见,是一个非常实用的功能。

Get JavaScript 之美 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.