基于递归下降的计算器堆栈溢出

时间:2013-02-06 21:04:31

标签: antlr recursive-descent

TL; DR:

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

长片

不久前 - 我的脑袋深深陷入了小规模的嵌入式系统 - 没有半脑的人会遇到堆栈溢出。现在因为一个更抽象的任务而感到谦卑,我来这里寻求建议。

激励项目是Android的计算器。目前的计算器在很多方面都显得不够,但我今天没带上我的肥皂盒,所以我只是直接解决我遇到的问题:堆栈溢出!

具体来说,当用户创建太多嵌套括号组时,包括函数等。这是因为我的计算器依赖于ANTLR语法,这意味着它使用递归下降。方便地,这允许它通过PEMDAS连续运行,允许容易地计算嵌套函数和括号。但!我发现 - 取决于手机 - 按下parens按钮20次左右会导致崩溃,这是由于调用堆栈产生的堆栈溢出导致大约100个函数调用深度,这是递归下降方法的自然结果。

我知道,flat比嵌套更好,但是它通过的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;};

2 个答案:

答案 0 :(得分:5)

看看Keith Clarke的这个不错的技巧:

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

ANTLR v4使用变体。

答案 1 :(得分:1)

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

我会创建自己的TokenStream来缓冲令牌,寻找&#39;)&#39;。当它看到时,向后搜索&#39;(&#39;。获取该标记序列并将其提供给您的解析器以获取该表达式的double值。定义一个可以保存double的自定义Token类它,以及一个特殊的令牌类型来指定一个计算结果,并将其粘贴到你的流中,这是parens过去的。修改你的语法,通过返回嵌入的双精度来处理新的令牌。

通过这种方式,您实际上无需在语法中使用'(' e=addsub ')'。所有这些都将被一个与您的特殊令牌匹配的规则所取代,并返回其中嵌入的值。

此外,您正在使用语法构建树,但是根据规则中的操作,您似乎立即计算表达式。假设您实际上没有使用树,则应该删除树构建注释并删除output=AST选项。