Haskell 如何将Alex monadic lexer与Happy一起使用?
我正在尝试学习使用Alex+Happy构建解析器,特别是我对学习使用Alex的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包装器
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