2010-11-20 20 views
4

Je ne connais pas à la fois Haskell et Parsec. Dans un effort pour en savoir plus sur la langue et cette bibliothèque en particulier, j'essaye de créer un analyseur qui peut analyser les fichiers variables sauvegardés par Lua. Dans ces variables fichiers peuvent prendre les formes suivantes:Problèmes liés à l'opérateur Parsec <|> de Haskell

varname = valeur

varname = {valeur, valeur, ...}

varname = {{valeur, valeur}, {valeur, valeur, ...}}

J'ai créé des analyseurs pour chacun de ces types mais quand je les enchaîne avec l'opérateur < |> choisi, j'obtiens une erreur de type.

Couldn't match expected type `[Char]' against inferred type `Char' 
    Expected type: GenParser Char st [[[Char]]] 
    Inferred type: GenParser Char st [[Char]] 
In the first argument of `try', namely `lList' 
In the first argument of `(<|>)', namely `try lList' 

Mon hypothèse est (bien que je ne le trouve pas dans la documentation) que chaque analyseur transmis à l'opérateur de choix doit retourner le même type. Voici le code en question:

data Variable = LuaString ([Char], [Char]) 
      | LuaList ([Char], [[Char]]) 
      | NestedLuaList ([Char], [[[Char]]]) 
      deriving (Show) 

main:: IO() 
main = do 
     case (parse varName "" "variable = {{1234,\"Josh\"},{123,222}}") of 
      Left err -> print err 
      Right xs -> print xs 

varName :: GenParser Char st Variable 
varName = do{ 
     vName <- (many letter); 
     eq <- string " = "; 
     vCon <- try nestList 
      <|> try lList 
      <|> varContent; 
     return (vName, vCon)} 

varContent :: GenParser Char st [Char] 
varContent = quotedString 
    <|> many1 letter 
    <|> many1 digit 

quotedString :: GenParser Char st [Char] 
quotedString = do{ 
     s1 <- string "\""; 
     s2 <- varContent; 
     s3 <- string "\""; 
     return (s1++s2++s3)} 

lList :: GenParser Char st [[Char]] 
lList = between (string "{") (string "}") (sepBy varContent (string ",")) 

nestList :: GenParser Char st [[[Char]]] 
nestList = between (string "{") (string "}") (sepBy lList (string ",")) 

Répondre

7

C'est exact.

(<|>) :: (Alternative f) => f a -> f a -> f a 

Notez que les deux arguments sont exactement du même type. Je ne comprends pas exactement le type de données Variable. C'est ainsi que je le ferais:

data LuaValue = LuaString String | LuaList [LuaValue] 
data Binding = Binding String LuaValue 

Cela permet à des valeurs arbitrairement imbriquées, non seulement imbriqué deux niveaux de profondeur comme le vôtre a. Puis écrire:

luaValue :: GenParser Char st LuaValue 
luaValue = (LuaString <$> identifier) 
     <|> (LuaList <$> between (string "{") (string "}") (sepBy (string ",") luaValue)) 

C'est l'analyseur pour luaValue. Ensuite, vous avez juste besoin d'écrire:

binding :: GenParser Char st Binding 
content :: GenParser Char st [Binding] 

Et vous l'aurez. L'utilisation d'un type de données représentant précisément ce qui est possible est important.

+0

Le type de données Variable était destiné à encapsuler le nom de la variable et le contenu de la variable dans un tuple. Je peux voir à partir de la vôtre et des réponses de Martijn que ce n'est pas le meilleur plan d'action parce que j'ai besoin d'une couche supplémentaire d'abstraction. J'ai une question à propos de votre réponse: À quoi servent les opérateurs figurant dans votre code? (Je pensais qu'ils étaient juste pour les messages d'erreur). – GraemeFore

+0

Je n'ai pas utilisé ''. Peut-être que vous voulez dire '<$>'. C'est juste un raccourci infixe pour 'fmap', donc' LuaString <$> identifier' signifie "analyser un identifiant, puis encapsuler le résultat avec la fonction' LuaString' ". – luqui

+0

Yikes! Exemple parfait de pourquoi vous ne devriez pas poser de questions pendant une nuit blanche. Merci encore. – GraemeFore

3

En effet, parseurs transmis à l'opérateur de choix doit avoir des types égaux. Vous pouvez dire par le type de l'opérateur de choix:

(<|>) :: GenParser tok st a -> GenParser tok st a -> GenParser tok st a 

Cela dit qu'il se fera un plaisir de combiner deux parseurs aussi longtemps que leurs types de jeton, les états et les types de résultats sont les mêmes.

Alors, comment pouvons-nous nous assurer que les analyseurs que vous essayez de combiner ont le même type de résultat? Eh bien, vous avez déjà un type de données Variable qui capture les différentes formes de variables qui peuvent apparaître dans Lua, donc ce que nous devons faire est de ne pas retourner String, [String] ou [[String]] mais juste Variable s. Mais quand nous essayons cela, nous rencontrons un problème. Nous ne pouvons pas laisser nestList etc. retourner Variable s car les constructeurs de Variable requièrent des noms de variables et nous ne les connaissons pas encore à ce moment-là. Il existe des solutions de contournement pour cela (telles que renvoyer une fonction String -> Variable qui attend toujours ce nom de variable) mais il existe une meilleure solution: séparez le nom de la variable des différents types de valeurs qu'une variable peut avoir.

data Variable = Variable String Value 
    deriving Show 

data Value = LuaString String 
      | LuaList [Value] 
      deriving (Show) 

Notez que j'ai supprimé le constructeur NestedLuaList. J'ai changé LuaList pour accepter une liste de Value s plutôt que String s, donc une liste imbriquée peut maintenant être exprimée comme LuaList de LuaList s. Cela permet aux listes d'être imbriquées de façon arbitraire plutôt que deux niveaux, comme dans votre exemple. Je ne sais pas si c'est autorisé dans Lua mais cela a facilité l'écriture des parseurs. :-)

Maintenant, nous pouvons laisser lList et nestList retour Value s:

lList :: GenParser Char st Value 
lList = do 
    ss <- between (string "{") (string "}") (sepBy varContent (string ",")) 
    return (LuaList (map LuaString ss)) 

nestList :: GenParser Char st Value 
nestList = do 
    vs <- between (string "{") (string "}") (sepBy lList (string ",")) 
    return (LuaList vs) 

Et varName, que je l'ai changé le nom variable ici, retourne maintenant un Variable:

variable :: GenParser Char st Variable 
variable = do 
    vName <- (many letter) 
    eq <- string " = " 
    vCon <- try nestList 
     <|> try lList 
     <|> (do v <- varContent; return (LuaString v)) 
    return (Variable vName vCon) 

Je pense vous constaterez que lorsque vous exécutez votre analyseur sur certaines entrées, il y a encore quelques problèmes, mais vous êtes déjà beaucoup plus proche de la solution qu'auparavant.

J'espère que cela aide!

+0

Cela aide beaucoup merci. Je pense que vous avez raison de dire que mon type de données n'était pas suffisant pour ce que je voulais faire. Bien que je trouve fascinant le système de typage de Haskell, je dois admettre que je ne le comprends pas encore complètement. Des choses comme définir correctement les types de données récursifs me gâchent encore. – GraemeFore