Разбор новой строки Java-сканера с помощью регулярных выражений (ошибка?) - PullRequest
4 голосов
/ 20 мая 2010

Я разрабатываю синтаксический анализатор вручную на Java, и я хотел бы использовать регулярные выражения для анализа различных типов токенов. Проблема в том, что я также хотел бы иметь возможность точно сообщать текущий номер строки, если ввод не соответствует синтаксису.

Короче говоря, я столкнулся с проблемой, когда пытался сопоставить символ новой строки с классом Scanner. Точнее говоря, когда я пытаюсь сопоставить символ новой строки с шаблоном, используя класс Scanner, происходит сбой. Почти всегда. Но когда я выполняю такое же сопоставление, используя Matcher и ту же исходную строку, он извлекает новую строку точно так, как вы ожидаете. Есть ли причина для этого, которую я не могу обнаружить, или это ошибка, как я подозреваю?

К вашему сведению: я не смог найти ошибку в базе данных Sun, которая описывает эту проблему, поэтому, если это ошибка, об этом не сообщалось.

Пример кода:

Pattern newLinePattern = Pattern.compile("(\\r\\n?|\\n)", Pattern.MULTILINE);
String sourceString = "\r\n\n\r\r\n\n";
Scanner scan = new Scanner(sourceString);
scan.useDelimiter("");
int count = 0;
while (scan.hasNext(newLinePattern)) {
    scan.next(newLinePattern);
    count++;
}
System.out.println("found "+count+" newlines"); // finds 7 newlines
Matcher match = newLinePattern.matcher(sourceString);
count = 0;
while (match.find()) {
    count++;
}
System.out.println("found "+count+" newlines"); // finds 5 newlines

Ответы [ 4 ]

6 голосов
/ 20 мая 2010

Ваша комбинация useDelimiter() и next() неисправна. useDelimiter("") вернет подстроку 1 длины на next(), потому что пустая строка фактически находится между каждыми двумя символами.

То есть, потому что "\r\n".equals("\r" + "" + "\n"), то есть "\r\n", фактически являются двумя токенами "\r" и "\n", разграниченными "".

Чтобы получить поведение Matcher, вам нужен findWithinHorizon, который игнорирует разделители.

    Pattern newLinePattern = Pattern.compile("(\\r\\n?|\\n)", Pattern.MULTILINE);
    String sourceString = "\r\n\n\r\r\n\n";
    Scanner scan = new Scanner(sourceString);
    int count = 0;
    while (scan.findWithinHorizon(newLinePattern, 0) != null) {
        count++;
    }
    System.out.println("found "+count+" newlines"); // finds 5 newlines

API ссылки

  • findWithinHorizon(Pattern pattern, int horizon)

    Пытается найти следующее вхождение указанного шаблона [...], игнорируя разделители [...] Если такой шаблон не обнаружен, то возвращается null [...] Если horizon равен 0, затем [...] этот метод продолжает поиск по входу в поисках указанного шаблона без границ.

Смежные вопросы

3 голосов
/ 20 мая 2010

То есть фактически ожидаемое поведение обоих. Сканер в первую очередь заботится о том, чтобы разбить вещи на токены с помощью разделителя. Так что он (лениво) берет ваш sourceString и видит его как следующий набор токенов: \r, \n, \n, \r, \r, \n и \n. Когда вы затем вызываете hasNext, он проверяет, соответствует ли следующий токен вашему шаблону (что они все тривиально делают благодаря ? на \r\n?). Поэтому цикл while выполняет итерацию по каждому из 7 токенов.

С другой стороны, сопоставитель будет жадно сопоставлять регулярное выражение - поэтому он объединяет \r\n вместе, как вы ожидаете.

Один из способов подчеркнуть поведение Сканера - изменить регулярное выражение на (\\r\\n|\\n). Это приводит к подсчету 0. Это потому, что сканер читает первый токен как \r ( не \r\n), а затем замечает, что он не соответствует вашему шаблону, поэтому возвращает false, когда вы Звоните hasNext.

(Сокращенная версия: сканер токенизируется с использованием вашего разделителя перед использованием шаблона токена, устройство сопоставления не выполняет никакой токенизации)

2 голосов
/ 20 мая 2010

Стоит упомянуть, что ваш пример неоднозначен. Это может быть:

\r
\n
\n
\r
\r
\n
\n

(семь строк)

или

\r\n
\n
\r
\r\n
\n

(пять строк)

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

0 голосов
/ 20 мая 2010

Когда вы используете Scanner с разделителем "", он будет выдавать токены длиной по одному символу. Это до того, как применяется новое регулярное выражение. Затем он сопоставляет каждый из этих символов с регулярным выражением новой строки; каждый соответствует, поэтому он производит 7 жетонов. Однако, поскольку он разбивает строку на токены из 1 символа, он не группирует смежные \r\n символы в один токен.

...