TL;博士:
我的计算器语法依赖于递归下降将括号组相互嵌套,但是嵌套括号过多(大约 20 个)会导致堆栈溢出。我怎样才能解决这个问题?有没有办法让问题更平坦?
长表:
不久前——我的脑袋深深陷入了小型嵌入式系统——没有半个大脑的人不应该遇到堆栈溢出。现在被一项更抽象的任务所困扰,我来这里寻求建议。
激励项目是一个适用于 Android 的计算器。当前的计算器在很多方面都明显不足,但我今天没有带我的肥皂盒,所以我直接解决我遇到的问题:堆栈溢出!
具体来说,当用户创建了太多嵌套的括号组时,包括函数等。这是因为我的计算器依赖于ANTLR
语法,这意味着它使用递归下降。方便的是,这允许它通过 PEMDAS 连续运行,从而可以轻松计算嵌套函数和括号。但!我发现 - 根据手机 - 按下括号按钮 20 次左右会导致崩溃,这是由调用堆栈引起的堆栈溢出引起的,大约 100 次函数调用深度,这是递归下降方法的自然结果。
我知道,平面比嵌套更好,但它所经历的 4 个级别(函数)是完全必要的,而其他几个级别使我的生活对数更容易。即使删除这些级别也不能解决问题:用户仍然能够在几分钟内导致系统崩溃。有一个“太多的括号!” 错误消息很糟糕(这是其他计算器之一会做的事情)。另外,我使用 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;};