java - 如何使用 Java 的 Scanner 类和正则表达式对输入进行标记化?

标签 java regex compiler-construction tokenize

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

最佳答案

“Scanner”这个名字有点误导,因为这个词通常用来表示词法分析器,而 Scanner 并不是这个意思。它只是您在 C、Perl、 中找到的 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

关于java - 如何使用 Java 的 Scanner 类和正则表达式对输入进行标记化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/244115/

相关文章:

java - Java 中的 Alchemy api 获取 URLGetRelations() 的异常

c# - 需要返回值的编译器 - 未注意到被调用方法中的无条件异常

c++ - c++ by-ref参数传递如何在汇编中编译?

javascript - 如何在javascript中使用正则表达式选择不在双引号/单引号之间的数字

c++ - C++ 编译器是否针对不同平台(Linux、Windows 等)多次编写?

java - Eclipse 调试器不适用于这两个小线程

java - ElasticSearch 中字段名称的最佳实践

java - 在tomcat上的servlet应用程序中保存文件,如何找出应用程序的根目录?

javascript - 使用 jQuery Validate 避免大写输入

javascript - 正则表达式难题