Как я могу токенизировать ввод, используя класс сканера Java и регулярные выражения? - PullRequest
5 голосов
/ 28 октября 2008

Просто для своих собственных целей я пытаюсь создать токенайзер в Java, где я могу определить обычную грамматику и заставить ее токенизировать ввод на основе этого. Класс StringTokenizer устарел, и я нашел в Scanner несколько функций, которые подсказывают, что я хочу делать, но пока не повезло. Кто-нибудь знает, как это сделать?

Ответы [ 4 ]

12 голосов
/ 29 октября 2008

Название «Сканер» немного вводит в заблуждение, потому что слово часто используется для обозначения лексического анализатора, а это не то, для чего предназначен Сканер. Все это является заменой функции scanf(), которую вы найдете в C, Perl, и др. . Как и StringTokenizer и split(), он предназначен для сканирования вперед до тех пор, пока не найдет совпадение с данным шаблоном, а все, что он пропустил по пути, будет возвращено в качестве токена.

Лексический анализатор, с другой стороны, должен исследовать и классифицировать каждый символ, даже если он только решает, может ли он безопасно их игнорировать. Это означает, что после каждого совпадения он может применять несколько шаблонов, пока не найдет тот, который соответствует , начиная с этой точки . В противном случае он может найти последовательность «//» и подумать, что нашел начало комментария, когда он действительно находится внутри строкового литерала и просто не заметил начальную кавычку.

Конечно, на самом деле все гораздо сложнее, но я просто иллюстрирую, почему встроенные инструменты, такие как StringTokenizer, split() и Scanner, не подходят для такого рода задач. Однако можно использовать классы регулярных выражений Java для ограниченной формы лексического анализа. Фактически, добавление класса Scanner сделало его намного проще благодаря новому API Matcher, который был добавлен для его поддержки, то есть регионам и методу 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

3 голосов
/ 28 октября 2008

Если я хорошо понимаю ваш вопрос, то вот два примера метода токенизации строки. Вам даже не нужен класс 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);
        }
    }

}
2 голосов
/ 12 июня 2009

Большинство ответов здесь уже превосходны, но я был бы упущен, если бы не указал ANTLR . Я создал целые компиляторы вокруг этого превосходного инструмента. В версии 3 есть несколько удивительных функций, и я бы порекомендовал ее для любого проекта, который требовал, чтобы вы анализировали ввод на основе четко определенной грамматики.

2 голосов
/ 28 октября 2008

Если это для простого проекта (для изучения, как все работает), то придерживайтесь того, что сказал Балинт Пато.

Если это для более крупного проекта, рассмотрите вариант использования генератора сканера, например JFlex . Несколько сложнее, но быстрее и мощнее.

...