在前一章中,我们为我们的编程语言 CSCS 创建了基本的构件。也就是说,我们看到了如何解析一个基本的数学表达式,以及如何定义一个函数并将其添加到 CSCS 中。
在本章中,我们将开发基本的控制流语句:if
–else
、while
以及其他一些语句。您将看到它们几乎都是以函数的形式实现的(非常类似于代码清单 10 中指数函数的实现)。我们将把for
循环的实现推迟到第 6 章,操作符、数组和字典,我们将在这里看到它和数组。
我们还将通过添加一些我们在上一章末尾提到的缺失特性来强化我们在上一章开始的基本解析算法。
为了实现if
语句功能,我们使用了第 2 章“向解析器注册函数”一节中使用的相同算法。
- 在
Constants
类中定义if
常量:public``const``string
IF ="if"
; - 实施一个新的类,
IfStatement
,以ParserFunction
为其基类,并覆盖其Evaluate
方法。将会有更多的工作来实现if
声明;真正的实现被转发到Interpreter
类(参见代码清单 15)。 - 向解析器注册该函数:
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 if
和else
控制流语句。原因是else if
和else
将在if
报表处理中一起处理。
在不同的类中实现if
语句的主要原因是,该实现将使用一些我们将与其他控制流语句共享的函数和方法(例如,while
和for
)。参见代码清单 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
块中的一个语句是break
或continue
语句,我们必须完成处理并将这个“中断”或“继续”传播到堆栈中(我们正在处理的if
语句可能在while
或for
循环中)。
我们将在下一节看到如何实现break
和continue
控制流语句。
所以基本上,我们需要跳过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 if
和else
条件及其对应的语句)。这在代码清单 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
条件后没有大括号字符,可以在ProcessBlock
和SkipBlock
方法中更改此功能。这同样适用于接下来的while
和for
循环。 |
现在让我们看看break
和continue
控制流语句的实现。
正如您在代码清单 17 中看到的,在if
语句处理中还有两种额外的变量类型:
if
(结果。Type == Variable
。VarType
。BREAK ||
结果。Type == Variable
。VarType
。CONTINUE) {
return
结果;
}
它们没有出现在代码清单 3 中,但是它们用于典型的break
和continue
控制流语句。我们如何在 CSCS 实施break
和continue
声明?
break
和continue
控制流语句的实现类似于上一节中的指数函数和if
语句,使用我们之前描述的三个步骤。但是首先,让我们也扩展一下代码清单 3 中 enum
VarType
的定义:
public
enum
VarType
{ NONE,NUMBER,STRING,ARRAY,BREAK,CONTINUE };
然后,按照我们的三个步骤:
- 我们在
Constants
类中定义了相应的常量:public``const``string
BREAK ="break"
;public``const``string
CONTINUE ="continue"
; - 我们引入了
Break
和Continue
函数,它们源自ParserFunction
类(参见代码清单 21)。 - 我们向解析器注册新创建的函数:
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);
}
}
就这样!那么这些 Break
和 Continue
功能是如何工作的呢?
正如我们在代码清单 16 中看到的,一旦我们得到一个break
或continue
语句,我们就停止所有的处理,并将它返回(传播)给调用这个语句的人。
Break
和 Continue
在while
和for
循环中有意义。注意,如果没有while
或for
循环,这里我们不会抛出任何错误,这些循环可以使用这些break
和continue
语句——我们只是停止处理当前块。
如果这让您感到困扰,您可以更改此功能,如果没有while
或for
循环,您可以抛出一个异常(我们可能需要一个额外的变量来跟踪嵌套的while
和for
循环)。
在接下来的两节中,我们将看到break
和continue
语句如何在while
和for
循环中使用。
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 == Variable
。VarType
。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只是跳过了“&&”
或“||”
之后的其余表达式。这个实现类似于我们之前看到的 SkipBlock
和 SkipRestBlocks
的实现,所以这里就不展示了——不过,可以在附带的源代码中查阅。
这种新的 UpdateIfBool
法从何而来?它是从 Split
方法调用的,如代码清单 4 所示,就在将新项目添加到列表之前:
UpdateIfBool(脚本,单元格); 列表大。Add(单元格);
接下来,我们展示了到目前为止已经看到的解析器类的 URL 图,我们将在本书的剩余部分中看到这些图。图 3 包含了所使用的主要类的整体结构。

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

4:parser function 类及其后代
在本章中,我们实现了一些基本的控制流语句。我们使用上一章开发的分割合并算法,将它们全部实现为函数。
在下一章中,我们将为我们的语言增加一些功能,实现不同的功能。