2

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;};
4

2 回答 2

5

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

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

ANTLR v4 使用了一个变体。

于 2013-02-06T21:43:07.480 回答
1

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

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

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

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

于 2013-02-08T19:12:47.360 回答