AST的最佳ADT表示形式

时间:2018-07-16 14:26:06

标签: parsing haskell compiler-construction abstract-syntax-tree

对于要作为Haskell ADT表示的表达式,我有以下语法:

Expr = SimpleExpr [OPrelation SimpleExpr]  
SimpleExpr = [OPunary] Term {OPadd Term}  
Term = Factor {OPmult Factor}  

其中:

  

{}表示0或更多
    []表示0或1
    OPmult,OPadd,OPrelation,OPunary是运算符的类别

请注意,此语法确实具有优先权。

这是我尝试过的事情:

data Expr  = Expr SimpleExpr (Maybe OPrelation) (Maybe SimpleExpr)
data SimpleExpr = SimpleExpr (Maybe OPunary) Term [OPadd] [Term]
data Term = Term Factor [OPmult] [Factor]

在事后看来,我认为这很糟糕,尤其是 [OPadd] [Term] [OPmult] [Factor] 部分。因为,例如,在 1 + 2 + 3 的分析树中,它将 [+,+] 放在一个分支中,而将 [2,3] < / em>,表示它们已经解耦了。

什么是良好的表示形式,它将在以后的编译的下一阶段中很好地发挥作用?

  • 将{}和[]分解为更多数据类型似乎是一个过大的决定
  • 使用列表似乎不太正确,因为它将不再是一棵树(仅仅是一个列表的节点)
  • 也许是{}。好主意吗?

最后,我假设在解析之后,我必须通过解析树并将其缩减为AST?还是应该将整个语法修改为不太复杂?还是足够抽象?

3 个答案:

答案 0 :(得分:3)

AST不必与语法很接近。语法分为多个级别以对优先级进行编码,并使用重复来避免左递归,同时仍然能够正确处理左联想运算符。 AST不必担心这类事情。

相反,我将像这样定义AST:

    #inner function
    function n_two() use($varr) {
        global $varr;

        #Unable to get variable
        echo $varr;
        if($varr)
        {
            echo 'yessssss';
        }
    }

答案 1 :(得分:2)

这是一个可能对您有所帮助的附加答案。我不想破坏您的乐趣,所以这是一个非常简单的示例语法:

-- Expr = Term ['+' Term]
-- Term = Factor ['*' Factor]
-- Factor = number | '(' Expr ')'
-- number = one or more digits

使用CST

作为一种方法,我们可以将该语法表示为具体的语法树(CST):

data Expr = TermE Term | PlusE Term Term            deriving (Show)
data Term = FactorT Factor | TimesT Factor Factor   deriving (Show)
data Factor = NumberF Int | ParenF Expr             deriving (Show)

基于Parsec的解析器将具体语法转换为CST可能看起来像这样:

expr :: Parser Expr
expr = do
  t1 <- term
  (PlusE t1 <$ symbol "+" <*> term)
    <|> pure (TermE t1)

term :: Parser Term
term = do
  f1 <- factor
  (TimesT f1 <$ symbol "*" <*> factor)
    <|> pure (FactorT f1)

factor :: Parser Factor
factor = NumberF . read <$> lexeme (many1 (satisfy isDigit))
    <|> ParenF <$> between (symbol "(") (symbol ")") expr

具有用于空白处理的辅助功能:

lexeme :: Parser a -> Parser a
lexeme p = p <* spaces

symbol :: String -> Parser String
symbol = lexeme . string

和主要切入点:

parseExpr :: String -> Expr
parseExpr pgm = case parse (spaces *> expr) "(string)" pgm of
  Right e -> e
  Left err -> error $ show err

之后我们可以运行:

> parseExpr "1+1*(3+4)"
PlusE (FactorT (Number 1)) (TimesT (Number 1) (ParenF (PlusE
(FactorT (Number 3)) (FactorT (Number 4)))))
>

要将其转换为以下AST:

data AExpr -- Abstract Expression
  = NumberA Int
  | PlusA AExpr AExpr
  | TimesA AExpr AExpr

我们可以这样写:

aexpr :: Expr -> AExpr
aexpr (TermE t) = aterm t
aexpr (PlusE t1 t2) = PlusA (aterm t1) (aterm t2)

aterm :: Term -> AExpr
aterm (FactorT f) = afactor f
aterm (TimesT f1 f2) = TimesA (afactor f1) (afactor f2)

afactor :: Factor -> AExpr
afactor (NumberF n) = NumberA n
afactor (ParenF e) = aexpr e

要解释AST,我们可以使用:

interp :: AExpr -> Int
interp (NumberA n) = n
interp (PlusA e1 e2) = interp e1 + interp e2
interp (TimesA e1 e2) = interp e1 * interp e2

然后写:

calc :: String -> Int
calc = interp . aexpr . parseExpr

之后,我们有了一个简单的小计算器:

> calc "1 + 2 * (6 + 3)"
19
>

跳过CST

作为一种替代方法,我们可以将解析器替换为直接将 解析为类型AExpr的AST的解析器:

expr :: Parser AExpr
expr = do
  t1 <- term
  (PlusA t1 <$ symbol "+" <*> term)
    <|> pure t1

term :: Parser AExpr
term = do
  f1 <- factor
  (TimesA f1 <$ symbol "*" <*> factor)
    <|> pure f1

factor :: Parser AExpr
factor = NumberA . read <$> lexeme (many1 (satisfy isDigit))
    <|> between (symbol "(") (symbol ")") expr

您可以看到这些解析器的结构几乎没有变化。消失的只是在 type 级别的表达式,术语和因子之间的区别,以及TermEFactorTParenF这样的构造函数,它们的唯一目的是以便将这些类型相互嵌入。

在更复杂的情况下,CST和AST解析器可能表现出更大的差异。 (例如,在允许1 + 2 + 3的语法中,它可以表示为CST中的单个构造函数data Expr = ... | PlusE [Term] | ...,但在同一{{1 }}如上所述的AST类型。

重新定义PlusA以返回AExpr并从parseExpr放下AExpr步骤之后,其他所有内容都保持不变,我们仍然有:

aexpr

参考程序

这是使用中间CST的完整程序:

calc

这是更传统的解决方案的完整程序,它跳过了明确的CST表示形式:

> calc "1 + 2 * (6 + 3)"
19
>

答案 2 :(得分:0)

好的,Buhr's answer很好。这是我在sepp2k的响应启发下所做的(没有CST):

AST:

data OP = OPplus | OPminus | OPstar | OPdiv 
    | OPidiv | OPmod |  OPand | OPeq | OPneq 
    | OPless | OPgreater | OPle | OPge 
    | OPin | OPor

data Expr = 
      Relation Expr OP Expr -- > < == >= etc..
    | Unary OP Expr  -- + -
    | Mult Expr OP Expr  -- * / div mod and
    | Add Expr OP Expr  -- + - or
    | FactorInt Int | FactorReal Double 
    | FactorStr String 
    | FactorTrue | FactorFalse 
    | FactorNil 
    | FactorDesig Designator  -- identifiers
    | FactorNot Expr
    | FactorFuncCall FuncCall deriving (Show)

解析器:

parseExpr :: Parser Expr
parseExpr = (try $ Relation <$> 
        parseSimpleExpr <*> parseOPrelation <*> parseSimpleExpr)
    <|> parseSimpleExpr

parseSimpleExpr :: Parser Expr
parseSimpleExpr = (try simpleAdd)
    <|> (try $ Unary <$> parseOPunary <*> simpleAdd)
    <|> (try $ Unary <$> parseOPunary <*> parseSimpleExpr)
    <|> parseTerm
    where simpleAdd = Add <$> parseTerm <*> parseOPadd <*> parseSimpleExpr

parseTerm :: Parser Expr
parseTerm = (try $ Mult <$> 
        parseFactor <*> parseOPmult <*> parseTerm)
    <|> parseFactor

parseFactor :: Parser Expr
parseFactor = 
        (parseKWnot >> FactorNot <$> parseFactor)
    <|> (exactTok "true"  >> return FactorTrue)
    <|> (exactTok "false" >> return FactorFalse) 
    <|> (parseNumber)
    <|> (FactorStr <$> parseString)
    <|> (betweenCharTok '(' ')' parseExpr)
    <|> (FactorDesig <$> parseDesignator)
    <|> (FactorFuncCall <$> parseFuncCall)

我没有包括诸如parseOPadd之类的基本解析器,因为它们是您所期望的并且易于构建。

我仍然根据语法进行分析,但对其进行了一些微调以使其与我的AST匹配。

您可以查看完整的源代码,它是Pascal here的编译器。