Я пытаюсь написать многократно используемую библиотеку разбора (для удовольствия).
Я написал класс Lexer
, который генерирует последовательность Tokens
. Token
- это базовый класс для иерархии подклассов, каждый из которых представляет собой различный тип токена, с его собственными специфическими свойствами. Например, существует подкласс LiteralNumber
(полученный из Literal
и через него из Token
), который имеет свои собственные специальные методы для обработки числового значения его лексемы. Методы обработки лексем вообще (получение их символьного представления строки, позиция в источнике и т.д.) Находятся в базовом классе Token
, потому что они являются общими для всех типов токенов. Пользователи этой иерархии классов могут выводить свои собственные классы для определенных типов токенов, которые не были предсказаны мной.
Теперь у меня есть класс Parser
, который читает поток токенов и пытается сопоставить их с определением синтаксиса. Например, у него есть метод matchExpression
, который, в свою очередь, вызывает matchTerm
, и этот вызов вызывает matchFactor
, который должен проверить, является ли текущий токен Literal
или Name
(оба получены из Token
base класс).
Проблема заключается в следующем:
Мне нужно проверить, каков тип текущего токена в потоке и соответствует ли он синтаксису или нет. Если нет, бросьте исключение EParseError
. Если да, действуйте соответственно, чтобы получить его значение в выражении, сгенерировать машинный код или сделать то, что должен выполнять парсер, когда синтаксис соответствует.
Но я много читал о том, что проверка типа во время выполнения и принятие решения из него - это bad design & trade;, и он должен быть реорганизован как полиморфные виртуальные методы. Конечно, я согласен с этим.
Итак, моя первая попытка состояла в том, чтобы поместить некоторый виртуальный метод type
в базовый класс Token
, который будет переопределяться производными классами и вернуть некоторый enum
с идентификатором типа.
Но я уже вижу недостатки такого подхода: пользователи, получающие из Token
собственные классы токенов, не смогут добавить дополнительный id к enum
, который находится в источнике библиотеки!: -/И цель заключалась в том, чтобы позволить им расширять иерархию для новых типов токенов, когда они понадобятся.
Я также мог бы вернуть некоторый string
из метода type
, который позволит легко определять новые типы.
Но все же в обоих случаях информация о базовых типах теряется (только тип листа возвращается из метода type
), а класс Parser
не сможет определить производный тип Literal
, когда кто-то извлек бы из него и переопределит type
, чтобы вернуть что-то другое, кроме "Literal"
.
И, конечно же, класс Parser
, который также предназначен для расширения пользователями (т.е. написание собственных парсеров, распознающих их собственные токены и синтаксис), не знает, какие потомки класса Token
будут там в будущем.
Многие часто задаваемые вопросы и книги по дизайну рекомендуют в этом сценарии взять поведение из кода, который необходимо решить по типу, и поместить его в виртуальный метод, переопределяющий производные классы. Но я не могу представить, как я мог бы поместить это поведение в потомки Token
, потому что это не их бизнес, например, для генерации машинного кода или оценки выражений. Кроме того, есть части синтаксиса, которые должны соответствовать более чем одному токену, поэтому нет ни одного конкретного токена, в который я мог бы поместить это поведение. Это скорее ответственность определенных правил синтаксиса, которые могли бы соответствовать более чем одному токену в качестве символов терминалов.
Любые идеи по улучшению этого дизайна?