You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}
}

原代码问题分析

  1. 复合运算符拆分错误:
    • 拆分Token的正则会将++、>=这类复合符号拆成单个字符,无法识别为整体。
    • 识别逻辑中判断复合运算符时误用了全局token变量而非当前子Token,且判断顺序晚于单个运算符,导致复合符号被优先拆分为单个Token。
  2. 非法Token处理错误:
    • 非法Token仅打印错误,但未将其标记为空,导致原内容仍被输出。
    • 标识符正则未覆盖常见规则(如允许下划线),且错误判断顺序导致非法Token未被正确过滤。
  3. 其他细节错误:
    • 正则表达式存在语法错误(如\\".*\\"、>[\\]),且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 "";
    }
}

关键修复说明

  1. 复合运算符处理:
    • 读取字符时主动判断是否能组合成复合运算符,避免提前拆分。
    • Token识别时优先判断复合运算符,确保++、>=被识别为单个Token。
    • 修正了LB/RB的识别错误。
  2. 非法Token处理:
    • 非法Token返回空字符串,不再输出原内容。
    • 修正标识符正则,符合常见语言规则,确保9user这类非法标识符被过滤。
  3. 性能与结构优化:
    • 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 15:51:44