Skip to content

Files

Latest commit

e1dab30 · Jan 11, 2022

History

History
879 lines (679 loc) · 29.2 KB

File metadata and controls

879 lines (679 loc) · 29.2 KB

五、异常和自定义函数

在本章中,我们将看到如何在 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``stringTHROW ="throw"ParserFunction`。注册功能` ( `Constants`)。THROW,`newThrowFunction`();

这意味着只要我们的解析器看到类似如下的内容:

"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``stringTRY ="try"

ParserFunction。RegisterFunction( Constants)。newTRY,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成员保存一个堆栈,该堆栈包含堆栈上被调用的每个函数的局部变量。 AddLocalVariableAddStackLevel 方法添加了一个新的局部变量,相应地,还有一个新的 StackLevel

| | 注意:如果已经有同名的全局变量或函数,则在这里不允许使用本地名称。 |

字典<字符串,parser function>s _ functions成员保存所有全局变量和函数(在 CSCS 所有变量和函数都是相同的;两者都源于 ParserFunction 类)。字典的关键字是函数名或变量名。 RegisterFunctionAddGlobal 方法都添加了新的变量或函数。还有 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 实现自定义方法和函数,我们需要两个类, FunctionCreatorCustomFunction ,这两个类都源自 ParserFunction 类;参见图 4。代码清单 50 显示了 FunctionCreator 类。我们向解析器注册它,如下所示:

public``const``stringFUNCTION ="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 函数是一个字符串数组,像arg1arg2、…、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 使用的数据结构。