在Haskell中评估解析的表达式

时间:2009-10-14 18:30:36

标签: parsing haskell expression

这是我关于SO的第一个问题:)

我的Haskell知识非常有限,所以我需要一点帮助才能让我开始。 我有这个BNF语法:

num ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
int ::= num | num int
var ::= A | B | C | ... | Z
expr ::= var | int | - expr
        | +(expr , expr) | *(expr , expr)
        | let var be expr in expr

我已经编写了一个解析器,在SO的另一篇文章中提供了一些帮助。

我的数据类型是:

data Expr =  Var Char | Tall Int | Sum Expr Expr | Mult Expr Expr | Neg Expr | Let Expr  Expr Expr

我需要的是评估解析器输出的表达式(Expr,String)。我真的不知道从哪里开始这个任务。任何人都可以帮助我吗?

我不确定需要哪些更多信息,如有必要,我会发布。

2 个答案:

答案 0 :(得分:7)

首先,在您的数据类型中,Let构造函数的第一个数据应该只是变量标识符(在您的情况下为Char)而不是Expr

尝试递归函数。您评估的是ExprInt,因此基本上您希望签名的功能

evaluate :: Expr -> Int

然后开始匹配Expr的构造函数并递归计算子表达式:

evaluate (Tall n) = n
evaluate (Sum e1 e2) = evaluate e1 + evaluate e2

当谈到Let绑定和变量时,您需要扩展签名以另外传递将变量映射到其值的环境。这可以像(Char, Int)对的列表一样简单。 Let会将变量及其值添加到传递给in表达式的环境中。所以你最终会得到类似的东西:

evaluate :: Expr -> Int
evaluate e = evaluate' e EmptyEnv
  where evaluate' :: Expr -> Env -> Int
        evaluate' (Tall n) _ = n
        ...

当然,如果使用未受let约束的变量,则必须提供错误处理。

这有帮助吗?

答案 1 :(得分:4)

环境

对于处理环境,您可以使用

  • 来自Prelude的lookup
  • 来自Data.Map库的
  • 或其同名lookup(以及该库的其他功能)

如果您选择使用Data.Map模块,则值得编写

Data.Map.lookup

而不只是lookup

或 - 另一个解决方案 - 隐藏Prelude的lookup

import Prelude hiding (lookup)

以便不接收有关两个lookup函数冲突的错误消息。

为简单起见,我首先使用简单的lookup函数编写解决方案,即Prelude。

为了简单起见,我还没有加入错误。

环境:

module Env (Env) where

type Env = [Binding]

type Binding = (Char, Integer)

表达式:

module Expr where

data Expr = Var Char
          | Tall Int
          | Sum Expr Expr
          | Mult Expr Expr
          | Neg Expr
          | Let Char Expr Expr

评价为:

module Semantics (evaluate) where

import Expr (Expr)
import Env (Env)

evaluate :: Expr -> Integer
evaluate = evaluate' []

evaluate' :: Env -> Expr -> Integer
evaluate' _   (Tall n) = n
evaluate' env (Var x) = case lookup x env of
    Just n -> n
    Nothing -> error ("Variable" ++ [x] ++ "is free!")
evaluate' env (Sum a b) = evaluate' env a + evaluate' env b
evaluate' env (Mult a b) = evaluate' env a * evaluate' env b
evaluate' env (Neg a) = - evaluate' env a
evaluate' env (Let x a b) = evaluate' ((x, a) : env) b

变量冲突

至于规划您的对象语言:在以后的版本中,值得计划一个策略,如何处理冲突的变量名称:

let A be 5 in (A +3)

很清楚,但应该是什么意思

let A be 5 in (let A be 3 in A)

在您的评估者的早期版本中,您不必进行此计划,因为lookup函数将根据其定义中的内在默认行为“自动”决定情况。但是如果你不喜欢它的默认行为,你可能会用一个有意识的策略来扩充你的评估者来处理变量名冲突。

错误处理

如果您在环境中计算表达式,并且表达式引用了环境中未包含的变量,那么解释器必须以某种方式报告错误。

你可以采取多种方式,最简单的方法:

使用error函数,您可以使用用户定义的错误消息强制程序暂停。这种解决方案有缺点,但很容易编写。

也许

您可以更改evaluate'功能。它没有签名

evaluate' :: Env -> Expr -> Integer

但是,而不是

evaluate' :: Env -> Expr -> Maybe Integer

在这种情况下,你必须严格改变评估的定义。你不能再写了:

evaluate' env (Sum a b) = evaluate' env a + evaluate' env b

但需要更复杂的定义

evaluate' env (Sum a b) = case (evaluate' env a, evaluate' env b) of
        (Just a0, Just b0) -> Just (a0 + b0)
        _                  -> Nothing

我们将Maybe-ed整数打包,求和,然后将它们打包回一个Maybe-ed整数。这就像在“包内”做总结。如果我们可以告诉Haskell它可以在“内部”中进行求和,那么我们可以节省很多工作。

我们可以这样做,如果我们利用那个可能是一个monad:我们可以使用为monad工作而设计的函数。这些辅助功能在Control.Monad库中提供。这里,liftM2是帮助我们围绕Maybe-packed值进行求和的函数:

evaluate' env (Sum a b) = liftM2 (+) (evaluate' env a) (evaluate' env b)

可以在Data.Maybe库中找到Maybe-ed值的其他一些辅助函数,但是在这里,我们不需要它们。

module Semantics (evaluate) where

import Expr (Expr)
import Env (Env)
import Control.Monad (liftM, liftM2)

evaluate :: Expr -> Maybe Integer
evaluate = evaluate' []

evaluate' :: Env -> Expr -> Maybe Integer
evaluate' _   (Tall n) = Just n
evaluate' env (Var x) = lookup x env
evaluate' env (Sum a b) = liftM2 (+) (evaluate' env a) (evaluate' env b)
evaluate' env (Mult a b) = liftM2 (*) (evaluate' env a) (evaluate' env b)
evaluate' env (Neg a) = liftM negate (evaluate' env a)
evaluate' env (Let x a b) = evaluate' ((x, a) : env) b