Мегапарсек: расширение макроса при разборе

В небольшом DSL я разбираю определения макросов, аналогично #define Директивы препроцессора C (здесь упрощенный пример):

_def mymacro(a,b) = a + b / a

Когда следующий вызов встречается парсером

c = mymacro(pow(10,2),3)

он расширен до

c = pow(10,2) + 3 / pow(10,2)

Мой текущий подход:

  • завернуть парсер в государственную монаду
  • при синтаксическом анализе определений макросов сохраняйте их в состоянии, а их тело не анализируется (анализируйте его как строку)
  • при синтаксическом анализе вызова макроса найдите определение в состоянии, замените аргументы в основном тексте, замените вызов этим телом и возобновите анализ.

Немного кода из последнего шага:

macrocallStmt
  = do  -- capture starting position and content of old input before macro call
        oldInput <- getInput
        oldPos <- getPosition
        -- parse the call
        ret <- identifier
        symbolCS "="
        i <- identifier
        args <- parens $ commaSep anyExprStr
        -- expand the macro call
        us <- get
        let inlinedCall = replaceMacroArgs i args ret us
        -- set up new input with macro call expanded
        remainder <- getInput
        let newInput = T.append inlinedCall (T.cons '\n' remainder)
        setPosition oldPos
        setInput newInput
        -- update the expanded input script
        modify (updateExpandedInput oldInput newInput)

anyExprStr = fmap praShow expression <|> fmap praShow algexpr

Такой подход делает работу достойно. Однако он имеет ряд недостатков.

Разбор несколько раз

Любое допустимое выражение DSL может быть аргументом вызова макроса. Поэтому, даже если мне нужно только их текстовое представление (для замены в теле макроса), мне нужно проанализировать их, а затем снова преобразовать их в строку - просто поиск следующей запятой не будет работать. Затем полный и настроенный макрос будет проанализирован. Таким образом, на практике макро-аргументы анализируются дважды (и также показываются, что имеет свою стоимость). Более того, каждый вызов требует нового анализа (почти того же) тела. Причина, по которой тело не разбирается в памяти, заключается в том, чтобы обеспечить максимальную гибкость: в теле даже ключевые слова DSL могут быть созданы из аргументов макроса.

Обработка ошибок

Поскольку расширенное тело вставляется перед неизрасходованным вводом (заменяя вызов), начальный и конечный ввод могут быть совершенно разными. В случае ошибки разбора доступна позиция, где произошла ошибка в расширенном вводе. Однако при обработке ошибки у меня есть только исходный, а не расширенный ввод. Таким образом, позиция ошибки не будет совпадать. Вот почему в приведенном выше фрагменте кода я использую состояние для сохранения расширенного ввода, чтобы оно было доступно при выходе анализатора с ошибкой. Это хорошо работает, но я заметил, что это становится довольно дорогостоящим, поскольку новые текстовые массивы (входной поток - это текст) выделяются для всего потока при каждом расширении. Возможно, в этом случае сохранение расширенного ввода в состоянии String, а не Text, будет дешевле, т. Е. Когда необходимо заменить среднюю часть?

Причины этого вопроса:

  • Буду признателен за предложения / комментарии по двум вопросам, описанным выше
  • Кто-нибудь может предложить лучший подход вообще?

0 ответов

Другие вопросы по тегам