Parsing 分析每行以特定符号开头的块

Parsing 分析每行以特定符号开头的块,parsing,yacc,lalr,Parsing,Yacc,Lalr,我需要解析一段代码,如下所示: * Block | Line 1 | Line 2 | ... 这很容易做到: block : head lines; head : '*' line; lines : lines '|' line | '|' line ; 现在我想知道如何添加嵌套块,例如: * Block | Line 1 | * Subblock | | Line 1.1 | | ... | Line 2 | ... 这可以用LALR语法表示吗 当然,我可以解

我需要解析一段代码,如下所示:

* Block
| Line 1
| Line 2
| ...
这很容易做到:

block : head lines;
head  : '*' line;
lines : lines '|' line
      | '|' line
      ;
现在我想知道如何添加嵌套块,例如:

* Block
| Line 1
| * Subblock
| | Line 1.1
| | ...
| Line 2
| ...
这可以用
LALR
语法表示吗


当然,我可以解析顶级块,然后再次运行解析器来处理每个顶级块。不过,我只是在学习这个话题,所以避免这种做法对我来说很有趣。

这个答案实际上是一条评论(…我的评论很难阅读)

如果您编写一个显式的块结束标记,事情就会变得更清楚

*Block{
  |Line 1
  *SubBlock{
     | line 1.1
     | line 1.2
  }
  |Line 2
  |...
}
语法也变得简单了

block : '*' ID '{' lines '}'
lines : lines  '|' line
      | lines  block
      |

嵌套块语言不是上下文无关的[注2],因此无法使用LALR(k)解析器对其进行解析

然而,嵌套括号语言当然是上下文无关的,通过替换词法扫描器中的初始|序列,将输入转换为插入形式相对容易。转换很简单:

  • 当| s的初始序列比前一行长时,插入
    BEGIN_块
    。(初始序列必须正好长一个|;否则可能是语法错误。)

  • 当| s的初始序列比前一行短时,插入足够的
    END_块
    s以使预期长度达到正确值

  • 这些|本身不会传递给解析器

这与用于解析布局感知语言(如Python和Haskell)的
INDENT
/
DEDENT
策略非常相似。主要区别在于,这里我们不需要一堆缩进级别

转换完成后,语法将如下所示:

content: /* empty */
       | content line
       | content block
block  : head BEGIN_BLOCK content END_BLOCK
       | head
head   : '*' line
flex实现的大致轮廓如下:(参见下面的注释1)

%x缩进内容
%%
静态整型深度=0,新整型深度=0;
/*处理挂起的结束块*/
发送结束:
如果(新深度<深度){
--深度;
返回端块;
}
^“|”[[:blank:][]*{new_depth=1;BEGIN(INDENT);}
^.{new_depth=0;yyless(0);BEGIN(CONTENT);
转到发送_end;}
^\n/*忽略空行*/
{
“|”[[:blank:][]*++新深度;
.{yyless(0);开始(内容);
如果(新深度>深度){
++深度;
if(new_depth>depth){/*报告语法错误*/}
返回开始块;
}否则就要发送完;
}
\n BEGIN(首字母);/*也许您关心这一空行*/
}
/*把你在这里使用的词汇扫描行*/
{
\n开始(初始);
}
笔记:
  • 不是每个人都会对转到感到满意,但它可以节省一些代码重复。状态变量(
    depth
    new\u depth
    )是本地
    静态
    变量这一事实使得lexer不可重入且不可重启(出错后)。这只对玩具代码有用;对于任何实际情况,都应该使词法扫描程序重新进入,并将状态变量放入
    extra
    数据结构中

  • 术语“上下文无关”和“上下文敏感”是对语法的技术描述,因此有点误导。基于这些词的意思的直觉往往是错误的。上下文敏感性的一个非常常见的来源是一种语言,其中有效性取决于产生相同标记序列的同一非终端的两个不同派生。(假设非终端可以派生多个令牌序列;否则,可以消除非终端。)

    在普通编程语言中有很多这样的上下文敏感的例子;通常,语法将允许这些构造,检查将在稍后的语义分析阶段执行。其中包括声明标识符的要求(标识符的两个派生产生相同的字符串)或使用正确数量的参数调用函数的要求(这里,只需要非终端的派生长度匹配,但这足以触发上下文敏感性)

    在这种情况下,要求连续行中两个可能被称为
    bar prefix
    的实例产生相同的| s字符串。在这种情况下,由于效果实际上是句法上的,因此推迟到以后的语义分析将无法解决解析问题。上下文敏感的其他示例是“句法”还是“语法”“语义”是一场辩论,它产生了惊人的热量,却没有对讨论投下太多的光


  • 您所展示的示例有一个不清楚的endoblock(语法会变得奇怪,因为“| |”staff。一些带有eofBlock标记的东西,比如*Block{1.1行*SubBlock{1.1.2行}| 2行|…}会更容易…如果您采用这种方法,您将拥有一种完全不同的语言:)而且
    *
    |
    符号根本就没有必要,因为
    {
    }
    就足够了。@rici,我知道;这就是为什么我说这是一个注释(一种讨论注释的sintax语言替代方案很酷!(通常在我的Lexer中,我包含一个待返回的标记队列)+1对不起,
    extra
    数据结构是flex提供的变量吗?
    %x INDENT CONTENT
    %%
      static int depth = 0, new_depth = 0;
      /* Handle pending END_BLOCKs */
      send_end:
        if (new_depth < depth) {
          --depth;
          return END_BLOCK;
      }
    ^"|"[[:blank:]]*   { new_depth = 1; BEGIN(INDENT); }
    ^.                 { new_depth = 0; yyless(0); BEGIN(CONTENT);
                         goto send_end; }
    ^\n                /* Ignore blank lines */
    <INDENT>{
      "|"[[:blank:]]*  ++new_depth;
      .                { yyless(0); BEGIN(CONTENT);
                         if (new_depth > depth) {
                           ++depth;
                           if (new_depth > depth) { /* Report syntax error */ }
                           return BEGIN_BLOCK;
                         } else goto send_end;
                       }
      \n               BEGIN(INITIAL); /* Maybe you care about this blank line? */
    }
      /* Put whatever you use here to lexically scan the lines */
    <CONTENT>{
      \n               BEGIN(INITIAL);
    }