乐者为王

Do one thing, and do it well.

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

默认情况下,ANTLR为每个规则生成一个单一的事件类型,而不管语法分析器匹配了哪个选项。这很不方便,因为监听器和访问者方法必须确定哪个选项被语法分析器匹配。在本节中,我们将看到如何得到更细粒度的事件。

为规则选项贴标签以得到精确的事件方法

为阐明事件粒度问题,让我们为以下的表达式语法构建一个带有监听器的简单计算器:

1
2
3
4
5
6
grammar Expr;
s : e ;
e : e op=MULT e    // MULT is '*'
  | e op=ADD e     // ADD is '+'
  | INT
  ;

按照上面的语法,规则e会产生一个相当无用的监听器,因为规则e的所有选项导致树遍历器触发相同的enterE()和exitE()方法。

1
2
3
public interface ExprListener extends ParseTreeListener {
    void enterE(ExprParser.EContext ctx);
    void exitE(ExprParser.EContext ctx);

监听器方法必须使用op记号标签和ctx的方法进行测试以查看语法分析器为每个e子树匹配了哪个选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void exitE(ExprParser.EContext ctx) {
    if (ctx.getChildCount() == 3) {    // operations have 3 children
        int left = values.get(ctx.e(0));
        int right = values.get(ctx.e(1));
        if (ctx.op.getType() == ExprParser.MULT) {
            values.put(ctx, left * right);
        } else {
            values.put(ctx, left + right);
        }
    } else {
        values.put(ctx, values.get(ctx.getChild(0)));    // an INT
    }
}

在exitE()中引用的MULT字段是在ExprParser中由ANTLR自动生成的:

1
2
public class ExprParser extends Parser {
    public static final int MULT=1, ADD=2, INT=3, WS=4;

如果我们查看在类ExprParser中的类EContext,我们可以看到ANTLR把来自3个选项的所有元素都塞进了相同的上下文对象。

1
2
3
4
5
public static class EContext extends ParserRuleContext {
    public Token op;                     // derived from label op
    public List<EContext> e() { ... }    // get all e subtrees
    public EContext e(int i) { ... }     // get ith e subtree
    public TerminalNode INT() { ... }    // get INT node if alt 3 of e

为得到更精确的监听器事件,ANTLR让我们使用#运算符给任何规则最外层的选项打标签。让我们从Expr派生语法LExpr,并给e的选项打上标签,以下是修改后的e规则:

1
2
3
4
e : e MULT e  # Mult
  | e ADD e   # Add
  | INT       # Int
  ;

现在,ANTLR为e的每个选项生成了单独的监听器方法,因此,我们不再需要op记号标签。对于选项标签X,ANTLR生成方法enterX()和exitX()。

1
2
3
4
5
6
7
8
9
public interface LExprListener extends ParseTreeListener {
    void enterMult(LExprParser.MultContext ctx);
    void exitMult(LExprParser.MultContext ctx);
    void enterAdd(LExprParser.AddContext ctx);
    void exitAdd(LExprParser.AddContext ctx);
    void enterInt(LExprParser.IntContext ctx);
    void exitInt(LExprParser.IntContext ctx);
    ...
}

注意,ANTLR也为选项生成特定的以标签命名的上下文对象(EContext的子类)。专门的上下文对象的getter方法只限于应用在那些相关的选项。例如,IntContext只有一个INT()方法,我们可以在enterInt()中调用ctx.INT(),但在enterAdd()中就不能。

监听器和访问者是极好的。我们只需要通过充实事件方法就可以得到可复用和可重定目标的语法以及封装的语言应用。ANTLR甚至会为我们自动生成骨架代码。

如何创建有效的图标

英文原文: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权威参考读书笔记(22)

基于监听器的方法是极好的,因为所有树遍历和方法触发被自动完成。尽管有时候自动树遍历也是一个缺点,因为我们不能控制遍历本身。例如,我们可能想遍历一段C程序的语法分析树,通过跳过函数体子树忽略函数中的一切。监听器事件方法也不能使用方法返回值去传递数据。当我们需要控制遍历或返回带有事件方法返回值的值时,我们使用访问者模式。现在,让我们构建一个基于访问者版本的属性文件加载器去比较这两种方法。

使用访问者实现应用

使用访问者代替监听器,我们只需要让ANTLR生成访问者接口和实现接口,然后创建一段测试代码在语法分析树上调用visit(),根本不需要触及到语法。

在命令行使用-visitor参数时,ANTLR生成接口PropertyFileVisitor和类PropertyFileBaseVisitor,后者有如下的默认实现:

1
2
3
4
5
6
7
public class PropertyFileBaseVisitor<T> extends AbstractParseTreeVisitor<T>
                                        implements PropertyFileVisitor<T> {
    @Override
    public T visitFile(PropertyFileParser.FileContext ctx) { ... }
    @Override
    public T visitProp(PropertyFileParser.PropContext ctx) { ... }
}

我们可以从监听器的exitProp()中拷贝映射功能,然后把它粘贴到与规则prop相关的访问者方法中。

1
2
3
4
5
6
7
8
9
10
public class TestPropertyFileVisitor {
    public static class PropertyFileVisitor extends PropertyFileBaseVisitor<Void> {
        Map<String,String> props = new OrderedHashMap<String, String>();
        public Void visitProp(PropertyFileParser.PropContext ctx) {
            String id = ctx.ID().getText();    // prop : ID '=' STRING '\n' ;
            String value = ctx.STRING().getText();
            props.put(id, value);
            return null;    // Java says must return something even when Void
        }
    }

这里是访问者接口和类之间的继承关系:

访问者在子节点上通过显式地调用接口ParseTreeVisitor的visit()方法遍历语法分析树。那些方法在AbstractParseTreeVisitor中实现。在这里,为prop调用创建的节点没有子树,因此visitProp()不需要调用visit()。

在监听器和访问者的测试代码(例如TestPropertyFileVisitor)之间最大的不同是访问者的测试代码不需要ParseTreeWalker,它只需要让访问者去访问由语法分析器创建的树。

1
2
3
PropertyFileVisitor loader = new PropertyFileVisitor();
loader.visit(tree);
System.out.println(loader.props);    // print results

以下是构建和测试序列:

1
2
3
antlr -visitor PropertyFile.g  # create visitor as well this time
compile *.java
run TestPropertyFileVisitor t.properties

这里是输出的内容:

1
{user="parrt", machine="maniac"}

我们可以使用监听器和访问者构建几乎任何我们想要的。一旦我们处于Java空间,就不再需要学习更多的ANTLR知识。我们只要知道语法、语法分析树、监听器和访问者事件方法之间的关系。除此之外,就是代码。在对识别中的输入短语的回答中,我们可以生成输出、收集信息、以某种方式验证短语,或者执行计算。

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

前文的语法仍然存在问题,因为它限制我们只能使用Java生成语法分析器。为使语法可复用和语言无关,我们需要完全避免嵌入动作。接下来就将展示如何用监听器做到这点。

使用语法分析树监听器实现应用

在构建语言应用时要避免应用和语法纠缠在一起,关键是让语法分析器生成语法分析树,然后遍历该树去触发特定的应用代码。我们可以使用我们最喜欢的技术遍历树,也可以使用ANTLR生成的树遍历机制中的一个。在本节中,我们将使用ANTLR内建的ParseTreeWalker构建一个基于监听器版本的属性文件应用。

让我们从属性文件语法的原始版本开始:

1
2
file : prop+ ;
prop : ID '=' STRING '\n' ;

下面是属性示例文件t.properties的内容:

1
2
user="parrt"
machine="maniac"

通过上述语法,ANTLR生成PropertyFileParser,该语法分析器会自动构建如下图所示的语法分析树:

有了语法分析树,我们就可以使用ParseTreeWalker去访问所有的节点,触发进入和退出方法。

现在来看一下ANTLR通过语法PropertyFile生成的监听器接口PropertyFileListener,当ANTLR的ParseTreeWalker发现和完成节点时,它会为每个规则子树分别触发进入和退出方法。因为在语法PropertyFile中只有两条语法规则,所以在接口中有4个方法:

1
2
3
4
5
6
public interface PropertyFileListener extends ParseTreeListener {
    void enterFile(PropertyFileParser.FileContext ctx);
    void exitFile(PropertyFileParser.FileContext ctx);
    void enterProp(PropertyFileParser.PropContext ctx);
    void exitProp(PropertyFileParser.PropContext ctx);
}

FileContext和PropContext对象是针对每条语法规则的语法分析树节点的实现,它们包含一些有用的方法。

为方便起见,ANTLR也会生成带有默认实现的类PropertyFileBaseListener,这些默认实现模仿在前文语法中@member区域我们手写的空白方法。

1
2
3
4
5
6
7
public class PropertyFileBaseVisitor<T> extends AbstractParseTreeVisitor<T>
                                        implements PropertyFileVisitor<T> {
    @Override
    public T visitFile(PropertyFileParser.FileContext ctx) { }
    @Override
    public T visitProp(PropertyFileParser.PropContext ctx) { }
}

默认实现让我们只需要覆盖和实现那些我们关心的方法。例如,以下是属性文件加载器的一个重新实现,像前面那样它只有一个方法,但使用监听器机制:

1
2
3
4
5
6
7
8
public static class PropertyFileLoader extends PropertyFileBaseListener {
    Map<String,String> props = new OrderedHashMap<String, String>();
    public void exitProp(PropertyFileParser.PropContext ctx) {
        String id = ctx.ID().getText();    // prop : ID '=' STRING '\n' ;
        String value = ctx.STRING().getText();
        props.put(id, value);
    }
}

主要的不同是这个版本扩展了基类监听器,而不是语法分析器,监听器方法在语法分析器完成后被触发。

这里面有很多的接口和类,让我们看一下在这些关键元素间的继承关系。

处于ANTLR运行库中的接口ParseTreeListener要求每个监听器对事件visitTerminal()、enterEveryRule()和exitEveryRule()作出反应,如果有语法错误的还要加上visitErrorNode()。ANTLR从语法文件PropertyFile生成接口PropertyFileListener,并且为类PropertyFileBaseListener的所有方法生成默认实现。我们仅需要构建PropertyFileLoader,它继承了PropertyFileBaseListener中的所有空方法。

方法exitProp()可以访问与规则prop相关的规则上下文对象PropContext,该上下文对象持有在规则prop中提到的每个元素(ID和STRING)的对应方法。因为这些元素是语法中的记号引用,所以方法返回语法分析树节点TerminalNode。我们既可以通过getText()直接访问记号的文本,也可以通过getSymbol()首先获取Token。

现在让我们创建测试文件TestPropertyFile.java遍历树,倾听来自PropertyFileLoader的声音:

1
2
3
4
5
6
// create a standard ANTLR parse tree walker
ParseTreeWalker walker = new ParseTreeWalker();
// create listener then feed to walker
PropertyFileLoader loader = new PropertyFileLoader();
walker.walk(loader, tree);    // walk parse tree
System.out.println(loader.props);    // print results

然后就是编译和生成代码,运行测试程序去处理输入文件:

1
2
3
antlr PropertyFile.g
compile *.java
run TestPropertyFile t.properties

这里是输出的内容:

1
{user="parrt", machine="maniac"}

测试程序成功地把文件中的属性赋值重组成内存中的映射数据结构。

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

单独的语法不是很有用,因为相关的语法分析器只能告诉我们某个输入句子是否遵守语言规格。为构建语言应用,我们需要语法分析器在看到特定的输入句子、短语或者记号时触发特定的动作。这些短语-动作对的集合表示语言应用或者至少是在语法和一个更大的周边应用之间的接口。

我们可以使用语法分析树监听器和访问者构建语言应用。监听器是一个响应规则进入和退出事件的对象,这些短语识别事件在语法分析树遍历器发现和完成节点时触发。为了支持应用必须控制树是如何被遍历的这种情况,ANTLR生成的语法分析树也支持著名的树访问者模式。

监听器和访问者之间最大的不同是,监听器方法不对显式地调用方法去遍历它们的子树负责,而访问者必须显式地触发访问子节点以保持树的遍历继续。通过这些显式的访问子树的调用,访问者控制遍历的顺序以及多少树被访问。为方便起见,我们使用术语“事件方法”来指代监听器回调或者访问者方法。

为了确切地知道ANTLR为我们构建了什么样的树遍历设施以及为什么,让我们首先看看监听器机制的起源以及如何使用监听器和访问者把特定应用的代码和语法分离。

从嵌入动作演化到监听器

监听器和访问者机制将语法从应用代码中解耦,提供了一些令人信服的好处。这种解耦很好地封装了应用程序,而不是把它切割成碎片分散到语法中的各个地方。没有嵌入动作,我们可以在不同的应用中复用相同的语法,甚至不需要重新编译生成的语法分析器。如果没有嵌入动作,ANTLR还可以用相同的语法生成不同编程语言的语法分析器。集成语法缺陷修复或更新也更容易,因为我们不必担心嵌入动作导致的合并冲突。

接下来,我们将探讨从带有嵌入动作的语法到完全解耦的语法和应用的演化。以下含有用«...»描述的嵌入动作的属性文件语法读取属性文件,每行一个属性赋值。像«start file»这样的动作只是Java代码适当的替代。

1
2
3
4
5
grammar PropertyFile;
file : {«start file»} prop+ {«finish file»} ;
prop : ID '=' STRING '\n' {«process property»} ;
ID   : [a-z]+ ;
STRING : '"' .*? '"' ;

这样的紧密耦合把语法束缚到一个特定的应用上。一个更好的方法是创建由ANTLR生成的语法分析器PropertyFileParser的子类,然后把嵌入动作转换成方法。重构仅留下不重要的方法调用在语法中触发新创建方法的动作,然后,通过子类化语法分析器,我们可以实现任意数量的不同应用而无需修改语法。这样的重构看起来像:

1
2
3
4
5
6
7
8
9
10
11
grammar PropertyFile;
@members {
    void startFile() { }    // blank implementations
    void finishFile() { }
    void defineProperty(Token name, Token value) { }
}

file : {startFile();} prop+ {finishFile();} ;
prop : ID '=' STRING '\n' {defineProperty($ID, $STRING)} ;
ID   : [a-z]+ ;
STRING : '"' .*? '"' ;

解耦可以让语法被不同的应用复用,但语法因为方法调用的关系仍然被绑定在Java上。

为演示已重构语法的复用性,让我们构建两个不同的应用。首先从遇到属性后只是打印它们的那个开始。这个过程只是去扩展由ANTLR生成的语法分析器类和覆盖一个或多个由语法触发的方法。

1
2
3
4
5
class PropertyFilePrinter extends PropertyFileParser {
    void defineProperty(Token name, Token value) {
        System.out.println(name.getText() + "=" + value.getText());
    }
}

由于ANTLR生成的PropertyFileParser类中的默认实现,我们不需要覆盖startFile()或finishFile()。

为了启动这个应用,我们需要创建一个特定的PropertyFilePrinter语法分析器子类的实例,而不是常规的PropertyFileParser的。

1
2
3
4
PropertyFileLexer lexer = new PropertyFileLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
PropertyFilePrinter parser = new PropertyFilePrinter(tokens);
parser.file();    // launch our special version of the parser

作为第二个应用,我们把属性加载到一个映射中而不是打印它们。我们需要做的就是创建一个新的子类并把不同的功能放到defineProperty()中。

1
2
3
4
5
6
class PropertyFileLoader extends PropertyFileParser {
    Map<String,String> props = new OrderedHashMap<String, String>();
    void defineProperty(Token name, Token value) {
        props.put(name.getText(), value.getText());
    }
}

在语法分析器执行完毕后,字段props将会包含名-值对。

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子树会把整数当作一个表达式。

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