2016-02-15 12 views
5

私は現在、単純な算術式を解析するためのパーサを作成しています。これは、数値と変数に対して+ - * /をサポートするためにのみ必要です。例:ast.Numをdecimalに変換します。精度はPython

100.50*num*discount 

これは製品の価格を計算するために使用されます。

これはPythonで書かれています。私は単純にするためにPython独自のパーサを使用したいと思います。現在、それがうまく機能...

ast.BinOpast.Addast.Numast.Nameなど、:アイデアはまず、小さなサブセットでASTのノードタイプを制限するために、ASTの上を歩く、その後、ASTに入力を解析すると言っていますastの浮動小数点数は正確ではありません。だから私はastのast.Numノードをast.Call(func=ast.Name(id='Decimal'), ...)に変換したいと思っています。しかし、問題は次のとおりです。ast.Numには、すでに解析済みの浮動小数点数であるnフィールドしか含まれていません。そして、元の数値リテラルをソースコードで入手するのは容易ではありません:How to get source corresponding to a Python AST node?

提案はありますか?

+0

ソースコード*の元の数字リテラルの意味を説明できますか? – Kasramvd

+0

申し訳ございません。*数値リテラル*:https://docs.python.org/2/reference/lexical_analysis.html?highlight =リテラル#数値リテラル – jayven

答えて

5

私は2段階アプローチを提案します。最初の手順では、Pythonのtokenizeモジュールを使用して、ソース内のすべての浮動小数点数値リテラルを'Decimal(my_numeric_literal)'の文字列に変換します。次に、あなたが提案する方法でASTで作業することができます。

トークン化モジュールdocumentationの最初の手順のレシピがあります。の存在を確認することで

from cStringIO import StringIO 
from tokenize import generate_tokens, untokenize, NAME, NUMBER, OP, STRING 

def is_float_literal(s): 
    """Identify floating-point literals amongst all numeric literals.""" 
    if s.endswith('j'): 
     return False # Exclude imaginary literals. 
    elif '.' in s: 
     return True # It's got a '.' in it and it's not imaginary. 
    elif s.startswith(('0x', '0X')): 
     return False # Must be a hexadecimal integer. 
    else: 
     return 'e' in s # After excluding hex, 'e' must indicate an exponent. 

def decistmt(s): 
    """Substitute Decimals for floats in a string of statements. 

    >>> from decimal import Decimal 
    >>> s = 'print +21.3e-5*-.1234/81.7' 
    >>> decistmt(s) 
    "print +Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7')" 

    >>> exec(s) 
    -3.21716034272e-007 
    >>> exec(decistmt(s)) 
    -3.217160342717258261933904529E-7 

    """ 
    result = [] 
    g = generate_tokens(StringIO(s).readline) # tokenize the string 
    for toknum, tokval, _, _, _ in g: 
     if toknum == NUMBER and is_float_literal(tokval): 
      result.extend([ 
       (NAME, 'Decimal'), 
       (OP, '('), 
       (STRING, repr(tokval)), 
       (OP, ')') 
      ]) 
     else: 
      result.append((toknum, tokval)) 
    return untokenize(result) 

オリジナルレシピは、浮動小数点リテラルを識別します。ここで(レシピ自体が欠落している必要が輸入品と一緒に)そのレシピからのコードだ、リンクだけの答えを回避するために、 '.'の値。それはのようなリテラルを除外し、1.0jのような想像上のリテラルを含みます(除外したいかもしれない)ので、完全に防弾ではありません。私はそのチェックを上記のis_float_literalの自分のバージョンに置き換えました。

あなたの例の文字列でこれをしようと、私はこの取得:

>>> tree = ast.parse(decistmt(expr), mode='eval') 
>>> # walk the tree to validate, make changes, etc. 
... 
>>> ast.dump(tree) 
"Expression(body=BinOp(left=BinOp(left=Call(func=Name(id='Decimal', ... 

し、最終的に評価する:あなたは今、以前のようにASTツリーに解析することができます

>>> expr = '100.50*num*discount' 
>>> decistmt(expr) 
"Decimal ('100.50')*num *discount " 

...

>>> from decimal import Decimal 
>>> locals = {'Decimal': Decimal, 'num': 3, 'discount': Decimal('0.1')} 
>>> eval(compile(tree, 'dummy.py', 'eval'), locals) 
Decimal('30.150') 
+0

これは正しい方法です、ありがとうございます! – jayven

関連する問題