乐者为王

Do one thing, and do it well.

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

在本节中,我们准备讲讲有时候事件方法需要传递部分结果或其它信息的问题。

在事件方法间共享信息

无论收集信息还是计算值,传递参数和返回值都是比使用字段和全局变量更方便良好的编程实践。问题是ANTLR自动生成的监听器方法的签名不需要特定应用的返回值或参数,ANTLR也自动生成访问者方法而不需要特定应用的参数。

接下来,我们将探讨让事件方法无需修改事件方法签名就能传递数据的机制。我们将构建同样的简单计算器的3个不同实现,基于前面章节的LExpr表达式语法。第一个实现使用访问者方法返回值,第二个定义了一个在事件方法间共享的字段,第三个则注解语法分析树节点以便储存感兴趣的值。

使用访问者遍历语法分析树

构建基于访问者的计算器,最简单的方法是让和规则expr相关的事件方法返回子表达式的值。例如,visitAdd()将返回两个子表达式相加的值,visitInt()将返回整型的值。传统的访问者不指定visit方法的返回值。当我们为特定应用需求实现一个类时添加返回类型是容易的,扩展LExprBaseVisitor并提供Integer作为类型参数。访问者代码看起来如下所示:

1
2
3
4
5
6
7
8
9
10
11
public static class EvalVisitor extends LExprBaseVisitor<Integer> {
    public Integer visitMult(LExprParser.MultContext ctx) {
        return visit(ctx.e(0)) * visit(ctx.e(1));
    }
    public Integer visitAdd(LExprParser.AddContext ctx) {
        return visit(ctx.e(0)) + visit(ctx.e(1));
    }
    public Integer visitInt(LExprParser.IntContext ctx) {
        return Integer.valueOf(ctx.INT().getText());
    }
}

EvalVisitor从ANTLR生成的AbstractParseTreeVisitor类继承通用的visit()方法,我们的访问者使用它去准确地触发子树访问。

注意,EvalVisitor没有针对规则s的访问者方法。在LExprBaseVisitor中的visitS()的默认实现调用预定义的方法ParseTreeVisitor.visitChildren(). visitChildren()返回从最后的子节点访问返回的值。在这里,visitS()返回访问它唯一的子节点(节点e)时返回的表达式的值。我们可以使用这种默认的行为。

在测试文件TestLEvalVisitor.java中,我们有常用代码去启动LExprParser和打印语法分析树,然后我们需要编码去启动EvalVisitor和打印出当访问树时计算出的表达式的值。

1
2
3
EvalVisitor evalVisitor = new EvalVisitor();
int result = evalVisitor.visit(tree);
System.out.println("visitor result = " + result);

要构建计算器,需要告诉ANTLR使用-visitor参数去生成访问者。(如果我们不再需要生成监听器,可以使用-no-listener参数)以下是完整的构建和测试序列:

1
2
3
antlr -visitor LExpr.g
compile *.java
run TestLEvalVisitor

接着输入以下内容:

1
2
1+2*3
EOF

你就会看到如下结果:

1
2
(s (e (e 1) + (e (e 2) * (e 3))))
visitor result = 7

如果我们需要特定应用的返回值,访问者工作的相当好,因为我们使用了内建的Java返回值机制。如果我们不希望显式地调用访问者方法去访问子节点,我们可以切换到监听器机制,不幸的是,这意味着我们要放弃使用Java方法返回值的整洁。

使用栈模拟返回值

ANTLR生成的监听器事件方法没有返回值。为了给在语法分析树更高节点上执行的监听器方法返回值,我们可以把部分的值存储在监听器的一个字段中。我们会想到用栈来存储值,方法就是把计算一个子表达式的结果推送到栈中,在语法分析树上用于子表达式的方法则把运算元从栈中弹出。以下是完整的Evaluator计算器监听器(代码在TestLEvaluator.java文件中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static class Evaluator extends LExprBaseListener {
    Stack<Integer> stack = new Stack<Integer>();
    public void exitMult(LExprParser.MultContext ctx) {
        int right = stack.pop();
        int left = stack.pop();
        stack.push(left * right);
    }
    public void exitAdd(LExprParser.AddContext ctx) {
        int right = stack.pop();
        int left = stack.pop();
        stack.push(left + right);
    }
    public void exitInt(LExprParser.IntContext ctx) {
        stack.push(Integer.valueOf(ctx.INT().getText()));
    }
}

要测试上面的这段代码,我们可以创建和使用在代码TestLEvaluator中的ParseTreeWalker,以下是完整的构建和测试序列:

1
2
3
antlr LExpr.g
compile *.java
run TestLEvaluator

接着输入以下内容:

1
2
1+2*3
EOF

你就会看到如下结果:

1
2
(s (e (e 1) + (e (e 2) * (e 3))))
stack result = 7

使用栈字段有点别扭但工作得很好。我们必须确保事件方法以正确的顺序压入和弹出跨越监听器事件的值。带有返回值的访问者没有栈的这种笨拙但却需要手工访问树的节点。第三种实现是通过把部分值隐藏在树节点中来捕获它们。

注解语法分析树

作为使用临时存储在事件方法间共享数据的替代,我们可以把这些值存储在语法分析树本身中。使用树注解方法时我们可以带有监听器或访问者,但在这里我们使用监听器来阐明如何使用它。让我们首先看一下用部分结果注解的1+2*3的LExpr语法分析树。

lexpr-parse-tree

每个子表达式对应一个子树根(和对应一个e规则调用)。从e节点发出的水平线指向的数字是我们想要返回的部分结果。

让我们看看节点注解策略将如何工作在来自LExpr语法的规则e上。

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

e选项的监听器方法每个都会存储一个结果在相对应的e语法分析树节点中。任何随后的在语法分析树更高节点上的add或multiply事件将通过查看存储在它们对应的子节点中的值来抓取子表达式的值。

现在,让我们假设每个语法分析树节点(每个规则上下文对象)都有一个字段value,那么exitAdd()看起来将是这样;

1
2
3
4
public void exitAdd(LExprParser.AddContext ctx) {
    // e(0).value is the subexpression value of the first e in the alternative
    ctx.value = ctx.e(0).value + ctx.e(1).value;    // e '+' e # Add
}

这看起来相当合理,但不幸的是,在Java中我们不能扩展类ExprContext去动态地添加字段。为了让语法分析树注解生效,我们需要一种方法去注解各式各样的节点而不需要手工修改由ANTLR生成的关联节点类。

注解语法分析树节点最简单的方式是使用与节点任意值相关联的一个Map。因此,ANTLR提供了一个简单的帮助类ParseTreeProperty。让我们在文件TestLEvaluatorWithProps.java中构建称作EvaluatorWithProps的另一个计算器版本,它使用ParseTreeProperty关联了LExpr语法分析树节点和部分结果。以下是在监听器开始处的适当的定义:

1
2
3
public static class EvaluatorWithProps extends LExprBaseListener {
    /** maps nodes to integers with Map<ParseTree,Integer> */
    ParseTreeProperty<Integer> values = new ParseTreeProperty<Integer>();

注意:如果你想使用自己的Map类型字段代替ParseTreeProperty,确保它继承自IdentityHashMap,而不是通常的HashMap。我们需要去注解特殊的节点,进行同一性测试而不是equals()。两个e节点可能是equals(),但在内存中不是同一个物理节点。

为注解一个节点,我们使用values.put(node, value)。为得到和一个节点有关联的值,我们使用values.get(node)。这很好,但是让我们创建一些有直白名字的帮助方法以便让代码更容易阅读。

1
2
public void setValue(ParseTree node, int value) { values.put(node, value); }
public int getValue(ParseTree node) { return values.get(node); }

让我们从最简单的表达式选项Int开始监听器方法。我们想使用它匹配的INT记号的整型值去注解它的语法分析树e节点。

1
2
3
4
public void exitInt(LExprParser.IntContext ctx) {
    String intText = ctx.INT().getText();    // INT    # Int
    setValue(ctx, Integer.valueOf(intText));
}

对于加法树,我们得到两个子表达式子节点的值(运算元)和带有和的注释的子树跟。

1
2
3
4
5
public void exitAdd(LExprParser.AddContext ctx) {
    int left = getValue(ctx.e(0));    // e '+' e    # Add
    int right = getValue(ctx.e(1));
    setValue(ctx, left + right);
}

方法exitMult()是相同的,只是运算的时候用multiply代替了add。

我们的测试代码从分析规则s开始。因此我们必须确保语法分析树根有e子树的值。为把值从e节点冒泡到根s节点,我们实现了exitS()。

1
2
3
4
/** Need to pass e's value out of rule s : e ; */
public void exitS(LExprParser.SContext ctx) {
    setValue(ctx, getValue(ctx.e()));    // like: int s() { return e(); }
}

以下是如何启动监听器以及打印出来自语法分析树根节点的表达式的值:

1
2
3
4
ParseTreeWalker walker = new ParseTreeWalker();
EvaluatorWithProps evalProp = new EvaluatorWithProps();
walker.walk(evalProp, tree);
System.out.println("properties result = " + evalProp.getValue(tree));

以下是构建和测试序列:

1
2
3
antlr LExpr.g
compile *.java
run TestLEvaluatorWithProps

接着输入以下内容:

1
2
1+2*3
EOF

你就会看到如下结果:

1
2
(s (e (e 1) + (e (e 2) * (e 3))))
stack result = 7

现在我们已经看到了相同计算器的3个实现,并且我们也已经准备好把我们的知识用于构建真实案例。因为每个方法都有它的优势和劣势,下面就让我们来比较下不同的技术。

比较信息共享方法

为得到可复用和可重定目标的语法,我们需要让它们完全清除用户定义的动作。这意味着要把所有特定应用的代码放到语法外的某些监听器和访问者中。监听器和访问者操作语法分析树,ANTLR自动生成合适的树遍历接口和默认实现。因为事件方法签名是固定的和不特定于应用的,所以事件方法可以共享信息的方式有3种:

  • 本地Java调用栈:访问者返回用户定义类型的一个值。如果访问者需要传递参数,它也必须使用下面两种技术的一种。
  • 基于栈:一个栈字段模仿参数和返回值,像Java调用栈那样。
  • 注解者:一个Map字段使用有用的值注解节点。

所有这3种方法是和语法本身完全解耦的,并且很好地封装在专门的对象中。除此之外,它们也都有各自的优点和缺点。我们可以根据问题的需要和个人的喜好决定采取哪种方法。你甚至可以在同一个应用中使用多种方法。

访问者方法很好懂,因为它们直接调用其它访问者方法去获取部分结果,并且能像其它任何方法那样返回值。这也是它们的缺点,访问者方法必须显式地访问它们的子节点。而监听器就不需要。因为访问者有个通用的接口,所以它不能定义参数。访问者必须使用其它解决方案的一种去传递参数给它在子节点上调用的访问者方法。访问者的空间效率很好,因为它在任何时间仅需保留少数的部分结果。在树遍历后没有部分结果保留。当访问者方法可以返回值时,每个值必须是同种类型,不想其它的解决方案。

基于栈的解决方案可以模仿参数和返回带有一个栈的值,但在手动管理栈时有个断开的机会。这可能会发生,因为监听器方法不能直接调用彼此。作为程序员,我们必须确定推入栈中的在将来事件方法调用能适当地弹出。栈可以传递多个值和多个返回值。基于栈的解决方案也是空间有效的,因为它不会把任何东西固定到树上。在树遍历后所有的部分结果存储消失。

注解者通常可以作为默认解决方案采用,因为它允许你任意地提供信息给事件方法操作语法分析树中上上下下的节点。你也可以传递多个值,它们可以是任意类型。在许多情况下注解胜于使用带有短暂值的栈。在各种方法的数据传递准备间很少有断开的机会。比起在编程语言中说返回值,使用setValue(ctx, value)注解树不太直观,但是更通用。超过其它两种的这种方法的唯一缺点是在树遍历期间部分结果是保留的,因此它有较大的内存占用。

从另一方面来说,在某些应用中能够注解树正是我们需要的。应用需要在树上通过多遍,第一遍是很方便在树上计算和储存数据的。当语法分析树遍历器重新遍历树的时候第二遍然后就很容易访问数据。总的来说,树注解非常灵活,有一个可接受的内存负担。

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

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

几何图形

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

effective-icons-1

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

effective-icons-2

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

effective-icons-3

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

effective-icons-4

界面图标

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

effective-icons-5

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

1、选择圆角矩形工具。

effective-icons-6

2、拖动出一个图形。

effective-icons-7

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

effective-icons-8

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

effective-icons-9

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

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

effective-icons-10

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

effective-icons-11

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

effective-icons-12

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

effective-icons-13

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

effective-icons-14

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

effective-icons-15

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

effective-icons-16

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

effective-icons-17

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

effective-icons-18

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

effective-icons-19

最终,我想说创建优秀图标的方法不仅仅是学习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
        }
    }

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

propertyfile-visitor-hierarchy

访问者在子节点上通过显式地调用接口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,该语法分析器会自动构建如下图所示的语法分析树:

propertyfile-parse-tree

有了语法分析树,我们就可以使用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);
    }
}

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

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

propertyfile-listener-hierarchy

处于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-preview

在投入并开始之前你没有必要理解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是为了把信息栏推到底部。我们的目标文章布局如下图所示:

flexbox-card

这里是代码:

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。就像下图左边所示的那样:

ambiguity-parse-tree

问题在于指定的规则可以像中间和右边语法分析树描绘的那样用两种方法解释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
     ;

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

power-parse-tree

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

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可以简化直接左递归的表达式规则的工作。这种新的机制不仅更有效率,而且表达式规则也更小和更容易理解。