Antlr解析器StackOverflowException用于解析正则表达式

如何解决Antlr解析器StackOverflowException用于解析正则表达式

我为解析正则表达式做了一个简单的语法。不幸的是,当我尝试在大型表达式上测试我的正则表达式编译器时,我遇到了StackOverflowException。该问题与this one相似,除了它们的解决方案在我的方案中不再起作用。这是我的语法:

union: concat | concat '|' union ;
concat: kleene_closure concat | kleene_closure;
kleene_closure: atomic '*' | atomic ;
atomic : '(' union ')' | LETTER ;

现在的问题是我有一个看起来很大的文件

something1 | something2 | something3 | .... | something1000

我使用ANTLR的Visitor类进行解析。我知道我可以像这样使用+ / *进行一些优化

union: (concat '|')* concat ;
concat: kleene_closure+;
kleene_closure: atomic '*' | atomic ;
atomic : '(' union ')' | LETTER ;

但是,由于这种语法的递归性质,它并不能真正解决问题。例如,现在在显然需要递归的以下示例上将失败:

(...(((something1) | something2) | something3) | .... ) | something1000

如何避免StackOverflowExcpetion?其他编译器(例如C编译器)如何处理具有数千行代码的超大型文本?

解决方法

如果要使用递归下降解析器,那么不可避免地会遇到超出调用堆栈深度的输入。诸如Java之类的语言能够控制其自身的堆栈深度,从而改善了此问题,从而产生了诸如StackOverflowException之类的可控结果。但这仍然是一个真正的问题。

Yacc / Bison和Java Cup之类的解析器生成器使用自下而上的LALR(1)算法,该算法使用显式堆栈进行临时存储,而不是为此目的使用调用堆栈。这意味着解析器必须管理解析器堆栈的存储(或使用主机语言的标准库中的容器ADT(如果有的话)使用容器),这稍微复杂一些。但是您不必处理这种复杂性。它内置在解析器生成器中。

解析器生成器的显式堆栈有几个优点:

  • 控制最大堆栈大小更容易;
  • (通常)最大堆栈大小仅受可用内存限制;
  • 由于不需要将控制流信息保存在堆栈帧中,因此内存使用效率可能更高。

不过,这不是万能药。足够复杂的表达式将超出任何固定的堆栈大小,并且可能导致某些程序无法解析。此外,如果您利用了以上第二点提到的灵活性(“仅受可用内存限制”),您很可能会发现编译器被OOM进程(或段错误)毫不客气地终止了,而不是能够响应一个更礼貌的内存不足异常(当然取决于操作系统和配置)。

关于:

其他编译器(例如C编译器)如何处理具有数千行代码的超大型文本?

如果在语法中使用重复运算符(或者在使用LALR(1)解析器的情况下,语法是左递归的),那么拥有数千行代码就不是问题。正如您在问题中指出的那样,当您的文本具有成千上万的嵌套块时,就会出现问题。答案是,许多C编译器无法很好地处理此类文本。这是一个使用gcc的简单实验:

$ # A function which generates deeply-nested C programs
$ type deep
deep is a function
deep () { 
    n=$1;
    printf "%s\n%s\n %s\n" '#include <stdio.h>' 'int main(void) {' 'int a0 = 0;';
    for ((i=0; i<n; ++i))
    do
        printf '%*s{ int a%d = a%d + 1;\n' $((i+1)) '' $((i+1)) $i;
    done;
    printf '%*sprintf("%%d\\n",a%d);\n' $n '' $n;
    for ((i=0; i<n; ++i))
    do
        printf "%s" '}';
    done;
    printf "%s\n" '}'
}
$ deep 3
#include <stdio.h>
int main(void) {
 int a0 = 0;
 { int a1 = a0 + 1;
  { int a2 = a1 + 1;
   { int a3 = a2 + 1;
   printf("%d\n",a3);
}}}}
$ # For small depths,GCC is OK with that.
$ deep 3 | gcc -x c - && ./a.out
3
$ # Let's go deeper:
$ deep 10 | gcc -x c - && ./a.out
10
$ deep 100 | gcc -x c - && ./a.out
100
$ deep 1000 | gcc -x c - && ./a.out
1000
$ deep 10000 | gcc -x c - && ./a.out
10000
$ # Ka-bang. (Took quite a long time,too.)
$ deep 100000 | gcc -x c - && ./a.out
gcc: internal compiler error: Segmentation fault (program cc1)
Please submit a full bug report,with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-7/README.Bugs> for instructions.

没有嵌套块,gcc仍然很慢,但可以处理程序:

$ type big
big is a function
big () 
{ 
    n=$1;
    printf "%s\n%s\n %s\n" '#include <stdio.h>' 'int main(void) {' 'int a0 = 0;';
    for ((i=0; i<n; ++i))
    do
        printf ' int a%d = a%d + 1;\n' $((i+1)) $i;
    done;
    printf ' printf("%%d\\n",a%d);\n' $n;
    printf "%s\n" '}'
}
$ big 3
#include <stdio.h>
int main(void) {
 int a0 = 0;
 int a1 = a0 + 1;
 int a2 = a1 + 1;
 int a3 = a2 + 1;
 printf("%d\n",a3);
}
$ $ big 3|gcc -x c - && ./a.out
3
$ big 10000|gcc -x c - && ./a.out
10000
$ big 100000|gcc -x c - && ./a.out
100000
,

您可以使用ABNF语法定义语法,然后将其交给TGS *进行迭代解析-无需使用线程专用堆栈进行递归。解析器生成器生成解析器,该解析器针对其所有所有操作进行迭代运行:词法分析,解析,树构造,树到字符串转换,树迭代和树破坏。

运行时的解析器还可以仅向您提供具有事件的树构建信息,然后您可以根据需要构建树(或在没有任何树的情况下进行任何计算)。在这种情况下,当您使用事件进行解析(确定性的解析器语法而不使用显式的树结构)时,如果您有足够的操作内存来容纳解析规则的深度,则实际上可以“流式传输”任何输入不管 >。

ABNF(RFC 5234)这样的语法中的确定性语法是这样的:

alternative     = concatenation *('|' concatenation)
concatenation   = 1* kleene-closure
kleene-closure  = atomic 0*1 '*'
atomic          = '(' alternative ')' / letter
letter          = 'a'-'z' / 'A'-'Z'

然而,该语法每个项目只有一个字母,对于输入为“ ab”的您将获得两个原子节点,每个节点一个字母。如果您想要更多的字母,那么这种语法可能会起作用:

alternative     = concatenation *('|' *ws concatenation)
concatenation   = element *(ws 0*1 element)
element         = primary 0*1 '*'
primary         = '(' *ws alternative ')' / identifier
identifier      = 1*('a'-'z' / 'A'-'Z')
ws              = %x20 / %x9 / %xA / %xD

您可以将其理解为:一种替代方案是由一个或多个concatenations隔开,|concatenation是一个或多个elements,由至少一个white space字符分隔。 element可以以*结尾,并且可以是范围内的alternativeidentifier,后者又是一个或多个字母。空格为spacetabnew linecarriage return。如果要使用更复杂的标识符,可以使用以下方法:

identifier      = (letter / '_') *(letter / '_' / digit)
letter          = 'a'-'z' / 'A'-'Z'
digit           = '0'-'9'

*我正在那个项目上。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-