Java词法分析器问题:复合运算符识别及无效Token过滤
Java词法分析器问题修复方案
问题描述
我用Java实现了一个词法分析器,负责将运算符、条件语句等语法结构Token化。当前逻辑是逐个读取字符,遇到空格时将连续字符组合后进行Token识别(比如把String识别为STR,;识别为SEMI),但存在两个问题:
- 复合运算符识别错误:
++被拆成两个ADD_OP,应该识别为单个INC;>=被拆成RT和ASSIGN,应该识别为单个GE。 - 非法Token处理错误:未定义的语法/标识符(比如
9user)不该输出,但现在会输出内容并报错。
原代码
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Lexer { public static void Tokenize(String fileName) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(fileName)); int r; String token = ""; while ((r = reader.read()) != -1) { char ch = (char) r; if (Character.isWhitespace(ch)) { if (!token.isBlank()) { String[] tokens = tokenizeToken(token); for (String t : tokens) { if (!t.isBlank()) { System.out.println(t); } } } token = ""; } else { token += ch; } } if (!token.isBlank()) { String[] tokens = tokenizeToken(token); for (String t : tokens) { if (!t.isBlank()) { System.out.println(t); } } } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { System.err.println("Error closing file: " + e.getMessage()); } } } private static String[] tokenizeToken(String token) { String[] tokens = token.split("(?=[\\[\\](){}<>=,;+-/*%|&!])|(?<=[\\[\\](){}<>=,;+-/*%|&!])"); for (int i = 0; i < tokens.length; i++) { String t = tokens[i].trim(); if (t.matches("procedure")) { tokens[i] = "PROC"; } else if (t.matches("int")) { tokens[i] = "INT"; } else if (t.matches("[0-9]+")) { tokens[i] = "INT_CONST"; } else if (t.matches("end")) { tokens[i] = "END"; } else if (t.matches("String") || t.matches("string")) { tokens[i] = "STR"; } else if (t.matches("[(]")) { tokens[i] = "LP"; } else if (t.matches("[)]")) { tokens[i] = "RP"; } else if (t.matches("\\".*\\"")) { tokens[i] = "STR_CONST"; } else if (t.matches("if")) { tokens[i] = "IF"; } else if (t.matches("for")) { tokens[i] = "FOR"; } else if (t.matches("while")) { tokens[i] = "WHILE"; } else if (t.matches("return")) { tokens[i] = "RETURN"; } else if (t.matches("[;]")) { tokens[i] = "SEMI"; } else if (t.matches("do")) { tokens[i] = "DO"; } else if (t.matches("break")) { tokens[i] = "BREAK"; } else if (t.matches("[a-zA-Z][a-zA-Z0-9]*")) { tokens[i] = "IDENT"; } else if (t.matches("[=]")) { tokens[i] = "ASSIGN"; } else if (t.matches("[<]")) { tokens[i] = "LT"; } else if (t.matches(">[")) { tokens[i] = "RT"; } else if (token.matches("[++]") { tokens[i] = "INC"; } else if (t.matches("[+]")) { tokens[i] = "ADD_OP"; } else if (token.matches("[{]")) { tokens[i] = "RB"; } else if (token.matches("[}]")) { tokens[i] = "LB"; } else if (token.matches("[*]")) { tokens[i] = "MUL_OP"; } else if (token.matches("[/]")) { tokens[i] = "DIV_OP"; } else if (token.matches("[>=]")) { tokens[i] = "GE"; } else { System.out.println("SYSTEM ERROR: INVALID IDENTIFIER NAME"); } } return tokens; } }
原代码问题分析
- 复合运算符拆分错误:
- 拆分Token的正则会将
++、>=这类复合符号拆成单个字符,无法识别为整体。 - 识别逻辑中判断复合运算符时误用了全局
token变量而非当前子Token,且判断顺序晚于单个运算符,导致复合符号被优先拆分为单个Token。
- 拆分Token的正则会将
- 非法Token处理错误:
- 非法Token仅打印错误,但未将其标记为空,导致原内容仍被输出。
- 标识符正则未覆盖常见规则(如允许下划线),且错误判断顺序导致非法Token未被正确过滤。
- 其他细节错误:
- 正则表达式存在语法错误(如
\\".*\\"、>[\\]),且LB/RB的识别逻辑完全颠倒。
- 正则表达式存在语法错误(如
修复后的代码
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Lexer { public static void Tokenize(String fileName) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(fileName)); int r; StringBuilder token = new StringBuilder(); while ((r = reader.read()) != -1) { char ch = (char) r; if (Character.isWhitespace(ch)) { processToken(token); token.setLength(0); } else { // 判断是否能与已有token组合成复合运算符 if (token.length() > 0 && isOperatorStart(ch) && isOperatorEnd(token.charAt(token.length()-1))) { String combined = token.toString() + ch; if (isCompositeOperator(combined)) { token.append(ch); } else { processToken(token); token.setLength(0); token.append(ch); } } else if (isSingleOperator(ch)) { processToken(token); token.setLength(0); token.append(ch); } else { token.append(ch); } } } processToken(token); } catch (IOException e) { System.err.println("读取文件错误: " + e.getMessage()); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { System.err.println("关闭文件错误: " + e.getMessage()); } } } private static void processToken(StringBuilder token) { if (token.length() == 0) return; String tokenStr = token.toString().trim(); String tokenType = identifyToken(tokenStr); if (!tokenType.isBlank()) { System.out.println(tokenType); } } private static boolean isOperatorStart(char ch) { return "+-*/%<>!&|=".indexOf(ch) != -1; } private static boolean isOperatorEnd(char ch) { return "+-*/%<>!&|=".indexOf(ch) != -1; } private static boolean isCompositeOperator(String str) { return str.equals("++") || str.equals("--") || str.equals(">=") || str.equals("<=") || str.equals("==") || str.equals("!=") || str.equals("&&") || str.equals("||"); } private static boolean isSingleOperator(char ch) { return "[\\[\\](){};,]".indexOf(ch) != -1; } private static String identifyToken(String token) { // 优先匹配关键字和复合运算符 switch (token) { case "procedure": return "PROC"; case "int": return "INT"; case "end": return "END"; case "String": case "string": return "STR"; case "if": return "IF"; case "for": return "FOR"; case "while": return "WHILE"; case "return": return "RETURN"; case "do": return "DO"; case "break": return "BREAK"; case "++": return "INC"; case "--": return "DEC"; case ">=": return "GE"; case "<=": return "LE"; case "==": return "EQ"; case "!=": return "NE"; case "&&": return "AND"; case "||": return "OR"; case "(": return "LP"; case ")": return "RP"; case "{": return "LB"; case "}": return "RB"; case ";": return "SEMI"; case "=": return "ASSIGN"; case "<": return "LT"; case ">": return "RT"; case "+": return "ADD_OP"; case "-": return "SUB_OP"; case "*": return "MUL_OP"; case "/": return "DIV_OP"; case "%": return "MOD_OP"; } // 匹配常量 if (token.matches("[0-9]+")) { return "INT_CONST"; } if (token.matches("\".*\"")) { return "STR_CONST"; } // 匹配合法标识符 if (token.matches("[a-zA-Z_][a-zA-Z0-9_]*")) { return "IDENT"; } // 非法Token,返回空字符串不输出 System.err.println("SYSTEM ERROR: INVALID IDENTIFIER NAME"); return ""; } }
关键修复说明
- 复合运算符处理:
- 读取字符时主动判断是否能组合成复合运算符,避免提前拆分。
- Token识别时优先判断复合运算符,确保
++、>=被识别为单个Token。 - 修正了
LB/RB的识别错误。
- 非法Token处理:
- 非法Token返回空字符串,不再输出原内容。
- 修正标识符正则,符合常见语言规则,确保
9user这类非法标识符被过滤。
- 性能与结构优化:
- 用
StringBuilder替代String拼接,提升性能。 - 拆分Token处理逻辑,代码结构更清晰。
- 修正所有正则语法错误。
- 用
修复后预期输出
PROC IDENT LP INT IDENT RP FOR LP INT IDENT ASSIGN INT_CONST SEMI IDENT LT IDENT SEMI IDENT ASSIGN IDENT INC RP LB IDENT ASSIGN IDENT MUL_OP LP IDENT DIV_OP INT_CONST RP SEMI IF LP IDENT GE INT_CONST RP BREAK SEMI RB RETURN IDENT SEMI END IDENT INT SYSTEM ERROR: INVALID IDENTIFIER NAME ASSIGN INT_CONST SEMI
内容的提问来源于stack exchange,提问作者hamsoace
相关产品推荐
相关产品推荐

