自己动手实现一个脚本语言第二篇-------文法分析

491 阅读3分钟

我们开始第二章,文法分析的实现

接上回的词法分析

学习文法分析,个人最好的建议是要了解巴克斯诺尔范式,另外还需要了解上下无关文法

在此推荐大家这本理论计算机的建造,里面很详细的讲解了图灵机,词法,文法树的构造。

讲解文法树之前 我先讲讲英语语法,作为知识的铺垫

我们先设计一组文法规则

句子元素{

名词,冠词,动词,副词,形容词,宾语

}

语法规则{

句子=>名词+动词

名词=>冠词+形容词+名词

动词=>动词+名词+副词

}

终结符{

}

句子=>

名词+动词=>

冠词+名词+动词=>

冠词+形容词+名词+动词+宾语=>

冠词+形容词+名词+动词+宾语+副词

这就是一个很简答的从左到右的语法树

举个例子

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
}

在实际生成语法树中,我们可以看到,为了给编程语言增加新的特性,我们在文法分析里面要对新添加的语言特性进行增加,那样的话我们就会修改旧的代码,如果我们了解设计模式的话,这是一种不推荐的模式。这里的话,我们推荐访问者设计模式来给语言添加新的特性。使用接口的可拓展性来实现。

如果不使用访问者模式,在生成语法解析上要根据不同的语法规则,制定十几个类,这样子会导致冗余。