parsing - 解析器实现比较:正确性确认和(可能)优化

标签 parsing comparison operator-precedence recursive-descent

我已经实现了两个表达式解析器,分别是递归下降和运算符优先级。它们在对象Pascal中实现。这是递归下降:

function ParseFactor: PNode;
var
  Temp: PNode;
begin
  Result := ParsePrimary;
  if t.Kind in [tkDoubleAsterisks] then begin
    New(Temp);
    Temp^.Kind := nkPower;
    Temp^.LeftOperand := Result;
    // power operator is right associative
    Lexer.Get(t);
    Temp^.RightOperand := ParseFactor();
    Result := Temp;
  end;
end;

function ParseTerm: PNode;
var
  Temp: PNode;
begin
  Result := ParseFactor;
  while t.Kind in [tkAmpersand,tkAsterisk,tkSlash,tkDoubleLessThan,tkDoubleGreaterThan] do begin
    New(Temp);
    case t.Kind of
      tkAmpersand        : Temp^.Kind := nkAnd;
      tkAsterisk         : Temp^.Kind := nkMultiplication;
      tkSlash            : Temp^.Kind := nkDivision;
      tkDoubleLessThan   : Temp^.Kind := nkShiftLeft;
      tkDoubleGreaterThan: Temp^.Kind := nkShiftRight;
    end;
    Temp^.LeftOperand := Result;
    Lexer.Get(t);
    Temp^.RightOperand := ParseFactor;
    Result := Temp;
  end;
end;

function ParseExpression: PNode;
var
  Temp: PNode;
begin
  Result := ParseTerm;
  while t.Kind in [tkHorzBar,tkCaret,tkPlus,tkMinus] do begin
    New(Temp);
    case t.Kind of
      tkHorzBar: Temp^.Kind := nkOr;
      tkCaret  : Temp^.Kind := nkXor;
      tkPlus   : Temp^.Kind := nkAddition;
      tkMinus  : Temp^.Kind := nkSubstraction;
    end;
    Temp^.LeftOperand := Result;
    Lexer.Get(t);
    Temp^.RightOperand := ParseTerm;
    Result := Temp;
  end;
end;

这是运算符的优先级:
function GetTokenPrecedence(const Kind: TTokenKind): Integer; inline;
begin
  case Kind of
    tkHorzBar,tkCaret,tkPlus,tkMinus:
      Result := 1;
    tkAmpersand,tkAsterisk,tkSlash,tkDoubleLessThan,tkDoubleGreaterThan:
      Result := 2;
    tkDoubleAsterisks:
      Result := 3;
    else
      Result := -1;
  end;
end;

function IsRightAssociative(const Kind: TTokenKind): Boolean; inline;
begin
  Result := Kind in [tkDoubleAsterisks];
end;

function ParseBinaryRHSExpression(LHS: PNode; const CurrentPrecedence: Integer): PNode;
var
  Op: TTokenKind;
  RHS: PNode;
begin
  while GetTokenPrecedence(t.Kind) >= CurrentPrecedence do begin
    Op := t.Kind;
    Lexer.Get(t);
    RHS := ParsePrimary;
    while (GetTokenPrecedence(t.Kind) > GetTokenPrecedence(Op))
      or (IsRightAssociative(t.Kind) and (GetTokenPrecedence(t.Kind) >= GetTokenPrecedence(Op)))
    do begin
      RHS := ParseBinaryRHSExpression(RHS,GetTokenPrecedence(t.Kind));
    end;
    New(Result);
    case Op of
      tkHorzBar          : Result^.Kind := nkOr;
      tkCaret            : Result^.Kind := nkXor;
      tkPlus             : Result^.Kind := nkAddition;
      tkMinus            : Result^.Kind := nkSubstraction;
      tkAmpersand        : Result^.Kind := nkAnd;
      tkAsterisk         : Result^.Kind := nkMultiplication;
      tkSlash            : Result^.Kind := nkDivision;
      tkDoubleLessThan   : Result^.Kind := nkShiftLeft;
      tkDoubleGreaterThan: Result^.Kind := nkShiftRight;
      tkDoubleAsterisks  : Result^.Kind := nkPower;
    end;
    Result^.LeftOperand := LHS;
    Result^.RightOperand := RHS;
    LHS := Result;
  end;
  Result := LHS;
end;

function ParseExpression: PNode;
begin
  Result := ParsePrimary;
  if Assigned(Result) then begin
    Result := ParseBinaryRHSExpression(Result,0);
  end;
end;

这些是两者之间唯一的本质区别。一些简单的测试表明,两者似乎都可以正确解析。但是,我不确定操作符优先级的实现,因为这是我第一次真正实现它。令我困扰的令人惊讶的事实是,它的运行速度比递归下降版本慢(需要1.5倍以上)!我的编译器类和我读过的所有文章都指出,由于函数调用较少,因此运算符优先级解析应该比递归下降更有效,这也是我所期望的,因为代码似乎表明了这一点。我已经内联了一些附加功能来获取运算符的优先级和右相关性测试,但这似乎并没有太大帮助。请告诉我我做错了什么。随意要求某些事情的清晰度。

最佳答案

与所有事物一样,权衡也很重要。递归下降显式检查每个运算符的标记,因此,如果您有N个运算符,则它实际上必须进行N个测试。运算符优先级仅需知道某物是运算符 token ,然后使用查找表即可。因此,它可以仅使用几个查询来代替N个测试。因此,如果您有很多运算符,运算符优先级应该更快。
如果您的语法只有几个,那么即使经过精心调整,也不清楚它的优势。

在总体方案中,解析器的速度可能无关紧要。无论使用哪种构建器来构建使用解析器的工具,都将花费比解析器更多的精力。

当机器很小时,运算符优先级是一个有趣的想法,因为可以在表中编码复杂的运算符层次结构。通常,它提供的权衡对于典型的工作流程(甚至是手持式)都无关紧要。对于简单的语法,我会坚持递归下降,对于其他任何事情,我都会坚持使用任何形式的解析器生成器。

关于parsing - 解析器实现比较:正确性确认和(可能)优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7866631/

相关文章:

python - Python中基于标识符的文件并行解析

swift - Swift 中的自定义运算符

java - 在字符串上解析 double 给出错误的结果

android - 在 Kotlinx 序列化中使字段可选

java - 使用 Java 解析 XML 中的日期

c - switch case 主体中条件语句的行为(C 语言)

boolean 类型的 Python 3 小问题

testing - 与Selenium的模糊截图对比

python - 比较两个字典并检查有多少(键,值)对是相等的

mysql - 需要帮助优化比较两个表的 mysql 查询