Attoparsec является чистым (Data.Attoparsec.Internal.Types.Parser
не является преобразователем и не включает IO
), поэтому вы правы, что вы не можете расширять включения непосредственно из анализатора.
Разделение синтаксического анализатора на два прохода кажется правильным подходом: один проход действует как препроцессор C, принимая файл с операторами include
, чередующимися с другими вещами.«Прочие вещи» должны быть в основном лексически верными, а не ваш полный синтаксический анализатор - точно так же, как препроцессор C заботится только о токенах и соответствующих скобках, а не о других скобках или чем-либо семантическом.Затем вы заменяете включаемые файлы, создавая полностью развернутый файл, который вы можете передать существующему анализатору.
Если включаемый файл должен быть в некотором смысле синтаксически «автономным» † , то вы можетеСначала проанализируйте весь файл, чередуя его с include
s, а затем замените их.Например:
-- Whatever items you’re parsing.
data Item
-- A reference to an included path.
data Include = Include FilePath
parse :: Parser [Either Include Item]
-- Substitute includes; also calls ‘parse’
-- recursively until no includes remain.
substituteIncludes :: [Either Include Item] -> IO [Item]
† Скажите, если вы просто используете attoparsec для лексических токенов, которые в любом случае не могут пересекать границы файла, или вы выполняете полный анализ, но хотите disallow включаемый файл, который содержит, например, несопоставленные скобки.
Другой вариант заключается в непосредственном внедрении IO
в ваш анализатор с использованием другой библиотеки синтаксического анализа, такой как megaparsec, которая обеспечиваетParsecT
преобразователь, который вы можете обернуть вокруг IO
, чтобы сделать IO
прямо в вашем парсере.Я бы, вероятно, сделал это для прототипа, но кажется более уместным максимально разделить проблемы синтаксического анализа и расширения.