我想先说这是我的第三年编程语言课程的家庭作业,我正在寻找一些帮助。我的任务是:
截止日期: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
答案 0 :(得分:7)
根据这个特定的分配,可以以功能的方式使用字符串处理。
试试这个算法:
begin
开头,以end
;
分开,并为每个部分(语句)执行以下步骤=
或while
子字符串+
或-
拆分输入,并为每个部分检查可变条件(字母数字内容)(
和)
并递归处理括号之间和之后的两个字符串<
或>
拆分的逻辑表达式,并检查部分是变量(字母数字)也许,这不是一个非常灵活的解决方案,但我认为这对你的任务是可以接受的。
修改强>
我觉得这个任务很有趣,并试图写一个完整的解决方案。
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()
。这就是递归下降解析的整个想法。