Skip to content

Files

Latest commit

8e4ed31 · Jan 10, 2022

History

History
381 lines (268 loc) · 16.5 KB

File metadata and controls

381 lines (268 loc) · 16.5 KB

三、基本控制流语句

在前一章中,我们为我们的编程语言 CSCS 创建了基本的构件。也就是说,我们看到了如何解析一个基本的数学表达式,以及如何定义一个函数并将其添加到 CSCS 中。

在本章中,我们将开发基本的控制流语句:ifelsewhile以及其他一些语句。您将看到它们几乎都是以函数的形式实现的(非常类似于代码清单 10 中指数函数的实现)。我们将把for循环的实现推迟到第 6 章,操作符、数组和字典,我们将在这里看到它和数组。

我们还将通过添加一些我们在上一章末尾提到的缺失特性来强化我们在上一章开始的基本解析算法。

功能

为了实现if语句功能,我们使用了第 2 章“向解析器注册函数”一节中使用的相同算法。

  1. Constants 类中定义if常量:public``const``stringIF ="if"
  2. 实施一个新的类, IfStatement ,以 ParserFunction 为其基类,并覆盖其 Evaluate 方法。将会有更多的工作来实现if声明;真正的实现被转发到 Interpreter 类(参见代码清单 15)。
  3. 向解析器注册该函数:ParserFunction。RegisterFunction( Constants)。new``IfStatement());

代码清单 15:if 语句的实现

    class IfStatement : ParserFunction
    {
      protected override Variable Evaluate(ParsingScript script)
      {
        Variable result = Interpreter.Instance.ProcessIf(script);
        return result;
      }
    } 

请注意,我们只将if 定义为一个函数,而没有使用else ifelse控制流语句。原因是else ifelse将在if报表处理中一起处理。

在不同的类中实现if语句的主要原因是,该实现将使用一些我们将与其他控制流语句共享的函数和方法(例如,whilefor)。参见代码清单 16 了解if语句的实现。

代码清单 16:if 语句的处理

  internal Variable ProcessIf(ParsingScript script)
  {
    int startIfCondition = script.Pointer;

    Variable result = script.ExecuteTo(Constants.END_ARG);
    bool isTrue = Convert.ToBoolean(result.Value);

    if (isTrue)  {
      result = ProcessBlock(script);

      if (result.Type == Variable.VarType.BREAK ||
          result.Type == Variable.VarType.CONTINUE) {
        // Got here from the middle of the if-block. Skip it.
        script.Pointer = startIfCondition;
        SkipBlock(script);
      }

      SkipRestBlocks(script);

      return result;
    }

    // We are in Else. Skip everything in the If statement.
    SkipBlock(script);

    ParsingScript nextData = new ParsingScript(script);
    string nextToken = Utils.GetNextToken(nextData);

    if (Constants.ELSE_IF_LIST.Contains(nextToken))  {
      script.Pointer = nextData.Pointer + 1;
      result = ProcessIf(script);
    } 
    else if (Constants.ELSE_LIST.Contains(nextToken))  {
      script.Pointer = nextData.Pointer + 1;
      result = ProcessBlock(script);
    }

    return Variable.EmptyInstance;
  } 

首先,我们注意到我们在解析脚本中位于 startIfCondition 变量内的位置,然后我们将分割合并算法应用于if关键字后括号中的整个表达式,以便评估if条件。

常数。END_ARG_ARRAY 是一个由)字符组成的数组,这意味着我们将对传递的字符串应用拆分-合并算法,直到我们得到与这个左括号匹配的右括号字符。这里重要的是语句“匹配这个左括号”

这意味着,如果我们在获得右括号之前获得另一个左括号,我们将递归地对下一个左括号与其对应的右括号之间的整个表达式应用拆分合并算法。

如果if语句中的条件为真,那么我们将处于 if (isTrue) 块内。在那里我们调用 ProcessBlock 方法来处理if块的所有语句。参见代码清单 17。

代码清单 17:解释器的实现。ProcessBlock 方法

  Variable ProcessBlock(ParsingScript script)
  {
    int blockStart = script.Pointer;
    Variable result = null;

    while(script.StillValid()) {
      int endGroupRead = script.GoToNextStatement();
      if (endGroupRead > 0) {
        return result != null ? result : new Variable();
      }

      if (!script.StillValid()) {
        throw new ArgumentException("Couldn't process block [" +
          script.Substr(blockStart, Constants.MAX_CHARS_TO_SHOW) + "]");
      }

      result = script.ExecuteTo();

      if (result.Type == Variable.VarType.BREAK ||
          result.Type == Variable.VarType.CONTINUE) {
        return result;
      }
    }
    return result;
  } 

流程块方法中,我们递归地将拆分合并算法应用于常量之间的所有语句。START_GROUP (定义为 {Constants类中)和常量。END_GROUP (定义为**}常量类中的**)字符。解析脚本。gotonexstatement是一个辅助方法,将当前脚本指针移动到下一条语句,如果常量,则返回大于零的整数。已到达 END_GROUP 。有关详细信息,请参见代码清单 18。

代码清单 18:解析脚本的实现。GoToNextStatement 方法

  public int GoToNextStatement()
  {
    int endGroupRead = 0;

    while (StillValid()) {
      char currentChar = Current;
      switch (currentChar) {
        case Constants.END_GROUP:      
  // '}'
          endGroupRead++;
          Forward();           
          return endGroupRead;
        case Constants.START_GROUP:     // '{'
        case Constants.QUOTE:           // '"'
        case Constants.SPACE:           // ' '
        case Constants.END_STATEMENT:   // ';'
        case Constants.END_ARG:         // ')'
          Forward();
          break;
        default: return endGroupRead;
      }
    }

    return endGroupRead;
  }  

让我们继续处理代码清单 16 中的if语句。

如果if块中的一个语句是breakcontinue语句,我们必须完成处理并将这个“中断”或“继续”传播到堆栈中(我们正在处理的if语句可能在whilefor循环中)。

我们将在下一节看到如何实现breakcontinue控制流语句。

所以基本上,我们需要跳过if块中的其余语句。为此,我们回到if块的开始,跳过其中的所有内容。实现如代码清单 19 所示。

请注意,我们还会跳过花括号内的所有嵌套块,如果左花括号和右花括号之间不匹配,则会引发异常。

代码清单 19:解释器的实现。SkipBlock 方法

  void SkipBlock(ParsingScript script)
  {
    int blockStart = script.Pointer;
    int startCount = 0;
    int endCount = 0;

    while (startCount == 0 || startCount > endCount) {
      if (!script.StillValid())  {
        throw new ArgumentException("Couldn't skip block [" +
          script.Substr(blockStart, Constants.MAX_CHARS_TO_SHOW) + "]");
      }
      char currentChar = script.CurrentAndForward();
      switch (currentChar) {
        case Constants.START_GROUP: startCount++; break;
        case Constants.END_GROUP:   endCount++; break;
      }
    }

    if (startCount != endCount) {
      throw new ArgumentException("Mismatched parentheses");
    }
  } 

一旦我们知道if条件为真,并且我们已经执行了属于该if条件的所有语句,我们必须跳过属于if控制流语句的其余语句(其余的也是else ifelse条件及其对应的语句)。这在代码清单 20 中完成:基本上我们在一个循环中调用 SkipBlock 方法。

代码清单 20:解释器的实现。SkipRestBlocks 方法

  void SkipRestBlocks(ParsingScript script)
  {
    while (script.StillValid())  {
      int endOfToken = script.Pointer;
      ParsingScript nextData = new ParsingScript(script);
      string nextToken = Utils.GetNextToken(nextData);

      if (!Constants.ELSE_IF_LIST.Contains(nextToken) &&
          !Constants.ELSE_LIST.Contains(nextToken))  {
        return;
      }

      script.Pointer = nextData.Pointer;
      SkipBlock(script);
    }
  } 

我们已经看到如果if语句中的条件为真会发生什么。当它为假时,我们就跳过整个if块,开始看接下来会发生什么。如果接下来出现一个else if 语句,那么我们递归调用 ProcessIf 方法。

如果是else语句,那么我们只需要执行else块内部的所有语句。我们可以通过调用我们已经在代码清单 19 中看到的 ProcessBlock 方法来做到这一点。

| | 注意:需要注意的是if语句总是需要花括号。如果if条件后没有大括号字符,可以在ProcessBlockSkipBlock方法中更改此功能。这同样适用于接下来的whilefor循环。 |

现在让我们看看breakcontinue控制流语句的实现。

语句

正如您在代码清单 17 中看到的,在if语句处理中还有两种额外的变量类型:

if(结果。Type == VariableVarType。BREAK || 结果。Type == VariableVarType。CONTINUE) { return结果; }

它们没有出现在代码清单 3 中,但是它们用于典型的breakcontinue控制流语句。我们如何在 CSCS 实施breakcontinue声明?

breakcontinue控制流语句的实现类似于上一节中的指数函数和if语句,使用我们之前描述的三个步骤。但是首先,让我们也扩展一下代码清单 3 中 enum VarType 的定义:

public enum VarType { NONE,NUMBER,STRING,ARRAY,BREAK,CONTINUE };

然后,按照我们的三个步骤:

  1. 我们在 Constants 类中定义了相应的常量:public``const``stringBREAK ="break"public``const``stringCONTINUE ="continue"
  2. 我们引入了 BreakContinue 函数,它们源自 ParserFunction 类(参见代码清单 21)。
  3. 我们向解析器注册新创建的函数:ParserFunction。RegisterFunction( Constants)。new``BreakStatement()); ParserFunction。RegisterFunction( Constants)。CONTINUE,new``ContinueStatement();

代码清单 21:中断和继续函数的实现

  class BreakStatement : ParserFunction
  {
    protected override Variable Evaluate(ParsingScript script)
    {
      return new Variable(Variable.VarType.BREAK);
    }
  }

  class ContinueStatement : ParserFunction
  {
    protected override Variable Evaluate(ParsingScript script)
    {
      return new Variable(Variable.VarType.CONTINUE);
    }
  }

就这样!那么这些 BreakContinue 功能是如何工作的呢?

正如我们在代码清单 16 中看到的,一旦我们得到一个breakcontinue语句,我们就停止所有的处理,并将它返回(传播)给调用这个语句的人。

BreakContinuewhilefor循环中有意义。注意,如果没有whilefor循环,这里我们不会抛出任何错误,这些循环可以使用这些breakcontinue语句——我们只是停止处理当前块。

如果这让您感到困扰,您可以更改此功能,如果没有whilefor循环,您可以抛出一个异常(我们可能需要一个额外的变量来跟踪嵌套的whilefor循环)。

在接下来的两节中,我们将看到breakcontinue语句如何在whilefor循环中使用。

循环

while循环的实现类似于if控制流语句的实现。while循环也被实现为从 ParserFunction 类派生的函数对象。我讨厌重复,但是同样有三个步骤:定义相应的常数,实现从 ParserFunction 类派生的类的 Evaluate 方法,并通过向解析器注册这个新创建的类将它们粘合在一起。所以我们跳过这些步骤。

现在我们也可以重用我们在if语句实现中看到的一些方法。

类似于代码清单 16 中的 ProcessIf 方法,我们在代码清单 22 中实现了 ProcessWhile 类。它们看起来确实很相似——我们只是在一段时间内检查条件是否为真,而不是一次。

代码清单 While 循环的实现

  internal Variable ProcessWhile(ParsingScript script)
  {
    int startWhileCondition = script.Pointer;
    int cycles = 0;
    bool stillValid = true;

    while (stillValid) {
      script.Pointer = startWhileCondition;
      Variable condResult = script.ExecuteTo(Constants.END_ARG);
      stillValid = Convert.ToBoolean(condResult.Value);
      if (!stillValid) {
        break;
      }

      // Check for an infinite loop if we are comparing same values:
      if (MAX_LOOPS > 0 && ++cycles >= MAX_LOOPS) {
        throw new ArgumentException("Looks like an infinite loop after " +

  cycles + " cycles.");
      }

      Variable result = ProcessBlock(script);
      if (result.IsReturn || result.Type == Variable.VarType.BREAK) {
        script.Pointer = startWhileCondition;
        break;
      }
    }

    // The while condition is not true anymore: must skip the whole while
    // block before continuing with next statements.
    SkipBlock(script);
    return Variable.EmptyInstance;
  } 

请注意,我们还会检查循环总数,并将其与 MAX_LOOPS 变量进行比较。

这个数字可以在配置文件中设置。如果它大于零,那么我们在这个循环数之后停止。没有任何编程语言会定义一个数字,在这个数字之后可能会假设你有一个无限循环。原因是,编程语言的架构师永远不会事先知道什么是你的“无限”数字。但是,您比那些架构师更了解自己的需求,所以您可以将这个限制设置为一个对您来说合理的数量——比如说 100,000——如果您知道您永远不会有那么多循环,除非您有编程错误。

当出现这种情况时,我们也从while循环中断开:

if(结果。IsReturn ||结果。Type == VariableVarType。BREAK) { 剧本。指针= startWhileCondition break; }

请注意,在脱离while循环之前,我们将解析脚本的指针重置为while条件的开头。我们这样做是因为通过调用 SkipBlock 方法更容易跳过花括号之间的整个块,但是您也可以跳过块的其余部分;然后,您需要记录已经在while块内部处理过的左右花括号的数量。

我们看到了如何触发结果的类型变量。最后一节末尾的 VarType.BREAK 。当在函数内部执行 return 语句时, IsReturn 属性将被设置为真。我们将在下一章看到它是如何完成的。

评估

计算布尔表达式时使用短路计算。它只是意味着只需要评估必要的条件。例如,如果条件 1 为真,那么在“条件 1 ||条件 2 ”中,条件 2 不需要评估,因为整个表达式都将为真,而不管条件 2

同样,如果条件 1 为假,那么在“条件 1 & &条件 2 ”中,条件 2 也不需要评估,因为整个表达式都是假的。这在代码清单 23 中实现。

代码清单 23:短路评估的实现

  static void UpdateIfBool(ParsingScript script, Variable current)
  {
    if ((current.Action == "&&" && current.Value == 0.0) ||
        (current.Action == "||" && current.Value != 0.0)) {
      // Short-circuit evaluation: don't need to evaluate anymore.
      Utils.SkipRestExpr(script);
      current.Action = Constants.NULL_ACTION;
    }
  }

实用主义的方法。skippresexpr只是跳过了“&&”“||”之后的其余表达式。这个实现类似于我们之前看到的 SkipBlockSkipRestBlocks 的实现,所以这里就不展示了——不过,可以在附带的源代码中查阅。

这种新的 UpdateIfBool 法从何而来?它是从 Split 方法调用的,如代码清单 4 所示,就在将新项目添加到列表之前:

UpdateIfBool(脚本,单元格); 列表大。Add(单元格);

接下来,我们展示了到目前为止已经看到的解析器类的 URL 图,我们将在本书的剩余部分中看到这些图。图 3 包含了所使用的主要类的整体结构。

![](img/image004.png)

3:解析器类的整体结构

图 4 包含了 ParserFunction 类以及从它派生的所有类。它并不完整,因为后代的数量实际上是无限的;例如,所有可能的数学函数,像幂、圆、对数、余弦等等,都属于那里。为了给解析器添加新的功能,您扩展了 ParserFunction 类,新的类将属于图 4。

![](img/image005.png)

4:parser function 类及其后代

在本章中,我们实现了一些基本的控制流语句。我们使用上一章开发的分割合并算法,将它们全部实现为函数。

在下一章中,我们将为我们的语言增加一些功能,实现不同的功能。