Опитвам се да напиша библиотека за многократно анализиране (за забавление).
Написах Lexer
клас, който генерира последователност от Tokens
. Token
е базов клас за йерархия от подкласове, всеки от които представлява различен тип токен, със свои специфични свойства. Например, има подклас LiteralNumber
(произлизащ от Literal
и чрез него от Token
), който има свои собствени специфични методи за работа с числената стойност на неговата лексема. Методите за работа с лексемите като цяло (извличане на тяхното представяне на символен низ, позиция в източника и т.н.) са в базовия клас, Token
, тъй като те са общи за всички типове токени. Потребителите на тази йерархия на класове могат да извлекат свои собствени класове за конкретни типове токени, които не са предвидени от мен.
Сега имам клас Parser
, който чете потока от токени и се опитва да ги съпостави със своята дефиниция на синтаксис. Например има метод matchExpression
, който от своя страна извиква matchTerm
, а този извиква matchFactor
, който трябва да тества дали текущият токен е Literal
или Name
(и двата получени от Token
базов клас).
Проблемът е:
Сега трябва да проверя какъв е типът на текущия токен в потока и дали съответства на синтаксиса или не. Ако не, хвърлете EParseError
изключение. Ако отговорът е да, действайте съответно, за да получите стойността му в израза, генерирайте машинен код или направете всичко, което анализаторът трябва да направи, когато синтаксисът съвпада.
Но съм чел много за това, че проверката на типа по време на изпълнение и вземането на решение от него е лош дизайн и трябва да се преработи като полиморфни виртуални методи. Разбира се, съгласен съм с това.
Така че първият ми опит беше да поставя някакъв type
виртуален метод в Token
базовия клас, който да бъде заменен от производните класове и да върне някои enum
с идентификатор на тип.
Но вече виждам недостатъци на този подход: Потребителите, извличащи от Token
собствените си класове токени, няма да могат да добавят допълнителни идентификатори към enum
, който е в източника на библиотека! :-/ И целта беше да им се позволи да разширят йерархията за нови типове токени, когато имат нужда от това.
Бих могъл също да върна някои string
от метода type
, което би позволило лесно дефиниране на нови типове.
Но все пак и в двата случая информацията за базовите типове се губи (от метода type
се връща само листен тип) и класът Parser
няма да може да открие производния тип Literal
, когато някой би извел от него и заменил type
да върне нещо различно от "Literal"
.
И, разбира се, класът Parser
, който също е предназначен за разширяване от потребителите (тоест, писане на техни собствени парсери, разпознаване на техните собствени токени и синтаксис) не знае какви наследници на класа Token
ще има в бъдеще.
Много често задавани въпроси и книги за дизайн препоръчват в този сценарий да се вземе поведението от кода, който трябва да реши по тип, и да се постави във виртуалния метод, заменен в производни класове. Но не мога да си представя как мога да вкарам това поведение в Token
потомците, защото не е техен бизнес, например, да генерират машинен код или да оценяват изрази. Освен това има части от синтаксиса, които трябва да съвпадат с повече от един токен, така че няма конкретен токен, в който да вложа това поведение. Това е по-скоро отговорност на определени синтаксични правила, които могат да съпоставят повече от един токен като техните терминални символи.
Някакви идеи как да подобря този дизайн?
boost::spirit
е полиморфен и мисля, че AX е също. - person Sebastian Mach   schedule 15.09.2011