Haskell 如何将Alex monadic lexer与Happy一起使用?

Haskell 如何将Alex monadic lexer与Happy一起使用?,haskell,monads,happy,alex,Haskell,Monads,Happy,Alex,我正在尝试学习使用Alex+Happy构建解析器,特别是我对学习使用Alex的monad包装器感兴趣。我已经看过了Alex和我的文档,但对我来说,他们两个都缺乏关于一起使用它们的有用信息。我设法使它们与basic和posn包装器一起工作,但我对monad感到不知所措 我已经研究了关于Alex、Happy和monadic lexers的不同问题(包括:但没有一个能够提供使用monad的简单示例) 大多数在线代码使用Happy with自定义lexer函数,或者使用basic或posnAlex包装器

我正在尝试学习使用Alex+Happy构建解析器,特别是我对学习使用Alex的
monad
包装器感兴趣。我已经看过了Alex和我的文档,但对我来说,他们两个都缺乏关于一起使用它们的有用信息。我设法使它们与
basic
posn
包装器一起工作,但我对
monad
感到不知所措

我已经研究了关于Alex、Happy和monadic lexers的不同问题(包括:但没有一个能够提供使用
monad
的简单示例)

大多数在线代码使用Happy with自定义lexer函数,或者使用
basic
posn
Alex包装器

下面是一个用于类似ini语法的简单lexer:

{
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)


}
下面是相对快乐的解析器:

{
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

}
如何修改解析器以使用alexMonadScan? 文档一点也不清楚,尽量不使用任何澄清的示例(或者从我的角度来看,提供的示例在澄清方面失败)


如果需要,我可以发布我的
posn
版本的这个lexer+解析器。

据我所知,你的lexer的定义是完全正确的。假设没有bug,你需要解决的唯一问题就是解析器的配置。首先,你使用的lexer是错误的。而这个函数是接口到Alex lexer,它具有

alexMonadScan :: Alex result
但是lexer Happy Wastes属于这种类型

lexer :: (Token -> P a) -> P a
其中,
p
是我们正在使用的单子。这意味着lexer应该为我们提供一个
Alex a
,当提供一个延续时。我们需要一个简单的包装器:

lexwrap :: (Token -> Alex a) -> Alex a
lexwrap cont = do
    token <- alexMonadScan
    cont token
其次,在
%lexer
指令中使用
alexEOF
会导致解析器在每次输入时都失败。您在那里提供的名称会插入到生成的代码中case语句的分支中,因此您必须使用数据构造函数的名称,而不是值——特别是,您需要使用Alex创建的数据构造函数我将发出EOF信号

这使得解析器中的lexer行略有不同

%lexer {lexwrap} {Eof}
(作为旁注,这就是您需要自己编写
alexEOF=return Eof
的原因。您在
alexEOF
中返回的数据构造函数需要与您标识为Happy作为文件结尾的数据构造函数进行模式匹配。Alex无法知道您想要发出什么,Happy也无法知道您选择通过Alex发射的内容。)

现在,下一个问题是parseError的类型不正确。如果只使用monad,这确实是您需要的类型,但是当您在混合中添加lexer时,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的类型由parseInFile的定义决定。在这里,它是一个
Alex[Section]
,因此返回一个
字符串[Section]


我想这就是一切。

上次我试过这个(几年前!),monad包装器的文档是完全错误的,而且似乎仍然是错误的。我不记得我到底要做什么才能使它工作,但你最好还是手动生成包装器代码,例如
language-c
haskell src exts
都是这样做的。投票人应该解释为什么他认为这是错误的这是个糟糕的问题。我相信我已经提供了所需的所有信息,包括MWE和所有内容。对不起,我会解决这个问题。我在使用它一段时间后才开始工作。你知道如何使用令牌错误来报告它一个解析错误并继续解析,以显示文件中所有可能的解析错误吗?我诚实地说不要。但是,[有使用错误标记的描述。在我看来,这是一种非常有限的恢复形式,因为您必须将其仅分配给一个非终端以避免冲突。您可以做的一件事是通过在结果类型中使用额外的标记来处理错误。生成的解析器在
%le中使用EOF标记xer
指令作为传递给延续的令牌上的第一个模式。对于
alexEOF
,该模式是一个匹配任何内容的变量,因此每个令牌似乎都是EOF。相反,它应该设置为与自定义
alexEOF
函数返回的EOF令牌匹配的模式。建议“但是,为了安全起见,我认为您应该使用alexEOF。”这是一个极其误导性的错误,它会在编译代码中导致一个无声错误,这将使解析器每次都失败。遵循此问题中的建议将使您的解析器每次都在任何输入上失败。有关更多信息,请参阅,这使我发现了错误。
parseError :: Token -> Alex a
parseError _ = alexError "Why is using happy and alex so hard"
parse :: String -> Either String [Section]
parse s = runAlex s parseIniFile