5

仅出于我自己的目的,我正在尝试在 Java 中构建一个标记器,我可以在其中定义一个常规语法并让它基于它对输入进行标记。StringTokenizer 类已被弃用,我在 Scanner 中发现了几个函数暗示我想要做什么,但还没有运气。有人知道解决这个问题的好方法吗?

4

4 回答 4

14

“Scanner”这个名字有点误导,因为这个词经常被用来表示词法分析器,而这不是 Scanner 的用途。它只是scanf()您在 C、Perl中找到的函数的替代品。与 StringTokenizer 和 一样split(),它旨在向前扫描,直到找到给定模式的匹配项,并且在途中跳过的任何内容都作为令牌返回。

另一方面,词法分析器必须检查和分类每个字符,即使它只是决定是否可以安全地忽略它们。这意味着,在每次匹配之后,它可能会应用多个模式,直到找到从该点开始匹配的模式。否则,它可能会找到序列 "//" 并认为它找到了注释的开头,而它实际上是在字符串文字中并且它只是没有注意到左引号。

当然,它实际上比这要复杂得多,但我只是说明了为什么像 StringTokenizer 和 Scanner 这样的内置工具split()不适合这种任务。但是,可以使用 Java 的正则表达式类进行有限形式的词法分析。事实上,添加 Scanner 类使其变得更加容易,因为添加了新的 Matcher API 来支持它,即区域和usePattern()方法。这是一个建立在 Java 正则表达式类之上的基本扫描器的示例。

import java.util.*;
import java.util.regex.*;

public class RETokenizer
{
  static List<Token> tokenize(String source, List<Rule> rules)
  {
    List<Token> tokens = new ArrayList<Token>();
    int pos = 0;
    final int end = source.length();
    Matcher m = Pattern.compile("dummy").matcher(source);
    m.useTransparentBounds(true).useAnchoringBounds(false);
    while (pos < end)
    {
      m.region(pos, end);
      for (Rule r : rules)
      {
        if (m.usePattern(r.pattern).lookingAt())
        {
          tokens.add(new Token(r.name, m.start(), m.end()));
          pos = m.end();
          break;
        }
      }
      pos++;  // bump-along, in case no rule matched
    }
    return tokens;
  }

  static class Rule
  {
    final String name;
    final Pattern pattern;

    Rule(String name, String regex)
    {
      this.name = name;
      pattern = Pattern.compile(regex);
    }
  }

  static class Token
  {
    final String name;
    final int startPos;
    final int endPos;

    Token(String name, int startPos, int endPos)
    {
      this.name = name;
      this.startPos = startPos;
      this.endPos = endPos;
    }

    @Override
    public String toString()
    {
      return String.format("Token [%2d, %2d, %s]", startPos, endPos, name);
    }
  }

  public static void main(String[] args) throws Exception
  {
    List<Rule> rules = new ArrayList<Rule>();
    rules.add(new Rule("WORD", "[A-Za-z]+"));
    rules.add(new Rule("QUOTED", "\"[^\"]*+\""));
    rules.add(new Rule("COMMENT", "//.*"));
    rules.add(new Rule("WHITESPACE", "\\s+"));

    String str = "foo //in \"comment\"\nbar \"no //comment\" end";
    List<Token> result = RETokenizer.tokenize(str, rules);
    for (Token t : result)
    {
      System.out.println(t);
    }
  }
}

顺便说一句,这是我发现该方法唯一的好用处lookingAt()。:D

于 2008-10-29T16:34:45.010 回答
3

如果我很好地理解了您的问题,那么这里有两种标记字符串的示例方法。您甚至不需要 Scanner 类,只有当您想要预先转换令牌,或者比使用数组更复杂地遍历它们时。如果一个数组就足够了,只需使用 String.split(),如下所示。

请提供更多要求以提供更准确的答案。

 import java.util.Scanner;


  public class Main {    

    public static void main(String[] args) {

        String textToTokenize = "This is a text that will be tokenized. I will use 1-2 methods.";
        Scanner scanner = new Scanner(textToTokenize);
        scanner.useDelimiter("i.");
        while (scanner.hasNext()){
            System.out.println(scanner.next());
        }

        System.out.println(" **************** ");
        String[] sSplit = textToTokenize.split("i.");

        for (String token: sSplit){
            System.out.println(token);
        }
    }

}
于 2008-10-28T18:06:24.110 回答
3

这里的大多数答案已经非常好,但如果我不指出 ANTLR ,我会失职。我已经围绕这个出色的工具创建了整个编译器。第 3 版具有一些惊人的功能,我建议将它用于任何需要您根据明确定义的语法解析输入的项目。

于 2009-06-12T06:09:29.300 回答
2

如果这是一个简单的项目(用于了解事物的工作原理),那么就按照巴林特帕托所说的去做。

如果这是一个更大的项目,请考虑使用JFlex之类的扫描仪生成器。稍微复杂一些,但速度更快,功能更强大。

于 2008-10-28T18:34:11.760 回答