2012-06-12 6 views
2

들여 쓰기 기반 문법에 대한 렉서를 쓰려고하는데 들여 쓰기에 문제가 있습니다.하스켈 알렉스 - 정규식이 잘못된 문자열과 일치합니까?

여기 내 코드입니다 :

{ 
module Lexer (main) where 

import System.IO.Unsafe 
} 


%wrapper "monadUserState" 

$whitespace = [\ \t\b] 
$digit  = 0-9           -- digits 
$alpha  = [A-Za-z] 
$letter  = [a-zA-Z]          -- alphabetic characters 
$ident  = [$letter $digit _]        -- identifier character 
$indent  = [\ \t] 

@number  = [$digit]+ 
@identifier = $alpha($alpha|_|$digit)* 

error:- 

@identifier { mkL LVarId } 

\n $whitespace* \n { skip } 
\n $whitespace* { setIndent } 
$whitespace+  { skip } 

{ 

data Lexeme = Lexeme AlexPosn LexemeClass (Maybe String) 

instance Show Lexeme where 
    show (Lexeme _ LEOF _) = " Lexeme EOF" 
    show (Lexeme p cl mbs) = " Lexeme class=" ++ show cl ++ showap p ++ showst mbs 
     where 
     showap pp = " posn=" ++ showPosn pp 
     showst Nothing = "" 
     showst (Just s) = " string=" ++ show s 

instance Eq Lexeme where 
    (Lexeme _ cls1 _) == (Lexeme _ cls2 _) = cls1 == cls2 

showPosn :: AlexPosn -> String 
showPosn (AlexPn _ line col) = show line ++ ':': show col 

tokPosn :: Lexeme -> AlexPosn 
tokPosn (Lexeme p _ _) = p 

data LexemeClass 
    = LVarId 
    | LTIndent Int 
    | LTDedent Int 
    | LIndent 
    | LDedent 
    | LEOF 
    deriving (Show, Eq) 

mkL :: LexemeClass -> AlexInput -> Int -> Alex Lexeme 
mkL c (p, _, _, str) len = return (Lexeme p c (Just (take len str))) 

data AlexUserState = AlexUserState { indent :: Int } 

alexInitUserState :: AlexUserState 
alexInitUserState = AlexUserState 0 

type Action = AlexInput -> Int -> Alex Lexeme 

getLexerIndentLevel :: Alex Int 
getLexerIndentLevel = Alex $ \[email protected]{alex_ust=ust} -> Right (s, indent ust) 

setLexerIndentLevel :: Int -> Alex() 
setLexerIndentLevel i = Alex $ \[email protected]{alex_ust=ust} -> Right (s{alex_ust=(AlexUserState i)},()) 

setIndent :: Action 
setIndent [email protected](p, _, _, str) i = do 
    --let !x = unsafePerformIO $ putStrLn $ "|matched string: " ++ str ++ "|" 
    lastIndent <- getLexerIndentLevel 
    currIndent <- countIndent (drop 1 str) 0 -- first char is always \n 
    if (lastIndent < currIndent) then 
     do setLexerIndentLevel currIndent 
      mkL (LTIndent (currIndent - lastIndent)) input i 
    else if (lastIndent > currIndent) then 
     do setLexerIndentLevel currIndent 
      mkL (LTDedent (lastIndent - currIndent)) input i 
    else alexMonadScan 
    where 
    countIndent str total 
     | take 1 str == "\t" = do skip input 1 
            countIndent (drop 1 str) (total+1) 
     | take 4 str == " " = do skip input 4 
            countIndent (drop 4 str) (total+1) 
     | otherwise = return total 

alexEOF :: Alex Lexeme 
alexEOF = return (Lexeme undefined LEOF Nothing) 

scanner :: String -> Either String [Lexeme] 
scanner str = 
    let loop = do 
     [email protected](Lexeme _ cl _) <- alexMonadScan 
     if (cl == LEOF) 
      then return [tok] 
      else do toks <- loop 
        return (tok:toks) 
    in runAlex str loop 

addIndentations :: [Lexeme] -> [Lexeme] 
addIndentations ([email protected](Lexeme pos (LTIndent c) _):ls) = 
    concat [iter lex c, addIndentations ls] 
    where iter lex c = if c == 0 then [] 
        else (Lexeme pos LIndent Nothing):(iter lex (c-1)) 
addIndentations ([email protected](Lexeme pos (LTDedent c) _):ls) = 
    concat [iter lex c, addIndentations ls] 
    where iter lex c = if c == 0 then [] 
        else (Lexeme pos LDedent Nothing):(iter lex (c-1)) 
addIndentations (l:ls) = l:(addIndentations ls) 
addIndentations [] = [] 


main = do 
    s <- getContents 
    return() 
    print $ fmap addIndentations (scanner s) 

} 

문제는 라인 \n $whitespace* { setIndent }에서, 정규식이 잘못 문자열을 일치하고이 잘못된 문자열 setIndent를 호출합니다.

begin  
     first indent 
|matched string: 
     first indent 
       second indent 
       second indent 
dedent 
dedent 
| 
|matched string: 
       second indent 
dedent 
| 
|matched string: 
dedent 
| 
|matched string: 
| 
Right [ Lexeme class=LVarId posn=1:1 string="begin", Lexeme class=LIndent posn=1:6, Lexeme class=LVarId posn=2:15 string="indent", Lexeme class=LIndent posn=2:21, Lexeme class=LDedent posn=3:30, Lexeme class=LDedent posn=3:30, Lexeme class=LVarId posn=4:1 string="dedent", Lexeme EOF] 

그래서 setIndent 단지 공백보다 더 불려 : 디버깅 목적을 위해, 나는 여기에 프로그램의 실행 예이야, setIndent 기능에 unsafePerformIO을 추가했습니다. 들여 쓰기를위한 어휘를 반환 한 후에 문자열의 다른 부분은 생략됩니다.

Alex의 버그입니까? 아니면 내가 뭘 잘못 했니?

답변

1

그래서 나는 자세히 코드를 분석하지 않은,하지만 난이 알아 차렸다 : str 입력의 나머지 부분뿐만 아니라 현재 토큰

setIndent :: Action 
setIndent [email protected](p, _, _, str) i = do 
    --let !x = unsafePerformIO $ putStrLn $ "|matched string: " ++ str ++ "|" 

하는 것으로. 현재 토큰을 얻으려면 take i str이 필요합니다. 아마도 이것은 토큰이 실제보다 많은 입력과 일치한다는 인상을줍니다.

물론 GHC 자체의 렉서에서 들여 쓰기를 처리하기 때문에 look there for ideas을 원할 수도 있습니다 (다소 복잡하고 복잡 할 수도 있음).

+0

답장을 보내 주셔서 감사합니다. 코드를 변경 했으므로 이제는 'str take str'을 사용하고 있지만 아무 것도 변경되지 않았습니다. 'setIndent'가 들여 쓰기 토큰을 반환 한 후에, Alex의 action 함수는 입력 문자열의 다음 부분이 무엇인지 결정하지 않기 때문에,'setIndent'와 관련이 없다고 생각합니다. 함수) – sinan

+0

글쎄, 당신의 정규식은 0 개 이상의 공백 문자가 뒤 따르는 개행 문자와 일치하며 디버그 출력에서 ​​일어나는 것과 똑같다고 말합니다. –

+0

공백 문자와 일치하지만 공백 문자 다음의 추가 문자와 일치하며이 문자는 개행 후에 공백 문자를 맺는 정규식 다음에 정규식과 일치하지 않습니다. 실제로 코드를 실행하고 들여 쓰기 된 줄을 입력하면 (들여 쓰기가 탭 문자의 4 공백 임) 들여 쓰기 토큰 뒤에 토큰이 어떻게 든 생략되는 것을 볼 수 있습니다. – sinan