在本章中,我们将看到如何在 CSCS 实现自定义函数和方法。我们还将看到如何抛出和捕获异常,因为在实现异常堆栈时,我们需要使用关于被调用的函数和方法的信息(“在堆栈上”)。
相关的函数和方法通常在同一个文件中一起实现,所以包含来自不同文件的代码是一个很好的特性。我们将看看下一步怎么做。
为了实现包含文件功能,我们使用了与其他函数相同的算法——在从 ParserFunction
类派生的类中实现。
IncludeFile
源自 ParserFunction
类(参见图 4 中的 UML 图)。查看代码清单 40 中的实现。
代码清单 IncludeFile 类的实现
class IncludeFile : ParserFunction
{
protected override Variable Evaluate(ParsingScript script)
{
string filename = Utils.GetItem(script).AsString();
string[] lines = File.ReadAllLines(filename);
string includeFile = string.Join(Environment.NewLine, lines);
Dictionary<int, int> char2Line;
string includeScript = Utils.ConvertToScript(includeFile,
out char2Line);
ParsingScript tempScript = new ParsingScript(includeScript, 0,
char2Line);
tempScript.Filename = filename;
tempScript.OriginalScript = string.Join(
Constants.END_LINE.ToString(), lines);
while (tempScript.Pointer < includeScript.Length) {
tempScript.ExecuteTo();
tempScript.GoToNextStatement();
}
return Variable.EmptyInstance;
}
}
Utils.GetItem
法一方面只是 Parser.SplitAndMerge
法的一种包装。此外,它还处理引号之间的字符串表达式。另一方面,它还将大括号之间的表达式转换为数组。
代码清单 GetItem 方法的实现
public static Variable GetItem(ParsingScript script)
{
script.MoveForwardIf(Constants.NEXT_ARG, Constants.SPACE);
Utils.CheckNotEnd(script);
Variable value = new Variable();
bool inQuotes = script.Current == Constants.QUOTE;
if (script.Current == Constants.START_GROUP) {
// We are extracting a list between curly braces.
script.Forward(); // Skip the first brace.
bool isList = true;
value.Tuple = GetArgs(script, Constants.START_GROUP,
Constants.END_GROUP, out isList);
return value;
}
else {
// A variable, a function, or a number.
Variable var = script.Execute(Constants.NEXT_OR_END_ARRAY);
value.Copy(var);
}
if (inQuotes) {
script.MoveForwardIf(Constants.QUOTE);
}
script.MoveForwardIf(Constants.SPACE);
return value;
}
此外, IncludeFile.Evaluate
方法调用 Utils.ConvertToScript
方法,如代码清单 42 所示。
这实际上是理解 CSCS 语言工作原理的关键方法之一。它显示了要解析的脚本的第一个预处理步骤。基本上,该方法将传递的字符串翻译成另一个字符串,我们的解析器可以理解。除此之外,该方法还删除了注释中的所有文本和所有不必要的空白,如空格、制表符或新行。
代码清单 Utils 的实现。ConvertToScript 方法
public static string ConvertToScript(string source,
out Dictionary<int, int> char2Line)
{
StringBuilder sb = new StringBuilder(source.Length);
char2Line = new Dictionary<int, int>();
bool inQuotes
= false;
bool spaceOK
= false;
bool inComments = false;
bool simpleComments = false;
char previous
= Constants.EMPTY;
int parentheses = 0;
int groups = 0;
int lineNumber = 0;
int lastScriptLength = 0;
for (int i = 0; i < source.Length; i++) {
char ch = source[i];
char next = i + 1 < source.Length ? source[i + 1] : Constants.EMPTY;
if (ch == '\n') {
if (sb.Length > lastScriptLength) {
char2Line[sb.Length - 1] = lineNumber;
lastScriptLength = sb.Length;
}
lineNumber++;
}
if (inComments && ((simpleComments && ch != '\n') ||
(!simpleComments && ch != '*'))) {
continue;
}
switch (ch) {
case '/':
if (inComments || next == '/' || next == '*') {
inComments = true;
simpleComments = simpleComments || next == '/';
continue;
}
break;
case '*':
if (inComments && next == '/') {
i++; // skip next character
inComments = false;
continue;
}
break;
case '“':
case '”':
case '"':
ch = '"';
if (!inComments && previous != '\\') {
inQuotes = !inQuotes;
}
break;
case ' ':
if (inQuotes) {
sb.Append (ch);
} else {
spaceOK = KeepSpace(sb, next)|| (previous != Constants.EMPTY &&
previous != Constants.NEXT_ARG && spaceOK);
bool spaceOKonce = KeepSpaceOnce(sb, next);
if (spaceOK || spaceOKonce) sb.Append(ch);
}
continue;
case '\t':
case '\r':
if (inQuotes) sb.Append(ch);
continue;
case '\n':
if (simpleComments) {
inComments = simpleComments = false;
}
spaceOK = false;
continue;
case Constants.END_ARG:
if (!inQuotes) {
parentheses--;
spaceOK = false;
}
break;
case Constants.START_ARG:
if (!inQuotes) parentheses++;
break;
case Constants.END_GROUP:
if (!inQuotes) {
groups--;
spaceOK = false;
}
break;
case Constants.START_GROUP:
if (!inQuotes) groups++;
break;
case Constants.END_STATEMENT:
if (!inQuotes) spaceOK = false;
break;
default: break;
}
if (!inComments) {
sb.Append(ch);
}
previous = ch;
}
// Here we can throw an exception if the “parentheses”
is not 0.
// Same
for “groups”. Nonzero means there are some unmatched
//
parentheses or curly braces.
return sb.ToString();
}
Utils.CovertToScript
法使用了一个辅助的 char2Line
字典。需要在解析脚本中有一个对原始行号的引用,以防抛出异常(或者代码是错误的),以便用户知道问题发生在哪一行。我们将在第 7 章本地化的“获取出现错误的行号”一节中更详细地看到它。
如果你想为注释定义一个新的样式,它将出现在这个方法中。这个方法还允许用户为一个引用字符使用不同的字符:“和”字符都被“字符”替换,这是我们的解析器唯一能理解的字符。
我们如何知道哪些空间必须移除,哪些不能移除?首先,如果我们在引号内,我们会保持一切原样,因为表达式只是一个字符串值。
在其他情况下,我们在代币之间最多留一个空格。对于某些函数,我们需要空格来分隔标记,但是对于其他函数,不需要空格,标记由其他字符分隔,例如左括号。
需要空格作为分隔标记的函数的一个例子是变更目录函数:cd C:\Windows
。不需要空格来分隔记号的函数是任何数学函数,例如在sin(2*10)
中。正弦函数和左括号之间不需要空格。
代码清单 43 包含在 CovertToScript
方法中使用的辅助函数,用于确定我们是否需要保留一个空格。
代码清单 43:保留空间函数的实现
public static bool EndsWithFunction(string buffer, List<string> functions)
{
foreach (string key in functions) {
if (buffer.EndsWith(key, StringComparison.OrdinalIgnoreCase)) {
char prev = key.Length >= buffer.Length ?
Constants.END_STATEMENT :
buffer [buffer.Length - key.Length - 1];
if (Constants.TOKEN_SEPARATION.Contains(prev)) {
return true;
}
}
}
return false;
}
public static bool SpaceNotNeeded(char next)
{
return (next == Constants.SPACE || next == Constants.START_ARG ||
next == Constants.START_GROUP || next == Constants.START_ARRAY ||
next == Constants.EMPTY);
}
public static bool KeepSpace(StringBuilder sb, char next)
{
if (SpaceNotNeeded(next)) {
return false;
}
return EndsWithFunction(sb.ToString(), Constants.FUNCT_WITH_SPACE);
}
public static bool KeepSpaceOnce(StringBuilder sb, char next)
{
if (SpaceNotNeeded(next)) {
return false;
}
return EndsWithFunction(sb.ToString(), Constants.FUNCT_WITH_SPACE_ONCE);
}
如您所见,我们区分了两种需要空格作为分隔标记的函数。参见代码清单 44。
代码清单 44:允许空格作为分隔符号的函数
// Functions that allow a space separator after them, on top of
the
// parentheses. The function arguments may have spaces as well,
// e.g.
“copy a.txt b.txt”
public static List<string> FUNCT_WITH_SPACE = new List<string> {
APPENDLINE, CD, CONNECTSRV, COPY, DELETE, DIR, EXISTS,
FINDFILES, FINDSTR, FUNCTION, MKDIR, MORE, MOVE, PRINT, READFILE, RUN,
SHOW, STARTSRV, TAIL, TRANSLATE, WRITE, WRITELINE, WRITENL
};
// Functions that allow a space separator after them, on top of the
// parentheses, but
only once, i.e. function arguments are not allowed
// to have spaces between them e.g. return a*b;
throw exc;
public static List<string> FUNCT_WITH_SPACE_ONCE = new List<string> {
RETURN, THROW
};
基本上,我们希望为我们的语言维护两种操作模式——命令行语言(或 Unix 术语中的“shell”语言),它主要使用空格作为标记之间的分隔标准,以及常规的脚本语言,它使用括号作为分隔标准。
让我们回到代码清单 40 中的 IncludeFile
类的实现。一旦我们将包含脚本的字符串转换成我们理解的格式(在代码清单 42 中),我们就检查脚本的每个语句,并将整个分割合并算法应用于每个语句。
要转到脚本中的下一条语句,我们使用解析脚本。gotonexstatement辅助方法(如代码清单 18 所示)。特别是,它处理的情况是,最后处理的语句也是一组语句中的最后一个语句(在大括号之间),或者我们需要去掉分隔不同语句的字符(定义为**END _ STATEMENT = ';'**在Constants
班。
为了抛出异常,我们使用了与其他控制流函数相同的方法:我们将 ThrowFunction
类实现为 ParserFunction
类。 ThrowFunction
类包含在图 4 中,它的实现在代码清单 45 中。
代码清单 ThrowFunction 类的实现
class ThrowFunction : ParserFunction
{
protected override Variable Evaluate(ParsingScript script)
{
// 1\. Extract what to throw.
Variable arg = Utils.GetItem(script);
// 2\. Convert it to a string.
string result = arg.AsString();
// 3\. Throw it!
throw new ArgumentException(result);
}
}
这个类在解析器中注册如下:
public``const``string
THROW ="throw"
; ParserFunction`。注册功能` ( `Constants`)。THROW,`new
ThrowFunction`();
这意味着只要我们的解析器看到类似如下的内容:
扔"Critical exception!"
;
在 CSCS 代码中,我们的 C# 代码将抛出一个异常。我们怎么才能抓住它?
要捕捉异常,我们必须有一个 try block。catch 模块的处理将跟随 try 块的处理。 TryBlock
类也是由 ParserFunction
类衍生而来;参见图 4。它的实现在代码清单 46 中。主要功能在Interpreter
类中,我们可以重用已经实现的 ProcessBlock
(代码清单 17)SkipBlock
(代码清单 19)和 SkipRestBlocks
(代码清单 20)方法。
代码清单 46:尝试和捕获功能的实现
class TryBlock : ParserFunction
{
protected override Variable Evaluate(ParsingScript script)
{
return Interpreter.Instance.ProcessTry(script);
}
}
internal Variable ProcessTry(ParsingScript script)
{
int startTryCondition = script.Pointer - 1;
int currentStackLevel = ParserFunction.GetCurrentStackLevel();
Exception exception = null;
Variable result = null;
try {
result = ProcessBlock(script);
} catch(ArgumentException exc) {
exception = exc;
}
if (exception != null ||
result.Type == Variable.VarType.BREAK ||
result.Type == Variable.VarType.CONTINUE) {
// We are here from the middle of the try-block either because
// an exception was thrown or because of a Break/Continue. Skip it.
script.Pointer = startTryCondition;
SkipBlock(script);
}
string catchToken = Utils.GetNextToken(script);
script.Forward(); // skip opening parenthesis
// The next token after the try block must be a catch.
if (!Constants.CATCH_LIST.Contains(catchToken)) {
throw new ArgumentException("Expecting a 'catch()' but got [" +
catchToken + "]");
}
string exceptionName = Utils.GetNextToken(script);
script.Forward(); // skip closing parenthesis
if (exception != null) {
string excStack = CreateExceptionStack(exceptionName,
currentStackLevel);
ParserFunction.InvalidateStacksAfterLevel(currentStackLevel);
GetVarFunction excFunc = new GetVarFunction(
new Variable(exception.Message + excStack));
ParserFunction.AddGlobalOrLocalVariable(exceptionName, excFunc);
result = ProcessBlock(script);
ParserFunction.PopLocalVariable(exceptionName);
} else {
SkipBlock (script);
}
SkipRestBlocks(script);
return result;
}
TryBlock
类在解析器中注册如下:
public``const``string
TRY ="try"
;
ParserFunction
。RegisterFunction( Constants
)。new
TRY,TryBlock
());
当我们捕捉到一个异常时,我们还会创建一个异常堆栈;参见代码清单 47。
代码清单 47:解释器的实现。CreateExceptionStack 方法
static string CreateExceptionStack(string exceptionName,
int lowestStackLevel)
{
string result = "";
Stack<ParserFunction.StackLevel> stack = ParserFunction.ExecutionStack;
int level = stack.Count;
foreach (ParserFunction.StackLevel stackLevel in stack) {
if (level-- < lowestStackLevel) {
break;
}
if (string.IsNullOrWhiteSpace(stackLevel.Name)) {
continue;
}
result += Environment.NewLine + " " + stackLevel.Name + "()";
}
if (!string.IsNullOrWhiteSpace (result)) {
result = " --> " + exceptionName + result;
}
return result;
}
为了使用 CSCS 的异常数据,我们添加了一个包含异常信息的变量。这个变量是 GetVarFunction
类,我们将其添加到解析器中:
ParserFunction
。addglobalorlocalvariable(异常名称,excfunc);
excFunc
变量的类型是**GetVarFunction
**;参见代码清单 48 中的实现。 GetVarFunction
类只是抛出异常的包装器。我们使用异常名向解析器注册它,这样一旦 CSCS 代码通过它的名称访问异常,它就会获得我们提供的异常信息。您可以很容易地在异常信息中添加更多有趣的东西,比如为异常名称和异常堆栈设置单独的字段。我们将在本章末尾看到一些异常的例子。
代码清单 GetVarFunction 类的实现
class GetVarFunction : ParserFunction
{
internal GetVarFunction(Variable value)
{
m_value = value;
}
protected override Variable Evaluate(ParsingScript script)
{
return m_value;
}
private Variable m_value;
}
为了让一切正常工作,我们为 ParserFunction
类定义了一些新的数据结构。特别是, StackLevel
类包含了 CSCS 函数内部使用的所有局部变量;参见代码清单 49。
堆栈****s _ locals成员保存一个堆栈,该堆栈包含堆栈上被调用的每个函数的局部变量。 AddLocalVariable
和 AddStackLevel
方法添加了一个新的局部变量,相应地,还有一个新的 StackLevel
。
| | 注意:如果已经有同名的全局变量或函数,则在这里不允许使用本地名称。 |
字典<字符串,parser function>s _ functions成员保存所有全局变量和函数(在 CSCS 所有变量和函数都是相同的;两者都源于 ParserFunction
类)。字典的关键字是函数名或变量名。 RegisterFunction
和 AddGlobal
方法都添加了新的变量或函数。还有 isNative
布尔标志,表示该函数是在 C# 中本机实现的,还是在 CSCS 实现的自定义函数。
当试图将函数或变量名与实际函数或变量相关联时,调用 GetFunction
。请注意,它首先搜索本地名称—它们优先于全局名称。
代码清单 ParserFunction 类中全局和局部变量的实现
public class ParserFunction
{
public class StackLevel {
public StackLevel(string name = null) {
Name = name;
Variables = new Dictionary<string, ParserFunction>();
}
public string Name { get; set; }
public Dictionary<string, ParserFunction> Variables { get; set; }
}
// Global functions and variables:
private static Dictionary<string, ParserFunction> s_functions =
new Dictionary<string, ParserFunction>();
// Local variables:
// Stack of the functions being executed:
private static Stack<StackLevel> s_locals = new Stack<StackLevel>();
public static Stack<StackLevel> ExecutionStack {
get { return s_locals; } }
public static ParserFunction GetFunction(string item)
{
ParserFunction impl;
// First search among local variables.
if (s_locals.Count > 0) {
Dictionary<string, ParserFunction> local = s_locals.Peek().Variables;
if (local.TryGetValue(item, out impl)) {
// Local function exists (a local variable).
return impl;
}
}
if (s_functions.TryGetValue(item, out impl)) {
// A global function exists and is registered
// (e.g. pi, exp, or a variable).
return impl.NewInstance();
}
return null;
}
public static bool FunctionExists(string item)
{
bool exists = false;
// First check if the local function stack has this variable defined.
if (s_locals.Count > 0) {
Dictionary<string, ParserFunction> local = s_locals.Peek().Variables;
exists = local.ContainsKey(item);
}
// If it is not defined locally, then check globally:
return exists || s_functions.ContainsKey(item);
}
public static void AddGlobalOrLocalVariable(string name,
ParserFunction function) {
function.Name = name;
if (s_locals.Count > 0) {
AddLocalVariable(function);
} else {
AddGlobal(name, function, false /* not native */);
}
}
public static void RegisterFunction(string name, ParserFunction function,
bool isNative = true) {
AddGlobal(name, function, isNative);
}
static void AddGlobal(string name, ParserFunction function,
bool isNative = true) {
function.isNative = isNative;
s_functions[name] = function;
if (string.IsNullOrWhiteSpace(function.Name)) {
function.Name = name;
}
if (!isNative) {
Translation.AddTempKeyword(name);
}
}
public static void AddLocalVariables(StackLevel locals)
{
s_locals.Push(locals);
}
public static void AddStackLevel(string name)
{
s_locals.Push(new StackLevel(name));
}
public static void AddLocalVariable(ParserFunction local)
{
local.m_isGlobal = false;
StackLevel locals = null;
if (s_locals.Count == 0) {
locals = new StackLevel();
s_locals.Push(locals);
} else {
locals = s_locals.Peek();
}
locals.Variables[local.Name] = local;
Translation.AddTempKeyword(local.Name);
}
public static void PopLocalVariables()
{
s_locals.Pop();
}
public static int GetCurrentStackLevel()
{
return s_locals.Count;
}
public static void InvalidateStacksAfterLevel(int level)
{
while (s_locals.Count > level) {
s_locals.Pop();
}
}
public static void PopLocalVariable(string name)
{
if (s_locals.Count == 0) {
return;
}
Dictionary<string, ParserFunction> locals = s_locals.Peek().Variables;
locals.Remove(name);
}
}
要在 CSCS 实现自定义方法和函数,我们需要两个类, FunctionCreator
和 CustomFunction
,这两个类都源自 ParserFunction
类;参见图 4。代码清单 50 显示了 FunctionCreator
类。我们向解析器注册它,如下所示:
public``const``string
FUNCTION ="function"
;
ParserFunction
。RegisterFunction( Constants
)。new
功能,FunctionCreator
();
| | 注意:不用担心,我们很快就会看到如何在配置文件中重新定义函数名。 |
所以我们语言中的一个典型函数看起来像:
function functionName(param1, param2, ..., paramN) {
// Function Body;
}
代码清单 50:函数创建者类的实现
class FunctionCreator : ParserFunction
{
protected override Variable Evaluate(ParsingScript script)
{
string funcName = Utils.GetToken(script, Constants.TOKEN_SEPARATION);
string[] args = Utils.GetFunctionSignature(script);
if (args.Length == 1 && string.IsNullOrWhiteSpace(args[0])) {
args = new string[0];
}
script.MoveForwardIf(Constants.START_GROUP, Constants.SPACE);
int parentOffset = script.Pointer;
string body = Utils.GetBodyBetween(script, Constants.START_GROUP,
Constants.END_GROUP);
CustomFunction customFunc = new CustomFunction(funcName, body, args);
customFunc.ParentScript = script;
customFunc.ParentOffset = parentOffset;
ParserFunction.RegisterFunction(funcName, customFunc,
false /* not native */);
return new Variable(funcName);
}
}
第一, FunctionCreator.Evaluate
法谓一辅助 Utils。GetToken 方法,提取函数定义中的 functionName
。然后是 Utils。GetFunctionSignature 辅助函数获取所有函数参数,参见代码清单 51。
请注意,我们的语言中没有明确的类型:**类型是根据上下文动态推导出来的。**因此 Utils 的结果。GetFunctionSignature 函数是一个字符串数组,像arg1
、arg2
、…、argN
。函数签名的一个例子是: function power(a, n)
。
代码清单 GetFunctionSignature 方法的实现
public static string[] GetFunctionSignature(ParsingScript script)
{
script.MoveForwardIf(Constants.START_ARG, Constants.SPACE);
int endArgs = script.FindFirstOf(Constants.END_ARG.ToString());
if (endArgs < 0) {
throw new ArgumentException("Couldn't extract function signature");
}
string argStr = script.Substr(script.Pointer, endArgs - script.Pointer);
string[] args = argStr.Split(Constants.NEXT_ARG_ARRAY);
script.Pointer = endArgs + 1;
return args;
}
辅助实用程序。方法提取代码清单 52 中函数的实际主体。作为方法参数,我们将开放字符作为常量传递。START_GROUP** (我定义为 { ),close 字符为常量。END_GROUP (我定义为 } )。**
代码清单 GetBodyBetween 方法的实现
public static string GetBodyBetween(ParsingScript script,
char open, char close)
{
// We are supposed to be one char after the beginning of the string,
// so
we must not have the opening char as the first character.
StringBuilder sb = new StringBuilder(script.Size());
int braces = 0;
for (; script.StillValid(); script.Forward()) {
char ch = script.Current;
if (string.IsNullOrWhiteSpace(ch.ToString()) && sb.Length == 0) {
continue;
} else if (ch == open) {
braces++;
} else if (ch == close) {
braces--;
}
sb.Append(ch);
if (braces == -1) {
if (ch == close) {
sb.Remove(sb.Length - 1, 1);
}
break;
}
}
return sb.ToString();
}
基本上, FunctionCreator
类创建了一个新的 CustomFunction
类的实例(参见代码清单 53 中的实现),并将其注册到解析器中。
代码清单 53:自定义函数类的实现
class CustomFunction : ParserFunction
{
internal CustomFunction(string funcName,
string body, string[] args) {
m_name = funcName;
m_body = body;
m_args = args;
}
protected override Variable Evaluate(ParsingScript script)
{
bool isList;
string parsing = script.Rest;
List<Variable> functionArgs = Utils.GetArgs(script,
Constants.START_ARG, Constants.END_ARG, out isList);
script.MoveBackIf(Constants.START_GROUP);
if (functionArgs.Count != m_args.Length) {
throw new ArgumentException("Function [" + m_name +
"] arguments mismatch: " + m_args.Length + " declared, " +
functionArgs.Count + " supplied");
}
// 1\. Add passed arguments as local variables to the Parser.
StackLevel stackLevel = new StackLevel(m_name);
for (int i = 0; i < m_args.Length; i++) {
stackLevel.Variables[m_args[i]] =
new GetVarFunction(functionArgs[i]);
}
ParserFunction.AddLocalVariables(stackLevel);
// 2\. Execute the body of the function.
Variable result = null;
ParsingScript tempScript = new ParsingScript(m_body);
tempScript.ScriptOffset = m_parentOffset;
if (m_parentScript != null) {
tempScript.Char2Line = m_parentScript.Char2Line;
tempScript.Filename = m_parentScript.Filename;
tempScript.OriginalScript = m_parentScript.OriginalScript;
}
while (tempScript.Pointer < m_body.Length - 1 &&
(result == null || !result.IsReturn)) {
string rest = tempScript.Rest;
result = tempScript.ExecuteTo();
tempScript.GoToNextStatement();
}
ParserFunction.PopLocalVariables();
result.IsReturn = false;
return result;
}
public ParsingScript ParentScript { set { m_parentScript = value; } }
public int ParentOffset { set { m_parentOffset = value; } }
public string Body { get { return m_body; } }
public string Header { get {
return Constants.FUNCTION + " " + Name + " " +
Constants.START_ARG + string.Join (", ", m_args) +
Constants.END_ARG + " " + Constants.START_GROUP;
}
}
private string m_body;
private string[] m_args;
private ParsingScript m_parentScript = null;
private int m_parentOffset = 0;
}
我们调用 Utils.GetArgs
辅助函数来提取参数(在代码清单 25 中定义)。
我们将在第 7 章的“获取出现错误的行号”一节中看到 m_parentOffset
, **m_parentScript**
(及其属性:**Char2Line
****Filename
**和 OriginalScript
)的用法。
现在让我们看看自定义函数和异常的作用。举个例子,我们将创建一个阶乘。阶乘函数,表示为n!
,定义如下:
n!= 1 * 2 * 3 ……(n-1)* n。
n = 0: 0! = 1
时有特殊定义。没有为负数定义阶乘。注意,我们也可以递归定义阶乘:n! = (n - 1)! * n
。
代码清单 54:阶乘递归实现的 CSCS 代码
function factorial(n)
{
if (!isInteger(n) || n < 0) {
exc = "Factorial is for nonnegative integers only (n=" + n + ")";
throw (exc);
}
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
function isInteger(candidate) {
return candidate
== round(candidate);
}
function factorialHelper(n)
{
try {
f = factorial(n);
print("factorial(", n, ")=", f);
} catch (exc) {
print("Caught exception: ", exc);
}
}
factorialHelper(0);
factorialHelper(10);
factorialHelper("blah");
CSCS 阶乘递归版本的实现如代码清单 54 所示。阶乘函数使用 isInteger
CSCS 函数来检查传递的参数是否为整数。这个函数也在代码清单 54 中实现。它调用 round
函数,这个函数已经在 C# 中实现了(参见代码清单 34)。
这些是运行代码清单 54 的 CSCS 脚本的结果:
factorial(0)=1
factorial(10)=3628800
Caught exception: Factorial is for nonnegative integers only (n=blah) --> exc
factorial()
factorialHelper()
在Caught exception
子句中,您可以看到异常堆栈,它是由 CreateExceptionStack
方法产生的(参见代码清单 47)。还可以添加发生异常的行号。我们将在第 7 章本地化中看到更多相关内容。
在本章中,我们继续向 CSCS 语言添加功能:我们看到了如何包含文件、如何抛出和捕获异常、如何添加局部和全局变量,以及如何在 CSCS 实现自定义函数。我们还看到了一些编写自定义函数的例子。
在下一章中,我们将看到如何开发一些在 CSCS 使用的数据结构。