一、实验目的

语法分析是编译程序中的核心部分。本实验通过设计一个典型的自顶向下语法分析程序,进一步理解并掌握语法分析的原理和实现技术。

二、实验原理

语法分析的主要任务是“组词成句”,将词法分析给出的单词序列按语法规则构成更大的语法单位,如“程序、语句、表达式”等;或者说,语法分析的作用是用来判断给定输入串是否为合乎文法的句子。

按照生成语法树的方向不同,常用的语法分析方法有两类:自顶向下分析和自底向上分析。自顶向下分析也称面向目标的分析方法,也就是从文法的开始符出发,试图推导出与输入单词串相匹配的句子,分为确定的自顶向下分析和不确定的自顶向下分析两种,LL分析法和递归下降分析法属于确定的自顶向下分析。自底向上分析也称移进 — 归约分析方法,从输入单词串开始,试图归约到文法的开始符。

本次实验采用递归下降分析算法,该算法分析程序由一组子程序组成,每个非终结符按其产生式结构写出相应的子程序,每个子程序的名字可以用该非终结符表示。

  1. 子程序的编写:每匹配一个终结符,则再读入一个符号;对于非终结符,则调用相应的子程  序。当一个非终结符有多个候选式,可按选择集SELECT来决定选用哪个候选式 。
  2. 通过子程序间的相互调用实现对输入串的识别。
  3. 由于文法的定义通常是递归的,分析程序也常具有递归结构,所以也称为递归子程序法。

三、实验步骤与要求

1. 请根据下面给定的文法设计并实现语法分析程序,能基于上次作业的词法分析程序所识别出的单词,识别出各类语法成分。输入输出及处理要求如下:

(1)需按文法规则,用递归子程序法对文法中定义的所有种语法成分进行分析;

(2)为了方便进行自动评测,输入的被编译源文件统一命名为testfile.txt;输出的结果文件统一命名为output.txt ;结果文件中包含如下两种信息:

    1)按词法分析识别单词的顺序,按行输出每个单词的信息(要求同词法分析作业,对于预读的情况不能输出)。

    2)在下列高亮显示的语法分析成分分析结束前,另起一行输出当前语法成分的名字,形如“<常量说明>”(注:未要求输出的语法成分仍需要分析)

<加法运算符> ::= +|-
<乘法运算符>  ::= *|/
<关系运算符>  ::=  <|<=|>|>=|!=|==
<字母>   ::= _|a|...|z|A|...|Z

<数字>   ::= 0|<非零数字>
<非零数字>  ::= 1|...|9
<字符>    ::=  '<加法运算符>'|'<乘法运算符>'|'<字母>'|'<数字>'

<字符串>   ::=  "{十进制编码为32,33,35-126的ASCII字符}"

<程序>    ::= [<常量说明>][<变量说明>]{<有返回值函数定义>|<无返回值函数定义>}<主函数>

<常量说明> ::=  const<常量定义>;{ const<常量定义>;}
<常量定义>   ::=   int<标识符>=<整数>{,<标识符>=<整数>}

                  | char<标识符>=<字符>{,<标识符>=<字符>}

<无符号整数>  ::= <非零数字>{<数字>}| 0
<整数>        ::= [+|-]<无符号整数>

<标识符>    ::=  <字母>{<字母>|<数字>}

<声明头部>   ::=  int<标识符> |char<标识符>

<变量说明>  ::= <变量定义>;{<变量定义>;}

<变量定义>  ::= <类型标识符>(<标识符>|<标识符>'['<无符号整数>']'){,(<标识符>|<标识符>'['<无符号整数>']' )}

                 //<无符号整数>表示数组元素的个数,其值需大于0

<类型标识符>      ::=  int | char

<有返回值函数定义>  ::=  <声明头部>'('<参数表>')' '{'<复合语句>'}'
<无返回值函数定义>  ::= void<标识符>'('<参数表>')''{'<复合语句>'}'
<复合语句>   ::=  [<常量说明>][<变量说明>]<语句列>

<参数表>    ::=  <类型标识符><标识符>{,<类型标识符><标识符>}| <空>
<主函数>    ::= void main‘(’‘)’ ‘{’<复合语句>‘}’

<表达式>    ::= [+|-]<项>{<加法运算符><项>}   //[+|-]只作用于第一个<项>
<项>     ::= <因子>{<乘法运算符><因子>}
<因子>    ::= <标识符>|<标识符>'['<表达式>']'|'('<表达式>')'|<整数>|<字符>|<有返回值函数调用语句>         
<语句>    ::= <条件语句>|<循环语句>| '{'<语句列>'}'| <有返回值函数调用语句>; 
                           |<无返回值函数调用语句>;|<赋值语句>;|<读语句>;|<写语句>;|<空>;|<返回语句>;

<赋值语句>   ::=  <标识符>=<表达式>|<标识符>'['<表达式>']'=<表达式>
<条件语句>  ::= if '('<条件>')'<语句>[else<语句>]
<条件>    ::=  <表达式><关系运算符><表达式> //整型表达式之间才能进行关系运算

       |<表达式>    //表达式为整型,其值为0条件为假,值不为0时条件为真

<循环语句>   ::=  while '('<条件>')'<语句>| do<语句>while '('<条件>')' |for'('<标识符>=<表达式>;<条件>;<标识符>=<标识符>(+|-)<步长>')'<语句>
<步长>::= <无符号整数>  
<有返回值函数调用语句> ::= <标识符>'('<值参数表>')'
<无返回值函数调用语句> ::= <标识符>'('<值参数表>')'
<值参数表>   ::= <表达式>{,<表达式>}|<空>
<语句列>   ::= {<语句>}
<读语句>    ::=  scanf '('<标识符>{,<标识符>}')'
<写语句>    ::= printf '(' <字符串>,<表达式> ')'| printf '('<字符串> ')'| printf '('<表达式>')'
<返回语句>   ::=  return['('<表达式>')']   

【输入形式】testfile.txt中的符合文法要求的测试程序。

【输出形式】按如上要求将语法分析结果输出至output.txt中,中文字符的编码格式要求是UTF-8。

【样例输入】

const int const1 = 1, const2 = -100;
const char const3 = '_';
int change1;
char change3;
int gets1(int var1,int var2){
    change1 = var1 + var2;
    return (change1);
}
void main(){
    printf("Hello World");
    printf(gets1(10, 20));
}

【样例输出】

CONSTTK const
INTTK int
IDENFR const1
ASSIGN =
INTCON 1
<无符号整数>
<整数>
COMMA ,
IDENFR const2
ASSIGN =
MINU -
INTCON 100
<无符号整数>
<整数>
<常量定义>
SEMICN ;
CONSTTK const
CHARTK char
IDENFR const3
ASSIGN =
CHARCON _
<常量定义>
SEMICN ;
<常量说明>
INTTK int
IDENFR change1
<变量定义>
SEMICN ;
CHARTK char
IDENFR change3
<变量定义>
SEMICN ;
<变量说明>
INTTK int
IDENFR gets1
<声明头部>
LPARENT (
INTTK int
IDENFR var1
COMMA ,
INTTK int
IDENFR var2
<参数表>
RPARENT )
LBRACE {
IDENFR change1
ASSIGN =
IDENFR var1
<因子>
<项>
PLUS +
IDENFR var2
<因子>
<项>
<表达式>
<赋值语句>
SEMICN ;
<语句>
RETURNTK return
LPARENT (
IDENFR change1
<因子>
<项>
<表达式>
RPARENT )
<返回语句>
SEMICN ;
<语句>
<语句列>
<复合语句>
RBRACE }
<有返回值函数定义>
VOIDTK void
MAINTK main
LPARENT (
RPARENT )
LBRACE {
PRINTFTK printf
LPARENT (
STRCON Hello World
<字符串>
RPARENT )
<写语句>
SEMICN ;
<语句>
PRINTFTK printf
LPARENT (
IDENFR gets1
LPARENT (
INTCON 10
<无符号整数>
<整数>
<因子>
<项>
<表达式>
COMMA ,
INTCON 20
<无符号整数>
<整数>
<因子>
<项>
<表达式>
<值参数表>
RPARENT )
<有返回值函数调用语句>
<因子>
<项>
<表达式>
RPARENT )
<写语句>
SEMICN ;
<语句>
<语句列>
<复合语句>
RBRACE }
<主函数>
<程序>

四、程序功能与框架

1. 预处理

按行读取文本信息,生成字符流信息。

2. 读取流信息,识别单词

读取每行的每一个字符,交由分析函数处理。

3. 用递归子程序法对识别出的所有单词进行语法分析

4. 将输出保存并输出到指定文件

五、设计说明

1.存储结构

数组,Map,HashMap,List,ArrayList。将单词名称和对应的种别码作为map的key和value存储。将当前读到的单词和对应的种别码分别保存在两个List中。

2.主要变量

Map keywords:关键字

Map operations:运算符

Map symbols:界符

static int p, lines:词法分析方法中指向当前所读到字符串的位置的指针

static BufferedWriter bw:输出流

char ch:词法分析方法中当前读取到的字符

String token:词法分析方法中识别到的单词

int flag:判断数字的小数点是否有且是否大于1

List Tokens:语法分析方法中当前读到的单词的种别码

List<String> vals:语法分析方法中当前读到的单词

Map<String,Integer> NVoidFunction:语法分析方法中存储有返回值函数的方法名,Value值为1表示该方法是有返回值的方法

int q:语法分析方法中当前读到的单词的位置

static final String path:输出文件的位置

3.算法思路

语法分析就是根据高级语言的语法规则对程序的语法结构进行分析。语法分析的任务就是在词法分析识别出正确的单词符号串是否符合语言的语法规则,分析并识别各种语法成分,同时进行语法检查和错误处理,为语义分析和代码生成做准备。判断依据是对给定的输入符号串能否根据文法的规则建立起一颗语法树。自上而下的语法分析方法就是对任何输入串(由token串构成的源程序),从文法开始符号(根结点)出发,自上而下的为输入串建立一棵语法树,或者说,为输入串寻找一个最左推导。

流程图: 

ll1语法分析器的设计与实现python 语法分析器实验原理_运算符

算法流程:

(1)词法分析

1. 初始化把数组转换为Map,分别为关键字、运算符、界符的键值对,其中单词名称为Key值、单词种别码为Value值。

2. 词法分析程序打开源文件,按行读取文件内容,直至结束。

3. 对源文件从头到尾进行扫描,扫描程序首先询问当前的字符是不是数字,若是数字,则进行数字的判断与判断,直至遇到空格、运算符或界符;然后询问这个字符是不是字母或字符’_’,若是则进行标识符和关键字的识别;然后询问这个字符是不是双引号,若是,则进行字符串的检查,直到遇到下一个双引号;然后询问这个字符是不是单引号,若是,则进行字符常量的判断,直至遇到下一个单引号;若遇到空格,则继续扫描;除以上情况之外,对字符进行符号的识别。确定完这个单词的种别码后,进行下一个单词的识别。

数字的识别:从当前位置开始向后识别,若遇到空格、运算符或界符停止识别;若遇到小数点,将flag标志加一,当第二次遇到小数点时,将err标志置为真值;若在向后识别的过程中遇到字母,同样将err标志置为真值;若本单词识别完成后判断最后一个字符为小数点,同样将err标志置为真值,在后面的输出中输出错误单词和单词所在的行数;识别完成后,输出单词的种别码INTCON和具体数字,并将输出信息添加到bw输出流的新行中;如果识别结束后p指向的位置不是本行的最后一位或p指向本行的最后一位但最后一位不是数字,将p减一,继续下面单词的识别。

标识符、关键字的识别:从当前位置向后识别,若遇到不是字母且不是’_’字符时,停止识别,否则将当前字符加入到token中;当前单词识别完成后,与keywords里的关键字进行比较,若keywords的key值中包含token,则说明识别成功,输出对应的种别码即keywords中key值对应的value,并将结果写入bw流中;若keywords不包含,说明token为标识符,输出种别码IDENFR和单词token,并写入bw流中;如果识别结束后p指向的位置不是本行的最后一位,或p指向本行的最后一位但最后一位不是字母和’_’,则将p减一,继续下面单词的识别。    

字符串的识别:从当前位置开始向后识别,若遇到双引号则停止检查;如果token的最后一位不是双引号,则输出错误单词和单词所在的行数并将信息写入bw流中;扫描完成后,首先将字符串中的双引号替换掉,输出单词的种别码STRCON和识别到的字符串。

字符常量的识别:从当前位置开始向后识别,将识别到的字符添加到token中,若遇到下一个单引号,则停止识别;若识别完成后,token单词的最后一位不是单引号,则输出错误单词和单词所在的行数并将信息写入bw流中;扫描完成后,首先将token中的单引号替换掉,输出种别码CHARCON和识别到的字符常量。

符号的识别:从当前位置开始识别,若界符symbols中包含该字符,输出对应的种别码和该字符并写入bw流中,并将指针p减一;如果界符中不包含当前字符,进行运算符的识别:如果运算符operations中不包含该字符,输出错误单词和单词所在的行数并将信息写入bw流中;

若包含该字符,指针p所指位置不是最后一位,与下一位字符拼接后不是运算符,则输出该运算符和对应种别码;然后判断与下一位字符进行拼接后的两位字符串是否为运算符,若包含且与下一位字符拼接后的单词不是运算符,则输出该运算符和对应种别码;若与下一位字符拼接后的三位字符依旧是运算符,则输出该运算符和对应种别码。期间,若拼接后的字符不包含于运算符中,且后一位不是空格,说明运算符出错,输出错误单词和单词所在的行数并将信息写入bw流中;若当前位置已到达了本行的最后位置,运算符在最后说明是错误文法,输出错误单词和单词所在的行数并将信息写入bw流中。

(2)语法分析

从根结点开始(即从<程序>结点开始),自上而下分析单词。按照以下语法规则生成语法树:

ll1语法分析器的设计与实现python 语法分析器实验原理_语法分析_02

从根结点开始,每读取一个单词的种别码,就判断他是否符合下一级语法规则,直至递归结束,输出识别到的语法成分。

六、用户操作指南

将要进行识别的程序代码置于指定位置的文件testfile.txt中,运行程序,在指定位置生成输出文件output.txt。(如下图)

ll1语法分析器的设计与实现python 语法分析器实验原理_语法分析_03

七、实验总结

1.遇到的问题与解决方法

  • 输出的汉字在测试平台变成 '?' 的问题

ll1语法分析器的设计与实现python 语法分析器实验原理_学习_04

使用writer输出字符流,且将输出文件的编码集指定为“UTF-8”: new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(path)), StandardCharsets.UTF_8));可解决此问题。因为中文至少占两个字节,以字节输出时读出的汉字就会变成乱码或‘?’,而write输出的是字符流,是一个一个字符的形式向外输出数据。在刚开始遇到这个问题时,因看到网页使用的编码集为GBK,以为是输出文件与网页采用的编码集不同导致该问题,但后面使用各种方法将输出文件的编码集转换为GBK也无用时,才意识到是别的原因。因此也花了很多时间尝试。

  • 有/无返回值函数的判定和调用问题

ll1语法分析器的设计与实现python 语法分析器实验原理_运算符_05

在声明头部时,若当前函数为有返回值的函数,则将当前函数名即标识符存入Map<String,Integer> NVoidFunction中,Key为函数的方法名,Value值设置为1表示该方法是有返回值的方法。当后面的函数中有调用该方法的情况出现时,进行判断,若标识符对应的Value值为1,则说明调用的是有返回值的函数。

八、源代码

@SuppressWarnings("unchecked")
public class GOU {

    //关键字
    static String[] keyWordKey = {"const", "int", "char", "void", "main", "getint", "if", "else", "switch", "case", "default", "while", "for", "scanf", "printf", "return", "auto", "short", "long", "float", "double", "struct", "union", "enum", "typedef", "unsigned", "signed", "extern", "register", "static", "volatile", "do", "goto", "continue", "break", "sizeof"};

//关键字种别码
    static String[] keyWordValue = {"CONSTTK", "INTTK", "CHARTK", "VOIDTK", "MAINTK", "GETINTTK", "IFTK", "ELSETK", "SWITCHTK", "CASETK", "DEFAULTTK", "WHILETK", "FORTK", "SCANFTK", "PRINTFTK", "RETURNTK", "AUTOTK", "SHORTTK", "LONGTK", "FLOATTK", "DOUBLETK", "STRUCTTK", "UNIONTK", "ENUMTK", "TYPEDEFTK", "UNSIGNEDTK", "SIGNEDTK", "EXTERNTK", "REGISTERTK", "STATICTK", "VOLATILLETK", "DOTK", "GOTOTK", "CONTINUETK", "BREAKTK", "SIZEOFTK"};

    //运算符
    static String[] operationKey = {"+", "-", "*", "/", "<", "<=", ">", ">=", "==", "!=", "="};
//运算符种别码
    static String[] operationValue = {"PLUS", "MINU", "MULT", "DIV", "LSS", "LEQ", "GRE", "GEQ", "EQL", "NEQ", "ASSIGN"};

    //界符
    static String[] symbolKey = {":", ";", ",", "(", ")", "{", "}", "[", "]"};

//界符种别码
    static String[] symbolValue = {"COLON", "SEMICN", "COMMA", "LPARENT", "RPARENT", "LBRACE", "RBRACE", "LBRACK", "RBRACK"};

//语法分析当前单词的种别码
    static List Tokens = new ArrayList();
//语法分析当前单词
    static List<String> vals = new ArrayList<>();

//key为函数的方法名,Value值为1表示该方法是有返回值的方法
    static Map<String,Integer> NVoidFunction = new HashMap<>();

    static Map keywords = null;
    static Map operations = null;
    static Map symbols = null;

    //指向当前所读到字符串的位置的指针
    static int p, lines;

//语法分析当前单词位置
    static int q;

    static BufferedWriter bw = null;

//输出文件路径
    private static final String path = "output.txt";

    static {
        try {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(path)), StandardCharsets.UTF_8)); //指定输出文件编码集
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        init(); //初始化存储结构
        File file = new File("testfile.txt"); //读取文件的位置
        lines = 1; //从第一行开始
        try (Scanner input = new Scanner(file)) {
            while (input.hasNextLine()) { //按行读取
                String str = input.nextLine(); //获取当前行
                analyze(str); //词法分析
                lines++; //下一行
            }
        }
        yufa(); //语法分析
        bw.flush();
        bw.close(); //关闭输出流
    }

    //初始化把数组转换为Map
    public static void init() {
        keywords = new HashMap(); //关键词
        for (int i = 0; i < keyWordKey.length; i++) {
            keywords.put(keyWordKey[i], keyWordValue[i]);
        }
        operations = new HashMap(); //运算符
        for (int i = 0; i < operationKey.length; i++) {
            operations.put(operationKey[i], operationValue[i]);
        }
        symbols = new HashMap(); //界符
        for (int i = 0; i < symbolKey.length; i++) {
            symbols.put(symbolKey[i], symbolValue[i]);
        }
    }

    public static void analyze(String str) throws IOException {
        p = 0;
        char ch;
        str = str.trim();
        for (; p < str.length(); p++) {
            ch = str.charAt(p);
            if (Character.isDigit(ch)) { //判断字符是否为数字
                digitCheck(str);
            } else if (Character.isLetter(ch) || ch == '_') { //字母或_
                letterCheck(str);
            } else if (ch == '"') {
                stringCheck(str);
            } else if (ch == '\'') {
                charCheck(str);
            } else if (ch == ' ') {
                continue;
            } else {
                symbolCheck(str);
            }
        }
    }

    /*数字的识别
     * 1、识别退出:
     *   1.1、遇到空格符
     *   1.2、遇到运算符或者界符
     * 2、错误情况:
     *   2.1、两个及以上小数点
     *   2.2、掺杂字母
     * */
    public static void digitCheck(String str) throws IOException {

        String token = String.valueOf(str.charAt(p++));
        //判断数字的小数点是否有且是否大于1
        int flag = 0;
        boolean err = false;
        char ch;
        for (; p < str.length(); p++) {
            ch = str.charAt(p);
            if (ch == ' ' || (!Character.isLetterOrDigit(ch) && ch != '.')) {
                //遇到空格符,运算符或者界符
                break;
            } else if (err) {
                token += ch;
            } else {
                token += ch;
                if (ch == '.') {
                    if (flag == 1) {
                        err = true;
                    } else {
                        flag++;
                    }
                } else if (Character.isLetter(ch)) {
                    err = true;
                }
            }
        }
        if (token.charAt(token.length() - 1) == '.') {
            err = true;
        }
        Tokens.add("INTCON");
        vals.add(token);
        if (p != str.length() - 1 || (p == str.length() - 1 && !Character.isDigit(str.charAt(p)))) {
            p--;
        }
    }

    //标识符,关键字的识别
    public static void letterCheck(String str) throws IOException {
        String token = String.valueOf(str.charAt(p++));
        char ch;
        for (; p < str.length(); p++) {
            ch = str.charAt(p);
            if (!Character.isLetterOrDigit(ch) && ch != '_') {
                break;
            } else {
                token += ch;
            }
        }
        if (keywords.containsKey(token)) {
            Tokens.add(keywords.get(token));
            vals.add(token);
        } else {
            Tokens.add("IDENFR");
            vals.add(token);
        }
        if (p != str.length() - 1 || (p == str.length() - 1 && (!Character.isLetterOrDigit(str.charAt(p)) && str.charAt(p) != '_'))) {
            p--;
        }
    }

    //字符串检查
    public static void stringCheck(String str) throws IOException {

        String token = String.valueOf(str.charAt(p++));
        char ch;
        for (; p < str.length(); p++) {
            ch = str.charAt(p);
            token += ch;
            if (ch == '"') {
                break;
            }
        }
        String Token = token.replace("\"", "");
        Tokens.add("STRCON");
        vals.add(Token);
    }

    //字符常量
    public static void charCheck(String str) throws IOException {

        String token = String.valueOf(str.charAt(p++));
        char ch;
        for (; p < str.length(); p++) {
            ch = str.charAt(p);
            token += ch;
            if (ch == '\'') {
                break;
            }
        }
        String Token = token.replace("\'", "");
        Tokens.add("CHARCON");
        vals.add(Token);
    }

    //符号的识别
    public static void symbolCheck(String str) throws IOException {

        String token = String.valueOf(str.charAt(p++));
        char ch;
        if (symbols.containsKey(token)) {
            Tokens.add(symbols.get(token));
            vals.add(token);
            p--;
        } else {
            if (operations.containsKey(token) || token.equals("!")) {
                if (p < str.length()) {
                    ch = str.charAt(p);
                    if (operations.containsKey(token + ch)) {
                        token += ch;
                        p++;
                        if (p < str.length()) {
                            ch = str.charAt(p);
                            if (operations.containsKey(token + ch)) {
                                token += ch;
                                Tokens.add(operations.get(token));
                                vals.add(token);
                            } else {
                                p--;
                                Tokens.add(operations.get(token));
                                vals.add(token);
                            }
                        } else {
                            Tokens.add(operations.get(token));
                            vals.add(token);
                        }
                    } else {
                        p--;
                        Tokens.add(operations.get(token));
                        vals.add(token);
                    }
                }
            } else {
                p--;
            }
        }
    }

    public static void yufa() throws IOException {
        q = 0;
        parse1(); //从<程序>节点开始
    }

    public static void parse0() throws IOException {
        //<字符串> ::=  "{十进制编码为32,33,35-126的ASCII字符}"
        MatchToken("STRCON");
        bw.write("<字符串>");
        bw.newLine();
        System.out.println("<字符串>");
    }

    public static void parse1() throws IOException {
        //<程序> ::= [<常量说明>][<变量说明>]{<有返回值函数定义>|<无返回值函数定义>}<主函数>
        if (Tokens.get(q) == "CONSTTK") {
            parse2(); //<常量说明> 
    }
        if ((Tokens.get(q) == "INTTK" || Tokens.get(q) == "CHARTK") && Tokens.get(q + 1) == "IDENFR" && Tokens.get(q + 2) != "LPARENT") {
            parse7(); //<变量说明> 
       }
        while ((Tokens.get(q) == "INTTK" || Tokens.get(q) == "CHARTK" || Tokens.get(q) == "VOIDTK") &&
                Tokens.get(q + 1) == "IDENFR" && Tokens.get(q + 2) == "LPARENT") {
            if (Tokens.get(q) == "VOIDTK") {
                parse10(); //<无返回值函数定义> 
            } else {
                parse9(); //<有返回值函数定义> 
            }
        }
        if (Tokens.get(q) == "VOIDTK" && Tokens.get(q + 1) == "MAINTK") {
            parse13(); //<主函数> 
        }
        if (q == Tokens.size()) {
            System.out.println("<程序>");
            bw.write("<程序>");
            bw.newLine();
        }
    }

    private static void parse2() throws IOException {
        //<常量说明> ::=  const<常量定义>;{ const<常量定义>;}
        do {
            MatchToken("CONSTTK");
            parse3(); //<常量定义>
            MatchToken("SEMICN");
        } while (Tokens.get(q) == "CONSTTK");
        System.out.println("<常量说明>");
        bw.write("<常量说明>");
        bw.newLine();

    }

    private static void parse3() throws IOException {
        //<常量定义>   ::=   int<标识符>=<整数>{,<标识符>=<整数>}| char<标识符>=<字符>{,<标识符>=<字符>}
        if (Tokens.get(q) == "INTTK") {
            MatchToken("INTTK");
            MatchToken("IDENFR");
            MatchToken("ASSIGN");
            parse5(); //<整数>
            while (Tokens.get(q) == "COMMA") {
                MatchToken("COMMA");
                MatchToken("IDENFR");
                MatchToken("ASSIGN");
                parse5();
            }
        }
        if (Tokens.get(q) == "CHARTK") {
            MatchToken("CHARTK");
            MatchToken("IDENFR");
            MatchToken("ASSIGN");
            MatchToken("CHARCON");
            while (Tokens.get(q) == "COMMA") {
                MatchToken("COMMA");
                MatchToken("IDENFR");
                MatchToken("ASSIGN");
                MatchToken("CHARCON");
            }
        }
        System.out.println("<常量定义>");
        bw.write("<常量定义>");
        bw.newLine();
    }

    private static void parse4() throws IOException {
//<无符号整数>
        MatchToken("INTCON");
        System.out.println("<无符号整数>");
        bw.write("<无符号整数>");
        bw.newLine();
    }

    private static void parse5() throws IOException {
        //<整数> ::= [+|-]<无符号整数>
        if (Tokens.get(q) == "PLUS")
            MatchToken("PLUS");
        else if (Tokens.get(q) == "MINU")
            MatchToken("MINU");
        parse4(); //<无符号整数>
        System.out.println("<整数>");
        bw.write("<整数>");
        bw.newLine();
    }

    private static void parse6() throws IOException {
        //<声明头部> ::=  int<标识符> |char<标识符>
        if (Tokens.get(q) == "INTTK")
            MatchToken("INTTK");
        else if (Tokens.get(q) == "CHARTK")
            MatchToken("CHARTK");
        MatchToken("IDENFR");
        System.out.println("<声明头部>");
        bw.write("<声明头部>");
        bw.newLine();
    }

    private static void parse7() throws IOException {
        //<变量说明> ::= <变量定义>;{<变量定义>;}
        do {
            parse8(); //<变量定义>
            MatchToken("SEMICN");
        } while ((Tokens.get(q) == "INTTK" || Tokens.get(q) == "CHARTK") &&
                Tokens.get(q + 1) == "IDENFR" && Tokens.get(q + 2) != "LPARENT");
        System.out.println("<变量说明>");
        bw.write("<变量说明>");
        bw.newLine();
    }

    private static void parse8() throws IOException {
        //<变量定义> ::= <类型标识符>(<标识符>|<标识符>'['<无符号整数>']'){,(<标识符>|<标识符>'['<无符号整数>']')}
        if (Tokens.get(q) == "INTTK")
            MatchToken("INTTK");
        else if (Tokens.get(q) == "CHARTK")
            MatchToken("CHARTK");
        MatchToken("IDENFR");
        if (Tokens.get(q) == "LBRACK") {
            MatchToken("LBRACK");
            parse4(); //<无符号整数>
            MatchToken("RBRACK");
        }
        while (Tokens.get(q) == "COMMA") {
            MatchToken("COMMA");
            MatchToken("IDENFR");
            if (Tokens.get(q) == "LBRACK") {
                MatchToken("LBRACK");
                parse4(); //<无符号整数>
                MatchToken("RBRACK");
            }
        }
        System.out.println("<变量定义>");
        bw.write("<变量定义>");
        bw.newLine();
    }

    private static void parse9() throws IOException {
        //<有返回值函数定义> ::=  <声明头部>'('<参数表>')' '{'<复合语句>'}'
        parse6(); //<声明头部>
        NVoidFunction.put(vals.get(q-1),1);
        MatchToken("LPARENT");
        parse12(); //<参数表>
        MatchToken("RPARENT");
        MatchToken("LBRACE");
        parse11(); //<复合语句>
        MatchToken("RBRACE");
        System.out.println("<有返回值函数定义>");
        bw.write("<有返回值函数定义>");
        bw.newLine();
    }

    private static void parse10() throws IOException {
        //<无返回值函数定义> ::= void<标识符>'('<参数表>')''{'<复合语句>'}'
        MatchToken("VOIDTK");
        MatchToken("IDENFR");
        MatchToken("LPARENT");
        parse12(); //<参数表>
        MatchToken("RPARENT");
        MatchToken("LBRACE");
        parse11(); //<复合语句>
        MatchToken("RBRACE");
        System.out.println("<无返回值函数定义>");
        bw.write("<无返回值函数定义>");
        bw.newLine();
    }

    private static void parse11() throws IOException {
        //<复合语句> ::=  [<常量说明>][<变量说明>]<语句列>
        if (Tokens.get(q) == "CONSTTK") {
            parse2(); //<常量说明>
        }
        if ((Tokens.get(q) == "INTTK" || Tokens.get(q) == "CHARTK") &&
                Tokens.get(q + 1) == "IDENFR" && Tokens.get(q + 2) != "LPARENT") {
            parse7(); //<变量说明>
        }
        parse26(); //<语句列>
        System.out.println("<复合语句>");
        bw.write("<复合语句>");
        bw.newLine();
    }

    private static void parse12() throws IOException {
        //<参数表> ::=  <类型标识符><标识符>{,<类型标识符><标识符>}| <空>
        if (Tokens.get(q) != "RPARENT")//如果下一个token为右小括号,则为空
        {
            if (Tokens.get(q) == "INTTK")
                MatchToken("INTTK");
            else if (Tokens.get(q) == "CHARTK")
                MatchToken("CHARTK");
            MatchToken("IDENFR");
            while (Tokens.get(q) == "COMMA") {
                MatchToken("COMMA");
                if (Tokens.get(q) == "INTTK")
                    MatchToken("INTTK");
                else if (Tokens.get(q) == "CHARTK")
                    MatchToken("CHARTK");
                MatchToken("IDENFR");
            }
        }
        System.out.println("<参数表>");
        bw.write("<参数表>");
        bw.newLine();
    }

    private static void parse13() throws IOException {
        //<主函数> ::= void main‘(’‘)’ ‘{’<复合语句>‘}’
        MatchToken("VOIDTK");
        MatchToken("MAINTK");
        MatchToken("LPARENT");
        MatchToken("RPARENT");
        MatchToken("LBRACE");
        parse11(); //<复合语句>
        MatchToken("RBRACE");
        System.out.println("<主函数>");
        bw.write("<主函数>");
        bw.newLine();
    }

    private static void parse14() throws IOException {
        //<表达式> ::= [+|-]<项>{<加法运算符><项>}
        if (Tokens.get(q) == "PLUS")
            MatchToken("PLUS");
        else if (Tokens.get(q) == "MINU")
            MatchToken("MINU");
        parse15(); //<项>
        while (Tokens.get(q) == "PLUS" || Tokens.get(q) == "MINU") {
            if (Tokens.get(q) == "PLUS")
                MatchToken("PLUS");
            else if (Tokens.get(q) == "MINU")
                MatchToken("MINU");
            parse15(); //<项>
        }
        System.out.println("<表达式>");
        bw.write("<表达式>");
        bw.newLine();
    }

    private static void parse15() throws IOException {
        //<项> ::= <因子>{<乘法运算符><因子>}
        parse16(); //<因子>
        while (Tokens.get(q) == "MULT" || Tokens.get(q) == "DIV") {
            if (Tokens.get(q) == "MULT") 
MatchToken("MULT");
            else if (Tokens.get(q) == "DIV") 
MatchToken("DIV");
            parse16(); //<因子>
        }
        System.out.println("<项>");
        bw.write("<项>");
        bw.newLine();
    }

    private static void parse16() throws IOException {
        //<因子> ::= <标识符>|<标识符>'['<表达式>']'|'('<表达式>')'|<整数>|<字符>|<有返回值函数调用语句>
        if (Tokens.get(q) == "IDENFR") {
            if (Tokens.get(q + 1) == "LBRACK") {
                MatchToken("IDENFR");
                MatchToken("LBRACK");
                parse14(); //<表达式>
                MatchToken("RBRACK");
            } else if (Tokens.get(q + 1) == "LPARENT")
                parse234(); //<有返回值函数调用语句>
            else MatchToken("IDENFR");
        } else if (Tokens.get(q) == "LPARENT") {
            MatchToken("LPARENT");
            parse14(); //<表达式>
            MatchToken("RPARENT");
        } else if (Tokens.get(q) == "INTCON" || Tokens.get(q) == "PLUS" || Tokens.get(q) == "MINU")
            parse5(); //<整数>
        else if (Tokens.get(q) == "CHARCON")
            MatchToken("CHARCON");
        System.out.println("<因子>");
        bw.write("<因子>");
        bw.newLine();
    }

    private static void parse17() throws IOException {
        /*<语句> ::= <条件语句>|<循环语句>| '{'<语句列>'}'
            | <有返回值函数调用语句>; |<无返回值函数调用语句>;|<赋值语句>;
            |<读语句>;|<写语句>;|<空>;|<返回语句>;*/
        if (Tokens.get(q) == "IFTK")
            parse19(); //<条件语句>
        else if (Tokens.get(q) == "WHILETK" || Tokens.get(q) == "DOTK" || Tokens.get(q) == "FORTK")
            parse21(); //<循环语句>
        else if (Tokens.get(q) == "LBRACE") {
            MatchToken("LBRACE");
            parse26(); //<语句列>
            MatchToken("RBRACE");
        } else if (Tokens.get(q) == "IDENFR") {
            if (Tokens.get(q + 1) == "LPARENT") {
                parse234(); //<有无返回值函数调用语句>
                MatchToken("SEMICN");
            } else {
                parse18(); //<赋值语句>
                MatchToken("SEMICN");
            }
        } else if (Tokens.get(q) == "SCANFTK") {
            parse27(); //<读语句>
            MatchToken("SEMICN");
        } else if (Tokens.get(q) == "PRINTFTK") {
            parse28(); //<写语句>
            MatchToken("SEMICN");
        } else if (Tokens.get(q) == "RETURNTK") {
            parse29(); //<返回语句>
            MatchToken("SEMICN");
        } else if (Tokens.get(q) == "SEMICN")
            MatchToken("SEMICN");
        System.out.println("<语句>");
        bw.write("<语句>");
        bw.newLine();
    }

    private static void parse18() throws IOException {
       //<赋值语句> ::=  <标识符>=<表达式>|<标识符>'['<表达式>']'=<表达式>
        MatchToken("IDENFR");
        if (Tokens.get(q) == "LBRACK") {
            MatchToken("LBRACK");
            parse14(); //<表达式>
            MatchToken("RBRACK");
        }
        MatchToken("ASSIGN");
        parse14(); //<表达式>
        System.out.println("<赋值语句>");
        bw.write("<赋值语句>");
        bw.newLine();
    }

    private static void parse19() throws IOException {
        //<条件语句> ::= if '('<条件>')'<语句>[else<语句>]
        MatchToken("IFTK");
        MatchToken("LPARENT");
        parse20(); //<条件>
        MatchToken("RPARENT");
        parse17(); //<语句>
        if (Tokens.get(q) == "ELSETK") {
            MatchToken("ELSETK");
            parse17(); //<语句>
        }
        System.out.println("<条件语句>");
        bw.write("<条件语句>");
        bw.newLine();
    }

    private static void parse20() throws IOException {
        //<条件>    ::=  <表达式><关系运算符><表达式> //整型表达式之间才能进行关系运算|<表达式>
        // 表达式为整型,其值为0条件为假,值不为0时条件为真
        parse14(); //<表达式>
        if (Tokens.get(q) == "LSS" || Tokens.get(q) == "LEQ" || Tokens.get(q) == "GRE"
                || Tokens.get(q) == "GEQ" || Tokens.get(q) == "EQL" || Tokens.get(q) == "NEQ") {
            MatchToken((String) Tokens.get(q));
            parse14(); //<表达式>
        }
        System.out.println("<条件>");
        bw.write("<条件>");
        bw.newLine();
    }

    private static void parse21() throws IOException {
        //<循环语句> ::=  while '('<条件>')'<语句>
        //          |do<语句>while '('<条件>')'
        //          |for'('<标识符>=<表达式>;<条件>;<标识符>=<标识符>(+|-)<步长>')'<语句>
        if (Tokens.get(q) == "WHILETK") {
            MatchToken("WHILETK");
            MatchToken("LPARENT");
            parse20(); //<条件>
            MatchToken("RPARENT");
            parse17(); //<语句>
        } else if (Tokens.get(q) == "DOTK") {
            MatchToken("DOTK");
            parse17(); //<语句>
            MatchToken("WHILETK");
            MatchToken("LPARENT");
            parse20(); //<条件>
            MatchToken("RPARENT");
        } else if (Tokens.get(q) == "FORTK") {
            MatchToken("FORTK");
            MatchToken("LPARENT");
            MatchToken("IDENFR");
            MatchToken("ASSIGN");
            parse14(); //<表达式>
            MatchToken("SEMICN");
            parse20(); //<条件>
            MatchToken("SEMICN");
            MatchToken("IDENFR");
            MatchToken("ASSIGN");
            MatchToken("IDENFR");
            if (Tokens.get(q) == "PLUS")
                MatchToken("PLUS");
            else if (Tokens.get(q) == "MINU")
                MatchToken("MINU");
            parse22(); //<步长>
            MatchToken("RPARENT");
            parse17(); //<语句>
        }
        System.out.println("<循环语句>");
        bw.write("<循环语句>");
        bw.newLine();
    }

    private static void parse22() throws IOException {
        //<步长>::= <无符号整数>
        parse4(); //<无符号整数>
        System.out.println("<步长>");
        bw.write("<步长>");
        bw.newLine();
    }

    private static void parse234() throws IOException {
        //<有无返回值函数调用语句> ::= <标识符>'('<值参数表>')'
        int FunctionType = 0;
        if(NVoidFunction.containsKey(vals.get(q))) {
            FunctionType = NVoidFunction.get(vals.get(q));
        }
        MatchToken("IDENFR");
        MatchToken("LPARENT");
        parse25(); //<值参数表>
        MatchToken("RPARENT");
        if (FunctionType == 1) {
            System.out.println("<有返回值函数调用语句>");
            bw.write("<有返回值函数调用语句>");
            bw.newLine();
        } else {
            System.out.println("<无返回值函数调用语句>");
            bw.write("<无返回值函数调用语句>");
            bw.newLine();
        }
    }

    private static void parse25() throws IOException {
        //<值参数表> ::= <表达式>{,<表达式>}|<空>
        if (Tokens.get(q) != "RPARENT") {
            parse14(); //<表达式>
            while (Tokens.get(q) == "COMMA") {
                MatchToken("COMMA");
                parse14();
            }
        }
        System.out.println("<值参数表>");
        bw.write("<值参数表>");
        bw.newLine();
    }

    private static void parse26() throws IOException {
        //<语句列> ::= {<语句>}
        while (Tokens.get(q) != "RBRACE")
            parse17(); //<语句>
        System.out.println("<语句列>");
        bw.write("<语句列>");
        bw.newLine();
    }

    private static void parse27() throws IOException {
        //<读语句> ::=  scanf '('<标识符>{,<标识符>}')'
        MatchToken("SCANFTK");
        MatchToken("LPARENT");
        MatchToken("IDENFR");
        while (Tokens.get(q) == "COMMA") {
            MatchToken("COMMA");
            MatchToken("IDENFR");
        }
        MatchToken("RPARENT");
        System.out.println("<读语句>");
        bw.write("<读语句>");
        bw.newLine();
    }

    private static void parse28() throws IOException {
        //<写语句> ::= printf '(' <字符串>,<表达式> ')'
        //          | printf '('<字符串> ')'
        //          | printf '('<表达式>')'
        MatchToken("PRINTFTK");
        MatchToken("LPARENT");
        if (Tokens.get(q) == "STRCON")
            parse0(); //<字符串>
        else
            parse14(); //<表达式>
        if (Tokens.get(q) == "COMMA") {
            MatchToken("COMMA");
            parse14(); //<表达式>
        }
        MatchToken("RPARENT");
        System.out.println("<写语句>");
        bw.write("<写语句>");
        bw.newLine();
    }

    private static void parse29() throws IOException {
        //<返回语句> ::=  return['('<表达式>')']
        MatchToken("RETURNTK");
        if (Tokens.get(q) == "LPARENT") {
            MatchToken("LPARENT");
            parse14(); //<表达式>
            MatchToken("RPARENT");
        }
        System.out.println("<返回语句>");
        bw.write("<返回语句>");
        bw.newLine();
    }

    public static void MatchToken(String expected) throws IOException {
        if (Tokens.get(q) == expected) {
            System.out.println(Tokens.get(q) + " " + vals.get(q));
            bw.write(Tokens.get(q) + " " + vals.get(q));
            bw.newLine();
            q++; //下一个单词
        }
    }
}