1

失礼なタイトルで申し訳ありません。これ以上いいものは思いつきませんでした

次の要件を持つ pyparsing を使用して DSL を実装しようとしています。

  1. 変数: すべて v_ で始まります
  2. 単項演算子: +、-
  3. 二項演算子: +、-、*、/、%
  4. 定数
  5. 変数が 1 つしかない場合の通常の関数と同様の関数
  6. 関数は次のように機能する必要があります: foo(v_1+v_2) = foo(v_1) + foo(v_2), foo(bar(10*v_6))=foo(bar(10))*foo(bar(v_6)). これは、バイナリ操作の場合に当てはまります。

私は1-5を働かせることができます

これは私がこれまでに持っているコードです

from pyparsing import *

exprstack = []

#~ @traceParseAction
def pushFirst(tokens):
    exprstack.insert(0,tokens[0])

# define grammar
point = Literal( '.' )
plusorminus = Literal( '+' ) | Literal( '-' )
number = Word( nums )
integer = Combine( Optional( plusorminus ) + number )
floatnumber = Combine( integer +
                       Optional( point + Optional( number ) ) +
                       Optional( integer )
                     )

ident = Combine("v_" + Word(nums))

plus  = Literal( "+" )
minus = Literal( "-" )
mult  = Literal( "*" )
div   = Literal( "/" )
cent   = Literal( "%" )
lpar  = Literal( "(" ).suppress()
rpar  = Literal( ")" ).suppress()
addop  = plus | minus
multop = mult | div | cent
expop = Literal( "^" )
band = Literal( "@" )

# define expr as Forward, since we will reference it in atom
expr = Forward()
fn = Word( alphas )
atom = ( ( floatnumber | integer | ident | ( fn + lpar + expr + rpar ) ).setParseAction(pushFirst) |
         ( lpar + expr.suppress() + rpar ))

factor = Forward()
factor << atom + ( ( band + factor ).setParseAction( pushFirst ) | ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) ) )

term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
print(expr)
bnf = expr

pattern =  bnf + StringEnd()


def test(s):
    del exprstack[:]
    bnf.parseString(s,parseAll=True)
    print exprstack

test("avg(+10)")
test("v_1+8")
test("avg(v_1+10)+10")

これが私が欲しいものです。

私の関数はこのタイプです:

foo(v_1+v_2) = foo(v_1) + foo(v_2)

他の二項演算でも同じ動作が期待されます。パーサーにこれを自動的に行わせる方法がわかりません。

4

1 に答える 1

2

関数呼び出しを別のサブ式として分割します。

function_call = fn + lpar + expr + rpar

次に、 expr_stack から演算子とオペランドをポップし、それらをスタックにプッシュする parse アクションを function_call に追加します。

  • オペランドの場合、オペランドをプッシュしてから関数
  • オペレーターの場合は、オペレーターをプッシュします

バイナリ操作のみを行っているため、最初に単純なアプローチを行う方がよい場合があります。

expr = Forward()
identifier = Word(alphas+'_', alphanums+'_')
expr = Forward()
function_call = Group(identifier + LPAR + Group(expr) + RPAR)

unop = oneOf("+ -")
binop = oneOf("+ - * / %")
operand = Group(Optional(unop) + (function_call | number | identifier))
binexpr = operand + binop + operand

expr << (binexpr | operand)

bnf = expr

これにより、exprstack をいじる必要なく、より単純な構造で作業できます。

def test(s):
    exprtokens = bnf.parseString(s,parseAll=True)
    print exprtokens

test("10")
test("10+20")
test("avg(10)")
test("avg(+10)")
test("column_1+8")
test("avg(column_1+10)+10")

与えます:

[['10']]
[['10'], '+', ['20']]
[[['avg', [['10']]]]]
[[['avg', [['+', '10']]]]]
[['column_1'], '+', ['8']]
[[['avg', [['column_1'], '+', ['10']]]], '+', ['10']]

fn(a op b)に展開したいがfn(a) op fn(b)fn(a)そのままにしておく必要があるため、解析された式の引数の長さをテストする必要があります。

def distribute_function(tokens):
    # unpack function name and arguments
    fname, args = tokens[0]

    # if args contains an expression, expand it
    if len(args) > 1:
        ret = ParseResults([])
        for i,a in enumerate(args):
            if i % 2 == 0:
                # even args are operands to be wrapped in the function
                ret += ParseResults([ParseResults([fname,ParseResults([a])])])
            else:
                # odd args are operators, just add them to the results
                ret += ParseResults([a])
        return ParseResults([ret])
function_call.setParseAction(distribute_function)        

テストの呼び出しは次のようになります。

[['10']]
[['10'], '+', ['20']]
[[['avg', [['10']]]]]
[[['avg', [['+', '10']]]]]
[['column_1'], '+', ['8']]
[[[['avg', [['column_1']]], '+', ['avg', [['10']]]]], '+', ['10']]

これは、 のような呼び出しでも再帰的に機能するはずfna(fnb(3+2)+fnc(4+9))です。

于 2012-10-25T10:17:18.077 に答える