Не так редко возникает ситуация, когда приходится писать парсер. Написание парсера вручную с нуля — задача не очень сложная, но довольно нудная и однообразная. Поэтому…
Для генерации парсеров существуют известные утилиты yacc и lex. Они позволяют достаточно кратко описать синтаксис некоего языка и по этому описанию автоматически сгенерировать код парсера для него.
По какой-то причине я не смог найти их версию для Delphi. Есть довольно известный проект TP Lex and Yacc, его корни уходят аж в 1990 год (да-да, TP — это именно Turbo Pascal). Добрые люди его долгое время поддерживали, но несколько лет назад и их энтузиазм иссяк.
В итоге я решил попробовать самостоятельно реанимировать этот проект, взяв за основу последнюю его инкарнацию из найденных мной — Delphi Lex & Yacc. Были и другие попытки оживить проект, но по-моему эта попытка самая свежая.
Во-первых, пришлось внести некоторые изменения, чтобы код компилировался и работал в свежей версии Delphi. Во-вторых, я добавил немного объектно-ориентированности — все-таки в 2013 году не очень приятно интегрировать в свое приложение нечто пусть и полезное, но использующее кучу глобальных переменных и STDIN/STDOUT для организации ввода-вывода.
Так родился Next Delphi Lex & Yacc (ndyacclex).
Это только первый шаг. На чем-то серьезном я этот код пока не тестировал. Остановился на том, что добился корректной работы тестового приложения, прилагавшегося к оригинальной версии кода, немного переделав его под современные реалии.
К сожалению, все еще нет никакой поддержки юникода. Всюду AnsiString и AnsiChar. Пожалуй, этим вопросом я и займусь, но это явно потребует некоторого времени.
Для тех, кому лень качать, прямо здесь маленькое демо.
Имея описание синтаксиса (в данном случае это арифметические выражения), утилиты ndyacclex позволяют сгенерировать исходный код класса-наследника TCustomParser, который затем можно использовать вот так:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); var StrStream: TStringStream; begin if Key = #13 then begin Key := #0; Memo1.Lines.Add('> ' + Edit1.Text); StrStream := TStringStream.Create; try StrStream.WriteString(Edit1.Text); StrStream.Position := 0; try Parser.parse(StrStream, WriteCB); except on E: EExprParserException do Memo1.Lines.Add(E.Message); end; Edit1.Text := ''; finally StrStream.Free; end; end; end; procedure TForm1.WriteCB(Value: Real); begin Memo1.Lines.Add(Format('%2.2f',[Value])); end;
Это практически весь код тестового приложения, если не учитывать сгенерированный парсер.
1. Alex W. Lulin
31 Мар 2013 7:50 пп
Я тоже под Delphi портировал. Тупой обёрткой в классы. В понедельник — могу сырцы поискать.
2. Alex W. Lulin
31 Мар 2013 7:54 пп
«Во-первых, пришлось внести некоторые изменения, чтобы код компилировался и работал в свежей версии Delphi. Во-вторых, я добавил немного объектно-ориентированности — все-таки в 2013 году не очень приятно интегрировать в свое приложение нечто пусть и полезное, но использующее кучу глобальных переменных и STDIN/STDOUT для организации ввода-вывода.» Во! Ровно это — я и делал :-)
3. Роман Янковский
31 Мар 2013 9:37 пп
Great minds think alike :)
4. Alex W. Lulin
1 Апр 2013 12:02 дп
Ну Great — это — сильно :-)
Кстати — про Delphi и Objective-C — http://18delphi.blogspot.com/2013/03/objective-c-delphi.html
Вы просто упомянули, что их судьбы — пересекаются :-)
5. Alex
10 Апр 2013 12:05 дп
А я вот написал свой SGLR прасер с блекджеком и прочими радостями. Для дельфы LALR не хватало — gold parser фейлил
6. Роман Янковский
10 Апр 2013 4:36 пп
Alex, а его можно где-то скачать? Я бы посмотрел.
7. Delphi и предметно-ориентированные языки « Роман.Янковский.me
26 Авг 2013 7:20 пп
[…] текста часто используют утилиты вроде Lex/Yacc. Недавно я писал о попытке реанимировать их версию для Delphi. И здесь они к […]