Проблемы с оператором Хаскелла Parsec <|> - PullRequest
4 голосов
/ 20 ноября 2010

Я новичок и в Хаскеле, и в Парсеке.В попытке узнать больше о языке и, в частности, об этой библиотеке, я пытаюсь создать анализатор, который может анализировать сохраненные переменные файлы Lua.В этих файлах переменные могут принимать следующие формы:

varname = value

varname = {value, value, ...}

varname = {{value, value}, {value, value, ...}}

Я создал парсеры для каждого из этих типов, но когда я связал их вместе с оператором выбора <|>, я получил ошибку типа.

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'

Мое предположение (хотя я не могу найти его в документации), что каждый парсер, переданный оператору выбора, должен возвращать один и тот же тип.Вот этот код:

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 ","))

Ответы [ 2 ]

7 голосов
/ 20 ноября 2010

Это верно.

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

Обратите внимание, что оба аргумента имеют одинаковый тип.

Я не совсем понимаю ваш тип данных Variable.Вот как я бы это сделал:

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

Это позволяет произвольно вкладывать значения, а не просто вкладывать два уровня глубоко, как у вас.Затем напишите:

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

Это синтаксический анализатор для luaValue.Тогда вам просто нужно написать:

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

И у вас это будет.Важно использовать тип данных, который точно представляет то, что возможно.

3 голосов
/ 20 ноября 2010

Действительно, парсеры, передаваемые оператору выбора, должны иметь одинаковые типы. Вы можете сказать по типу оператора выбора:

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

Это говорит о том, что он удачно объединит два парсера, если их типы токенов, типы состояний и типы результатов одинаковы.

Итак, как мы можем убедиться, что те парсеры, которые вы пытаетесь объединить, имеют одинаковый тип результата? Ну, у вас уже есть тип данных Variable, который фиксирует различные формы переменных, которые могут появиться в Lua, поэтому нам нужно не вернуть String, [String] или [[String]], а просто Variable с.

Но когда мы пытаемся это сделать, мы сталкиваемся с проблемой. Мы не можем позволить nestList и т. Д. Возвращать Variable с, поскольку конструкторы Variable требуют имен переменных, и мы пока не знаем их. Для этого есть обходные пути (например, возвращать функцию String -> Variable, которая все еще ожидает это имя переменной), но есть лучшее решение: отделить имя переменной от различных типов значений, которые может иметь переменная.

data Variable = Variable String Value
  deriving Show

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

Обратите внимание, что я удалил конструктор NestedLuaList. Я изменил LuaList, чтобы принимать список Value с, а не String с, поэтому вложенный список теперь можно выразить как LuaList из LuaList с. Это позволяет произвольно углублять списки, а не только два уровня, как в вашем примере. Я не знаю, разрешено ли это в Lua, но это облегчило написание синтаксических анализаторов. : -)

Теперь мы можем позволить lList и nestList вернуть 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)

И varName, который я переименовал здесь variable, теперь возвращает 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)

Я думаю, вы обнаружите, что когда вы запускаете парсер на каком-то вводе, все еще есть некоторые проблемы, но вы уже намного ближе к решению, чем раньше.

Надеюсь, это поможет!

...