如何在Happy中使用Alex单子词分析器?

13
我正在尝试使用Alex + Happy构建解析器,特别是我有兴趣学习如何使用Alex的monad包装器。我已经查看了Alex和Happy的文档,但它们对于我来说都缺乏有用的关于如何将它们一起使用的信息。我已经成功地将它们与basicposn包装器一起使用,但是对于monad我感到不知所措。
我已经查看了关于Alex、Happy和单子词法分析器的不同问题(包括:是否有任何使用Alex + Happy构建简单解释器的教程?),但没有一个能够提供一个简单的例子来使用monad
大多数在线代码使用Happy与自定义词法分析器函数,或者使用basicposn Alex包装器。
这里是一个类似ini语法的简单词法分析器:
{
module IniLexer where
}

%wrapper "monad"



$spaces = [\ \t]
$alpha = [a-zA-Z]
$digits = [0-9]
$alnum = [$alpha$digits]


@identifier = $alpha $alnum*

@comment = \#.*

@integer = $digits+

@boolean = (true) | (false)

@string = \"[^\"]*\"


:-

@integer    { mkL LInteger }
@boolean    { mkL LBoolean }
@string     { mkL LString }

@identifier  { mkL LIdentifier }

\[@identifier\] { mkL LSection }

=           { mkL LAssign }

\;          { mkL LEndAssign }
@comment    ;
[\ \t \n]+  ;


{

data LexemeClass = LInteger | LBoolean | LString | LIdentifier | LSection | LAssign | LEndAssign | LEOF
    deriving (Eq, Show)


mkL :: LexemeClass -> AlexInput -> Int -> Alex Token
mkL c (p, _, _, str) len = let t = take len str
                           in case c of
                                LInteger -> return (IntegerNum ((read t) :: Integer) p)
                                LBoolean -> return (BooleanVal (if t == "true"
                                                                   then True
                                                                   else False
                                                               ) p)
                                LString -> return (StringTxt (take (length t - 2) (drop 1 t)) p)
                                LIdentifier -> return (Identifier t p)
                                LSection -> return (SectionHeader (take (length t - 2) (drop 1 t)) p)
                                LAssign -> return (Assignment p)
                                LEndAssign -> return (EndAssignment p)


-- No idea why I have to write this myself. Documentation doesn't mention it.
alexEOF :: Alex Token
alexEOF = return Eof



data Token = SectionHeader {identifier :: String, position :: AlexPosn} |
             Identifier {name :: String, position :: AlexPosn}          |
             Assignment {position :: AlexPosn}                          |
             EndAssignment {position :: AlexPosn}                       |
             IntegerNum {value :: Integer, position :: AlexPosn}        |
             BooleanVal {istrue :: Bool, position :: AlexPosn}          |
             StringTxt  {text :: String, position :: AlexPosn}          |
             Eof
    deriving (Eq, Show)


}

这里是相对路径的 Happy 解析器:

{
module Main where

import IniLexer

}



%name parseIniFile
%error {parseError}
%lexer  {alexMonadScan} {AlexEOF}
%monad {Alex}
%tokentype {Token}
%token
    SECTION     {SectionHeader name _ }
    IDENT       {Identifier name _ }
    '='         {Assignment _ }
    INT         {IntegerNum value _ }
    BOOL        {BooleanVal istrue _ }
    STRING      {StringTxt text _ }
    ';'         {EndAssignment _ }


%%


ConfigFile : SequenceOfSections                    {reverse $1}

SequenceOfSections : {- empty -}                   {   []  }
                   | SequenceOfSections Section    {$2 : $1}


Section : SECTION SectionBody                      {Section (identifier $1) (reverse $2)}


SectionBody : {- empty -}        {[]}
            | SectionBody AssignmentLine ';' {$2 : $1}


AssignmentLine : IDENT '=' Value      {(name $1, $3)}

Value : INT         {IntV (value $1)}
      | BOOL        {BoolV (istrue $1)}
      | STRING      {StringV (text $1)}


{

data Value = IntV Integer | BoolV Bool | StringV String
    deriving (Eq, Show)

data Section = Section String [(String, Value)]
    deriving (Eq, Show)

data IniFile = IniFile [Section]
    deriving (Eq, Show)


parseError :: [Token] -> Alex a
parseError t = fail "a"

main = do
    s <- getContents
    print $ parseIniFile $ runAlex s alexMonadScan

}

这会引发很多编译器错误:

[...]
Couldn't match expected type `(AlexReturn t1 -> Alex a0) -> t0'
                with actual type `Alex Token'
    The function `alexMonadScan' is applied to one argument,
    but its type `Alex Token' has none
[...]

我应该如何修改解析器以使用alexMonadScanHappy文档并不清晰,而且试图使用任何澄清的例子(或提供的示例无法从我的角度进行澄清)。

如果需要,我可以发布我的posn版本的相同词法分析器+解析器。


上一次我尝试这个(几年前!),monad包装器的文档完全是错误的,而且似乎现在仍然是错误的。我不记得我当时为了让它工作所做的确切操作,但你最好手动生成包装器代码,例如language-chaskell-src-exts - John L
下投票者应该解释为什么他认为这是一个糟糕的问题。我相信我已经提供了所有必要的信息,包括一个MWE等等。 - Bakuriu
1个回答

17

就我所知,您的词法分析器定义完全没有问题。假设没有错误,您需要解决的唯一问题是解析器的配置。首先,您使用的词法分析器是错误的。虽然该函数是与Alex词法分析器交互的接口,但它的类型却是

alexMonadScan :: Alex result

但是Happy想要的词法分析器类型是

lexer :: (Token -> P a) -> P a

当我们使用单子P时,这意味着在给定 continuation 后,词法分析器应该提供给我们一个 Alex a。在这里,我们只需要一个简单的包装器:

lexwrap :: (Token -> Alex a) -> Alex a
lexwrap cont = do
    token <- alexMonadScan
    cont token

或等效地

lexwrap = (alexMonadScan >>=)

其次,在%lexer指令中使用alexEOF会导致解析器在每个输入上失败。您在那里提供的名称被插入到生成代码中的一个case语句分支中,因此您必须使用数据构造函数的名称而不是值 - 特别地,您需要使用Alex将发出的表示EOF的数据构造函数。

这使得我们在解析器中的词法分析器行有点不同。

%lexer {lexwrap} {Eof}

顺便提一句,这就是你需要亲自编写alexEOF = return Eof的原因。在alexEOF中返回的数据构造函数需要与你在Happy中标识的结束文件的数据构造函数进行模式匹配。Alex不知道你想要发出什么信号,而Happy也不知道你选择通过Alex发出了什么信号。

现在的问题是,parseError的类型不正确。当仅使用单个单子时,确实需要该类型,但当加入词法分析器时,parseError必须具有不同的类型。此外,建议不要使用fail函数,以下是稍微好一点的定义:

parseError :: Token -> Alex a
parseError _ = alexError "Why is using happy and alex so hard"

最后,这里定义的主函数有点奇怪。我们想要调用解析器的方法是使用runAlex。因此,这里有一个快速的包装器。传入的字符串是您希望解析的字符串。

parse :: String -> Either String [Section]
parse s = runAlex s parseIniFile

函数parse的类型由parseIniFile的定义确定。在这里,它是一个Alex [Section],因此返回一个Either String [Section]

我想那就是全部内容了。


抱歉,我会修复它的。经过一段时间的努力,我已经让它正常工作了。 - Mezuzza
你知道如何在Happy中使用token error报告解析错误并继续解析,以显示文件中所有可能的解析错误吗? - chamini2
生成的解析器在 %lexer 指令中使用 EOF 标记作为传递给 continuation 的标记中的第一个模式。使用 alexEOF,该模式是匹配任何内容的变量,因此每个标记都被视为 EOF。相反,它应该设置为与自定义 alexEOF 函数返回的 EOF 标记匹配的模式。 - pat
2
“然而,我认为为了安全起见,你应该使用alexEOF。”这个建议是极其误导性的,它会在编译后的代码中引起静默错误,使解析器每次都失败。遵循这个问题中的建议将使您的解析器每次都失败,在任何输入上。有关更多信息,请参见此问题,这让我发现了这个错误。 - Patrick Collins
很棒的答案。我很快就让我的Happy单子解析器和Alex单子词法分析器工作了。 - Jeff Sharp
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接