Parsing 解析过程调用玩具语言

Parsing 解析过程调用玩具语言,parsing,compiler-construction,recursive-descent,Parsing,Compiler Construction,Recursive Descent,我有一种特定的玩具语言,它使用EBNF语法定义过程和过程调用: program = procedure, {procedure} ; procedure = "procedure", NAME, bracedblock ; bracedBlock = "{" , statementlist , "}" ; statementlist = statement, { statement } ; statement = define | if

我有一种特定的玩具语言,它使用EBNF语法定义过程和过程调用:

program = procedure, {procedure} ;
procedure = "procedure", NAME, bracedblock ;
bracedBlock = "{" , statementlist , "}" ;
statementlist = statement, { statement } ;
statement = define | if | while | call | // others omitted for brevity  ;
define = NAME, "=", expression, ";"
if = "if", conditionalblock, "then", bracedBlock, "else", bracedBlock
call = "call" , NAME, ";" ;
// other definitions omitted for brevity
该语言中的程序的标记器已经实现,并返回一个标记向量

现在,在没有过程调用的情况下解析所述程序相当简单:可以直接使用上述语法定义递归下降解析器,并简单地通过标记进行解析。一些进一步的说明:

  • 每个过程可以直接或间接地调用除自身以外的任何其他过程(即无递归),并且这些过程不必按照源代码中的出现顺序(即
    B
    可以在
    A
    之后定义,
    A
    可以调用
    B
    ,反之亦然)

  • 过程名称必须是唯一的,“保留关键字”可用作变量/过程名称

  • 空白并不重要,至少在不同类型的标记中是如此:类似于C/C++

  • 没有范围规则:所有变量都是全局变量

  • “行号”的概念很重要:每个语句都有一个或多个与之关联的行号:
    define
    语句每个只有一个行号,而
    if
    语句本身是两个语句列表的父语句,它有多个行号。例如:

  • LN码
    程序A{
    1.a=5;
    2.b=7;
    3.c=3;
    4.5.如果(b2){
    9.d=d+1;}
    }
    程序C{
    10.e=10;
    11.f=8;
    12.呼叫B;
    }
    
  • 整个程序中的行数是连续的;只有程序定义和<代码> E/SCOR>关键字没有被分配给行编号。行数是由语法定义的,而不是它们在源代码中的位置:例如,考虑“行”<代码> 4 < /代码>和<代码> 5 < /代码> .<

  • 给定每个语句及其行号、使用的变量、变量集和子容器,需要在数据库中设置一些关系。这是一个关键考虑事项

  • 因此,我的问题是:如何解析这些函数调用、维护行号的完整性以及设置关系

    我考虑过“操作系统”的处理方式:在遇到过程调用时,查找与所调用过程匹配的过程,解析被调用方,并将调用堆栈展开回调用方。但是,这会破坏行号顺序:如果以这种方式解析上述程序,
    C
    将具有行号<代码>6至
    8
    包括在内,而不是
    10
    12
    包括在内

    另一个解决方案是按顺序解析整个程序一次,维护一个toposort过程调用,然后按照所述toposort进行第二次解析。由于实现细节,这是有问题的


    有没有更好的方法可以做到这一点?

    尝试在一个在线过程中完全处理程序文本总是很有诱惑力的。不幸的是,这实际上从来不是最简单的解决方案。尝试在线性级数中同时处理每件事会导致一种交织计算的意大利面,并使其全部工作正常t总是对语言进行不必要的限制,这将在以后被证明是不幸的

    因此,我鼓励您重新考虑一些设计决策。如果您使用解析器只是构建程序的某种结构表示——无论是抽象语法树还是三地址码向量,或者其他替代方案——然后在该结构上进行一系列单用途传递的进一步处理在这些表示中,您可能会发现代码是:

    • 更简单,因为计算不必混合
    • 更一般,因为每个过程都可以以最方便的顺序进行,而不是限制输入以适应线性顺序
    • 更具可读性和可维护性
    在多个过程中持久化数据结构可能会略微增加存储需求。但这些结构不太可能占用足够的存储空间,这一点很明显。而且这可能不会增加计算时间;事实上,它甚至可能会减少时间,因为单个过程更简单,更容易优化

    LN      CODE
            procedure A {
    1.         a = 5;
    2.         b = 7;
    3.         c = 3;
    4. 5.      if (b < c) then { call C; } else {
    6.               call B;
               }
    
            procedure B {
    7.         d = 5;
    8.         while (d > 2) { 
    9.             d = d + 1; }
            }
    
            procedure C {
    10.          e = 10; 
    11.          f = 8;
    12.         call B;
            }