乐者为王

Do one thing, and do it well.

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
?

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

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

现在我们准备构建一个工具,用来把ANTLR 4权威参考读书笔记(7)中idata.txt里的数据按group分行显示,就像这样:

1
2
2 9 10
3 1 2 3

我们可以借助语法分析树的监听器机制来对词法分析结束后生成的记号流进行改写,我们不需要实现每一个监听器接口方法,只需要在捕获到group的时候把换行符插到它末尾就行。实现改写的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public class RewriteListener extends IDataBaseListener {
    TokenStreamRewriter rewriter;

    public RewriteListener(TokenStream tokens) {
        rewriter = new TokenStreamRewriter(tokens);
    }

    @Override
    public void enterGroup(IDataParser.GroupContext ctx) {
        rewriter.insertAfter(ctx.stop, '\n');
    }
}

接着就是写一个小程序来调用我们上面的改写类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class IData {

    public static void main(String[] args) throws Exception {
        InputStream is = args.length > 0 ? new FileInputStream(args[0]) : System.in;

        ANTLRInputStream input = new ANTLRInputStream(is);
        IDataLexer lexer = new IDataLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        IDataParser parser = new IDataParser(tokens);
        ParseTree tree = parser.file();

        RewriteListener listener = new RewriteListener(tokens);

        System.out.println("Before Rewriting");
        System.out.println(listener.rewriter.getText());

        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(listener, tree);

        System.out.println("After Rewriting");
        System.out.println(listener.rewriter.getText());
    }
}

这里的关键是TokenStreamRewriter对象知道如何在不修改流的情况下提供一个记号流的修改过的视图。它把所有的操作方法当作指令并把它们排进队列,等到在遍历记号流把它作为文本渲染回去的时候再执行。每次我们调用getText()时rewriter就会执行那些指令。

最后就是构建和测试应用:

1
2
3
antlr IData.g
compile *.java
run IData idata.txt

以下是输出结果:

1
2
3
4
5
Before Rewriting
29103123
After Rewriting
2910
3123

仅用几行代码,我们就能够没有任何烦恼地对某些内容做轻微的调整。这种策略对于源代码检测或重构这类一般性的问题是非常有效的。TokenStreamRewriter是一个非常强大且有效的操作记号流的工具。

开发者被灯光蒙蔽了双眼

英文原文:http://programmingzen.com/2008/12/30/developers-are-blinded-by-the-light/

Blinded by the light,
revved up like a deuce,
another runner in the night
— Bruce Springsteen

人类在计算几率方面是异常地差。我们有限的经验强烈地影响着我们对事件的可能性的认知。例如,我们往往极大地高估由恐怖袭击、意外枪支走火或者飓风引起死亡的几率,并且极大地低估像坠落、溺水或者流感死亡的原因。其原因是媒体经常提醒我们恐怖主义、飓风的危险或者关于孩子们被意外射杀的瞬间故事。你很少发现关于一个人溺水、坠落或者由于流感死亡的故事在国家新闻频道上被报道。新闻报道有一种倾向是耸人听闻,为的是引起人们的注意和钩住大量的观众,因此当谈到估计什么可能/不可能发生时它们促成人们的偏见。

同样的,在电视和报纸上过度曝光心花怒放的彩票中奖者举起他们超大的支票往往歪曲人们对通过购买一注彩票胜利的可能性的认知。对这个问题持一个严谨和客观的态度将会很快揭露中奖的几率比它们表面上看起来的要差得多。[1]

我注意到这种情况也正在开发/创业的世界里发生。这是一波新的淘金热。太多的开发者正在试图建立下一个大的社交网络,成为下一个Facebook(或YouTube),聚集数以百万计的人群,希望被大公司以一笔数量荒谬的钱收购。媒体喜欢这类故事。

因此,正在试图构建下一个Facebook的开发者类似于彩票买家。他们中的一些人会成功和获胜,但大部分人会惨遭失败。我们真正需要多少社交网络?广告支撑的模式适合某些设法吸引庞大的人群同时保持其费用最低(例如PlentyOfFish)或者被收购(例如YouTube,它也在花Google的钱)的幸运的公司。在这个过程中其他人都是在烧钱以及浪费VC的钱和诚意。

我担心很多开发者被灯光蒙蔽了双眼。他们对获得成功的真实几率的认知受到了媒体连续报道的百万——如果不是10亿——美元收购和成功故事的扭曲。而且有些VC鼓励这种行为,希望能在他们的投资上看到高回报。毕竟这些都是非常富有的人,他们对小规模的成功不感兴趣。

除了明显的浪费时间和资源之外,我认为许多开发者为了追求极不可能的结果放弃了极好的机会。用一个传统商业计划挣1,000万的可能性和按YouTube的方式挣10亿的可能性的比例,与这些金额能负担得起你的不同生活质量不成正比。如果你破产了,有3万美元的信用卡债务,或者你是中产阶级,你会发现1,000万美元可以提高的生活质量总是远远超过从1,000万到10亿能提高的。而且重要的是,我们要认识到瞄准尽管更小,但更有可能的结果不会以任何方式阻止你以后的“伟大梦想”,一旦你的第一次(或者第一次成功的)创业已经取得了成功。

你愿意参加20次有1次胜利机会的100万美元抽奖,还是50,000,000次有1次机会的5亿美元抽奖?理性的人会选择第1个,然而在今天,大部分创业公司都倾向于选择第2个。他们这么做是因为他们极大地高估了他们以第2个抽奖成功的几率。

创建一个产品并让人们为它付费。不要拿VC的钱,除非你真的不考虑依靠自己的力量启动你的公司。软件世界的主要优点之一是在开始的时候极少量的资本需要。如果你想做Web应用,可以使用软件即服务(SaaS)模型,让你的用户为你提供的软件和服务付费。你将会有更加少的受众,更少的可伸缩性问题和费用,以及有更多的收入和更大的盈利机会。Joel Spolsky(和他那华丽的办公空间)挣得数百万收入是因为他的公司在出售一套Web版的bug追踪器。你知道有多少免费的bug追踪器?在这个市场上存在多少竞争对手?我确信有许多。然而,虽然Joel的人气毫无疑问地帮助到了他的公司,但此案例仍然展示了一个企业如何通过构建一个更好的产品而成功。

就像David Heinemeier Hansson提及的,有无数不受关注的公司在像那样挣钱。[2]如果你把视线从聚光灯上移开,你将看到许多公司在它们所做的事情上面非常成功,尽管它们不出名或者没有制造新闻头条。它们中的有些公司实际上努力不去吸引太多的关注到它们的成功上(经常用数百万美元来衡量),以便防止竞争对手的涌现。

不管你的名字是不是家喻户晓,你甚至不必创建Web应用才能非常成功。你可能会想到为智能手机包括iPhone开发移动应用。但是良好的老式桌面应用让各种各样的软件公司继续发展。这就是为什么“你不能再用商业桌面软件挣钱,或者桌面应用都死了”的扭曲的认知太荒谬的原因。作为开发者/微型ISV/创业公司,你用良好设计的桌面软件挣钱的机会远远高于构建任何一款YouTube、Flickr或者Facebook复制品的机会。

要了解我们的认知是怎么被扭曲的,你只需要和那些公开分享它们软件销售统计的公司交谈。你将会被用相对普通的软件挣到钱的数额震惊。Balsamiq制作了一款UI草图应用卖79美元。作者成功挣到了10万美元收入在前5个月,大部分是通过销售应用的桌面版本。在这个产业里他当然远非最大的赢家之一。我提到这个不过是因为它表明那是一个非常不错的想法,很好执行,当你让你的用户付费时能很快带来收入。如果你认为在5个月里10万美元很少,那我来问你有多少免费网站达成一个类似的每月收入净额。如果你正在寻找更大的收入,了解下Omni Graffle,它给Omni Group赚取了数百万美元,或者把你的目光放到B2B应用上(在那个市场里某些应用卖上千美元一份)。

当许多开发者被灯光蒙蔽了双眼的时候,有创业想法的智者正在建立真正的软件业务。我请你走出来做同样的事情。

脚注

[1] 我在这里总结的概念被Dan Gilbert在这个TED演讲里更详细地说明了。

[2] David Heinemeier Hansson在他的一篇帖子中持类似的观点,该帖给了本文以灵感。

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

  • sentinel 哨兵
  • tag 标签

目前我们看到的输入文件都只包含一种语言,但在实际应用中我们可能会遇到某些包含多种语言的文件格式。例如,Java的文档注释,XML文件等。这些环绕着模板表达式的文本需要不同的处理方式,它们被称为孤岛语言。

ANTLR有提供一个称之为“词法模型”的词法分析器特性,它让我们可以很容易地处理包含混合格式的文件。基本思路是:当词法分析器看到特殊的哨兵字符序列时,就让它在模式之间来回切换。

XML是个很好的例子,它通常会在同一个文件中包含不同的词法结构。一个XML语法分析器会把除标签和实体引用(例如&hearts;)之外的任何东西当作文本块。当词法分析器看到<时,它切换到INSIDE模式;当它看到>/>时,就切换回默认模式。以下的语法展示了该特性是如何工作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
lexer grammar XMLLexer;

// Default "mode": Everything OUTSIDE of a tag
OPEN        :   '<'                 -> pushMode(INSIDE) ;
COMMENT     :   '<!--' .*? '-->'    -> skip ;
EntityRef   :   '&' [a-z]+ ';' ;
TEXT        :   ~('<'|'&')+ ;    // match any 16 bit char minus < and &

// ----------------- Everything INSIDE of a tag ---------------------
mode INSIDE;

CLOSE       :   '>'                 -> popMode ;    // back to default mode
SLASH_CLOSE :   '/>'                -> popMode ;
EQUALS      :   '=' ;
STRING      :   '"' .*? '"' ;
SlashName   :   '/' Name ;
Name        :   ALPHA (ALPHA|DIGIT)* ;
S           :   [ \t\r\n]           -> skip ;

fragment
ALPHA       :   [a-zA-Z] ;

fragment
DIGIT       :   [0-9] ;

把上述语法保存为XMLLexer.g文件,然后使用包含以下内容的t.xml文件作为输入来测试它:

1
2
3
<tools>
  <tool name="ANTLR">A parser generator</tool>
</tools>

以下是构建和运行测试的命令:

1
2
3
antlr XMLLexer.g
compile *.java
grun XML tokens -tokens t.xml

这里是输出的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[@0,0:0='<',<1>,1:0]
[@1,1:5='tools',<10>,1:1]
[@2,6:6='>',<5>,1:6]
[@3,7:10='\r\n  ',<4>,1:7]
[@4,11:11='<',<1>,2:2]
[@5,12:15='tool',<10>,2:3]
[@6,17:20='name',<10>,2:8]
[@7,21:21='=',<7>,2:12]
[@8,22:28='"ANTLR"',<8>,2:13]
[@9,29:29='>',<5>,2:20]
[@10,30:47='A parser generator',<4>,2:21]
[@11,48:48='<',<1>,2:39]
[@12,49:53='/tool',<9>,2:40]
[@13,54:54='>',<5>,2:45]
[@14,55:56='\r\n',<4>,2:46]
[@15,57:57='<',<1>,3:0]
[@16,58:63='/tools',<9>,3:1]
[@17,64:64='>',<5>,3:7]
[@18,65:66='\r\n',<4>,3:8]
[@19,67:66='<EOF>',<-1>,4:0]

上面输出的每一行代表一个记号,包含记号索引、开始和结束字符、记号文本、记号类型,最后的行和字符位置则告诉我们词法分析器如何标记化输入。

在命令行中,XML tokens序列处通常是一个语法名字后面跟着开始规则,但在这里,我们使用语法名字后面跟着特殊的规则名字tokens来告诉TestRig应该运行词法分析器而不是语法分析器。接着使用选项-tokens打印出匹配的记号列表。