【问题标题】:How to use an Alex monadic lexer with Happy?如何在 Happy 中使用 Alex monadic 词法分析器?
【发布时间】:2013-12-17 10:04:37
【问题描述】:

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

我已经在 SO 上查看过关于 Alex、Happy 和 monadic 词法分析器的不同问题(包括:Are there any tutorials on building a simple interpreter using 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 做。
  • 投反对票的人应该解释为什么他认为这是一个糟糕的问题。我相信我确实已经提供了所需的所有信息,包括 MWE 和一切。

标签: haskell monads happy alex


【解决方案1】:

据我所知,您的词法分析器的定义完全没有问题。假设那里没有错误,您需要修复的唯一问题是解析器的配置。第一件事是您使用的词法分析器是错误的。虽然该函数是 Alex 词法分析器的接口,但它具有类型

alexMonadScan :: Alex result

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

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

P 是我们正在使用的 monad。这就是说,当给予延续时,词法分析器应该为我们提供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}

(作为旁注,this 是您需要自己编写alexEOF = return Eof 的原因。您在alexEOF 中返回的数据构造函数需要与您识别的数据构造函数进行模式匹配以 Happy 作为文件的结尾。Alex 无法知道您要发出什么,Happy 也无法知道您选择通过 Alex 发出什么。)

现在下一个问题是您的 parseError 的类型不正确。当只使用 monad 时,这确实是您需要的类型,但是当您将词法分析器添加到组合中时,您的 parseError 必须具有不同的类型。另外,可能不建议使用 fail,所以这里有一个更好的定义:

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

最后,这里定义的 main 函数有点奇怪。我们想要调用解析器的方法是使用 runAlex 调用它。所以这里有一个快速的包装。传入的字符串就是你要解析的字符串。

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

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

我认为这就是一切。

【讨论】:

  • 抱歉,我会解决的。经过一段时间的工作,我得到了它。
  • 你知道如何使用 Happy 中的令牌错误来报告解析错误并继续解析,以显示文件中所有可能的解析错误吗?
  • 我真的没有。但是,[haskell.org/happy/doc/html/sec-monads.html] 有使用错误令牌的描述。在我看来,这是一种非常有限的恢复形式,因为您必须将其仅分配给一个非终端以避免冲突。您可以做的一件事是通过在结果类型中使用额外的标记自己处理错误。
  • 生成的解析器使用%lexer 指令中的EOF 标记作为传递给延续的标记的第一个模式。对于alexEOF,模式是一个匹配任何东西的变量,所以每个标记看起来都是EOF。相反,它应该设置为与自定义 alexEOF 函数返回的 EOF 令牌匹配的模式。
  • 建议“但是,为了安全起见,您应该改用 alexEOF。”是极度误导,它会导致编译代码中的静默错误,这将使解析器每次都失败。遵循此问题中的建议将使您的解析器每次在任何输入时都失败。有关详细信息,请参阅 this question,这使我发现了错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-24
  • 1970-01-01
  • 2015-12-30
  • 1970-01-01
相关资源
最近更新 更多