Как написать парсер lisp в Haskell? - PullRequest
0 голосов
/ 28 мая 2018

Я пытаюсь написать интерпретатор lisp на haskell, вдохновленный Norvig's на Python (http://norvig.com/lispy.html). У меня есть успешный токенизатор, на который я могу ссылаться, если это будет необходимо. Здесь он выводит правильный код до Norvig'sТокенайзер Python.

program = "(begin (define r 10) (* pi (* r r)))"
astTokenized = tokenize program
astTokenized == ["(","begin","(","define","r","10",")","(","*","pi","(","*","r","r",")",")",")"]

Здесь я определяю мой тип данных абстрактного синтаксического дерева, хотя я знаю, что в нем уже есть неявная ошибка, поскольку он не заключен в список.

data Ast x = Val x | Node [Ast x] deriving (Show)

Здесь была моя первая попытка:

parse :: [[Char]] -> [Ast [Char]]
parse (x:xs)
  | x == "(" = [Node (parse xs)]
  | x == ")" = []
  | otherwise = (Val x) : parse xs

Обнадеживающий, за исключением того, что он заканчивается после первого ')'.

Prelude> parse astTokenized
[Node [Val "begin",Node [Val "define",Val "r",Val "10"]]]

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

parse [] = []
parse (x:xs)
  | x == "(" = [Node (parse xs)]
  | x == ")" = parse xs
  | otherwise = (Val x) : parse xs

Prelude> parse astTokenized
[Node [Val "begin",Node [Val "define",Val "r",Val "10",Node [Val "*",Val "pi",Node [Val "*",Val "r",Val "r"]]]]]

В некотором смысле ему необходимо разрешить "параллельное"деревья, а не просто вложенные.Любая помощь будет оценена.

Ответы [ 2 ]

0 голосов
/ 20 августа 2019

Вы ищете что-то подобное?

[Node [Val "begin",Node [Val "define",Val "r",Val "10"],Node [Val "*",Val "pi",Node [Val "*",Val "r",Val "r"]]]]

Попробуйте:

data Ast x = Val x | Node [Ast x] deriving (Show)

parseh :: [[Char]] -> [Ast [Char]] -> (Ast [Char], [String])
parseh [] as = (Node as, [])
parseh (x : xs) as
  | x == "(" = (let (a, xs') = (parseh xs []) in (parseh xs' (as ++ [a])))
  | x == ")" = (Node as, xs) 
  | otherwise = parseh xs (as ++ [Val x])

parse :: [[Char]] -> [Ast [Char]]
parse xs = let (Node as, _) = parseh xs [] in as
0 голосов
/ 28 мая 2018

Вопрос, являющийся домашней работой / упражнением, ниже следует объяснение, которое пытается избежать порчи решения;он делает это, будучи написанным на Common Lisp (то есть, и потому что я не знаю Haskell ;-)).

В Common Lisp есть промежуточная функция с именем READ-DELIMITED-LIST, который читает несколько форм в виде списка, пока читатель не достигнет конечного символа.При обнаружении открывающей скобки читатель берет все формы до закрывающей скобки, а затем продолжает анализ с этого места.Разница в том, что он работает с потоками символов, а не с токенами, и что этот поток используется для побочных эффектов.

Без побочных эффектов

В чисто функциональном подходе, как вВ вашем коде функция parse должна вернуть оставшиеся токены для обработки вместе с возвращенным AST.Это позволяет вам использовать столько токенов, сколько вам нужно во время синтаксического анализа, и позволяет вызывающей программе продолжать анализ с того места, где анализатор завершился.

Другими словами, когда вы закрываете скобки, вы должны вернуть xsв контекст вызова.Итак, вы несете объект-накопитель (состояние) вместе с вашим кодом.Я слышал, что монады могут помочь вам с шаблоном.

Ускоренный курс

  • MULTIPLE-VALUE-BIND и VALUESработать вместе: (values x y) возвращает несколько значений, а multiple-value-bind захватывает несколько значений из выражения и связывать каждое из них с переменной.

  • DESTRUCTURING-BINDявляется прародителем сопоставления с образцом и просто разбивает список на компоненты.

  • Неспециализированная форма (f x1 .. x2) - это приложение функции

  • (case test . clauses) - это переключатель, где каждое предложение представляет собой список, начинающийся с буквального значения (или списка таких значений), за которым следуют выражения.t и otherwise - это специальные ключевые слова, которые всегда совпадают (регистр по умолчанию).

  • Остальные значения должны быть достаточно понятны (спросите иначе).

Обратите внимание, что я использую символы < и > для представления соответственно открывающих и закрывающих токенов, чтобы избежать путаницы сСкобки Лиспа.Например, список (< a b c < d > >) содержит 8 токенов;Я мог бы использовать left-paren и right-paren, или даже сложные структуры данных, это просто внутреннее представление.

Parser

Функция точки входа parse принимает списоктокены и возвращает проанализированное значение и оставшиеся токены в качестве вторичного значения;это зависит от parse-until-close, определенного ниже:

(defun parse (tokens)
  (when tokens
    (destructuring-bind (head . tail) tokens
      (case head
        (> (error "unmatched closing parenthesis"))
        (< (parse-until-close tail))
        (otherwise (values head tail))))))

Затем parse-until-close - функция, которая рекурсивно анализирует токены, пока не найдет токен, близкий к нему;обратите внимание, что tokens перепривязывается в разных точках:

(defun parse-until-close (tokens)
  (when tokens
    (case (first tokens)
      (> (values nil (rest tokens)))
      (otherwise
       ;; first read the element in head of tokens
       (multiple-value-bind (head tokens) (parse tokens)
         ;; then recurse to read the remaining items in list
         (multiple-value-bind (tail tokens) (parse-until-close tokens)
           (values (cons head tail) tokens)))))))

Вышеуказанный рекурсивно анализирует токены и составляет список.Если наш список токенов начинается с >, токена закрытия, мы возвращаем пустой список, а также остальные токены.

В противном случае, мы анализируем один элемент и рекурсируем с parse-until-close.

Тест

Каждый вызов возвращает два значения: проанализированный токен и остальные:

(parse '(one token))
=> ONE
   (TOKEN)

(parse '(< abc < x > y >))
=> (ABC (X) Y)
   NIL

(parse '(< abc def >))
=> (ABC DEF)
   NIL

;; incomplete input
(parse '(< < < abc))
=> (((ABC)))
   NIL
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...