Как я могу токенизировать ввод, используя класс сканера Java и регулярные выражения?

StackOverflow https://stackoverflow.com/questions/244115

Вопрос

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

Это было полезно?

Решение

Название " Сканер " немного вводит в заблуждение, потому что это слово часто используется для обозначения лексического анализатора, а это не то, для чего предназначен сканер. Все это - замена функции 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

Другие советы

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

}

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top