実際の言語のパーサーの単純化された部分問題として、標準の命令型言語 (Python、JavaScript など) に似た架空の言語の式のパーサーを実装しようとしています。その構文には、次の構造があります。
- 整数
- 識別子 (
[a-zA-Z]+
) +
and*
と括弧を使用した算術式- を使用した構造アクセス
.
(例:foo.bar.buz
) - タプル (例:
(1, foo, bar.buz)
) (あいまいさをなくすため、1 タプルは のように記述します(x,)
) - 関数の適用 (例
foo(1, bar, buz())
) - 関数はファーストクラスであるため、他の関数から返して直接適用することもできます(たとえば、関数を返す可能性がある
foo()()
ため合法です)foo()
したがって、この言語のかなり複雑なプログラムは
(1+2*3, f(4,5,6)(bar) + qux.quux()().quuux)
結合性は
( (1+(2*3)), ( ((f(4,5,6))(bar)) + ((((qux.quux)())()).quuux) ) )
私は現在、非常に優れuu-parsinglib
たアプリカティブ パーサー コンビネーター ライブラリを使用しています。
最初の問題は明らかに、直感的な式の文法 (が左再帰的であることです。しかし、コンビネータexpr -> identifier | number | expr * expr | expr + expr | (expr)
を使用してその問題を解決できました (以下の例を参照)。pChainl
parseExpr
残りの問題 (したがって、この質問) は、他の関数 ( f()()
) から返された関数を使用した関数の適用です。繰り返しますが、文法は recursive のままexpr -> fun-call | ...; fun-call -> expr ( parameter-list )
です。を使用してこの問題をエレガントに解決する方法はありuu-parsinglib
ますか? parsec
(この問題は、attoparsec
および他のパーサー コンビネーターにも直接適用されるはずです)。
以下の私の現在のバージョンのプログラムを参照してください。それはうまく機能しますが、関数の適用は識別子に対してのみ機能し、左再帰を削除します。
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
module TestExprGrammar
(
) where
import Data.Foldable (asum)
import Data.List (intercalate)
import Text.ParserCombinators.UU
import Text.ParserCombinators.UU.Utils
import Text.ParserCombinators.UU.BasicInstances
data Node =
NumberLiteral Integer
| Identifier String
| Tuple [Node]
| MemberAccess Node Node
| FunctionCall Node [Node]
| BinaryOperation String Node Node
parseFunctionCall :: Parser Node
parseFunctionCall =
FunctionCall <$>
parseIdentifier {- `parseExpr' would be correct but left-recursive -}
<*> parseParenthesisedNodeList 0
operators :: [[(Char, Node -> Node -> Node)]]
operators = [ [('+', BinaryOperation "+")]
, [('*' , BinaryOperation "*")]
, [('.', MemberAccess)]
]
samePrio :: [(Char, Node -> Node -> Node)] -> Parser (Node -> Node -> Node)
samePrio ops = asum [op <$ pSym c <* pSpaces | (c, op) <- ops]
parseExpr :: Parser Node
parseExpr =
foldr pChainl
(parseIdentifier
<|> parseNumber
<|> parseTuple
<|> parseFunctionCall
<|> pParens parseExpr
)
(map samePrio operators)
parseNodeList :: Int -> Parser [Node]
parseNodeList n =
case n of
_ | n < 0 -> parseNodeList 0
0 -> pListSep (pSymbol ",") parseExpr
n -> (:) <$>
parseExpr
<* pSymbol ","
<*> parseNodeList (n-1)
parseParenthesisedNodeList :: Int -> Parser [Node]
parseParenthesisedNodeList n = pParens (parseNodeList n)
parseIdentifier :: Parser Node
parseIdentifier = Identifier <$> pSome pLetter <* pSpaces
parseNumber :: Parser Node
parseNumber = NumberLiteral <$> pNatural
parseTuple :: Parser Node
parseTuple =
Tuple <$> parseParenthesisedNodeList 1
<|> Tuple [] <$ pSymbol "()"
instance Show Node where
show n =
let showNodeList ns = intercalate ", " (map show ns)
showParenthesisedNodeList ns = "(" ++ showNodeList ns ++ ")"
in case n of
Identifier i -> i
Tuple ns -> showParenthesisedNodeList ns
NumberLiteral n -> show n
FunctionCall f args -> show f ++ showParenthesisedNodeList args
MemberAccess f g -> show f ++ "." ++ show g
BinaryOperation op l r -> "(" ++ show l ++ op ++ show r ++ ")"