質問
関数の引数を非厳密 ( by-name で渡す) として宣言する方法はありますか?
これが直接不可能な場合:同様のことを達成するのに役立つヘルパー関数またはデコレータはありますか?
具体例
これは、実験するための少しおもちゃの例です。
1
括弧付きの算術式 (簡単にするために、数値を単一のリテラル値に置き換えたもの) の次の古典的な文法に対処できる、小さなパーサー コンビネーター ライブラリを構築したいとします。
num = "1"
factor = num
| "(" + expr + ")"
term = factor + "*" + term
| factor
expr = term + "+" + expr
| term
パーサー コンビparse
ネーターを、トークンのリストと現在の位置を取得し、解析エラーをスローするか、結果と新しい位置を返すことができるメソッドを持つオブジェクトとして定義するとします。(連結) と(代替)ParserCombinator
を提供する基本クラスを適切に定義できます。次に、定数文字列を受け入れるパーサー コンビネーターを定義し、 and を実装します。+
|
+
|
# Two kinds of errors that can be thrown by a parser combinator
class UnexpectedEndOfInput(Exception): pass
class ParseError(Exception): pass
# Base class that provides methods for `+` and `|` syntax
class ParserCombinator:
def __add__(self, next):
return AddCombinator(self, next)
def __or__(self, other):
return OrCombinator(self, other)
# Literally taken string constants
class Lit(ParserCombinator):
def __init__(self, string):
self.string = string
def parse(self, tokens, pos):
if pos < len(tokens):
t = tokens[pos]
if t == self.string:
return t, (pos + 1)
else:
raise ParseError
else:
raise UnexpectedEndOfInput
def lit(str):
return Lit(str)
# Concatenation
class AddCombinator(ParserCombinator):
def __init__(self, first, second):
self.first = first
self.second = second
def parse(self, tokens, pos):
x, p1 = self.first.parse(tokens, pos)
y, p2 = self.second.parse(tokens, p1)
return (x, y), p2
# Alternative
class OrCombinator(ParserCombinator):
def __init__(self, first, second):
self.first = first
self.second = second
def parse(self, tokens, pos):
try:
return self.first.parse(tokens, pos)
except:
return self.second.parse(tokens, pos)
これまでのところ、すべて問題ありません。ただし、文法の非終端記号は相互に再帰的に定義されており、考えられるすべてのパーサーの組み合わせのツリーを熱心に展開することはできないため、パーサー コンビネーターのファクトリを使用して、次のようなものにラップする必要があります。
# Wrapper that prevents immediate stack overflow
class LazyParserCombinator(ParserCombinator):
def __init__(self, parserFactory):
self.parserFactory = parserFactory
def parse(self, tokens, pos):
return self.parserFactory().parse(tokens, pos)
def p(parserFactory):
return LazyParserCombinator(parserFactory)
これにより、EBNF に非常に近い方法で文法を書き留めることができます。
num = p(lambda: lit("1"))
factor = p(lambda: num | (lit("(") + expr + lit(")")))
term = p(lambda: (factor + lit("*") + term) | factor)
expr = p(lambda: (term + lit("+") + expr) | term)
そして実際に動作します:
tokens = [str(x) for x in "1+(1+1)*(1+1+1)+1*(1+1)"]
print(expr.parse(tokens, 0))
ただし、p(lambda: ...)
すべての行で少し面倒です。それを取り除く慣用的な方法はありますか?無限相互再帰の熱心な評価をトリガーせずに、何らかの方法でルールの RHS 全体を「名前で」渡すことができれば素晴らしいでしょう。
私が試したこと
コア言語で利用できるものを確認しました。「短絡」できるのは と のみのようです。間違っている場合は修正してくださいif
。and
or
私は、他の非おもちゃの例のライブラリがこれをどのように行うかを調べてみました。
たとえば、 funcparserlib は明示的な前方宣言を使用して、相互再帰を回避します ( github README.md サンプル コードの
forward_decl
andの部分を見てください)。value.define
いくつかの
parsec.py
特別な@generate
デコレータを使用し、コルーチンを使用したモナド解析のようなことをしているようです。それはすべて非常に素晴らしいことですが、私の目標は、Python で利用可能な基本的な評価戦略に関してどのようなオプションがあるかを理解することです。
のようなものも見つけましたがlazy_object_proxy.Proxy
、そのようなオブジェクトをより簡潔な方法でインスタンス化するのに役立たないようです。
では、引数を名前で渡し、相互に再帰的に定義された値の爆発を回避するより良い方法はありますか?