Висновки до яких я прийшов, розробляючи генератори парсерів

Досить багато часу та зусиль я інвестував у експериментальні розробки різних інструментів для роботи з програмними кодами. Усі вони потребували парсингу цих самих кодів. У цій публікації я хочу поділитися власними висновками щодо парсингу.

Теорія без практики – ніщо, рефлексія над практикою створює теорію

Я розпочав роботу над генераторами парсерів без будь-якої теоретичної бази (за освітою я взагалі гірничий інженер – підземник, тобто справжній інженер). Трохи пізніше я думав підтягнути теорію, але побачив, що мої власні розробки – це вже передова теорії парсингу, а ще книжки по парсингу занадто нудні та занадто складно розповідають дуже прості речі.

Люді, що працювали над мовами програмування, люди з академічної середи, вирішили створити курс або окремі курси про автомати, граматики, мови програмування і таке інше, систематизувавши та структурувавши власні знання. Проблема у тому, що користуючись цією літературою можна мало що дійсно зрозуміти, а не просто вивчити. Більш того, знання отримані із власного досвіту не мають такої структури, вона є досить синтетичною для них і існує лише через вимогу щодо структурованості учбових матеріалів.

Іншими словами, власний досвід розробки парсерів чи генераторів парсерів насправді не лише більш цікавий за читання книг, а й значно більш ефективний для отримання справжнього розуміння та практичних навичок.

Генератори парсерів не складні та не цікаві

Товсті книжки змушують вважати парсери та генератори парсерів чимось складним, хоча на практиці мова йде про реалізацію досить простих семантих, та кодову базу розміров від декількох сотень до 2-5 тисяч строк коду, що є зовсім незначною у плані складності розуміння чи розробки.

Генератори парсерів можуть здатися цікавою темою і я знаю декілька випадків коли вони можуть бути досить корисними, але на практиці, на скільки я це знаю, вони не використовуються у реальних популярних мовах програмування. Зазвичай розробники пишуть самостійно парсер для мови програмування, оскільке його генерація виключає багато можливостей щодо оптимізації.

Із власного досвіду можу сказати, що складні граматики рідко бувають виправдані, а синтаксиси багатьох мов є переускладненими. Можливо я помиляюся, можливо це справа смаку, але мені здається, що мови програмування мають бути значно простішими, аніж більшість є наразі. Особисто мені подобається підхід в таких мовах, як OCaml, CommonLISP та FORTH, де синтаксис дуже простий або майже відсутній.

Повертаючись до генераторів парсерів, якщо Ви можете створити парсер (а це я вважаю рівень т.з. middle розробника), то можете створити і генератор парсерів. Ви створюете парсер нотації граматик, а потім вже користуючись AST генеруєте парсер для текстів створених за цими граматиками.

Поділ на токенайзер та парсер зовсім не обов’язковий

Типовим підходом, про який пишуть майже в усіх книжках та публікаціях є підхід, коли текст прогоняється через токенайзер, що повертає массив, або стрім токенів, з яких вже парсер будує абстрактне синтаксичне дерево. Насправді це зовсім не обов’язково, та й майже усі парсери та генератори парсерів, що я писав, використовують підхід, де крок токенізації просто відсутній. Я не досліджував переваги та недоліки такого підходу, просто так вийшло і результатами я задоволений.

Оптимізація мови під парсинг – хороша ідея

Розробка парсерів, генераторів парсерів та нотацій для опису граматик – це корисна вправа, яка дозволяє зрозуміти як зробити синтаксис мови дійсно простим. Простий синтаксис знижує поріг входження, пришвидшує парсинг, та дозволяє мати більші контроль та розуміння роботи парсеру. Звісно, це буде корисно лише тим людям, що цікавляться теорією мов програмування, розробкою власних мов та розширень.

Простий приклад, який я люблю завжди приводити – це приклад про парсинг SQL. В SQL значення розділяються за допомогою коми, тобто сенс коми – це розділення значень. В такому випадку виникає особлива умова – останне значення має бути без коми після нього. Отже, ми маємо дивитися дещо наперед, що ускладнює та сповільную реалізацію парсингу. Аби цього уникнути я пропоную використовувати іншу семантику – кома – це символ завершення значення, будь яке значення має завершуватися комою. В такому випадку граматика та парсер виглядають простіше, а парсинг відбувається ефективніше. Звісно, я не можу та не палаю бажанням вплинути на стандарт SQL, але у власних експеріментах із мовами я використовую багато таких спрощень.

Інший приклад стосується того, що в JSON порядок ключів та значень немає різниці. {"a": 1, "b": 2} та {"b": 2, "a": 1} тотожні. Ця особливість також негативно впливає на швидкість парсингу, як і те, що ми не знаємо точно які повинні бути ключі та значення. Таким чином, парсери конкретних схем даних будуть більш ефективні, аніж парсери довільного JSON.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *