【问题标题】:How to write a lisp parser in Haskell?如何在 Haskell 中编写一个 lisp 解析器?
【发布时间】:2018-05-28 08:30:26
【问题描述】:

我正在尝试在 Haskell 中编写一个 lisp 解释器,灵感来自 Python 中的 Norvig (http://norvig.com/lispy.html)。我有一个成功的标记器,如果需要,我可以链接到它。在这里,它将正确的代码输出到 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"]]]]]

在某种程度上,它需要允许“平行”树,而不仅仅是嵌套。任何帮助将不胜感激。

【问题讨论】:

标签: parsing haskell compilation


【解决方案1】:

问题是作业/练习,以下是试图避免直接放弃解决方案的解释;它是用 Common Lisp 编写的(因为我不知道 Haskell ;-))。

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

无副作用

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

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

速成课程

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

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

  • 非特殊形式(f x1 .. x2)是函数应用

  • (case test . clauses) 是一个开关,其中每个子句都是一个以文字值(或此类值的列表)开头并后跟表达式的列表。 totherwise 是始终匹配的特殊关键字(默认大小写)。

  • 其余的应该是不言自明的(另外问)。

请注意,我使用符号 <> 分别表示 openingclose 标记,以避免与 Lisp 的括号混淆。例如,(< a b c < d > >) 列表包含 8 个标记,可以是 "(a b c (d))" 的内部表示。我可以使用left-parenright-paren,甚至是复杂的数据结构,这只是一个内部表示。词法分析器在此不详。

解析器

入口点parse函数接受一个token列表并返回解析值和剩余token作为辅助值;它取决于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)))))))

上述递归解析令牌并构建列表。如果我们的令牌列表以&gt;(关闭令牌)开头,我们将返回空列表以及其余令牌。

否则,我们解析一个元素并使用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

【讨论】:

    【解决方案2】:

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

    [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
    

    【讨论】:

    • 这将解析单个 s 表达式。一个普通的lisp源码,s-expressions列表如何处理?
    猜你喜欢
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 2023-04-08
    相关资源
    最近更新 更多