Java中的递归下降解析器

时间:2013-02-16 18:46:09

标签: java parsing recursion

我想先说这是我的第三年编程语言课程的家庭作业,我正在寻找一些帮助。我的任务是:

  

截止日期:2013年2月22日晚上11:55
  提交:请将以下内容上传到CMS。

  1.源代码
  2.程序执行的屏幕截图,包括您使用的输入文件

     

使用您喜欢的任何编程语言编写递归下降解析器,该解析器解析由以下EBNF描述生成的语言。您的解析器应检测输入程序是否有任何语法错误。它不必指定错误的内容和位置。

<program>   begin <stmt_list> end
<stmt_list>  <stmt> {;<stmt_list>}
<stmt>    <assign_stmt> | <while_stmt>
<assign_stmt>  <var> = <expr>
<var>  identifier  (An identifier is a string that begins with a letter followed by 0     or more letters and digits)
<expr>  <var> { (+|-) <var>}           
<while_stmt>   while (<logic_expr>)  <stmt>
<logic_expr> ® <var> (< | >) <var>  (Assume that logic expressions have only less than     or greater than operators)

看起来很滑稽的符号只是指向右边的箭头。

我现在的问题更符合逻辑,那就是编程:在我的第一次尝试中,我读取整个输入程序,将其保存为字符串,然后解析该字符串并将每个符号转换为终端,expr,或者你有什么。

我最终发现这种方式不起作用,因为,A:我不认为它是RDP,B:许多非终端都是由超过1个语句组成的。

我放弃了这种方法,并决定在我浪费更多时间编程之前,我会伪装一切。我的新想法是为每个非终端符号制作1个方法,然后只按符号解析输入字符串,希望在这些方法之间进行。这种方法似乎是合乎逻辑的,但是当我开始编写伪代码时,我很失落并且对于我需要做什么感到困惑。 我将如何完成此代码?

以下是RDP的一些伪代码:

intputString;

public void parseProgram (Symbol.typeIsProgram) {
    if getNextSymbol == "begin" {
        if (intputString.substring (inputString.length()-3,
                inputString.length()) == "end") {
            Symbol stmt_lsit = new Symbol (intputString)
            parseStmt_list(stmt_list);              
        } else {
            Out "error, prog must end with end"
        }
    } else {
        Out "error, prog must begin with begin"
    }   
}

public void parseStmt_list (Stmbol.typeIsStmt_list) {
    symbol = getNextSymbol;
    if (Symbol.typeIsVar) {
        parseVar(symbol)
    } else if (Symbol.typeIsWhile)  {
        // weve only capture if the first word return is a while, we dont have the whole while statement yet
        ParseWhile_stmt(symbol)
    } else { }
}

public void parseStmt () { }
public void parseAssign_stmt () { }
public void parseVar () { }
public void parseExpr () { }
public void parseWhile_stmt () { }
public void parseLogic_expr () { }

public Symbol getNextSymbol() {
    //returns the next symbol in input string and removes it from the input string
}

只是一个FYI,我的解析器的示例输入程序将是。

begin 
total = var1 + var2; 
while (var1 < var2) 
while ( var3 > var4)
var2 = var2 - var1 
end

3 个答案:

答案 0 :(得分:7)

根据这个特定的分配,可以以功能的方式使用字符串处理。

试试这个算法:

  1. 检查,该行以begin开头,以end
  2. 结尾
  3. 如果是 - 将剩余的字符串与;分开,并为每个部分(语句)执行以下步骤
  4. 检查语句是否包含=while子字符串
  5. 对于作业检查,您可以使用+-拆分输入,并为每个部分检查可变条件(字母数字内容)
  6. for while - 检查()并递归处理括号之间和之后的两个字符串
  7. 最后,对于由子串<>拆分的逻辑表达式,并检查部分是变量(字母数字)
  8. 也许,这不是一个非常灵活的解决方案,但我认为这对你的任务是可以接受的。

    修改

    我觉得这个任务很有趣,并试图写一个完整的解决方案。

    public enum Parser {
    PROGRAM {
        void parse(String s) throws ParseException {
            s = s.trim();
            if (s.startsWith("begin") && s.endsWith("end")) {
                STATEMENT_LIST.parse(s.substring(5, s.length() - 3));
            } else {
                throw new ParseException("Illegal begin/end");
            }
        }
    },
    
    STATEMENT_LIST {
        void parse(String s) throws ParseException {
            String[] parts = s.trim().split(";");
            for (String part : parts) {
                STATEMENT.parse(part.trim());
            }
        }
    },
    
    STATEMENT {
        void parse(String s) throws ParseException {
            if (s.startsWith("while")) {
                WHILE.parse(s.substring(5));
            } else if (s.contains("=")) {
                ASSIGNMENT.parse(s);
            } else {
                throw new ParseException("Illegal statement: " + s);
            }
        }
    },
    
    WHILE {
        void parse(String s) throws ParseException {
            int i = s.indexOf("(");
            int j = s.indexOf(")");
            if (i != -1 && j != -1) {
                LOGICAL.parse(s.substring(i + 1, j).trim());
                STATEMENT.parse(s.substring(j + 1).trim());
            } else {
                throw new ParseException("Illegal while: " + s);
            }
        }
    },
    
    ASSIGNMENT {
        void parse(String s) throws ParseException {
            int i = s.indexOf("=");
            if (i != -1) {
                VARIABLE.parse(s.substring(0, i).trim());
                EXPRESSION.parse(s.substring(i + 1).trim());
            }
        }
    },
    
    EXPRESSION {
        void parse(String s) throws ParseException {
            String[] parts = s.split("\\+|-");
            for (String part : parts) {
                VARIABLE.parse(part.trim());
            }
        }
    },
    
    LOGICAL {
        void parse(String s) throws ParseException {
            int i;
            if (s.contains("<")) {
                i = s.indexOf("<");
            } else if (s.contains(">")) {
                i = s.indexOf(">");
            } else {
                throw new ParseException("Illegal logical: " + s);
            }
    
            VARIABLE.parse(s.substring(0, i).trim());
            VARIABLE.parse(s.substring(i + 1).trim());
        }
    },
    
    VARIABLE {
        void parse(String s) throws ParseException {
            if (!s.matches("^[a-zA-Z][a-zA-Z0-9]*$")) {
                throw new ParseException("Illegal variable: " + s);
            }
        }
    };
    
    abstract void parse(String s) throws ParseException;
    
    public static void main(String[] args) {
        try {
            PROGRAM.parse("begin \n" +
                    "total = var1 + var2; \n" +
                    "while (var1 < var2) \n" +
                    "while ( var3 > var4)\n" +
                    "var2 = var2 - var1 \n" +
                    "end");
            System.out.println("OK");
        } catch (ParseException e) {
            System.out.println(e.getMessage());
        }
    }
    }
    
    class ParseException extends Exception {
    public ParseException(String message) {
        super(message);
    }
    }
    

答案 1 :(得分:4)

1)标记化

首先将输入分解为令牌。在这种情况下,每个标识符和运算符和文字。列出所有输入令牌的大清单。一旦你有了令牌,那么你就可以开始解析了。使令牌成为一个链表,这样你就可以说Token.Next读取下一个令牌或Token.Next.Next来读取前面的2个令牌。最后放一堆EOF代币,这样你就永远不会跳过它。

2)解析

解析就像你已经拥有的一样。因此,而不是思考符号思维令牌。 Parse Statements列表是一个while循环,它将语句解析到最后。

对于Parse Statement

public void parseStmt ()
{
  if (Token.kind == KEYWORD && Token.id == kw_while) {
    return ParseWhileStatement();
  }
  else {
    return ParseAssignStatement();
  }
}

解析while语句将循环回解析语句,因此它将“递归地下降”回Parse语句,产生嵌套的while循环等......

解析赋值语句非常相似。解析左侧,然后右侧。你需要一堆函数....

此处的节点是Ast节点。抽象语法树。

像...一样的东西。

class Node {

}
class OpNode {
  OpNode Lhs;
  OpNode Rhs;
}
class MultiplyNode : OpNode {
  MultiplyNode(byref Token tok, OpNode Left, OpNode right) {
    tok = tok.Next;
    Lhs = left;
    Rhs = right;
  }
}




Node ParseSimpleExp() {
  if (Token.kind == Identifier) {
    Node n = new IdentNode;
    NextToken();
    return n;
  }
  if (Token.kind == Number) {
    Node n = new NumberNode;
    NextToken();
    return n;
  }
}


// In these examples move the token to next token when you create the new nodes
Node ParseMulExp() {
  Node n = ParseSimpleExp();
  while (1) {
    if (Token.Kind == Multiply) {
      n = new MultiplyNode(Token,n,ParseSimpleExp());
      continue;
    }
    if (Token.Kind == Divide) {
      n = new DivideNode(Token,n,ParseSimpleExp());
      continue;
    }
    return n;
 }
}

Node ParseAddExp() {
  Node n = ParseMulExp();
  while (1) {
    if (Token.Kind == Add) {
      n = new AddNode(Token,n,ParseMulExp());
      continue;
    }
    if (Token.Kind == Subtract) {
      n = new SubtractNode(Token,n,ParseMulExp());
      continue;
    }
    return n;
  }
}


Node ParseAssignStatement() {
  Node n = ParseAddExp();
  if (Token.kind == ASSIGN) {
    return new AssignStatement(Token,n,ParseAddExp());
  }
}

如果您遵循逻辑,您可以看到递归到达每个目标后如何遵循优先规则。解析表达式并从assign开始不是循环。就像这里所示。显然这很简单,但它显示了这种技术。

因此,RDP是通过查看当前令牌然后跳转到某个函数来处理令牌引起的。当然,这可以回归到相同的功能,因此是递归的。如果您查看ParseSimpleExp函数,那么您可以看到这是一个可以处理带括号的表达式的好地方。一个parens表达式将导致递归回到简单的exp,并且可能所有其他的都像mul和add。

答案 2 :(得分:1)

解析器代码的结构应该类似于语言语法的结构。 E.g。

 <program>  ::= begin <stmt_list> end

会转换为类似

的内容
function parse_program() {
  parse_begin();
  repeat parse_stmt();
  parse_end();
}

您可能不希望将令牌处理(扫描程序)与解析结构(解析器)混淆。

我会使用异常处理而不是if / else结构来进行错误处理。 您可能希望跟踪源(扫描仪)部件中的位置以显示正确的错误消息。只需询问扫描仪的状态即可。

幸运的是,这项任务似乎不需要解决冲突,所以你的递归正常应该运作良好。有趣的部分是解析

的地方
<while_stmt> ::= while (<logic_expr>)  <stmt>

你最终会以递归方式呼叫parse_stmt()。这就是递归下降解析的整个想法。