うーん、「標準」文法を使用して式を解析することは、再帰的な適切な方法では事実上不可能です。
E = E + E | E - E | ... | VALUE
これは、呼び出しE
によって無限再帰が発生する可能性があるためです。演算子の優先順位や結合性などの問題が他にもあります...
このような問題に対処する方法として 2 つの方法がよく知られていますが、実際には、この 2 つの方法はほとんどの問題に適用できます。
1) ボトムアップ アプローチを使用します。1 つの例は、Shift-Reduce 解析http://en.wikipedia.org/wiki/Shift-reduce_parserです。この方法は、コードの複雑さを犠牲にして文法を保持します。
2) トップダウン アプローチを使用します。1 つの例は再帰的適切ですが、別の文法を使用します。http://en.wikipedia.org/wiki/LL_parser左再帰のないものです。これは通常、元の文法を変更してすべてを更新することによって行われます。直接左の再帰呼び出しを削除するルール。これにより、文法が (可能であれば) 変更され、かなり長くなりますが、コードはより単純になります。
メソッドを再帰的にするように要求したので、おそらく2番目の方法がより良いアプローチです。
最初にこの新しい文法を定義し、ルールごとに新しい関数を作成する必要があります。
あなたはこの例を挙げました: 3 + 7 * (!3)^2
したがって、次の演算子を優先順位の高い順に推測しました。
operators: +, -, *, /, ^, -, !
values: 0,1,2,3 ...
これは、優先順位を保持する単純でよく知られている LL(1) 文法です ...
<arith_expr> : <term> {+ <term>}
| <term> {- <term>}
<term> : <power> {* <power>}
| <power> {/ <power>}
<power> : <factor> <exponent>
<exponent> : ^ <factor> <exponent> | ''
<factor> | NUMERIC_VALUE
| - <factor>
| ! <factor>
| ( <arith_expr> )
前の文法といくらか同等ですが、直接の左再帰呼び出しはありません。文法を LL(*) 文法に変換するプロセスはいくぶん機械的です ...
私は通常、割り当て、特に単純なもののコードを提供しませんが、これは開始するためのものであり、残りは実装できるはずです。
一般に空白は無視されるため、入力を文法の各ルールに適用可能な一連の値に量子化しました。文法で別段の指示がない限り、空白は除外されます。このプロセスはトーキナイゼーションと呼ばれ、各値はトークンと呼ばれます。これにはregexpを使用するのが一般的ですが、読みやすさのために手動のアプローチを好みました... Tokinizationは非常に簡単です...
"""
`{}` means 0 or more
<arith_expr> : <term> {+ <term>}
| <term> {- <term>}
<term> : <factor> {* <factor>}
<factor> | NUMERIC_VALUE
| - <factor>
| ( <arith_expr> )
"""
import string
def evaluate(str_value):
def tokenize(value):
all_symbols = set(('+', '-', '*',)) | set(('(', ')'))
white_space = set((' ', '\n', '\t'))
tokens = []
index = 0
while index < len(value):
current_char = value[index]
if current_char in white_space:
index += 1
continue
if current_char in all_symbols:
tokens.append(current_char)
index += 1
continue
if (current_char in string.digits) or (current_char == '.'):
numeric_value = current_char
index += 1
while (index < len(value)) and ((value[index] in string.digits) or (value[index] == '.')):
if (values[index] == '.') and ('.' in numeric_value):
raise Exception('Multiple decimal points detected while tokenizing numeric value!')
numeric_value.append(value[index])
index += 1
tokens.append(float(numeric_value) if '.' in numeric_value else int(numeric_value))
continue
raise Exception('Unable to tokenize symbol %s' % value[index])
return tokens
def factor(tokens):
'''
<factor> | NUMERIC_VALUE
| - <factor>
| ( <arith_expr> )
'''
if not tokens:
return None
if type(tokens[0]) in set((int, float)): # NUMERIC_VALUE
return tokens.pop(0)
if tokens[0] == '-': # - <factor>
tokens.pop(0)
return -1 * factor(tokens)
if tokens[0] == '(': # ( <arith_expr> )
tokens.pop(0)
value = arith_expr(tokens)
assert tokens.pop(0) == ')'
return value
def term(tokens):
'''
<term> : <factor> {* <factor>}
'''
left_value = factor(tokens)
operators = set(('*',))
while tokens and (tokens[0] in operators): # {* <factor>}
op = tokens.pop(0)
right_value = factor(tokens)
left_value *= right_value
return left_value
def arith_expr(tokens):
'''
<arith_expr> : <term> {+ <term>}
| <term> {- <term>}
'''
left_value = term(tokens)
operators = set(('+', '-'))
while tokens and (tokens[0] in operators):
op = tokens.pop(0)
right_value = term(tokens)
if op == '+':
left_value += right_value
else:
left_value -= right_value
return left_value
return arith_expr(tokenize(str_value))
注: これはテストされていません。