我们开始第二章,文法分析的实现
接上回的词法分析
学习文法分析,个人最好的建议是要了解巴克斯诺尔范式,另外还需要了解上下无关文法
在此推荐大家这本理论计算机的建造,里面很详细的讲解了图灵机,词法,文法树的构造。
讲解文法树之前 我先讲讲英语语法,作为知识的铺垫
我们先设计一组文法规则
句子元素{
名词,冠词,动词,副词,形容词,宾语
}
语法规则{
句子=>名词+动词
名词=>冠词+形容词+名词
动词=>动词+名词+副词
}
终结符{
}
句子=>
名词+动词=>
冠词+名词+动词=>
冠词+形容词+名词+动词+宾语=>
冠词+形容词+名词+动词+宾语+副词
这就是一个很简答的从左到右的语法树
举个例子
I like
I like you
I fall in love with you
I fall in love with charming you
好了,简单介绍完毕,因为本人功力有限,如果想更清楚的去了解文法,可以看乔姆斯基范式,我们现在继续lox语言的语法设计。
expression → literal
| unary
| binary
| grouping ;
literal → NUMBER | STRING | "true" | "false" | "nil" ;
grouping → "(" expression ")" ;
unary → ( "-" | "!" ) expression ;
binary → expression operator expression ;
operator → "==" | "!=" | "<" | "<=" | ">" | ">="
| "+" | "-" | "*" | "/" ;
根据此上面的例子,我们可以看到,expression可以指定生成四种语法 然后每种语法又能生成特定的文法结构。
实现上面的epression
我们创建一个类expr
abstract class Expr {
static class Binary extends Expr {
Binary(Expr left, Token operator, Expr right) {
this.left = left;
this.operator = operator;
this.right = right;
}
final Expr left;
final Token operator;
final Expr right;
}
// Other expressions...
}
接下来,我们创建一个ast语法解析树生成器
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
public class GenerateAst {
public static void main(String[] args) throws IOException {
if (args.length != 1) {
System.err.println("Usage: generate_ast <output directory>");
System.exit(1);
}
String outputDir = args[0];
defineAst(outputDir, "Expr", Arrays.asList(
"Binary : Expr left, Token operator, Expr right",
"Grouping : Expr expression",
"Literal : Object value",
"Unary : Token operator, Expr right"
));
}
private static void defineAst(
String outputDir, String baseName, List<String> types)
throws IOException {
String path = outputDir + "/" + baseName + ".java";
PrintWriter writer = new PrintWriter(path, "UTF-8");
writer.println("package com.craftinginterpreters.lox;");
writer.println();
writer.println("import java.util.List;");
writer.println();
writer.println("abstract class " + baseName + " {");
// The AST classes.
for (String type : types) {
String className = type.split(":")[0].trim();
String fields = type.split(":")[1].trim();
defineType(writer, baseName, className, fields);
}
writer.println("}");
writer.close();
}
//< define-ast
//> define-visitor
private static void defineVisitor(
PrintWriter writer, String baseName, List<String> types) {
writer.println(" interface Visitor<R> {");
for (String type : types) {
String typeName = type.split(":")[0].trim();
writer.println(" R visit" + typeName + baseName + "(" +
typeName + " " + baseName.toLowerCase() + ");");
}
writer.println(" }");
}
//< define-visitor
//> define-type
private static void defineType(
PrintWriter writer, String baseName,
String className, String fieldList) {
//> omit
writer.println("//> " +
baseName.toLowerCase() + "-" + className.toLowerCase());
//< omit
writer.println(" static class " + className + " extends " +
baseName + " {");
//> omit
// Hack. Stmt.Class has such a long constructor that it overflows
// the line length on the Appendix II page. Wrap it.
if (fieldList.length() > 64) {
fieldList = fieldList.replace(", ", ",\n ");
}
//< omit
// Constructor.
writer.println(" " + className + "(" + fieldList + ") {");
//> omit
fieldList = fieldList.replace(",\n ", ", ");
//< omit
// Store parameters in fields.
String[] fields = fieldList.split(", ");
for (String field : fields) {
String name = field.split(" ")[1];
writer.println(" this." + name + " = " + name + ";");
}
writer.println(" }");
//> accept-method
// Visitor pattern.
writer.println();
writer.println(" @Override");
writer.println(" <R> R accept(Visitor<R> visitor) {");
writer.println(" return visitor.visit" +
className + baseName + "(this);");
writer.println(" }");
//< accept-method
// Fields.
writer.println();
for (String field : fields) {
writer.println(" final " + field + ";");
}
writer.println(" }");
//> omit
writer.println("//< " +
baseName.toLowerCase() + "-" + className.toLowerCase());
//< omit
}
//< define-type
//> pastry-visitor
interface PastryVisitor {
void visitBeignet(Beignet beignet); // [overload]
void visitCruller(Cruller cruller);
}
//< pastry-visitor
//> pastries
abstract class Pastry {
//> pastry-accept
abstract void accept(PastryVisitor visitor);
//< pastry-accept
}
class Beignet extends Pastry {
//> beignet-accept
@Override
void accept(PastryVisitor visitor) {
visitor.visitBeignet(this);
}
//< beignet-accept
}
class Cruller extends Pastry {
//> cruller-accept
@Override
void accept(PastryVisitor visitor) {
visitor.visitCruller(this);
}
//< cruller-accept
}
//< pastries
}
在实际生成语法树中,我们可以看到,为了给编程语言增加新的特性,我们在文法分析里面要对新添加的语言特性进行增加,那样的话我们就会修改旧的代码,如果我们了解设计模式的话,这是一种不推荐的模式。这里的话,我们推荐访问者设计模式来给语言添加新的特性。使用接口的可拓展性来实现。
如果不使用访问者模式,在生成语法解析上要根据不同的语法规则,制定十几个类,这样子会导致冗余。