如何在Haskell中编写lisp解析器?

时间:2018-05-28 08:30:26

标签: parsing haskell compilation

我正在尝试在haskell中编写一个lisp解释器,受到Norvig的Python(http://norvig.com/lispy.html)的启发。我有一个成功的tokenizer,如果需要我可以链接到。在这里,它输出正确的代码到Norvig的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 :(得分:7)

在Common Lisp中有一个名为READ-DELIMITED-LIST的中间函数,它将多个表单作为列表读取,直到读者到达结束字符。当遇到左括号时,阅读器会抓取所有表单直到右括号,然后从那里继续解析。不同之处在于它适用于字符流,而不是令牌,并且流用于副作用。

没有副作用

在纯函数方法中,就像在代码中一样,解析函数需要返回要处理的剩余标记以及返回的AST。 这允许您在解析期间消耗尽可能多的令牌,并允许调用者继续从解析器结束的位置进行解析。

换句话说,当您关闭括号时,您必须将xs返回到调用上下文。因此,您携带一个累加器对象(状态)以及您的代码。我听说monad可以帮助你使用样板。

以下是Common Lisp中的非破坏性概念证明。

速成课程

  • MULTIPLE-VALUE-BINDVALUES一起工作:(values x y)返回多个值,multiple-value-bind从表达式中捕获多个值,并将每个值绑定到变量。

  • DESTRUCTURING-BIND是模式匹配的祖先,只是将列表解构为组件。

  • 其余的应该是不言自明的(否则请问)。请注意,我使用符号<>分别表示开头结束令牌。

分析器

首先,定义parse-until-close

(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递归。这有点复杂,因为我们必须在每一步都绑定tokens来处理延续。

(defun parse (tokens)
  (when tokens
    (destructuring-bind (head . tail) tokens
      (case head
        (< (parse-until-close tail))
        (otherwise (values head tail))))))

测试

每次调用都会返回两个值,即已解析的令牌和剩余的值:

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

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

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

答案 1 :(得分:0)

您是否正在寻找这样的结果?

[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