#P16968. [SGU418] Deducing Grammar

[SGU418] Deducing Grammar

题目描述

给定一份具有固定框架的 Pascal 自顶向下解析器程序。解析器的主体包含若干个“解析过程”(parsing routine),每个解析过程对应文法中的一个非终结符。你的任务是根据程序结构,机械地恢复出它所对应的 BNF 文法。

解析器整体结构如下:

Program Parser;
Procedure Skip; Forward;
Function Peek:Char; Forward;
Procedure Error; Forward;

<所有解析过程的 Forward 声明>
<所有解析过程的定义>

Var
  St:String;
  Pos:Integer;
...

其中解析过程定义形如:

Procedure <name>;
Begin
  <segment>
  <segment>
  ...
End;

每个过程体至少包含一个 segment

1. 无条件段

无条件段是对另一个解析过程的调用:

<name>;

它在产生式中贡献非终结符 <name>

2. 条件段

条件段具有如下形式:

If Peek=<character> Then Begin
  Skip;
  <segment>...
End Else If Peek=<character> Then Begin
  Skip;
  <segment>...
End Else If ...
End;

也可以只有一个 If 而没有 Else

最后一个 End; 还可以写成:

End Else Error;

表示除前面列出的分支之外,其他情况都会失败。

每个 <character> 都是 ASCII 码在 3312633\sim126 之间、且不是单引号 ' 的单个字符,并用单引号包围,例如 'a'

在每个条件分支中,一旦 Peek 匹配,就一定立即执行一次 Skip;,并且程序中只有这里会调用 Skip

条件分支内部可以没有其他 segment,也可以继续嵌套条件段。

3. 标识符、大小写和空白

  • 解析过程名只由英文字母组成,长度不超过 20;
  • 名字大小写不敏感;
  • 不同解析过程名字不同;
  • 解析过程名不会与 Pascal 关键字或内部函数 SkipPeekError 重名;
  • 必须存在一个名为 Parse 的解析过程;
  • 标识符和关键字的大小写可以任意;
  • 除了两个单词之间必须至少有一个空白、单词内部以及字符串字面量内部不能插入空白外,其余位置可以任意加入或删除空白。

文法转换规则

请严格按照以下规则转换,而不要判断得到的文法是否真的与解析器接受的语言完全等价。

  1. 每个解析过程对应一个非终结符,输出时名字全部转成小写,例如过程 Parse 对应 <parse>
  2. 终结符集合为 ASCII 码 3312633\sim126 中除单引号外的字符;
  3. 对于一个解析过程,考虑其过程体中所有 If 的所有可能求值方式;每一种不会调用 Error 的执行方式都对应一条产生式;
  4. 即使某些执行方式实际上不可能发生(例如同一个条件链里出现重复的 Peek='x'),也必须把它们分别当作产生式处理;
  5. 条件分支匹配一个字符并执行 Skip 时,该字符成为一个终结符;
  6. 无条件调用过程时,加入相应非终结符;
  7. 若条件链结尾没有 Else Error,则“所有条件均不满足、什么也不做”也是一个合法选择,因此会产生空串分支;
  8. 相邻的终结符必须合并为一个字符串字面量,例如应输出 'AB',而不是 'A''B'
  9. 同一非终结符的所有产生式按字典序排序;
  10. 所有非终结符对应的整行也按字典序排序;
  11. 开始符号为 Parse

输入格式

输入是一份满足上述限制的 Pascal 解析器源代码。

输入文件大小不超过 1000010000 字节,每个单词长度不超过 2020

输出格式

输出底层文法的 BNF 形式。

每个解析过程输出一行,格式类似:

<name>::=production1|production2|...

输出中除每行末尾的换行符外不能出现任何空白字符

保证正确输出总长度不超过 1000010000 字节。

样例

Program Parser;
Procedure Skip; Forward;
Function Peek:Char; Forward;
Procedure Error; Forward;

Procedure Parse; Forward;
Procedure Addend; Forward;
Procedure Term; Forward;
Procedure Number; Forward;

Procedure Term;
Begin
  If Peek='0' Then Begin
    Skip;
    Number;
  End Else If Peek='1' Then Begin
    Skip;
    Number;
  End Else If Peek='(' Then Begin
    Skip;
    Parse;
    If Peek=')' Then Begin
      Skip;
    End Else Error;
  End Else If Peek='P' Then Begin
    Skip;
    If Peek='I' Then Begin
      Skip;
    End Else If Peek='E' Then Begin
      Skip;
    End Else Error;
  End Else Error;
End;

Procedure Number;
Begin
  If Peek='0' Then Begin
    Skip;
    Number;
  End Else If Peek='1' Then Begin
    Skip;
    Number;
  End;
End;

Procedure Addend;
Begin
  Term;
  If Peek='*' Then Begin
    Skip;
    Addend;
  End Else If Peek='/' Then Begin
    Skip;
    Addend;
  End;
End;

Procedure Parse;
Begin
  Addend;
  If Peek='+' Then Begin
    Skip;
    Parse;
  End Else If Peek='-' Then Begin
    Skip;
    Parse;
  End;
End;

Var
  St:String;
  Pos:Integer;
Procedure Error;
Begin
  WriteLn('NO');
  Halt;
End;
Procedure Skip;
Begin
  Inc(Pos);
  If Pos>Length(St) Then Error;
End;
Function Peek:Char;
Begin
  Peek:=St[Pos];
End;
Begin
  ReadLn(St);
  St:=St+'#';
  Pos:=1;
  Parse;
  If Pos=Length(St) Then WriteLn('YES') Else WriteLn('NO');
End.
<addend>::=<term>|<term>'*'<addend>|<term>'/'<addend>
<number>::=|'0'<number>|'1'<number>
<parse>::=<addend>|<addend>'+'<parse>|<addend>'-'<parse>
<term>::='('<parse>')'|'0'<number>|'1'<number>|'PE'|'PI'