2010-06-11 5 views
14

Ich versuche, eine indentation-basierte Sprache (denke Python, Haskell selbst, Boo, YAML) in Haskell Parsec zu analysieren. Ich habe die IndentParser-Bibliothek gesehen, und es sieht so aus, als wäre es die perfekte Übereinstimmung, aber was ich nicht herausfinden kann, ist, wie ich meine TokenParser in einen Einrückungsparser machen kann. Hier ist der Code Ich habe bisher:Parsing Indentation-basierte Syntax in Haskell's Parsec

import qualified Text.ParserCombinators.Parsec.Token as T 
import qualified Text.ParserCombinators.Parsec.IndentParser.Token as IT 

lexer = T.makeTokenParser mylangDef 
ident = IT.identifier lexer 

Dies führt den Fehler:

parser2.hs:29:28: 
    Couldn't match expected type `IT.TokenParser st' 
      against inferred type `T.GenTokenParser s u m' 
    In the first argument of `IT.identifier', namely `lexer' 
    In the expression: IT.identifier lexer 
    In the definition of `ident': ident = IT.identifier lexer 

Was mache ich falsch? Wie soll ich ein IT.TokenParser erstellen? Oder ist IndentParser kaputt und sollte vermieden werden?

Antwort

12

Es sieht aus wie Sie verwenden Parsec 3 hier, während IndentParser erwartet Parsec 2. Ihr Beispiel für mich mit -package parsec-2.1.0.1 kompiliert.

Also IndentParser ist nicht unbedingt kaputt, aber der/die Autor (en) sollte/haben die Versionen in der Liste der Abhängigkeiten genauer spezifiziert. Es ist möglich, beide Versionen von Parsec zu installieren. Es gibt also keinen Grund, IndentParser nicht zu verwenden, es sei denn, Sie sind aus anderen Gründen zur Verwendung von Parsec 3 verpflichtet.


UPDATE: Eigentlich keine Änderungen an der Quelle sind notwendig IdentParser Arbeit mit Parsec 3. Das Problem, das wir beide zu bekommen hatten scheint durch die Tatsache verursacht werden, dass cabal-install eine "soft preference" für Parsec hat 2 . Sie können einfach IndentParser mit einer expliziten Einschränkung für die Parsec Version neu zu installieren:

cabal install IndentParser --reinstall --constraint="parsec >= 3" 

Alternativ können Sie die source und build and install in the normal way herunterladen.

+0

Sie, Herr, sind fantastisch! Vielen Dank! Woher wusstest du, dass ich Parsec 3 benutzt habe? Eine Annahme? Weil ich denke, mein Beispiel könnte entweder ... – pavpanchekha

+1

Ich fürchte, dass meine Detektivarbeit hier nicht wirklich sehr aufregend war: Ich habe Ihren Code mit Parsec 3 kompiliert, einen Fehler ähnlich dem Ihren bekommen und dann Parsec 2 ausprobiert, was hat funktioniert. Übrigens scheint es nicht zu schwer zu sein, IndentParser mit Parsec 3 arbeiten zu lassen; Sie können es in Betracht ziehen, wenn Sie IndentParser nützlich finden. –

+0

Ich könnte, aber im Moment lerne ich gerade erst Haskell; Ich fürchte, ich würde mich in einer fremden Codebase so verlieren. – pavpanchekha

6

Hier ist eine Reihe von Parser-Kombinatoren für Parsec 3, die für Haskell-Stil-Layout verwendet werden können, die für Sie von Nutzen sein könnten. Die wichtigsten Überlegungen sind, dass laidout startet und eine Layoutregel ausführt, und dass Sie die space und spaced Kombinatoren anstelle des Parsec Kombinator für den gleichen Zweck verwenden sollten. Aufgrund der Interaktion von Layout und Kommentaren musste ich die Kommentaranalyse in den Tokenizer einbinden.

{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 
module Text.Parsec.Layout 
    (laidout   -- repeat a parser in layout, separated by (virtual) semicolons 
    , space   -- consumes one or more spaces, comments, and onside newlines in a layout rule 
    , maybeFollowedBy 
    , spaced   -- (`maybeFollowedBy` space) 
    , LayoutEnv  -- type needed to describe parsers 
    , defaultLayoutEnv -- a fresh layout 
    , semi    -- semicolon or virtual semicolon 
    ) where 

import Control.Applicative ((<$>)) 
import Control.Monad (guard) 

import Data.Char (isSpace) 

import Text.Parsec.Combinator 
import Text.Parsec.Pos 
import Text.Parsec.Prim hiding (State) 
import Text.Parsec.Char hiding (space) 

data LayoutContext = NoLayout | Layout Int deriving (Eq,Ord,Show) 

data LayoutEnv = Env 
    { envLayout :: [LayoutContext] 
    , envBol :: Bool -- if true, must run offside calculation 
    } 

defaultLayoutEnv :: LayoutEnv 
defaultLayoutEnv = Env [] True 

pushContext :: Stream s m c => LayoutContext -> ParsecT s LayoutEnv m() 
pushContext ctx = modifyState $ \env -> env { envLayout = ctx:envLayout env } 

popContext :: Stream s m c => String -> ParsecT s LayoutEnv m() 
popContext loc = do 
    (_:xs) <- envLayout <$> getState 
    modifyState $ \env' -> env' { envLayout = xs } 
    <|> unexpected ("empty context for " ++ loc) 

getIndentation :: Stream s m c => ParsecT s LayoutEnv m Int 
getIndentation = depth . envLayout <$> getState where 
    depth :: [LayoutContext] -> Int 
    depth (Layout n:_) = n 
    depth _ = 0 

pushCurrentContext :: Stream s m c => ParsecT s LayoutEnv m() 
pushCurrentContext = do 
    indent <- getIndentation 
    col <- sourceColumn <$> getPosition 
    pushContext . Layout $ max (indent+1) col 

maybeFollowedBy :: Stream s m c => ParsecT s u m a -> ParsecT s u m b -> ParsecT s u m a 
t `maybeFollowedBy` x = do t' <- t; optional x; return t' 

spaced :: Stream s m Char => ParsecT s LayoutEnv m a -> ParsecT s LayoutEnv m a 
spaced t = t `maybeFollowedBy` space 

data Layout = VSemi | VBrace | Other Char deriving (Eq,Ord,Show) 

-- TODO: Parse C-style #line pragmas out here 
layout :: Stream s m Char => ParsecT s LayoutEnv m Layout 
layout = try $ do 
    bol <- envBol <$> getState 
    whitespace False (cont bol) 
    where 
    cont :: Stream s m Char => Bool -> Bool -> ParsecT s LayoutEnv m Layout 
    cont True = offside 
    cont False = onside 

    -- TODO: Parse nestable {-# LINE ... #-} pragmas in here 
    whitespace :: Stream s m Char => 
     Bool -> (Bool -> ParsecT s LayoutEnv m Layout) -> ParsecT s LayoutEnv m Layout 
    whitespace x k = 
      try (string "{-" >> nested k >>= whitespace True) 
     <|> try comment 
     <|> do newline; whitespace True offside 
     <|> do tab; whitespace True k 
     <|> do (satisfy isSpace <?> "space"); whitespace True k 
     <|> k x 

    comment :: Stream s m Char => ParsecT s LayoutEnv m Layout 
    comment = do 
     string "--" 
     many (satisfy ('\n'/=)) 
     newline 
     whitespace True offside 

    nested :: Stream s m Char => 
     (Bool -> ParsecT s LayoutEnv m Layout) -> 
     ParsecT s LayoutEnv m (Bool -> ParsecT s LayoutEnv m Layout) 
    nested k = 
      try (do string "-}"; return k) 
     <|> try (do string "{-"; k' <- nested k; nested k') 
     <|> do newline; nested offside 
     <|> do anyChar; nested k 

    offside :: Stream s m Char => Bool -> ParsecT s LayoutEnv m Layout 
    offside x = do 
     p <- getPosition 
     pos <- compare (sourceColumn p) <$> getIndentation 
     case pos of 
      LT -> do 
       popContext "the offside rule" 
       modifyState $ \env -> env { envBol = True } 
       return VBrace 
      EQ -> return VSemi 
      GT -> onside x 

    -- we remained onside. 
    -- If we skipped any comments, or moved to a new line and stayed onside, we return a single a ' ', 
    -- otherwise we provide the next char 
    onside :: Stream s m Char => Bool -> ParsecT s LayoutEnv m Layout 
    onside True = return $ Other ' ' 
    onside False = do 
     modifyState $ \env -> env { envBol = False } 
     Other <$> anyChar 

layoutSatisfies :: Stream s m Char => (Layout -> Bool) -> ParsecT s LayoutEnv m() 
layoutSatisfies p = guard . p =<< layout 

virtual_lbrace :: Stream s m Char => ParsecT s LayoutEnv m() 
virtual_lbrace = pushCurrentContext 

virtual_rbrace :: Stream s m Char => ParsecT s LayoutEnv m() 
virtual_rbrace = try (layoutSatisfies (VBrace ==) <?> "outdent") 

-- recognize a run of one or more spaces including onside carriage returns in layout 
space :: Stream s m Char => ParsecT s LayoutEnv m String 
space = do 
    try $ layoutSatisfies (Other ' ' ==) 
    return " " 
    <?> "space" 

-- recognize a semicolon including a virtual semicolon in layout 
semi :: Stream s m Char => ParsecT s LayoutEnv m String 
semi = do 
    try $ layoutSatisfies p 
    return ";" 
    <?> "semi-colon" 
    where 
     p VSemi = True 
     p (Other ';') = True 
     p _ = False 

lbrace :: Stream s m Char => ParsecT s LayoutEnv m String 
lbrace = do 
    char '{' 
    pushContext NoLayout 
    return "{" 

rbrace :: Stream s m Char => ParsecT s LayoutEnv m String 
rbrace = do 
    char '}' 
    popContext "a right brace" 
    return "}" 

laidout :: Stream s m Char => ParsecT s LayoutEnv m a -> ParsecT s LayoutEnv m [a] 
laidout p = try (braced statements) <|> vbraced statements where 
    braced = between (spaced lbrace) (spaced rbrace) 
    vbraced = between (spaced virtual_lbrace) (spaced virtual_rbrace) 
    statements = p `sepBy` spaced semi 
+0

Können Sie ein Beispiel für die Verwendung geben? Ich habe es versucht [so] (https://gist.github.com/gergoerdi/af1829b18ea80e21ba79728a5d271cd9), aber ich kann 'bindings' nicht bekommen, um etwas zu akzeptieren, das so einfach ist, wie' unlines ["x = y", "a = b "]'. – Cactus

+1

Ich denke derzeit, dass die obige Quelle kaputt ist, aber ich hatte keine Chance, sie noch einmal zu besuchen. –