13.6.09

В поисках Затерянной Кавычки

Пока я тут тихо-мирно писал алгоритм обработки кода, разбивающий его на складные блоки, здец подкрался незаметно.. Обнаружил совершенно дурацкую ошибку в своём парсере полугодичной(!) давности. Он сам, этот парсер, - тема отдельного разговора, так что не стану сейчас особо распространяться на его счёт, опишу только пару ключевых моментов.

Предназначен он для лексического разбора кода (с целью, скажем, подсветить синтаксис прораммы на веб-странице). Изначально был написан на Lua и для Lua - просто скрипт, пережёвывающий сам себя - эдакий автофаг - и выдающий хтм-файлик с подсвеченным кодом. Позже я перевёл его на жабаскрипт, обработал напильником, и стало возможным юзать его прямо на странице, в полуавтоматическом режиме. В обоих инкарнациях активно применялись регулярные выражения: текст перебирался посимвольно, а при обнаружении многосимвольной лексемы (строки, например, или коментария), её конец локализовался посредством регекса.

Так вот, обнаруженная ошибка связана с поиском закрывающей кавычки строки. Для людей, совсем уж далёких от всей этой кодерской магии, еще одно пояснение: в контексте лексического разбора текста программы на любом высокоуровневом языке, строка - ни что иное как последовательность символов, заключённая, как правило, в кавычки ("двойные" или 'одинарные'). Казалось бы, задача тривиальна: подумаешь, найти символ! Ща мы быстренько выполним регекс /"/ с позиции, следующей за открывающей кавычкой - и будет нам счастье))
Но счастье длится ровно до тех пор, пока мы не вспоминаем об отмене кавычек бекслешем: "Hello, world. \"Hello\", I said!". Значит, надо искать кавычку, перед которой нет бекслеша! Что ж, "взгляда через плечо" (look-behind) в жабаскриптовом регексе нет, но ничего, "и не таких валили".. Меняем регекс на /[^\\]"/, поиск начинаем уже не со следующей, а с текущей позиции (для правильной обработки пустых строк - ""), а к позиции найденного совпадения прибавляем единицу, потому что возвращается позиция не-бекслеша. "Ну теперь я точно всё учёл", - подумал наивный я, протестировав алгоритм на большом куске кода.

... Который, к несчастью, не содержал ничего наподобие "\\". А вот сегодня мне такой код под руку попался.. Вы всё ещё не готовы отказаться от мысли о тривиальности задачи? =) Тогда продолжаем.
Теперь искать надо два варианта: (1) кавычка, перед которой нет бекслеша ИЛИ (2) кавычка, перед которой два бекслеша. Мой регекс должен был бы выглядеть как-то вот так: /(?:\\\\|[^\\])"/, если бы не одна заковырка: длина матча этого выражения неизвестна заранее. Она составит или два или три символа.. А значит, чтобы найти фактическое расположение кавычки, к возвращённой позиции придётся прибавлять длину матча (за вычетом 1), которую надо ещё получить. х_Х
Думаете, это предел? А что если парсеру встретися такое: "\\\""1? Да у него разрыв мозга случится от осознания факта, что матч должен быть найден не на второй кавычке, а на третьей. А заодно и у меня, при попытке решить это без лук-бихайнда. Есть, правда, мысля, что вместо двух бекслешей можно задать произвольное чётное кол-во (как-то так: /(?:(?:\\{2})+|[^\\])"/), но чую, меня ждёт очередной фейл.

Сижу вот и думаю, а по той ли я дорожке пошёл изначально?.. Может, сразу надо было регулярки в топку отправить, и перебирать всё вручную.. Чем, по-видимому, и придётся заняться. Хотя бы для строк.


  1. Щас подумал.. похоже, что если поменять местами выражения \\\\ и [^\\], то в данном случае всё вроде бы пучком. Но кто же знает, на чём этот регекс ещё может споткнуться, ведь, как известно, ни один, даже самый профессиональный девелопер, не может за сколь либо приемлимый срок сказать чему соответствует та или иная регулярка, глядя на неё.

2 комментария:

  1. Здоров, Маньяк )) Я вот тут на твой бложок попал.

    По поводу поста... Вроде такая регекспа будет матчить все возможные строки.
    \"(\\\\|\\\"|.)*?\"

    Кстати, есть хороший онлайн чекер регексп myregexp.com

    ОтветитьУдалить
  2. Привет)

    Онлайновые чекеры у меня из головы совершенно вылетели.. Это же сверхъудобная вещь!
    Погонял там твой регекс, похоже работает. Спасибо дважды) А если еще и в коде заработает - то ажно трижды. =))

    ОтветитьУдалить