2013-02-06 31 views
2

TL; DR:递归下降为基础的计算器堆栈溢出

我的计算器语法依赖于递归下降巢括号内彼此群体,但太多的嵌套的括号(约20)导致堆栈溢出。我怎样才能解决这个问题?有没有办法让问题更加平坦?

长篇:

这是不久前 - 我的头卡住深入到小型嵌入式系统 - 任何人动动脑子应该运行到堆栈溢出。现在被一个更抽象的任务所迷惑,我来这里寻求建议。

激励项目是Android的计算器。目前的计算器在很多方面明显不足,但是我今天没有带上我的soapbox,所以我会直接解决我遇到的问题:堆栈溢出!

具体来说,当用户创建了太多的嵌套括号组,包括函数等。这是因为我的计算器依赖于一个ANTLR语法,这意味着它使用递归下降。方便地,它可以通过PEMDAS连续运行,从而可以轻松计算嵌套函数和括号。但!我发现 - 根据手机的不同,按下parens按钮20次左右会导致崩溃,这是由堆栈溢出导致的,该堆栈溢出源自调用堆栈大约100次函数调用深度,这是递归下降方法的自然结果。我知道,扁平比嵌套更好,但其中4个层次(函数)是完全必要的,而其他几个层次使我的生活更加容易。即使删除这些级别也不能解决问题:用户仍然能够在几分钟内使系统崩溃。有一个“太多parens!”错误信息是不好的(它是其他计算器中的一个)。另外,我使用AST输出来格式化输入字符串,使它变得非常像,所以对parens组进行预先计算会让整个系统太复杂。

所以,问题:

即使问这个问题似乎很傻,但:有没有实现在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;}; 
+0

我认为这是我第一次看到堆栈溢出堆栈溢出相关的问题。 :) – vijay

回答

5

退房从基思·克拉克这个漂亮的技术:

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

ANTLR V4采用的是变化。

+0

ANTLR 4仍然使用递归表达式,如''('expr')'',所以它仍然会面临递归限制。但是,单个堆栈框架趋向于更小,因此可能有更深的嵌套。目前的弱点是内部“关闭”操作的递归;如果用一个工作表替换,我会期望* V4比V3更深的表达式嵌套是可能的。 –

1

听起来像ANTLR v4是根据Ter的答案去的方法,但另一种方法是自己扫描输入字符串并递归地挑出并用他们的计算值替换最低的括号组。

我会创建自己的TokenStream缓冲令牌,寻找')'。当它看到的时候,向后搜索'(')。获取这个令牌序列并将它提供给解析器以获得该表达式的双重值。定义一个自定义令牌类,该类可以包含一个双精度令牌,以及一个特殊令牌键入来指定一个计算结果,并将其粘贴到你的流中,在那里你可以修改你的语法,通过返回嵌入的double来修改你的语法来处理新的标记。

通过这种方式,您实际上可以在语法中消除对'(' e=addsub ')'的需求。所有这些将被替换为匹配您的特殊令牌并返回嵌入其中的值的规则。

此外,您正在用语法构建树,但考虑到规则中的操作,它看起来像是在立即计算表达式。假设你实际上并没有使用这棵树,你应该消除树的建立注释并摆脱output=AST选项。

+0

事实上,我似乎并不需要AST输出,但在第一篇文章中我没有提到我使用输出AST格式化输入计算字符串。大多数情况下,这允许我使指数组被上标。感谢您的建议! – Vatsu1