0

単純な言語のパーサーを作成しようとしています。基本的に今のところ、リテラル、if、関数適用などがあり、他にはあまりありません。

これが私が持っているコードです:

import Text.ParserCombinators.Parsec
import Control.Monad (liftM)

data Expr = Term Term
          | Apply Expr Expr
          | If Expr Expr Expr
          deriving (Show)

data Term = Bool Bool
          | Num Double
          | String String
          | Identifier String
          | Parens Expr
          deriving (Show)

sstring s = spaces >> string s
schar c = spaces >> char c

keyword k = do
  kw <- try (sstring k)
  notFollowedBy alphaNum
  return kw

pBool :: Parser Bool
pBool = do
  bool <- keyword "True" <|> keyword "False"
  case bool of
    "True" -> return True
    "False" -> return False

pDouble :: Parser Double
pDouble = do
  ds <- many1 digit
  dot <- optionMaybe $ char '.'
  case dot of
    Nothing -> return $ read ds
    _ -> do
      ds' <- many1 digit
      return $ read (ds ++ "." ++ ds')

pString :: Parser String
pString = do
  char '"'
  str <- many1 $ noneOf "\""
  char '"'
  return str

pIdentifier :: Parser String
pIdentifier = spaces >> many1 letter

pParens :: Parser Expr
pParens = do
  schar '('
  expr <- pExpr
  schar ')'
  return expr

pTerm :: Parser Term
pTerm = try (liftM Bool pBool)
  <|> try (liftM Num pDouble)
  <|> try (liftM String pString)
  <|> try (liftM Identifier pIdentifier)
  <|> try (liftM Parens pParens)

-- TODO: make this left-associative
pApply :: Parser Expr
pApply = do
  term <- pTerm'
  mApp <- spaces >> optionMaybe pApply
  return $ case mApp of
    Just app -> Apply term app
    Nothing -> term

-- pulls "parens" expressions out of terms
pTerm' :: Parser Expr
pTerm' = do
  term <- pTerm
  case term of 
    Parens expr -> return expr
    otherwise -> return $ Term term

pIf :: Parser Expr
pIf = do
  keyword "if"
  cond <- pExpr
  keyword "then"
  ifTrue <- pExpr
  keyword "else"
  ifFalse <- pExpr
  return $ If cond ifTrue ifFalse

pExpr :: Parser Expr
pExpr = try pIf <|> pApply

test parser = parse parser ""

ここで、ghci で単一の数値式を解析しようとすると、すべてうまくいきます。

> test pExpr "1"
Right (Term (Num 1.0))

すごい!そして、他の多くのことも機能します:

> test pExpr "1.234"
Right (Term (Num 1.234))
> test pApply "neg 1"
Right (Apply (Term (Identifier "neg")) (Term (Num 1.0)))
> test pExpr "f g 1"
Right (Apply (Term (Identifier "f")) (Apply (Term (Identifier "g")) (Term (Num 1.0))))

ifしかし今、ステートメントを解析しようとすると、エラーが発生します。

> test pIf "if 1 then 2 else 3"
Left (line 1, column 4):
unexpected "1"
expecting space, "if", "True", "False", letter or "("

これは私には意味がありません!if ステートメントを解析するためのルールを見て、これを順を追って説明しましょう。

"if"キーワードを解析します(問題ありません)。次に、次の解析 ( 1) のために、 を解析する必要があります。pExprこれは、それ自体がpIfまたは である可能性がありpApplyます。これは if ではないので、 apply を試します。それ自体がpTerm'を試行pTermし、 を試行し、 a を試行pBoolしますが、失敗し、 apNumが成功します! 次にpTerm、 aで成功するNum 1.0ので、 a で成功します。つまり、 a で成功し、それが変数に渡されます...そうですか? 失敗しているからです。ここで失敗する理由がわかりません。pTerm'Term (Num 1.0)pExprTerm (Num 1.0)cond

4

2 に答える 2

3

すべてのスペースを食べないという問題があり、thenandelseが識別子として解釈されています。lexemeルールは、トークンの後にスペースを食べるのに便利です。予約語を飲み込んpIdentifierでいないことを明示的に確認する必要があります。私はこれらの問題を修正し、既存のコンビネータのいくつかを自由に使用して、適用可能なスタイルに変更しました...

import Text.ParserCombinators.Parsec
import Control.Applicative hiding ((<|>))

data Expr = Term Term
          | Apply Expr Expr
          | If Expr Expr Expr
          deriving (Show)

data Term = Bool Bool
          | Num Double
          | String String
          | Identifier String
          | Parens Expr
          deriving (Show)

keywords = ["if", "then", "else", "True", "False"]
lexeme p = p <* spaces
schar = lexeme . char

keyword k = lexeme . try $
  string k <* notFollowedBy alphaNum

pBool :: Parser Bool
pBool = (True <$ keyword "True") <|> (False <$ keyword "False")

pDouble :: Parser Double
pDouble = lexeme $ do
  ds <- many1 digit
  option (read ds) $ do
    char '.'
    ds' <- many1 digit
    return $ read (ds ++ "." ++ ds')

pString :: Parser String
pString = lexeme . between (char '"') (char '"') . many1 $ noneOf "\""

pIdentifier :: Parser String
pIdentifier = lexeme . try $ do
  ident <- many1 letter
  if ident `elem` keywords
    then unexpected $ "reserved word " ++ show ident
    else return ident

pParens :: Parser Expr
pParens = between (schar '(') (schar ')') pExpr

pTerm :: Parser Term
pTerm = choice [ Bool       <$> pBool
               , Num        <$> pDouble
               , String     <$> pString
               , Identifier <$> pIdentifier
               , Parens     <$> pParens
               ]

-- TODO: make this left-associative
pApply :: Parser Expr
pApply = do
  term <- pTerm'
  option term $
    Apply term <$> pApply

-- pulls "parens" expressions out of terms
pTerm' :: Parser Expr
pTerm' = do
  term <- pTerm
  case term of
    Parens expr -> return expr
    _ -> return $ Term term

pIf :: Parser Expr
pIf = If <$ keyword "if"   <*> pExpr 
         <* keyword "then" <*> pExpr
         <* keyword "else" <*> pExpr

pExpr :: Parser Expr
pExpr = pIf <|> pApply

test parser = parse (spaces *> parser <* eof) ""
于 2013-10-23T15:59:01.213 に答える
1

いくつかの変更を加える必要があります。

pExpr :: Parser Expr
pExpr = try pIf <|> pTerm'


pIf :: Parser Expr
pIf = do
  keyword "if"
  spaces
  cond <- pExpr
  keyword "then"
  spaces
  ifTrue <- pExpr
  keyword "else"
  spaces
  ifFalse <- pExpr
  return $ If cond ifTrue ifFalse
于 2013-10-23T09:05:58.860 に答える