我正在尝试学习使用Alex + Happy来构建解析器,特别是我有兴趣学习使用Alex的monad
包装器。我已经查看了Alex和Happy的文档,但是对于我来说,它们都是真的缺少有关将它们一起使用的任何有用信息。我设法让它们与basic
和posn
包装器一起工作,但我对monad
感到茫然。
关于亚历克斯,快乐和monadic lexers(包括:Are there any tutorials on building a simple interpreter using Alex + Happy?),我已经看过关于SO的不同问题,但没有一个能够提供使用monad
的简单示例。
大多数在线代码使用Happy来定制词法分析器函数,或者使用basic
或posn
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)
}
这是相对快乐的解析器:
{
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
[...]
如何修改解析器以使用alexMonadScan
?
Happy文档根本不清楚,并且努力不使用任何澄清示例(或者从我的观点来看,提供的示例无法澄清)。
如果需要,我可以发布我同一个lexer +解析器的posn
版本。
答案 0 :(得分:11)
据我所知,你的词法分析器的定义完全没问题。假设那里没有bug,你需要解决的唯一问题就是解析器的配置。首先,你使用的词法分析器是错误的。虽然该函数是Alex词法分析器的接口,但它的类型为
alexMonadScan :: Alex result
但乐天堂的快乐想要的是
型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 >>=)
其次,在alexEOF
指令中使用%lexer
会导致解析器在每次输入时失败。您在那里提供的名称将插入到生成代码中的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"
最后,主要功能在这里有点奇怪。我们想要调用解析器的方法是使用runAlex调用它。所以这里有一个快速包装器。传入的字符串是您要解析的字符串。
parse :: String -> Either String [Section]
parse s = runAlex s parseIniFile
函数解析的类型由parseIniFile的定义决定。在这里,它是Alex [Section]
,因此返回Either String [Section]
。
我认为这就是一切。