【问题标题】:Parser implementations comparison: correctness confirmation and (perhaps) optimization解析器实现比较:正确性确认和(也许)优化
【发布时间】:2011-12-13 13:50:26
【问题描述】:

我已经实现了两个表达式解析器,递归下降和运算符优先级。它们在 Object 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 次)!我的编译器类和我阅读的所有文章都指出,由于函数调用较少,运算符优先级解析应该比递归下降更有效,这也是我所期望的,因为代码似乎表明了这一点。我有 inline-d 附加函数来获取运算符优先级和右关联性测试,但这似乎没有多大帮助。请告诉我我是否做错了什么。随时要求澄清某些事情。

【问题讨论】:

    标签: parsing comparison operator-precedence recursive-descent


    【解决方案1】:

    与所有事物一样,权衡很重要。递归下降显式检查每个操作符标记,因此如果您有 N 个操作符,它本质上必须进行 N 次测试。运算符优先级只需知道某物是运算符标记,并使用查找表。因此,它可以只使用几次查找,而不是 N 次测试。因此,如果您有很多运算符,则运算符优先级应该更快。 如果你的语法只有几个,那么即使仔细调整也不清楚它是否是一个胜利。

    总体而言,解析器的速度可能并不重要。无论您正在构建使用解析器的任何工具,都会在解析器之外的其他地方花费更多的精力。

    当机器很小时,运算符优先级是一个有趣的想法,因为可以在表中编码复杂的运算符层次结构。大多数情况下,它提供的折衷对于典型的工作台(甚至手持)来说并不重要。对于简单的语法,我会坚持使用递归下降,而对于其他任何东西,我都会坚持使用任何类型的解析器生成器。

    【讨论】:

    • So operator precedence should be faster if you have a lot of operators 典型的编程语言,即使支持运算符重载,通常也没有很多运算符。你能给出一个被认为是“很多”的大概数字吗?
    • 没有。有太多次要影响(您如何编写词法分析器,用您的语言进行字符串管理的开销,......)您几乎必须构建它,调整它,然后确定它是否足够好。我的论点是运算符优先级不太可能在性能方面为您带来任何有趣的东西。所以不值得麻烦。你做了这个实验很好,所以你可以对答案感到满意。
    • 我这样做只是为了“乐趣”(乐趣:自我研究,我大脑的科学方面不知何故被激活),我想这对我来说是值得的,因为有一天我可能会将它传播给任何询问的人。不过,次要影响应该不是问题,因为两个解析器都使用相同的词法分析器。如果一个人放慢了速度,另一个人也应该感觉到。解析器代码根本不处理字符串。
    猜你喜欢
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    • 2014-04-03
    • 2022-08-22
    • 2014-03-15
    • 2023-02-11
    • 2020-03-27
    • 1970-01-01
    相关资源
    最近更新 更多