对于要作为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?还是应该将整个语法修改为不太复杂?还是足够抽象?
答案 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):
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
>
作为一种替代方法,我们可以将解析器替换为直接将 解析为类型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 级别的表达式,术语和因子之间的区别,以及TermE
,FactorT
和ParenF
这样的构造函数,它们的唯一目的是以便将这些类型相互嵌入。
在更复杂的情况下,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的编译器。