【问题标题】:How do I tokenize input using Java's Scanner class and regular expressions?如何使用 Java 的 Scanner 类和正则表达式标记输入?
【发布时间】:2008-10-28 17:16:16
【问题描述】:

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

【问题讨论】:

    标签: java regex compiler-construction tokenize


    【解决方案1】:

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

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

    当然,它实际上比这要复杂得多,但我只是说明为什么像 StringTokenizer、split() 和 Scanner 这样的内置工具不适合这种任务。但是,可以使用 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

    【讨论】:

    • 您的 pos
    • 好收获。是的,在 for 循环之后应该有一个“pos++”。这可能是一个没有错误检查的简单示例,但我至少应该确保它没有任何潜在的无限循环。
    • 我真的很喜欢这种方法,并在昨天将其用作我自己的代码的示例。但是,我确实注意到规则列表的顺序会影响结果。在我的解决方案中,我尝试匹配所有规则,而不是在第一场比赛后打破。然后我选择最长的匹配。
    【解决方案2】:

    如果我很好地理解了您的问题,那么这里有两个标记字符串的示例方法。您甚至不需要 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);
            }
        }
    
    }
    

    【讨论】:

    • 是的,我应该详细说明。这有助于将字符串 around 匹配到正则表达式,但对于查找实际匹配正则表达式的标记没有帮助。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 我也强烈推荐 JFlex 用于任何重要的事情。编写扫描仪规范需要一些练习,但 JFlex 有很好的入门文件,是一项很好的技能。
      猜你喜欢
      • 1970-01-01
      • 2013-04-10
      • 1970-01-01
      • 2011-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-30
      • 1970-01-01
      相关资源
      最近更新 更多