乐者为王

Do one thing, and do it well.

协议分析器的威力

英文原文:http://arstechnica.com/information-technology/2016/09/the-power-of-protocol-analyzers/

问题发生在错综复杂的网络世界里。但要在一时激动之下确定一种新型问题的确切原因变得有些冒险。在这种情况下,当Google-fu耗尽的时候甚至其他能干的工程师也可能会被迫去依赖试错法。

幸运的是,有个秘密武器等待乐意的工程师去部署——协议分析器。该工具允许你明确地确定几乎任何错误的根源,给你提供在底层协议上自我学习的能力。现在唯一的问题是,许多工程师因为(毫无根据的)恐惧而完全回避它。

什么是协议分析器?

协议分析器,或者数据包嗅探器,是一个用于拦截通信量,存储它们,并以一个已解码的、人类可读的状态呈现它们的工具。现代协议分析器比如Wireshark甚至可以靠自己发现基本的问题,然后使用捕获的数据执行统计分析。

不理会特性,数据包嗅探器都以基本相同的方式工作。它们把自己插入到网络堆栈中,把所有通信量复制到一个缓冲区或文件。大部分还会将网络驱动置于“混杂模式”,该模式从根本上说允许这些工具取回所有进入网络堆栈的通信量,而不是只采集前往系统本身的通信量。

协议分析仪如何帮助

在很多情况下,解决一个困难的网络问题的最难部分是找到和理解问题的根源。这种困难的部分源于这样的事实,你对大多数问题使用的工具不是正确的对底层问题的工具。

如果你是一个系统管理员,很有可能你经常用于数据采集的工具是某种日志和错误消息。通常,这些都是解释工具。这些实体试图把原始数据总结为对非开发者或非工程师有意义的东西。因为解释工具是从应用层的视角提供问题的汇总数据,它们往往不能帮助你解决底层的问题。

例如,一条事件日志消息可以告诉你应用程序无法连接到服务器。它甚至可以告诉你根本原因是超时。但这条消息不大可能告诉你超时是由一个黑洞路由器丢弃一个大帧引起。它不能,因为事件日志消息服务不知道错误为何发生。为了使工具知道那个,它需要预测(不解释)这个非常问题,在MTU稳步减少的情况下发送数据包,直到一个通过。如果一个事件日志消息服务早就被编写好要做那件事,从一开始你就不会有这个问题。

当使用错误的工具时,你可能会在某处花上几个小时甚至几周的时间,直到你侥幸得到解决方案。然而,通过使用协议分析器和历久弥新的ping命令,你可以非常容易地在大约5分钟内诊断这个问题。就像早在高中时我的汽车技术辅导员就告诉我的,它全都是关于对任务使用恰当的工具。

除了确定错误,协议分析器提供为数不多的方法之一去证实问题的根源。以前我在微软的时候,棘手问题在团队间来回穿梭是很常见的,因为每个组误解由解释工具提供的数据。首先,问题可能被发送到Exchange团队,接着它可能被穿梭到Active Directory团队,然后最后到Networking团队。

通常,这是因为在其它团队的能力范围之内一个问题好像是合理的。然而,烫手山芋的游戏往往停止在Networking团队。为什么?因为Networking团队的头号工具是证实问题根源的救世主。

网络,像所有的计算,其核心是完全合乎逻辑的。一旦你了解它在幕后是如何工作的,你就有能力在底层确定问题,不论问题是多么独特。作为协议分析的一个伟大副作用,你也将学到很多关于网络的知识,它们将帮助你解决各种各样的网络问题(即使那些不需要协议分析)。

Wireshark基础

现在,有各种各样的协议分析器可供选择,从免费的和相当功能的微软消息分析器到特性极其丰富但十分昂贵的Savvius Omnipeek。多年来我已经使用过大量的分析器,但我最喜欢的用于常规故障排除的协议分析器是Wireshark。它是免费的,开源的,多平台的具有很多特性的分析器。有个充满活力的社区站在它背后,而且Wireshark也相当容易习惯。这让它成为一个很好的开始的地方。

你可以从 https://www.wireshark.org/ 下载用于你操作系统的Wireshark。安装它没有什么特别的,但如果你是安装在Windows上,确保也安装了捆绑的WinPCAP驱动程序。这允许Wireshark实际上捕获数据包(没有它,你只能观看存档的数据包)。

Wireshark通常将你的NIC置于混杂模式。正常情况下,你的NIC只会保留前往你的MAC或者广播MAC(FF-FF-FF-FF-FF-FF)的帧。启用混杂模式后,不管怎样,你的NIC保留所有它听到的帧。

从理论上讲,这意味着你应该接收所有的在你Ethernet段上的数据包。不过,实际上如今几乎所有的Ethernet网络都是交换网络。如果你想接收所有的通信量,必须多做些工作。

一旦你已经安装了Wireshark,使用它是相当简单的。把它打开,你将看到如下显示的屏幕:

这个屏幕给你展示选择一个在它上面捕获数据包的NIC的选项和输入一个用于只捕获一部分入站数据包的过滤器的选项。如果你选择一个NIC然后点击在文件菜单下面的小鱼翅图标,Wireshark将立即开始捕获数据包。

随着数据包被捕获,Wireshark在主界面中实时地显示它们。当你准备停止时,你只需点击在鱼翅图标旁边的小红方块。

数据包列表部分显示在这个点捕获的每件事物,按它们被捕获的顺序排序(默认)。(你可以通过点击要作为排序依据的标题任意地排序这些数据包。)

数据包细节部分显示Wireshark解码的在数据包中的每个报头。基本上,Wireshark有几乎今天在用的每个协议的解码器,并且为了显示分成字段的数据,解码器工具自动应用到每个数据包。

举例来说,如下,我已经为一个典型的HTTP数据包增加了Ethernet II报头。

你可以清楚地看到Wireshark已经析出了Destination和Source的MAC地址,以及Type字段,它是0x0800。Type字段指出下个报头应该是一个IPv4报头,Wireshark很方便告诉你这个。

这个解码特性使你不必自己计算字节和解码它(不过,如果你愿意,你仍然还可以在原始字节部分做)。如果对原始字节部分有兴趣:Wireshark同时为所有数据提供ASCII转换,它有时提供令人惊讶的数据。在下图,你可以在ASCII视图里的这个数据包中清楚地看到HTTP请求发送的细节。

Wireshark同时也提供一些非常实用的分析和统计特性,包括测量响应时间和往返时间的能力。但到目前为止,最有用的特性是过滤功能。

在数据包列表直接的上方是一个文本框,那里你可以输入显示过滤器。默认不使用过滤器,意味着显示被捕获的所有数据包。然而,你经常最后得到信息过载,而过滤掉噪音是数据包分析的一个非常重要的部分。

Wireshark中的过滤器按照一门简单的语言结合协议字段、比较运算符和逻辑运算符以便过滤掉不匹配条件的数据包。例如,过滤器http将只显示HTTP通信量,而过滤器ip.addr == 192.168.1.10将只显示源或目标的IP地址是192.168.1.10的数据包。

当你是第一次开始的时候,过滤可能会有点令人生畏,但通常在Wireshark中学习过滤器的最简单的方法是使用内建的表达式工具。可以通过点击过滤器文本框右边的Expression按钮访问它。

这个工具允许你寻遍Wireshark本身支持的所有协议,挑选你想过滤的字段而不需要知道过滤器动词或语法。只需选择协议,填写呈现的字段,然后过滤器将会为你而建。这里,我使用表达式工具构建一个仅查找Ethernet广播的过滤器。

注意工具如何在底部的绿框中显示最终的过滤器语法。通过注意这个字段,你将最终变得熟悉你的最常用的过滤器。

然而,你不能用表达式工具做的一件事是把过滤器串起来。因为那个原因,你需要学习一些逻辑运算符。Wireshark的基本逻辑运算符是and(&&)、or(||)和not(!)。

and被用于结合过滤器,只有满足所有条件的数据包会被显示。例如,过滤器http && ip.addr == 192.168.1.10将只显示在第7层报头中的HTTP协议和在IP报头中的IP地址192.168.1.10两者都包括的数据包。

or被用于查找两者中的任何一个过滤器,因此满足你输入的任何条件的数据包都会被显示。举例来说,过滤器http || ip.addr == 192.168.1.10将显示在第7层报头中的HTTP协议或在IP报头中的IP地址192.168.1.10的数据包。

not被用于从结果中过滤掉一些东西。例如,过滤器ip.addr == 192.168.1.10 ! http将显示有在IP报头中的IP地址192.168.1.10但没有在第7层报头中的HTTP协议的数据包。

关于基本的Wireshark功能最后要注意的事是,除保存你的原始捕获外,你也有多种多样的选项导出捕获。

首先,你导出当前选择的数据包、所有数据包、你标记的数据包或者一段范围的数据包。在这些选项的每一个中,你可以选择导出所有被捕获的数据包或只是被显示的数据包(考虑当前应用的过滤器)。这些选项让你非常具体地知道你想导出哪些数据包。

此外,你可以把数据包导出成几乎任何常用的格式。在Wireshark中用于文档和电子邮件转出的最好的特性之一是以纯文本或CSV格式导出解剖数据包(完整的数据包解码)的能力。要做到这个,只需从文件菜单里选择“Export Packet Dissections”。

理解你所看到的

尽管所有这些功能都很好,底线是如果你不明白在每个报头中字段的目的它们都是无用的。幸运的是,除了少量专有协议,你遇到的几乎每个协议的规格说明都是免费在线的。

例如:Ethernet,你可以直接到IEEE下载标准;802.3标准可以在 http://standards.ieee.org/about/get/802/802.3.html 获得。它是免费的直接来自权威人士。如果你在查找802.3 Ethernet帧格式,你将发现真的只有3个感兴趣的字段:目标MAC地址、源MAC地址和类型/长度字段。下图中在Wireshark解剖体左边的是来自IEEE 802.3规格说明Section 1中Part 3.1.1的Figure 3-1:

如果你想知道preamble和SFD发生了什么,它们在帧从NIC到Wireshark向上传递给栈之前被移除。同样地,你通常不会在末尾看到FCS,因为它在向上传递帧之前被剥去。

在第2层上面,所有TPCTCP/IP协议由IETF管理和由RFCs(请求评论)定义。所有这些RFCs可以在站点 https://www.rfc-editor.org/ 上即刻地免费地获得。虽然它们有点简洁(并且因为这个原因有时难以理解),它们总是正确的,具体问题的澄清可以使用Google快速搜索获得。

举例来说,通常混淆新手的事情之一是大量TCP重置,或者在TCP报头中数据包有打开的RST标记。浏览RFC 793(TCP),你可能会得到RST总是用信号告知一些坏事情的印象。几乎所有的35个左右提到的RST与某种错误条件有关联。

然而,使用关键词“tcp rst from client”的Google快速搜索将让你得到大量的关于这个现象的很好的讨论。也许最好的是来自Wireshark论坛,在那里他们解释说这很平常,因为客户端应用仅仅被编码去重置链接而不是优雅地关闭它。在这种情况下,服务端已经发送一个FIN。作为回复一个FIN/ACK并等待最终的ACK的替换,客户端只需通过发送一个RST并中止会话来优化过程。在下面的示例中这可以清楚地被看到。

除了规格说明和Google,另一个学习协议通常如何运转的良好来源是示例跟踪的资料库。这些示例跟踪允许你去查看相当典型的既常见又晦涩的协议操作,以及一些十分罕见的平常可能不会碰到的错误。

一个很好的起点是Wireshark Wiki上的样板捕获:https://wiki.wireshark.org/SampleCaptures 。在这里有大量非常有用的捕获让你去下载以及用过滤和其它Wireshark特性做实验,包括像广播风暴、病毒和攻击套装这样有意思的错误。如果这些还不够,在这个页面上还有一些其它资源的链接去协助你。但是毫无疑问地,变得擅长协议分析的最快方式是仔细观看大量的捕获并试图理解被使用的协议。

如何得到好的捕获

如果不能捕获正确的数据,世界上所有的领悟都无济于事。在最基本的层面,目标是只捕获涉及你试图解决的问题的数据包,有效减少你跟踪里的噪音。

为了做到这点,你可以使用一个捕获过滤器去从捕获中排除那些匹配过滤器外的所有数据。如果你确切地知道你在寻找什么这会工作的很好,但往往这种方法会导致你不能觉察一些重要的事情。大多数时候你只有一个问题是什么的粗略想法,或者你忘了一些潜在的找到错误的关键的过程。如果这种情况发生,使用捕获过滤器就没那么幸运,而且你不能返回和没重设置它就不过滤捕获。

例如,在诊断一个网站的性能问题时,你可能决定使用一个捕获过滤器集从Web服务器自身取得捕获,以便只捕获在其与客户端系统和后台SQL服务器之间往返的数据。然而,这个问题实际上可能仅仅是Web服务器使用的身份验证服务器过载,等待身份验证才是引起整个性能问题的原因。使用你选择的捕获过滤器你将永远不会发现这个问题。

这是我倾向于捕获所有数据并且使用显示过滤器去减少跟踪里的噪音的原因。这不是说捕获过滤器完全不必要。捕获过滤器的一个常见用途是当你有一个非常繁忙的你正在捕获的千兆或万兆连接的时候,捕获过滤器变得有用仅仅是因为大量的数据。不过,你始终需要牢记过滤器的限制。

得到一个好的捕获的第二部分是正确识别你需要捕获的系统。举例来说,在前面关于Web服务器性能问题的例子中,我可能首先会从Web服务器和Web客户端两者取得同时发生的捕获。这样你可以看到两边的正常预期行为的任何偏差,这有助于你将问题隔离到服务器或客户端。

一旦查明延迟是一个与身份验证有关的服务端问题,然后我会从Web服务器和身份验证服务器两者取得其它的跟踪。这样,我可以看到是本地到身份验证服务器的问题还是等待像DNS这样其它服务的问题或者Global Catalog是实际上的罪魁祸首。

得到一个好的捕获的第三步是在成功和失败条件中都使用捕获。举例来说,如果你有一个间歇性的Web服务器性能问题,设法在站点正常和不正常工作时都得到跟踪。这能给你一个好的和坏的比较跟踪,可以使用它去隔离问题。

最后,当处理一个间歇性的问题时,你会发现很难得到一个失败捕获。在这种情况下,Wireshark有一个很重要的特性被称为环形缓冲区,它允许你持续地捕获。

通常,特别在一个繁忙的网络上,持续的捕获将冒填满磁盘的风险。但有了环形缓冲区,Wireshark会写入文件直到它达到指定大小或者经过一段时间,然后它会切换到一个新文件。一旦指定数量的文件已经被写入,程序删除最旧的文件。例如,看看下面我已经定义的设置:

这个配置告诉Wireshark不管文件大小每10分钟创建一个新文件,并且确保程序保留总计3个文件,根据需要删除最旧的。这确保从错误被通知的时间起我有30分钟去停止捕获。这是一个非常有用的技术用于捕获极其间歇性的问题。

这些是你需要的以便用Wireshark开始故障排除的所有基本技术。使用这些技术和资源,你会发现你经常能用比几乎任何其它技术更短的时间找到和验证网络问题的原因。快乐的故障排除。

如何创建有效的图标

英文原文:http://www.awwwards.com/how-to-create-effective-icons.html

我可能就是你所说的图标爱好者。我喜欢图标,而且我更加喜欢制作它们!作为一个艺术家,我的背景在很大程度上是绘画——我喜欢绘画,并且我已经画了一辈子(甚至远远早于我知道什么是图形设计)。我想,这是我理解创建图标的一个关键。绘画教你看——然后把你所看到的转化成纸上的线条和图形——而这正是如何创建有效的图标。

几何图形

因此,对初学者而言,基本上任何东西都可以用这四种图形组合而成:

当我想把某事物转换成一个图标时,我观察它然后尽可能地将其拆分为最简单的图形。例如,水滴可以用一个三角形和一个圆形组成。

心形图标可以由两个圆形和一个三角形构成。

我每次都是在Adobe Illustrator中创建这些图形。使用矢量图形可以让我控制线条的粗细,以及图形和其锚点的相互作用。Illustrator也可以让我自由地把线条转换成图形,反之亦然。这一切也许看起来十分基础,但它是我用于创建最复杂图标的同样的方法。下面是我最近在做的一个略微更加复杂些的《权利法案》图标的示例,在这里我应用了同样的原则。

界面图标

我最近有机会为一款超赞的iPhone应用Parker Planner制作一组图标。我很喜欢做这个项目,这个项目其中最重要的一个方面是创建一组易懂的、私有的、实用的和美观的用户界面图标,可以帮助用户浏览操作这款略微复杂的计划应用。

让我们选取这些图标的其中之一分解看看我如何创建它。例如,垃圾桶图标是由三个圆角矩形和三条线构成。

1、选择圆角矩形工具。

2、拖动出一个图形。

3、调整笔划宽度直到你满意。

我通常选择在整组图标中使用一到两种笔划宽度。

这使它们看起来更一致和感觉更有整体性。

4、用另一个圆角矩形创建盖子。

5、再一个圆角矩形创建盖子的把手。

6、擦除圆角矩形的下半部分。

7、现在,通过添加三条竖线到桶身上给桶添加条纹。

8、然后你就获得了它!一个垃圾桶图标……如果你喜欢,你可以用颜色或线条宽度做进一步调整。

我在创建图标时经常使用的一些其它真正有用的工具是Pathfinder,我使用它来剪切、连接和挖空图形。

Stroke/Fill工具,它帮助你将图形在填满和笔划间切换。

以及我非常喜欢的工具Stroke Panel,它帮助你将拐角和线的末端从直角转换到圆角。

当我完成一组图标,我通常将它们全体紧挨着排成一排,看看是否有哪个看起来很奇怪或不到位。然后我会做任何必要的修改。

最后,我总是在应用中测试它们以确保它们感觉正确和功能良好。

最终,我想说创建优秀图标的方法不仅仅是学习Illustrator技巧,尽管它们也是必需的。最好的做法是练习把你周围看到的事物分解成简单图形。你在这点上越是变得更好,你越是能够成为更高超的图像设计师!加油!

ANTLR 4权威参考读书笔记(19)

  • component 构件

因为词法规则可以使用递归,所以词法解析器在技术上和语法解析器一样强大。那意味着我们甚至可以在词法分析器中匹配语法结构。或者,在另一个极端,我们可以把字符当作记号,使用语法分析器去把语法结构应用到字符流(这种被称为无扫描语法分析器)。这导致什么在词法分析器中匹配和什么在语法分析器中匹配的界线在哪里并不是很明显。幸运的是,有几条经验法则可以让我们做出判断:

  • 在词法分析器中匹配和丢弃任何语法分析器根本不需要见到的东西。例如,在词法分析器中识别和扔掉像注释和空格这些东西。否则,语法分析器必须经常查看是否有注释或空格在记号间。
  • 在词法分析器中匹配诸如标志符、关键字、字符串和数字这样的常用记号。语法分析器比词法分析器有更多的开销,因此,我们不必让语法分析器承受把数字放在一起识别成整数的负担。
  • 把那些语法分析器不需要去辨别的词法结构合并成一个单独的记号类型。例如,如果我们的应用把整数和浮点数当作同一事物对待,然后把它们合并成记号类型NUMBER,那么就没必要向语法分析器发送单独的记号类型。
  • 合并能被语法分析器视为一个单独实体的任何东西。例如,如果语法分析器不在乎XML标签里的内容,词法分析器可以把尖括号中的任何东西合并成一个单独的被称为TAG的记号类型。
  • 如果语法分析器需要先拆开一小块文本后才能去处理它,那么词法分析器应该传递独立的构件作为记号给语法分析器。例如,如果语法分析器需要处理一个IP地址的元素,词法分析器应该发送IP构件(整数和点)的独立的记号。

想象下现在需要处理Web服务器上的日志文件,每一行表示一条记录。让我们假设每条记录都有一个请求IP地址、HTTP协议命令和结果代码。这里是一个日志条目的示例:

1
192.168.209.85 "GET /download/foo.html HTTP/1.0" 200

如果想要统计文件中有多少行,那么我们可以忽略掉任何东西除了换行字符的序列:

1
2
3
file  : NL+ ;               // 匹配换行符(NL)序列的语法规则
STUFF : ~'\n'+ -> skip ;    // 匹配和丢弃除'\n'外的任何东西
NL    : '\n' ;              // 返回NL给语法分析器或调用代码

词法分析器不必识别太多的结构,语法分析器会匹配换行记号的序列。

接下来,我们需要从日志文件中收集一系列的IP地址。这意味着我们需要一条规则去识别IP地址的词法结构。并且我们也可以提供其它记录元素的词法规则:

1
2
3
4
5
IP    : INT '.' INT '.' INT '.' INT ;    // 192.168.209.85
INT   : [0-9]+ ;                         // 匹配IP八位组或者HTTP结果代码
STRING: '"' .*? '"' ;                    // 匹配HTTP协议命令
NL    : '\n' ;                           // 匹配日志文件记录终结符
WS    : ' ' -> skip ;                    // 忽略空格

拥有一套完整的记号后,我们可以让语法规则匹配日志文件中的记录:

1
2
file : row+ ;                // 匹配日志文件中行的语法规则
row  : IP STRING INT NL ;    // 匹配日志文件记录

更进一步,我们需要把文本IP地址转换成32位的数字。使用便利的库函数split('.'),我们可以把IP地址切割成字符串传递给语法分析器让它去处理。但是,更好的做法是让词法分析器匹配IP地址的词法结构,然后把匹配出的构件作为记号传递给语法分析器。

1
2
3
4
5
6
7
file  : row+ ;                           // 匹配日志文件中行的语法规则
row   : ip STRING INT NL ;               // 匹配日志文件记录
ip    : INT '.' INT '.' INT '.' INT ;    // 在语法分析器中匹配IP地址
INT   : [0-9]+ ;                         // 匹配IP八位组或者HTTP结果代码
STRING: '"' .*? '"' ;                    // 匹配HTTP协议命令
NL    : '\n' ;                           // 匹配日志文件记录终结符
WS    : ' ' -> skip ;                    // 忽略空格

把词法规则IP切换成语法规则ip显示了我们可以多么轻易地移动这条分界线。

如果要求处理HTTP协议命令字符串的内容,我们可以遵循相同的思考过程。如果不需要检查字符串的部分,那么词法分析器可以把整个字符串作为一个单独的记号传递给语法分析器。如果我们需要抽出各种不同的部分,最好就是让词法分析器去识别那些部分后再把这些匹配出的构件传递给语法分析器。

ANTLR 4权威参考读书笔记(18)

编程语言在词法上看起来惊人地相似,无论是函数式、过程式、声明式还是面向对象语言,看起来几乎都是一样的。这很棒,因为我们只需要学习一次如何描述标志符和整数,没有太大的变化,就可以把它们应用到大多数编程语言上。正如语法分析器以及词法分析器使用规则去描述各种语言构造体一样,我们要使用基本相同的表示法。唯一的区别是语法分析器识别在记号流中的语法结构,而词法分析器识别在字符流中的语法结构。

因为词法分析和语法分析有相似的结构,ANTLR允许我们把两者合并在单个语法文件中。但是因为词法分析和语法分析是语言识别的两个不同阶段,我们必须告诉ANTLR每个规则是和哪个阶段相关联的。我们可以通过以大写字母开始的词法规则名字和以小写字母开始的语法规则名字做到这点。例如,ID是一个词法规则名字,expr则是一个语法规则名字。

当开始构建一个新的语法时,对于那些常用的词法构造体:标志符、数字、字符串、注释以及空格等,我们可以从已经存在的语法中拷贝粘贴规则。然后,通过一些细微的调整,就可以让它运行起来。几乎所有的语言,甚至像JSON和XML这样的非编程语言,都有这些记号的变体。例如,C语言的词法分析器完全可以标记化以下的JSON代码:

1
2
3
4
{
  "title":"Cat wrestling",
  "chapters":[ {"Intro":"..."}, ... ]
}

另一个例子就是块注释。在C语言中,它们是被/* ... */括起来的。而在XML里,注释是被<!-- ... -->括起来的。但它们除了开始和结束符号之外,或多或少都有相同的词法构造。

对于关键字、运算符和标点符号,我们不需要词法规则,因为我们可以在语法分析规则中直接引用它们,用单引号括起来,就像'while''*''++'这样。有些开发者更喜欢使用像MUL而不是字面量'*'这样的词法规则引用,这些都没问题,因为它们都有相同的记号类型。

为了阐明词法规则看起来像什么,让我们从标志符开始构建一个常用记号的简单版本。

匹配标志符

在语法伪代码中,一个基本的标志符是由大写或小写字母组成的一个非空序列。根据已经学习到的知识,我们知道需要用(...)+表示法来表示这样的序列模式。因为序列元素可以是大写或小写字母,所以在子规则中我们需要使用选择运算符:

1
ID : ('a'..'z'|'A'..'Z')+ ;    // 匹配一个或多个大小写字母

唯一的新ANTLR表示法是范围运算符:'a'..'z'代表从a到z的任意字符。或者你也可以使用Unicode码位字面量'\uXXXX',这里的XXXX是Unicode字符码位值的十六进制值。

作为字符集的一个简写,ANTLR支持我们使用更熟悉的正则表达式集合表示法:

1
ID : [a-zA-Z]+ ;    // 匹配一个或多个大小写字母

有时候我们会发现像下面这样的语法貌似存在冲突的现象:

1
2
3
enumDef : 'enum' '{' ... '}' ;
FOR : 'for' ;
ID : [a-zA-Z]+ ;    // 不匹配'enum'或者'for'

规则ID也可以同时匹配enum和for这样的关键字,这意味着同样的字符串能被多个规则匹配。但事实上,ANTLR处理这种混合语法时会把字符串字面量以及词法规则与语法规则分隔开,像enum这样的字面量就变成了词法规则并紧随在语法规则之后和在显式的词法规则之前。

ANTLR词法分析器通过偏爱首先指定的规则来解决词法规则间的二义性,这意味着ID规则应该定义在所有的关键字规则之后。ANTLR把隐式的为字面量生成的词法规则放在显式的词法规则之前,因此它们总是有更高的优先级。在这里,'enum'被自动赋予比ID高的优先级。

因为ANTLR总是会重新排序词法规则并让它们在语法规则之后发生。所以上面的语法与下面的变体是相同的:

1
2
3
FOR : 'for' ;
ID : [a-zA-Z]+ ;    // 不匹配'enum'或者'for'
enumDef : 'enum' '{' ... '}' ;

匹配数字

描述像10这样的整型数字非常容易,因为它只是一个数字序列。

1
INT : '0'..'9'+ ;    // 匹配一个或多个数字

或者

1
INT : [0..9]+ ;    // 匹配一个或多个数字

浮点数要复杂的多,但如果我们忽略指数的话,可以很容易地制作一个简化版本。浮点数是数字序列后面跟着一个句点和一个可选的小数部分;或者以一个句点开始,然后是数字序列。单独一个句点是不合法的。因此我们的浮点规则使用一个选择和一些序列模式:

1
2
3
4
5
6
FLOAT: DIGIT+ '.' DIGIT*    // 匹配1. 39. 3.14159等等
     | '.' DIGIT+           // 匹配.1 .14159
     ;

fragment
DIGIT: [0-9] ;              // 匹配单个数字

这里我们使用了一个帮助规则DIGIT,因此我们不必到处去写[0-9]。通过在规则前面加上fragment前缀,我们让ANTLR知道该规则仅被其它词法规则使用。它本身不是一个记号,这意味着我们不能在语法规则中引用它。

匹配字符串字面量

计算机语言中共同具有的下一个常用记号是字符串字面量,例如"hello"。大部分使用双引号作分隔符,有些使用单引号或者两者都使用。以双引号为分隔符而言,在语法伪代码中,一个字符串就是在双引号中的任意字符序列:

1
STRING : '"' .*? '"' ;    // 匹配在双引号中的任意字符

语法中的点是通配符运算符,它可以匹配任意单个字符。因此,“.*”是一个能够匹配任意零个或多个字符的序列的循环。当然,它也将消费字符直到文件结尾,所以不是很有用。幸运的是,ANTLR通过正则表达式表示法(?后缀)提供对非贪婪模式规则的支持。非贪婪模式意味着“直到看见在词法规则中跟在子规则后的字符时才停止吃掉字符”。更确切地说,非贪婪模式规则匹配最小数量的字符,同时仍然允许整个周围的规则被匹配。相反,“.*”被认为是贪婪模式,因为它贪婪地消费能够匹配循环内部的所有字符。

以上的STRING规则做得还不够好,因为它不允许字符串中有双引号。为了做到这点,大部分语言定义了以反斜杠开始的转义字符。在字符串中的双引号我们可以使用“\"”。为支持常用的转义字符,我们需要使用以下规则:

1
2
3
4
STRING: '"' (ESC|.)*? '"' ;

fragment
ESC : '\\"' | '\\\\' ;    // 匹配字符\"和\\

ANTLR自身也需要避开转义字符,所以这里我们需要用“\”去指定反斜杠字符。

现在,在STRING规则中的循环既可以通过调用fragment规则ESC去匹配转义字符序列,也可以通过点通配符去匹配任意字符。当看到一个非转义双引号字符时,“*?”子规则运算符终止“(ESC|.)*?”循环。

匹配注释和空格

词法分析器会把匹配到的记号通过记号流传递给语法分析器,然后语法分析器检查流的语法结构。但我们希望当词法分析器匹配到注释和空格时能把它们扔掉。那样,语法分析器就不必为匹配无处不在的可选的注释和空格担心。例如,当WS是一个空格的词法规则时以下的语法规则就非常尴尬和容易出错:

1
assign : ID (WS|COMMENT)? '=' (WS|COMMENT)? expr (WS|COMMENT)? ;

定义这些被丢弃的记号和定义非丢弃的记号一样,我们只需要使用skip指令去表明词法分析器应该扔掉它们。以下是匹配那些衍生自C的语言的单行和多行注释的语法规则:

1
2
LINE_COMMENT : '//' .*? '\r'? '\n' -> skip ;    // 匹配"//" stuff '\n'
COMMENT      : '/*' .*? '*/'       -> skip ;    // 匹配"/*" stuff "*/"

在COMMENT中,“.*?”消费在“/*”和“*/”之间的任意字符。在LINE_COMMENT中,“.*?”消费“//”之后的任意字符,直到它看到一个换行符。

词法分析器接受若干跟随在->运算符后的指令,skip只是它们中的一个。例如,我们可以通过使用channel指令把传递给语法分析器的记号放进隐藏通道。

最后,让我们处理空格(换行符等也被当作空格)这个常用记号。大部分编程语言都把空格当作记号分隔符,但是另一方面又忽略它们。(Python是个例外,因为它把空格用作特殊语法目的:终止命令和缩进级别的换行符。)以下是告诉ANTLR如何扔掉空格的语法:

1
WS : (' '|'\t'|'\r'|'\n')+ -> skip ;    // 匹配一个或多个空格但丢弃

或者

1
WS : [ \t\r\n]+ -> skip ;    // 匹配一个或多个空格但丢弃

当换行符既是要被忽略的空格又是命令终结符时,就会有个问题。换行符是上下文有关的,在语法上下文中,我们要扔掉换行符,但在其它地方,我们需要把它传递给语法分析器以便让它知道某个命令已经结束。例如,在Python中,后面有换行符的f()会执行代码,调用f()方法,但是如果我们在括号中也插入一个额外的换行符,Python就会在执行调用前等待直到右括号后面的换行符为止。

如何使用Flexbox构建新闻站点布局

英文原文:http://webdesign.tutsplus.com/tutorials/how-to-build-a-news-website-layout-with-flexbox--cms-26611

在投入并开始之前你没有必要理解Flexbox的每个方面。在这篇教程中,我们将介绍一些Flexbox的特性,同时设计一个类似The Guardian的“新闻布局”。

我们正在使用Flexbox的原因是它提供了非常强大的特性:

  • 我们可以很容易制作响应式列
  • 我们可以使列等高
  • 我们可以把内容推到容器的底部

那么让我们开始吧!

1. 从两个列开始

在CSS中创建列一直是个挑战。长期以来,唯一的选项是使用float或者table,但它们都有它们自己的问题。

Flexbox使这个过程更简单,给予我们:

  • 更简洁的代码:我们只需要一个带有display: flex的容器
  • 不需要清除float,以防止意外的布局行为
  • 语义化的标记
  • 灵活性:我们可以用几行CSS代码来调整列的大小、拉伸或者对齐列

让我们从创建两个列开始:一个是容器宽度的2/3,一个是1/3。

1
2
3
4
5
6
7
8
<div class="columns">
  <div class="column main-column">
    2/3 column
  </div>
  <div class="column">
    1/3 column
  </div>
</div>

这里有两个元素:

  • columns容器
  • 两个column子容器,其中一个带有附加的名为main-column的class,我们将用它来让该子容器更宽
1
2
3
4
5
6
7
8
9
10
11
.columns {
  display: flex;
}

.column {
  flex: 1;
}

.main-column {
  flex: 2;
}

作为有一个flex值为2的主列,它将占用其它列的两倍空间。

通过添加一些附加的视觉样式,这里是我们得到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
html {
  font-family: sans-serif;
}

.columns {
  display: flex;
}

.column {
  background: #eee;
  border: 5px solid #ccc;
  flex: 1;
  padding: 20px;
}

.main-column {
  flex: 2;
}

2. 让每一列都成为Flexbox容器

这两列中的每一个都将包含若干垂直堆叠的文章,因此我们打算把column元素也转变成Flexbox容器。我们想要:

  • 文章被垂直堆叠
  • 文章拉伸并填充可用空间
1
2
3
4
5
6
7
8
.column {
  display: flex;
  flex-direction: column;  /* 使文章垂直堆叠 */
}

.article {
  flex: 1;  /* 拉伸文章以填补剩余的空间 */
}

容器上的flex-direction: column规则结合在子容器上的flex: 1规则确保文章将填补整个垂直空间,保持最初两列具有相同的高度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="columns">
  <div class="column main-column">
    <article class="article">
      Hello World
    </article>
    <article class="article">
      Hello World
    </article>
  </div>
  <div class="column">
    <article class="article">
      Hello World
    </article>
    <article class="article">
      Hello World<br>
      Foo Bar
    </article>
    <article class="article">
      Hello World
    </article>
  </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
html {
  font-family: sans-serif;
}

.columns {
  display: flex;
}

.column {
  background: #eee;
  border: 5px solid #ccc;
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 10px;
}

.main-column {
  flex: 2;
}

.article {
  background: mediumseagreen;
  border: 5px solid seagreen;
  color: white;
  flex: 1;
  margin: 10px;
  padding: 20px;
}

3. 让每篇文章都成为Flexbox容器

现在,为了给我们额外的控制,让我们把每篇文章也转变成一个Flexbox容器。每篇文章将包含:

  • 一个标题
  • 一个段落
  • 一个带有作者和评论数量的信息栏
  • 一张可选的响应式图片

我们在这里使用Flexbox是为了把信息栏推到底部。我们的目标文章布局如下图所示:

这里是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<a class="article first-article">
  <figure class="article-image">
    <img src="">
  </figure>
  <div class="article-body">
    <h2 class="article-title">
      <!-- 标题 -->
    </h2>
    <p class="article-content">
      <!-- 内容 -->
    </p>
    <footer class="article-info">
      <!-- 信息 -->
    </footer>
  </div>
</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.article {
  display: flex;
  flex-direction: column;
  flex-basis: auto;  /* 基于其内容设置初始元素大小 */
}

.article-body {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.article-content {
  flex: 1;  /* 这将使内容填补剩余的空间,并因此在底部压入信息栏 */
}

文章元素被垂直放置归功于flex-direction: column规则。

我们把flex: 1应用到article-content元素以便它填补空白空间,并且把article-info推到底部,无论列的高度如何。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<main class="main columns">
  <section class="column main-column">
    <a class="article first-article" href="#">
      <figure class="article-image is-4by3">
        <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-01.png" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
          Maecenas non massa sem.
          Etiam finibus odio quis feugiat facilisis.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>

    <a class="article" href="#">
      <figure class="article-image is-16by9">
        <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-02.png" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
          Maecenas non massa sem.
          Etiam finibus odio quis feugiat facilisis.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
          Maecenas non massa sem.
          Etiam finibus odio quis feugiat facilisis.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
  </section>

  <section class="column">
    <a class="article" href="#">
      <figure class="article-image is-3by2">
        <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-03.png" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
  </section>
</main>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
html {
  background: mediumseagreen;
  font-family: sans-serif;
  font-size: 14px;
}

a {
  text-decoration: none;
}

div, h2, p, figure {
  margin: 0;
  padding: 0;
}

.main {
  margin: 0 auto;
  max-width: 1040px;
  padding: 20px;
}

.columns {
  display: flex;
}

.column {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.main-column {
  flex: 2;
}

.article {
  background: white;
  color: #666;
  display: flex;
  flex: 1;
  flex-direction: column;
  flex-basis: auto;
  margin: 10px;
}

.article-image {
  background: #eee;
  display: block;
  padding-top: 75%;
  position: relative;
  width: 100%;
}

.article-image img {
  display: block;
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

.article-image.is-3by2 {
  padding-top: 66.6666%;
}

.article-image.is-16by9 {
  padding-top: 56.25%;
}

.article-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 20px;
}

.article-title {
  color: #333;
  flex-shrink: 0;
  font-size: 1.4em;
  font-weight: bold;
  font-weight: 700;
  line-height: 1.2;
}

.article-content {
  flex: 1;
  margin-top: 5px;
}

.article-info {
  display: flex;
  font-size: 0.85em;
  justify-content: space-between;
  margin-top: 10px;
}

4. 添加一些嵌套列

在左边的列中,我们真正想要的是另一组列。因此我们将以我们已经用过的同样的columns容器代替第二篇文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="columns">
  <div class="column nested-column">
    <a class="article">
      <!-- 文章内容 -->
    </a>
  </div>

  <div class="column">
    <a class="article">
      <!-- 文章内容 -->
    </a>
    <a class="article">
      <!-- 文章内容 -->
    </a>
    <a class="article">
      <!-- 文章内容 -->
    </a>
  </div>
</div>

因为我们想要第一个嵌套列更宽,我们添加一个带有附加样式的名为nested-column的class:

1
2
3
.nested-column {
  flex: 2;
}

这将使新列的宽度是其它列的两倍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<main class="main columns">
  <section class="column main-column">
    <a class="article first-article" href="#">
      <figure class="article-image is-4by3">
        <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-01.png" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
          Maecenas non massa sem.
          Etiam finibus odio quis feugiat facilisis.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>

    <div class="columns">
      <div class="column nested-column">
        <a class="article" href="#">
          <figure class="article-image is-16by9">
            <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-02.png" alt="">
          </figure>
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
              Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
              Maecenas non massa sem.
              Etiam finibus odio quis feugiat facilisis.
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
              Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
              Maecenas non massa sem.
              Etiam finibus odio quis feugiat facilisis.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
      </div>

      <div class="column">
        <a class="article" href="#">
          <figure class="article-image is-16by9">
            <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-03.png" alt="">
          </figure>
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
        <a class="article" href="#">
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
        <a class="article" href="#">
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet feugiat facilisis.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
      </div>
    </div>
  </section>

  <section class="column">
    <a class="article" href="#">
      <figure class="article-image is-3by2">
        <img src="https://s3.amazonaws.com/cms-assets.tutsplus.com/uploads/users/1366/posts/26611/attachment/image-04.png" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
  </section>
</main>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
html {
  background: mediumseagreen;
  font-family: sans-serif;
  font-size: 14px;
}

a {
  text-decoration: none;
}

div, h2, p, figure {
  margin: 0;
  padding: 0;
}

.main {
  margin: 0 auto;
  max-width: 1040px;
  padding: 20px;
}

.columns {
  display: flex;
}

.column {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.main-column {
  flex: 3;
}

.nested-column {
  flex: 2;
}

.article {
  background: white;
  color: #666;
  display: flex;
  flex: 1;
  flex-direction: column;
  flex-basis: auto;
  margin: 10px;
}

.article-image {
  background: #eee;
  display: block;
  padding-top: 75%;
  position: relative;
  width: 100%;
}

.article-image img {
  display: block;
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

.article-image.is-3by2 {
  padding-top: 66.6666%;
}

.article-image.is-16by9 {
  padding-top: 56.25%;
}

.article-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 20px;
}

.article-title {
  color: #333;
  flex-shrink: 0;
  font-size: 1.4em;
  font-weight: bold;
  font-weight: 700;
  line-height: 1.2;
}

.article-content {
  flex: 1;
  margin-top: 5px;
}

.article-info {
  display: flex;
  font-size: 0.85em;
  justify-content: space-between;
  margin-top: 10px;
}

5. 给第一篇文章一个水平布局

第一篇文章真的很大。为了优化空间的使用,让我们把它的布局切换成水平的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.first-article {
  flex-direction: row;
}

.first-article .article-body {
  flex: 1;
}

.first-article .article-image {
  height: 300px;
  order: 2;
  padding-top: 0;
  width: 400px;
}

这里的order属性是非常有用的,因为它允许我们改变HTML元素的顺序而不影响HTML标记。在标记中article-image实际上出现在article-body的前面,但它表现的好像出现在后面

6. 使布局可响应

这些就是我们想看到的,虽然它有点儿扁平。让我们通过响应式来修复它。

Flexbox一个极好的特性是你只需要移除容器上的display: flex规则就可以完全禁用Flexbox,同时保持所有其它的Flexbox属性(例如align-items或者flex)有效。

结果是,你可以通过仅在某个断点上启用Flexbox来触发一个响应式布局。

我们将从.columns和.column选择器中移除display: flex,而不是用一个Media Query包装它们:

1
2
3
4
5
6
@media screen and (min-width: 800px) {
  .columns,
  .column {
    display: flex;
  }
}

就是这样!在更小的屏幕上,所有的文章都在彼此的上面。超过800px时,它们将会被放置在两列中。

7. 添加最后的润色

为使布局在大屏幕上更具吸引力,让我们添加一些CSS微调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@media screen and (min-width: 1000px) {
  .first-article {
    flex-direction: row;
  }

  .first-article .article-body {
    flex: 1;
  }

  .first-article .article-image {
    height: 300px;
    order: 2;
    padding-top: 0;
    width: 400px;
  }

  .main-column {
    flex: 3;
  }

  .nested-column {
    flex: 2;
  }
}

第一篇文章的内容是水平放置的,文本在左边,图片在右边。而且,主列现在更宽(75%),嵌套列也是(66%)。这里是最终结果!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<header class="header">
  <h1>The Envato Tuts+ Report</h1>
  <h2><a href="http://webdesign.tutsplus.com/tutorials/how-to-build-a-news-website-layout-with-flexbox--cms-26611">Read tutorial</a></h2>
</header>

<main class="main columns">
  <section class="column main-column">
    <a class="article first-article" href="#">
      <figure class="article-image is-4by3">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/210284/image-01-lo.jpg" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
          Maecenas non massa sem.
          Etiam finibus odio quis feugiat facilisis.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin ornare magna eros.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>

    <div class="columns">
      <div class="column nested-column">
        <a class="article" href="#">
          <figure class="article-image is-16by9">
            <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/210284/image-02-lo.jpg" alt="">
          </figure>
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
              Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
              Maecenas non massa sem.
              Etiam finibus odio quis feugiat facilisis.
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
              Proin ornare magna eros, eu pellentesque tortor vestibulum ut.
              Maecenas non massa sem.
              Etiam finibus odio quis feugiat facilisis.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
      </div>

      <div class="column">
        <a class="article" href="#">
          <figure class="article-image is-16by9">
            <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/210284/image-03-lo.jpg" alt="">
          </figure>
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
        <a class="article" href="#">
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
        <a class="article" href="#">
          <div class="article-body">
            <h2 class="article-title">
              Hello World
            </h2>
            <p class="article-content">
              Lorem ipsum dolor sit amet feugiat facilisis.
            </p>
            <footer class="article-info">
              <span>By Joe Smith</span>
              <span>42 comments</span>
            </footer>
          </div>
        </a>
      </div>
    </div>
  </section>

  <section class="column">
    <a class="article" href="#">
      <figure class="article-image is-3by2">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/210284/image-04-lo.jpg" alt="">
      </figure>
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
    <a class="article" href="#">
      <div class="article-body">
        <h2 class="article-title">
          Hello World
        </h2>
        <p class="article-content">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <footer class="article-info">
          <span>By Joe Smith</span>
          <span>42 comments</span>
        </footer>
      </div>
    </a>
  </section>
</main>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700);
@import url(https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css);

html {
  background: mediumseagreen;
  font-size: 14px;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  min-width: 300px;
  overflow-x: hidden;
  overflow-y: scroll;
  text-rendering: optimizeLegibility;
}

body {
  color: #666;
  font-family: "Source Sans Pro", "Helvetica", "Arial", sans-serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.4;
}

a {
  text-decoration: none;
  transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
}

div, h2, p, figure {
  margin: 0;
  padding: 0;
}

.header {
  color: white;
  padding: 40px 0 20px;
  text-align: center;
}

.header h1 {
  font-size: 40px;
  font-weight: bold;
}

.header h2 a {
  border-bottom: 1px solid rgba(255, 255, 255, 0.5);
  color: white;
  font-size: 20px;
  opacity: 0.5;
}

.header h2 a:hover {
  border-bottom-color: white;
  opacity: 1;
}

.main {
  margin: 0 auto;
  max-width: 1040px;
  padding: 10px;
}

.column {
  flex: 1;
  flex-direction: column;
}

.article {
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
  color: #666;
  display: flex;
  flex: 1;
  flex-direction: column;
  flex-basis: auto;
  margin: 10px;
}

.article:hover,
.article:focus {
  box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
  color: #444;
}

.article-image {
  background: #eee;
  display: block;
  padding-top: 75%;
  position: relative;
  width: 100%;
}

.article-image img {
  display: block;
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

.article-image.is-3by2 {
  padding-top: 66.6666%;
}

.article-image.is-16by9 {
  padding-top: 56.25%;
}

.article-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 20px;
}

.article-title {
  color: #333;
  flex-shrink: 0;
  font-size: 1.4em;
  font-weight: 700;
  line-height: 1.2;
}

.article-content {
  flex: 1;
  margin-top: 5px;
}

.article-info {
  display: flex;
  font-size: 0.85em;
  justify-content: space-between;
  margin-top: 10px;
}

@media screen and (min-width: 800px) {
  .columns,
  .column {
    display: flex;
  }
}

@media screen and (min-width: 1000px) {
  .first-article {
    flex-direction: row;
  }

  .first-article .article-body {
    flex: 1;
  }

  .first-article .article-image {
    height: 300px;
    order: 2;
    padding-top: 0;
    width: 400px;
  }

  .main-column {
    flex: 3;
  }

  .nested-column {
    flex: 2;
  }
}

总结

我希望我已经向你展示在投入和开始使用Flexbox前你不需要理解它的每个方面!响应式新闻布局是一个真正有用的模式。拆解它,把玩它,让我们知道你是如何进展的!

ANTLR 4权威参考读书笔记(17)

用自顶向下的语法指定和通过手工的递归下降语法分析器识别表达式一直是个麻烦。首先是因为大部分自然语法是模糊的,其次是因为大部分自然语法规格使用一种被称为左递归的特殊类型递归。所以自顶向下的语法和语法分析器不能处理传统形式上的左递归。

为了阐明这个问题,设想一个算术表达式语言,它只有乘法和加法运算符以及整数。表达式是自相似的。也就是说,一个乘法表达式是由“*”运算符连接的两个子表达式。同样的,一个加法表达式是由“+”运算符连接的两个子表达式。我们也可以把整数看作表达式。整个语法规则看起来就像下面显示的那样:

1
2
3
4
expr : expr '*' expr    // 匹配由“*”运算符连接的子表达式
     | expr '+' expr    // 匹配由“+”运算符连接的子表达式
     | INT              // 匹配简单整数
     ;

问题是上述规则对于某些输入短语来说是模棱两可的。换句话说,这个规则能用多种方法匹配单个输入流。对于简单的整数和像1+2和1*2这样的单运算符表达式是没问题的,因为只有一种方法能去匹配它们。例如,规则可以仅用第二个选项匹配1+2。就像下图左边所示的那样:

问题在于指定的规则可以像中间和右边语法分析树描绘的那样用两种方法解释1+2*3这样的输入。两者的解释是不同的,因为中间的树说1加到2乘3的结果上,而右边的树说1加2的结果乘以3。这是一个运算符优先级的问题,但常规语法根本没有指定优先级的方法。大部分语法工具使用额外的符号来指定运算符优先级。

与此相反的是,ANTLR使用有利于首先给出的选项,隐式地允许我们指定运算符优先级的方法来解决二义性。规则expr有一个乘法选项在加法选项之前,因此,ANTLR解决1+2*3的运算符二义性的方法有利于乘法。

默认情况下,ANTLR是从左到右结合运算符,然而某些像指数这样的运算符则是从右到左。因此,我们必须使用参数assoc手动指定运算符记号上的相关性。这里是一个能正确地把输入2^3^4解释成2^(3^4)的表达式规则:

1
2
3
expr : expr '^'<assoc=right> expr    // 运算符是右结合的
     | INT
     ;

下图中的语法分析树阐明了运算符左右结合版本的不同。右边的语法分析树是惯常的解释:

为了把指数、乘法和加法这三个运算符合并成一条规则,我们把指数表达式选项放在其它表达式选项之前,因为它的运算符优先级比乘法和加法都要高。合并后的语法如下所示:

1
2
3
4
5
expr : expr '^'<assoc=right> expr    // 运算符是右结合的
     | expr '*' expr                 // 匹配由“*”运算符连接的子表达式
     | expr '+' expr                 // 匹配由“+”运算符连接的子表达式
     | INT                           // 匹配简单整数
     ;

不像其它常规的语法分析器生成器那样,ANTLR v4是可以处理直接左递归的。左递归规则是指在选项的左边缘直接或者间接调用自身的规则。规则expr是直接左递归的,因为除INT选项外的其它所有选项都开始于规则expr自身的引用。如果规则expr的引用处在某些选项的右边缘,那么它就是右递归的。虽然ANTLR v4可以处理直接左递归,但它不能处理间接左递归。

1
2
expr : expo ;    // 通过expo左递归地间接调用expr
expo : expr '^'<assoc=right> expr ;

ANTLR v4可以简化直接左递归的表达式规则的工作。这种新的机制不仅更有效率,而且表达式规则也更小和更容易理解。

ANTLR 4权威参考读书笔记(16)

  • initializer 初始值设定项
  • construct 构造体

现在,我们已经有了一个自顶向下的草拟出语法的通用策略,下面我们要专注于一些常用的语言模式。尽管在过去几十年里有大量的语言被发明,但仍然只有较少的基本语言模式需要被处理。这是因为人们趋向于设计遵循自然语言约束的语言,语言也会因为设计者遵循数学上的常用表示法而趋向于相似。甚至在词法级别,语言趋向于重用一些相同的结构,例如标志符、整数、字符串等。这些单词顺序和依赖的约束来源于自然语言,并逐渐演化成为四种抽象的语言模式:

模式1:序列

这是像数组初始值设定项中的值那样的元素序列,也是在计算机语言中最常见的结构。例如,下面是登录到POP服务器时的序列:

1
2
3
USER parrt
PASS secret
RETR 1

这些命令本身也是序列。大部分命令是一个关键字(保留标志符,例如USER和RETR)跟随一个运算元再跟随一个换行符。为了在语法中指定此类序列,我们可以按照顺序简单地列出各个元素。以下是检索命令的序列(其中INT表示整数记号类型):

1
retr : 'RETR' INT '\n' ;

我们可以给RETR序列打上retr规则的标签,这样在语法的其它地方,我们就能使用规则名字作为简写来引用RETR序列。

对于任意长度的序列像矢量[1 2 3]这样的简单整数列表,虽然它是一个有限序列,但我们不可能通过像INT INT INT ...这样的规则片段来列出所有可能的整数列表。为了编码这样的一个或者多个元素,我们使用“+”子规则运算符。例如,{INT}+表示任意长度的整数序列,或者使用简写INT+也可以。至于可以为空的列表,我们则使用零个或者多个运算符“*”。

这种模式的变体有带终结符的序列和带分隔符的序列,CSV文件就很好地示范了这两者。

1
2
3
file : (row '\n')* ;           // 带一个“\n”终结符的序列
row  : field (',' field)* ;    // 带一个“,”分隔符的序列
field: INT ;                   // 假设字段只是整数

规则file使用带终结符模式的列表去匹配零个或者多个row '\n'序列,记号“\n”终结序列的每个元素。规则row使用带分隔符模式的列表去匹配一个field后面有零个或者多个',' field序列,记号“,”分隔各个字段。

最后,还有个特殊类型的零个或者一个序列,用“?”指定。可以使用它去表达可选的构造体。

模式2:选择

这是一个在多个可供替代的短语之间的选择,比如在编程语言中不同种类的语句。为了在语言中表示选择的这个概念,我们使用“|”作为ANTLR中的“or”运算符去分隔被称为“选项”的语法选择。

回到CVS语法,我们可以通过整数或者字符串的选择让规则field变得更灵活。

1
field: INT | STRING ;

任何时候,如果你发现正在说“语言结构x可以是这个或者那个”,那么你就可以确定应该使用选择模式,在规则x中使用“|”。

模式3:记号依赖

记号依赖表示一个记号的存在需要在短语的其它地方有它的对等物的存在,比如匹配的左右括号。前面我们曾经使用INT+去表达矢量[1 2 3]中的整数非空序列。为指定周围有方括号的矢量,我们需要一种方法去表达记号中的依赖。如果我们在句子中看到一个符号,那么我们必须在句子的其它地方找到它的对等物。为表达这种语法,我们必须使用同时指定对等符号的序列,它们通常包围或分组着其它元素。在这个案例中,我们这样指定矢量:

1
vector : '[' INT+ ']' ;    // [1], [1 2], [1 2 3], ...

扫视任何有效的代码,你会看到必须成对出现的各种分组符号:(...),[...],{...}。但是要牢记,依赖符号并不是必须配对的,类C语言都有的a ? b : c三元运算符就指定了当看到“?”符号时需要在接下来的短语中看到“:”符号。

模式4:嵌套短语

嵌套短语有一个自相似的语言结构,它的子短语也遵循相同的结构。表达式是典型的自相似语言结构,由被运算符分隔的嵌套子表达式组成。类似地,while的代码块是嵌套在外部代码块内的一个代码块。我们在语法中使用递归规则表达自相似的语言结构。因此,如果规则的伪代码引用它自身,我们将需要一个递归的自引用规则。

让我们来看下代码块的嵌套是如何工作的。while语句是关键字while后随一个在括号中的条件表达式再后接一条语句。我们也可以把多条语句包裹在花括号里当作一个语句块。表达语法如下所示:

1
2
3
stat : 'while' '(' expr ')' stat    // 匹配WHILE语句
     | '{' stat* '}'                // 匹配在括号中的语句块
     ;

这里的stat可以是单条语句或者被花括号括起来的一组语句。规则stat是直接递归的,因为它在两个选项中直接引用它自身。如果我们把第二个选项移到它自己的规则中,规则stat和block将是双向间接递归的。语法如下所示:

1
2
3
4
stat : 'while' '(' expr ')' stat    // 匹配WHILE语句
     | '{' stat* '}'                // 匹配语句块
     ;
block : '{' stat* '}' ;             // 匹配在括号中的语句块

看下面仅有3类表达式(索引数组引用、括号表达式和整数)的简单语言的语法:

1
2
3
4
expr : ID '[' expr ']'    // a[1], a[b[1]], a[(2*b[1])]
     | '(' expr ')'       // (1), (a[1]), (((1))), (2*a[1])
     | INT                // 1, 94117
     ;

注意递归是如何自然地发生的。数组索引表达式的索引组件是表达式本身,因此我们只需要在选项中引用expr即可。

下图是关于两个例子输入的语法分析树:

分析树中的内部树节点是规则引用,叶子是记号引用。从树根到任何节点的路径表示元素的规则调用栈(或者ANTLR生成的递归下降语法分析器调用栈)。路径代表递归嵌套的子树有多个相同规则的引用。规则节点是其下方子树的标签。根节点是expr,所以整棵树是一个表达式。在1之前的那棵expr子树会把整数当作一个表达式。

实现上述模式,我们只需要由选项、记号引用、规则引用组成的语法规则即可。我们还可以把这些元素组成子规则,子规则是裹在括号内的行内规则。我们也可以将子规则标记为“?”或“*”或“+”循环去识别被包围的语法片段多次。

ANTLR 4权威参考读书笔记(15)

在聚焦到具体的语法规则内部结构之前,我们要先讨论下语法的整体剖析以及如何形成一套初始的语法骨架。

语法文件通常是由一个命名语法的头和一系列可以彼此调用的规则组成。就像下面这样:

1
2
3
4
5
grammar MyG;

rule1 : «stuff» ;
rule2 : «more stuff» ;
...

设计语法就是要搞清楚«stuff»是什么?哪个规则是开始规则。这要求我们需要知道给定语言的一系列代表性的输入例子。当然,从语言参考手册甚至另一个语法分析器生成器格式而来的语法也是有帮助的。

正确设计语法的方法是借鉴功能分解或者自顶向下的设计,从粗粒度级别到细粒度级别逐步定义语言结构并把它们编码为语法规则。所以,我们的第一个任务就是找到粗粒度语言结构的名字,同时它也是开始规则。在英语中我们使用sentence,对于XML文件来说它则是document。

设计开始规则的内容是用英语伪代码描述整个输入格式的问题。例如,“a comma-separated-value (CSV) file is a sequence of rows terminated by newlines.”这段文字,在is a左边的至关重要的单词file是规则名字,在is a右边的所有内容则成为在规则定义右则的«stuff»的内容:

1
file : «a sequence of rows terminated by newlines» ;

然后我们通过描述在开始规则右侧被确定的元素来进行下一个粒度级别的设计。在规则右侧的名词通常是对记号或尚未定义的规则的引用,这些记号是那些我们在正常情况下视为单词、标点符号、运算符的元素。就像单词是英语句子中的原子成分那样,记号在语法规则中也是如此。规则引用则涉及到像row那样需要被分解为更详细部分的其它语言结构。

进入细节的另外一层,我们可以说row是一系列被逗号分隔的field,而field则是一个数字或字符串。就像以下所示:

1
2
row   : «a sequence of fields separated by commas» ;
field : «number or string» ;

当没有规则再需要定义时,我们就得到了语法的一个粗略的草图。

如果有其它格式的语法作为参考的话设计语法会容易的多,但要小心不要盲目地遵循它,否则你会误入歧途的。非ANTLR格式的语法只是让你知道别人是如何决定分解语言中的短语的,它最大的作用就是可以给我们一份规则名称的列表作为参考。

不推荐从参考手册上复制粘贴语法到ANTLR,然后再通过细微的调整让它工作。把它当作一套指南而不是一段代码是更好的办法。为了清晰地描述语法,参考手册通常是相当松散的。这意味着语法能识别大量不在语言中的句子,或者语法可能不够明确,可以用多种方法匹配相同的输入序列。例如,语法可能会说表达式可以调用一个构建器或者访问一个函数,问题是像T(i)这样的输入可以同时匹配两者。理想情况下,在语法中是不能有这样的二义性的,每个输入句子我们只需要一种解释。

在另一个极端,参考手册中的语法有时过于明确地说明了规则。有些约束是需要在分析完输入后实施的,而不是试图对语法结构实施约束。例如,W3C XML语法就显式地指定标签中什么地方必须要有空格以及什么地方的空格可以省略。但事实是我们可以简单地让词法分析器在把记号发送给语法分析器之前去除空格,不需要在语法中到处测试它。

规格还说<?xml ...>标签可以有两个附加属性encoding和standalone。我们需要知道约束,但它是很容易去允许任何属性名字,然后在语法分析后检查语法分析树,以确保所有这些限制都满足的。 归根结底,XML只是嵌在文本中的一对标签,因此它的语法结构是相当直白的。唯一的挑战是如何分别对待什么在标签内以及什么在标签外。

识别语法规则并用伪代码表示它们的右侧部分最初是个挑战,但当你为更多的语言构建语法后它会变得越来越容易。一旦我们有了伪代码,我们就需要把它转换成ANTLR表示法,以便能得到一个可以工作的语法。

ANTLR 4权威参考读书笔记(14)

对于大多数语法而言,注释和空格都是语法分析器可以忽略的东西。如果我们不想让注释和空格在语法中到处都是,那么就需要让词法分析器把它们扔掉。不幸的是,这意味着任何后续的处理步骤都不能再访问注释和空格。保留但忽略注释和空格的秘密是把这些发送给语法分析器的记号放到一个“隐藏通道”中。因为语法分析器只能调谐到单个通道,所以我们可以把任何我们想要的东西传递到其它通道中。这里是如何实现的语法:

1
2
3
4
5
6
COMMENT
    : '/*' .*? '*/' -> channel(HIDDEN)    // match anything between /* and */
    ;

WS  : [ \r\t\n]+    -> channel(HIDDEN)
    ;

就像我们前面讨论过的-> skip那样,-> channel(HIDDEN)也是一个词法分析器指令。在这里,它设置那些记号的通道号码以便它们可以被语法分析器忽略。记号流仍然维护着原始的记号序列,但在传递记号给语法分析器时会略过隐藏通道中的记号。

逻辑题-谁养鱼

  1. 在一条街上,有5座房子,喷了5种颜色。
  2. 每座房子里住着不同国籍的人。
  3. 每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物。

问题是:谁养鱼?

提示:

  1. 英国人住红色房子。
  2. 瑞典人养狗。
  3. 丹麦人喝茶。
  4. 绿色房子在白色房子左面。
  5. 绿色房子主人喝咖啡。
  6. 抽Pall Mall香烟的人养鸟。
  7. 黄色房子主人抽Dunhill香烟。
  8. 住在中间房子的人喝牛奶。
  9. 挪威人住第一间房子。
  10. 抽Blends香烟的人住在养猫的人隔壁。
  11. 养马的人住抽Dunhill香烟的人隔壁。
  12. 抽Blue Master的人喝啤酒。
  13. 德国人抽Prince香烟。
  14. 挪威人住蓝色房子隔壁。
  15. 抽Blends香烟的人有一个喝水的邻居。

在回答上述问题前先画一个6行5列的表格,从上到下的每一行分别代表房子的顺序(A表示左边第一间房子)、哪国人、房子颜色、饮料、香烟、宠物。下表是问题的初始状态:

A B C D E
? ? ? ? ?
? ? ? ? ?
? ? ? ? ?
? ? ? ? ?
? ? ? ? ?

根据提示8、9和14可以得到:

A B C D E
挪威人 ? ? ? ?
? 蓝色 ? ? ?
? ? 牛奶 ? ?
? ? ? ? ?
? ? ? ? ?

由提示4和5可以判定房子D的颜色是绿色,房子主人喝咖啡,房子E的颜色是白色。如下表所示:

A B C D E
挪威人 ? ? ? ?
? 蓝色 ? 绿色 白色
? ? 牛奶 咖啡 ?
? ? ? ? ?
? ? ? ? ?

结合提示1、7和11可以知道房子C是红色的,住的是英国人,房子A是黄色的。挪威人抽Dunhill香烟。住房子B的人养马。如下表所示:

A B C D E
挪威人 ? 英国人 ? ?
黄色 蓝色 红色 绿色 白色
? ? 牛奶 咖啡 ?
Dunhill ? ? ? ?
? ? ? ?

依据上表可知,挪威人喝的饮料是水、茶或者啤酒。结合提示3和12可以断定挪威人喝的是水。如下表所示:

A B C D E
挪威人 ? 英国人 ? ?
黄色 蓝色 红色 绿色 白色
? 牛奶 咖啡 ?
Dunhill ? ? ? ?
? ? ? ?

通过提示15可以得出住房子B的人抽Blends香烟。如下表所示:

A B C D E
挪威人 ? 英国人 ? ?
黄色 蓝色 红色 绿色 白色
? 牛奶 咖啡 ?
Dunhill Blends ? ? ?
? ? ? ?

结合提示12可以推断住房子E的人抽Blue Master香烟、喝啤酒。住房子B的人喝茶。如下表所示:

A B C D E
挪威人 ? 英国人 ? ?
黄色 蓝色 红色 绿色 白色
牛奶 咖啡 啤酒
Dunhill Blends ? ? Blue Master
? ? ? ?

由提示3、13得到房子B住的是丹麦人。房子D住的是德国人,抽Prince香烟。如下表所示:

A B C D E
挪威人 丹麦人 英国人 德国人 ?
黄色 蓝色 红色 绿色 白色
牛奶 咖啡 啤酒
Dunhill Blends ? Prince Blue Master
? ? ? ?

再由提示6和10确定住房子C的人抽Pall Mall香烟、养鸟。挪威人养猫。如下表所示:

A B C D E
挪威人 丹麦人 英国人 德国人 ?
黄色 蓝色 红色 绿色 白色
牛奶 咖啡 啤酒
Dunhill Blends Pall Mall Prince Blue Master
? ?

最后结合提示2推断得到房子E住的是瑞典人,养狗。如下表所示:

A B C D E
挪威人 丹麦人 英国人 德国人 瑞典人
黄色 蓝色 红色 绿色 白色
牛奶 咖啡 啤酒
Dunhill Blends Pall Mall Prince Blue Master
?

现在,结果已经出来了:德国人养鱼。