Java中的词法分析器

12

我一直在尝试用Java编写一个简单的词法分析器。

Token.java文件如下:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public enum Token {

    TK_MINUS ("-"), 
    TK_PLUS ("\\+"), 
    TK_MUL ("\\*"), 
    TK_DIV ("/"), 
    TK_NOT ("~"), 
    TK_AND ("&"),  
    TK_OR ("\\|"),  
    TK_LESS ("<"),
    TK_LEG ("<="),
    TK_GT (">"),
    TK_GEQ (">="), 
    TK_EQ ("=="),
    TK_ASSIGN ("="),
    TK_OPEN ("\\("),
    TK_CLOSE ("\\)"), 
    TK_SEMI (";"), 
    TK_COMMA (","), 
    TK_KEY_DEFINE ("define"), 
    TK_KEY_AS ("as"),
    TK_KEY_IS ("is"),
    TK_KEY_IF ("if"), 
    TK_KEY_THEN ("then"), 
    TK_KEY_ELSE ("else"), 
    TK_KEY_ENDIF ("endif"),
    OPEN_BRACKET ("\\{"),
    CLOSE_BRACKET ("\\}"),
    DIFFERENT ("<>"),

    STRING ("\"[^\"]+\""),
    INTEGER ("\\d"), 
    IDENTIFIER ("\\w+");

    private final Pattern pattern;

    Token(String regex) {
        pattern = Pattern.compile("^" + regex);
    }

    int endOfMatch(String s) {
        Matcher m = pattern.matcher(s);

        if (m.find()) {
            return m.end();
        }
        return -1;
    }
}

词法分析器如下:Lexer.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

public class Lexer {
    private StringBuilder input = new StringBuilder();
    private Token token;
    private String lexema;
    private boolean exausthed = false;
    private String errorMessage = "";
    private Set<Character> blankChars = new HashSet<Character>();

    public Lexer(String filePath) {
        try (Stream<String> st = Files.lines(Paths.get(filePath))) {
            st.forEach(input::append);
        } catch (IOException ex) {
            exausthed = true;
            errorMessage = "Could not read file: " + filePath;
            return;
        }

        blankChars.add('\r');
        blankChars.add('\n');
        blankChars.add((char) 8);
        blankChars.add((char) 9);
        blankChars.add((char) 11);
        blankChars.add((char) 12);
        blankChars.add((char) 32);

        moveAhead();
    }

    public void moveAhead() {
        if (exausthed) {
            return;
        }

        if (input.length() == 0) {
            exausthed = true;
            return;
        }

        ignoreWhiteSpaces();

        if (findNextToken()) {
            return;
        }

        exausthed = true;

        if (input.length() > 0) {
            errorMessage = "Unexpected symbol: '" + input.charAt(0) + "'";
        }
    }

    private void ignoreWhiteSpaces() {
        int charsToDelete = 0;

        while (blankChars.contains(input.charAt(charsToDelete))) {
            charsToDelete++;
        }

        if (charsToDelete > 0) {
            input.delete(0, charsToDelete);
        }
    }

    private boolean findNextToken() {
        for (Token t : Token.values()) {
            int end = t.endOfMatch(input.toString());

            if (end != -1) {
                token = t;
                lexema = input.substring(0, end);
                input.delete(0, end);
                return true;
            }
        }

        return false;
    }

    public Token currentToken() {
        return token;
    }

    public String currentLexema() {
        return lexema;
    }

    public boolean isSuccessful() {
        return errorMessage.isEmpty();
    }

    public String errorMessage() {
        return errorMessage;
    }

    public boolean isExausthed() {
        return exausthed;
    }
}

可以使用以下方式通过Try.java进行测试:

public class Try {

    public static void main(String[] args) {

        Lexer lexer = new Lexer("C:/Users/Input.txt");

        System.out.println("Lexical Analysis");
        System.out.println("-----------------");
        while (!lexer.isExausthed()) {
            System.out.printf("%-18s :  %s \n",lexer.currentLexema() , lexer.currentToken());
            lexer.moveAhead();
        }

        if (lexer.isSuccessful()) {
            System.out.println("Ok! :D");
        } else {
            System.out.println(lexer.errorMessage());
        }
    }
}

假设Input.txt文件中包含以下内容:

define mine 
a=1000;
b=23.5;

我期望的输出是:
define : TK_KEYWORD
mine : IDENTIFIER
a : IDENTIFIER
= : TK_ASSIGN
1000 : INTEGER
; : TK_SEMI
b : IDENTIFIER
= : TK_ASSIGN
23.5 : REAL

但我面临的问题是:它把每个数字都当作一个单独的字符来处理。
1 INTEGER
0 INTEGER
0 INTEGER
0 INTEGER

还有它不识别实数。我得到了以下错误信息:

意外符号:'.'

需要做哪些更改才能获得预期结果?

3
你是否已经使用调试器逐步执行了你的代码? - Thomas
1
无法回答“实数部分”的问题,因为您的输入示例中仅提到了TK_REAL;但是在您的源代码中并没有TK_REAL!请修复它,我可能也能帮忙。我的意思是:您关于错误的最后几条信息...与您展示的代码不符。请创建一个真正的[mcve]。 - GhostCat
1
你的 REAL 模式可以是 (\d+)?\.\d+,以匹配像 .123.45.678 这样的数字。 - Thomas
这看起来像是一份作业任务。OP不知道没有REAL令牌,但他/她却期望有。 - f1sh
1个回答

11

您匹配整数的模式是:

INTEGER ("\\d"), 

那匹配恰好 一个 数字。

如果你想匹配多个,可以选择

INTEGER ("\\d+"), 

例如。

为了完整起见,浮点数的另一种缺失模式可以看起来像这样:

REAL ("(\\d+)\\.\\d+")

正如评论所指出的那样。或者

REAL ("(\\d*)\\.\\d+")

允许

0.23

也可以 - 如果这是你要找的!


谢谢!我对Java、模式匹配等方面是新手。第一个问题已解决。至于下一个问题,就像你所说的,我在令牌中包括了REAL(“(\d+)\.\d+”),并且我得到了输出: 23:INTEGER .5:REAL - Vicky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接