2

TL;DR:

私の電卓の文法は、再帰降下に依存して括弧グループを相互にネストしますが、ネストされた括弧が多すぎる (約 20) と、スタック オーバーフローが発生します。どうすればこれを修正できますか? 問題をより平坦にする方法はありますか?

長い形式:

それほど前のことではありませんが、私の頭は小規模な組み込みシステムに深く突き刺さっていましたが、脳の半分しかスタック オーバーフローに陥るはずがありませんでした。今はもっと抽象的な仕事に頭を悩ませているので、アドバイスを求めてここに来ました。

やる気を起こさせるプロジェクトは、Android 用の電卓です。現在の電卓は多くの点で明らかに不十分ですが、今日はソープボックスを持ってこなかったので、私が直面している問題に直行します: スタック オーバーフローです!

具体的には、ユーザーが関数などを含むネストされた括弧グループを作成しすぎた場合です。ANTLRこれは、計算機が文法に依存しているために発生します。つまり、再帰降下を使用しています。便利なことに、これにより PEMDAS を介して継続的に実行できるため、ネストされた関数と括弧の計算が簡単になります。しかし!電話によっては、かっこボタンを 20 回ほど押すとクラッシュが発生することがわかりました。これは、約 100 の関数呼び出しの深さから発生するスタック オーバーフローが原因で、再帰的降下法の自然な結果です。

フラットはネストされたものよりも優れていることはわかっていますが、それが通過する 4 つのレベル (関数) は完全に必要であり、他のいくつかのレベルは私の人生を対数的に容易にします。これらのレベルを削除しても問題は解決しません。ユーザーは数分以内にシステムをクラッシュさせることができます。「かっこが多すぎる!」エラーメッセージが悪いです (他の計算機の 1 つが行うことです)。また、私は AST 出力を使用して入力文字列を書式設定し、きれいなようにします。そのため、括弧グループの事前計算により、システム全体が少し複雑になりすぎます。

だから、質問:

この質問をすることさえばかげているように思えますが、コール スタックを爆発させることなく、複雑で深くネストされた式を解析および解釈できる文法を ANTLR に実装する方法はありますか?

文法:

grammar doubleCalc;

options {
    language = Java;
    output = AST;
//  k=2;
}

// Calculation function.
prog returns [double value]
    :   e=addsub EOF {$value = $e.value;}
    ;

addsub returns [double value]
    :   e=muldiv {$value = $e.value;}
        (   PLUS^ e=muldiv {$value += $e.value;}
        |   MINUS^ e=muldiv {$value -= $e.value;}
        )*
    ;

muldiv returns [double value]
    :   e=power {$value = $e.value;} 
        (   MUL^ e=power {$value *= $e.value;}
        |   DIV^ e=power {$value /= $e.value;}
        )*
    ; 

power returns [double value]
    :   e = negate {$value = $e.value;} 
        (   POW^ f=power {$value = java.lang.Math.pow($value, $f.value);}   
        )?
    ; 

negate returns [double value]
    :   (   MINUS^ neg = atom {$value = -$neg.value;}
        |   neg = atom {$value = $neg.value;}
        )
    ;

atom returns [double value]
    :   LOG10^ '(' e=addsub ')' {$value = java.lang.Math.log10($e.value);} 
    |   LOG8^ '(' e=addsub ')' {$value = java.lang.Math.log10($e.value)/java.lang.Math.log10(8.0);} 
    |   LOG2^ '(' e=addsub ')' {$value = java.lang.Math.log10($e.value)/java.lang.Math.log10(2.0);} 
    |   LN^ '(' e=addsub ')' {$value = java.lang.Math.log($e.value);} 
    |   ASIN^ '(' e=addsub ')' {$value = Math.asin(Math.PI*(($e.value/Math.PI) \% 1));}//com.brogramming.HoloCalc.Trig.asin($e.value);} 
    |   ACOS^ '(' e=addsub ')' {$value = Math.acos(Math.PI*(($e.value/Math.PI) \% 1));}
    |   ATAN^ '(' e=addsub ')' {$value = Math.atan(Math.PI*(($e.value/Math.PI) \% 1));}
    |   SIN^ '(' e=addsub ')' {$value = Math.sin(Math.PI*(($e.value/Math.PI) \% 1));} 
    |   COS^ '(' e=addsub ')' {$value = Math.cos(Math.PI*(($e.value/Math.PI) \% 1));} 
    |   TAN^ '(' e=addsub ')' {$value = Math.tan(Math.PI*(($e.value/Math.PI) \% 1));}
    |   ASIND^ '(' e=addsub ')' {$value = Math.asin(Math.PI*(($e.value/180f) \% 1));}//com.brogramming.HoloCalc.Trig.asin($e.value);} 
    |   ACOSD^ '(' e=addsub ')' {$value = Math.acos(Math.PI*(($e.value/180f) \% 1));}
    |   ATAND^ '(' e=addsub ')' {$value = Math.atan(Math.PI*(($e.value/180f) \% 1));}
    |   SIND^ '(' e=addsub ')' {$value = Math.sin(Math.PI*(($e.value/180f) \% 1));} 
    |   COSD^ '(' e=addsub ')' {$value = Math.cos(Math.PI*(($e.value/180f) \% 1));} 
    |   TAND^ '(' e=addsub ')' {$value = Math.tan(Math.PI*(($e.value/180f) \% 1));}
    |   SQRT^ '(' e=addsub ')' {$value = (double) java.lang.Math.pow($e.value, 0.5);} 
    |   CBRT^ '(' e=addsub ')' {$value = (double) java.lang.Math.pow($e.value, 1.0/3.0);} 
    |   ABS^ '(' e=addsub ')' {$value = (double) java.lang.Math.abs($e.value);}
    // Numbers
    |   n = number {$value = $n.value;}
    |   '(' e=addsub ')' {$value = $e.value;}
    ;

number returns [double value]
    :   PI {$value = java.lang.Math.PI;}
    |   EXP {$value = java.lang.Math.E;}
    |   INT {$value = (double) Double.parseDouble($INT.text.replaceAll(",", ""));}
    |   DOUBLE {$value = Double.parseDouble($DOUBLE.text.replaceAll(",", ""));}
    ;

LN  :    'ln';
LOG10   :   'log10';
LOG8    :   'log8';
LOG2    :   'log2';
SIN :   'sin';
COS :   'cos';
TAN :   'tan';
ASIN    :   'asin';
ACOS    :   'acos';
ATAN    :   'atan';
SINH    :   'sinh';
COSH    :   'cosh';
TANH    :   'tanh';
ASINH   :   'asinh';
ACOSH   :   'acosh';
ATANH   :   'atanh';
SIND    :   'sind';
COSD    :   'cosd';
TAND    :   'tand';
ASIND   :   'asind';
ACOSD   :   'acosd';
ATAND   :   'atand';
SINHD   :   'sinhd';
COSHD   :   'coshd';
TANHD   :   'tanhd';
ASINHD  :   'asinhd';
ACOSHD  :   'acoshd';
ATANHD  :   'atanhd';
PI  :   'pi';
IM  :   'i';
EXP :   'e';
ABS :   'abs';
FACT    :   'fact';
SQRE    :   'sqre';
CUBE    :   'cube';
SQRT    :   'sqrt';
CBRT    :   'cbrt';
POW : '^';
PLUS : '+';
MINUS : '-';
MUL : ('*');
DIV : '/';
BANG    :   '!';
DOUBLE: ('0'..'9' | ',')+ '.'('0'..'9')* ;
INT :   ('0'..'9' | ',')+ ;
NEWLINE:'\r'? '\n' ;
PERCENT
    :   '%';
EOF :   '<EOF>' {$channel=HIDDEN;};
4

2 に答える 2

5

Keith Clarke による次の優れたテクニックをご覧ください。

http://antlr.org/papers/Clarke-expr-parsing-1986.pdf

ANTLR v4 はバリエーションを使用します。

于 2013-02-06T21:43:07.480 に答える
1

Terの答えによると、ANTLR v4が道のりのように聞こえますが、別のアプローチは、入力文字列を自分でスキャンし、再帰的に選択して、括弧で囲まれた最小のグループを計算された値に置き換えることです。

「)」を探して、トークンをバッファリングする独自の TokenStream を作成します。それが見つかったら、「(」を逆方向に検索します。その一連のトークンを取得し、それをパーサーにフィードして、その式の double 値を取得します。double を保持できるカスタム Token クラスと、特別なトークンを定義します。計算された結果を指定するように入力し、括弧が使用されていたストリームにそれを貼り付けます. 埋め込まれた double を返すことにより、その新しいトークンを処理するように文法を変更します.

'(' e=addsub ')'このようにして、実際に文法の必要性を排除します。これらはすべて、特別なトークンに一致し、それに埋め込まれた値を返すルールに置き換えられます。

また、文法を使用してツリーを構築していますが、ルールのアクションを考えると、式をすぐに計算しているように見えます。実際にツリーを使用していないと仮定すると、ツリー構築の注釈を削除し、output=ASTオプションを削除する必要があります。

于 2013-02-08T19:12:47.360 に答える