28、深入探索Stitch语言:从语法测试到并行执行

深入探索Stitch语言:从语法测试到并行执行

1. 语法测试驱动

在开发Stitch语言时,对定义好的语法进行测试驱动是至关重要的一步。这里有两种主要的方法可以用来测试语法:

  • 使用ANTLR IDE 2.1.0的GUI功能 :在Eclipse中,首先要将Stitch.g设置为活动文件。之后,在代码编辑器底部会出现三个选项卡,其中“Grammar”选项卡用于编写语法,“Interpreter”选项卡用于测试语法。选择“Interpreter”选项卡后,在左上角会显示Stitch.g中定义的所有解析器和词法分析器规则。可以在上方的窗格中输入测试脚本,通过右上角的保存图标保存脚本,点击运行图标执行测试脚本,执行结果会以树状形式显示在下方窗格中,通过检查这个树状结构就能判断定义的语法是否按预期解析测试脚本。
  • 编写C#代码测试生成的词法分析器和解析器文件 :以下是一个C#示例代码,用于测试StitchParser类和StitchLexer类:
static void RunParserExample()
{
    ICharStream input = new ANTLRFileStream(@"Scripts\testScript1.st");
    StitchLexer lexer = new StitchLexer(input);
    ITokenStream tokenStream = new CommonTokenStream(lexer);
    StitchParser parser = new StitchParser(tokenStream);
    IList<IFunction> functions = parser.program();
    Console.WriteLine("There are {0} scripts in the source file.", functions.Count);
}

这个代码示例使用 testScript1.st 文件作为测试脚本,具体步骤如下:
1. 打开 testScript1.st 文件作为文件流。
2. 将文件流传递给词法分析器对象。
3. 从词法分析器对象创建一个令牌流。
4. 将令牌流传递给解析器对象。
5. 调用解析器对象的 program 方法,该方法返回一个 IFunction 对象列表。

建议同时使用这两种技术来测试语法,确保语法按预期工作。通常在实验创建中的语法时使用GUI方法,当语法稳定后,使用C#方法编写单元测试,以便快速自动检查语法的正确性。

2. Stitch运行时概述

词法分析器和解析器本身不足以执行Stitch代码,Stitch运行时是执行Stitch代码的组件。它与两种类型的组件进行交互:客户端应用程序和语言插件。

  • 与语言插件的交互 :Stitch运行时为语言插件提供了两个接口 ILanguagePlugin IScript 。如果要扩展Stitch运行时以支持新语言,需要实现这两个接口。
  • 与客户端应用程序的交互 :客户端应用程序可以通过两种方式使用Stitch运行时来运行Stitch代码:
    • 直接使用C# Stitch项目中的 StitchScriptEngine 类。
    • 通过DLR托管API间接使用 StitchScriptEngine 。Stitch运行时提供了 StitchContext 类和 StitchScriptCode 类来支持通过DLR托管API调用 StitchScriptEngine

下面是Stitch运行时与其他组件交互的关系图:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(Demo App<br>(StitchDemoApplication)):::process --> B(StitchScriptEngine):::process
    C(DLR Hosting API):::process --> B
    B --> D(Stitch):::process
    D --> E(Stitch grammar<br>(Stitch.g)):::process
    D --> F(IScript):::process
    D --> G(lLanguagePlugin):::process
    H(PowerShell Script Plugin):::process --> G
3. 运行时流程概述

当Stitch运行时执行Stitch脚本文件时,其活动流程如下:
1. 使用词法分析器将文本形式的Stitch源代码转换为令牌流。
2. 令牌流被输入到解析器中,解析器将令牌转换为抽象语法树(AST),该树由 Stitch.Ast 命名空间中的类实例组成。
3. Stitch运行时使用函数执行协调器来协调 StitchParser program 方法返回的 IFunction 对象列表的执行。Stitch运行时提供了并行协调器和顺序协调器来协调Stitch函数的执行。
4. 协调器根据输入的 IFunction 对象列表和支持的语言注册表,为每个 IFunction 对象创建一个脚本运行器。脚本运行器通过调用 IScript 实例的 Execute 方法来运行脚本代码。

下面是Stitch运行时执行Stitch脚本的活动流程图:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(Stitch source code):::process --> B(Lexer<br>(StitchLexer.cs)):::process
    B --> C(tokens):::process
    C --> D(Parser<br>(StitchParser.cs)):::process
    D --> E(Stitch functions<br>(IFunction.cs)):::process
    E --> F(Function execution coordinator):::process
    G(Registry of language plugins):::process --> F
    F --> H(Script runner<br>(IScriptRunner.cs)):::process
    F --> I(Script runner<br>(IScriptRunner.cs)):::process
    H --> J(IScript.Execute(...)):::process
    I --> K(IScript.Execute(...)):::process
4. 脚本引擎

使用 StitchScriptEngine 执行Stitch代码是一个两步过程:
1. 调用 StitchScriptEngine 的构造函数创建类的实例。构造函数需要传入执行模式和语言插件集合,根据执行模式,构造函数会创建 ParallelFunctionExecutionCoordinator SequentialFunctionExecutionCoordinator 的实例。以下是构造函数的代码:

public StitchScriptEngine(ExecutionMode executionOption,  
    ICollection<ILanguagePlugin> plugins) 
{ 
    switch (executionOption) 
    { 
        case ExecutionMode.ParallelNoWait: 
            this.coordinator = new ParallelFunctionExecutionCoordinator(false); 
            break; 
        case ExecutionMode.ParallelWaitAll: 
            this.coordinator = new ParallelFunctionExecutionCoordinator(true); 
            break; 
        case ExecutionMode.Sequential: 
            this.coordinator = new SequentialFunctionExecutionCoordinator(); 
            break; 
    } 
    // … language plugin related code omitted … 
}
  1. 调用 RunScriptFile 方法或 RunScriptCode 方法执行Stitch代码。以下是 RunScriptCode 方法的代码:
public void RunScriptCode(String code) 
{ 
    ICharStream input = new ANTLRStringStream(code); 
    StitchLexer lexer = new StitchLexer(input); 
    ITokenStream tokenStream = new CommonTokenStream(lexer); 
    StitchParser parser = new StitchParser(tokenStream); 
    IList<IFunction> functions = parser.program(); 
    coordinator.RunScripts(functions, registry); 
}
5. 函数执行协调器

Stitch脚本可能包含多个Stitch函数,并且有些函数可能依赖于其他函数的输出。因此,Stitch运行时使用协调器来管理函数的执行。函数执行协调器的概念在C# Stitch项目中定义为 IFunctionExecutionCoordinator 接口:

interface IFunctionExecutionCoordinator 
{ 
    void RunScripts(IList<IFunction> functions, ILanguageRegistry registry); 
}

C# Stitch项目中有两个类实现了该接口: ParallelFunctionExecutionCoordinator SequentialFunctionExecutionCoordinator 。这里主要介绍 ParallelFunctionExecutionCoordinator 类的 RunScripts 方法:

public void RunScripts(IList<IFunction> functions, ILanguageRegistry registry) 
{ 
    IList<ParallelScriptRunner> scriptRunners =  
this.CreateScriptRunners(functions, registry); 
    foreach (var scriptRunner in scriptRunners) 
        scriptRunner.Run(); 
    // … code omitted … 
}

该方法调用 CreateScriptRunners 方法为每个 IFunction 对象创建一个 ParallelScriptRunner 实例,然后调用这些脚本运行器的 Run 方法开始执行Stitch函数。

创建并行脚本运行器时,需要获取一个知道如何执行Stitch函数中特定语言代码的 IScript 实例。以下是 CreateScriptRunners 方法的代码:

private IList<ParallelScriptRunner> CreateScriptRunners( 
                    IList<Ast.IFunction> functions, ILanguageRegistry registry) 
{ 
    IDictionary<String, ParallelScriptRunner> returnValueToRunnerDict =  
                        new Dictionary<String, ParallelScriptRunner>(); 
    IList<ParallelScriptRunner> scriptRunners = new List<ParallelScriptRunner>(); 
    foreach (var function in functions) 
    { 
        IScript script = registry.CreateScript(function); 
        ParallelScriptRunner scriptRunner = new ParallelScriptRunner( 
                          script, function.InputParameters); 
        scriptRunners.Add(scriptRunner); 
        foreach (var returnValue in function.ReturnValues) 
        { 
            returnValueToRunnerDict.Add(returnValue, scriptRunner); 
        } 
    } 
    for (int i = 0; i < functions.Count; i++) 
    { 
        ParallelScriptRunner scriptRunner = scriptRunners[i]; 
        foreach (var item in functions[i].InputParameters) 
        { 
            if (returnValueToRunnerDict.ContainsKey(item)) 
                scriptRunner.AddPrerequisite(returnValueToRunnerDict [item]); 
        } 
    } 
    return scriptRunners; 
}

该方法除了为每个 IFunction 对象创建 ParallelScriptRunner 实例外,还会跟踪Stitch函数的依赖关系。

6. .NET并行扩展

由于并行脚本运行器利用了.NET任务并行库(TPL)进行并行编程,这里简单介绍一下TPL中与后续讨论相关的部分。TPL是并行扩展中的一个库,在并行扩展发布之前,处理.NET线程池的代码非常难写且难以维护,而TPL让多线程编程变得更加容易。

以下是一个TPL示例代码,展示了任务依赖如何影响任务的并行执行:

private static void RunTaskDependencyExample() 
{ 
    Task task1 = Task.Factory.StartNew(() => { Console.WriteLine("task 1"); }); 
    Task task2 = Task.Factory.StartNew(() => { Console.WriteLine("task 2"); }); 
    Task task3 = Task.Factory.StartNew(() => { Console.WriteLine("task 3"); }); 
    Task task4 = Task.Factory.ContinueWhenAll(new[] {task1},  
        tasks => { Console.WriteLine("task 4"); }); 
    Task task5 = Task.Factory.ContinueWhenAll(new[] { task2 },  
        tasks => { Console.WriteLine("task 5"); }); 
    Task task6 = Task.Factory.ContinueWhenAll(new[] { task4 },  
        tasks => { Console.WriteLine("task 6"); }); 
    Task task7 = Task.Factory.ContinueWhenAll(new[] { task2, task4, task5 },  
        tasks => { Console.WriteLine("task 7"); }); 
    Task task8 = Task.Factory.ContinueWhenAll(new[] { task3, task5 },  
        tasks => { Console.WriteLine("task 8"); }); 
}

在这个示例中, task1 task2 task3 不依赖于其他任务,使用 Task.Factory.StartNew 方法创建并立即开始运行。 task4 依赖于 task1 ,使用 Task.Factory.ContinueWhenAll 方法创建,只有当 task1 完成后才会开始执行。其他任务的依赖关系同理。

7. 脚本运行器

脚本运行器的概念在C# Stitch项目中定义为 IScriptRunner 接口:

interface IScriptRunner 
{ 
    void Run(); 
}

ParallelScriptRunner 类实现了该接口。以下是 ParallelScriptRunner 类的 StartTask 方法:

Task<IDictionary<String, Object>> StartTask() 
{ 
    if (task != null) 
        return task; 
    if (prerequisites.Count == 0) 
    { 
        task = Task.Factory.StartNew<IDictionary<String, Object>>(() => 
        { 
            IDictionary<String, object> scope = new Dictionary<String, object>(); 
            return this.script.Execute(scope); 
        }); 
        return task; 
    } 
    List<Task<IDictionary<String, Object>>> taskList =  
                                    new List<Task<IDictionary<string, object>>>(); 
    foreach (var prerequisite in prerequisites) 
        taskList.Add(prerequisite.StartTask()); 
    task = Task.Factory.ContinueWhenAll(taskList.ToArray(), 
            (tasks) => 
            { 
                IDictionary<String, object> scope = new Dictionary<String, object>(); 
                foreach (var prerequisiteTask in tasks) 
                { 
                    foreach (var item in prerequisiteTask.Result) 
                    { 
                        if (!scope.ContainsKey(item.Key) && 
                        this.inputParameters.Contains(item.Key)) 
                            scope.Add(item); 
                    } 
                } 
                return this.script.Execute(scope); 
            }); 
    return task; 
}

如果脚本运行器没有任何先决条件,它可以立即运行,使用 Task.Factory.StartNew 方法启动一个新任务。如果有先决条件,需要先运行这些先决条件并等待它们完成,然后再启动脚本运行器。

Stitch运行时内置了一个 DlrScript 类,为所有基于DLR的语言实现了 IScript 接口。对于像PowerShell这样的非DLR语言,可以扩展Stitch运行时来支持。

通过以上内容,我们详细了解了Stitch语言从语法测试到并行执行的整个过程,包括语法测试方法、运行时组件、脚本引擎、函数执行协调器、并行扩展以及脚本运行器等方面的知识。这些知识对于开发和使用Stitch语言的开发者来说是非常重要的。

深入探索Stitch语言:从语法测试到并行执行(续)

8. 并行脚本运行器的工作机制

并行脚本运行器在Stitch语言的并行执行中起着关键作用。我们已经了解了 ParallelScriptRunner 类的 StartTask 方法,下面进一步分析其工作机制。

当一个脚本运行器没有先决条件时,它会立即启动一个新的TPL任务。这个任务会执行一个lambda委托,在委托中创建一个空的字典对象来作为语言特定代码所需的输入变量容器。这是因为没有先决条件意味着语言特定代码不需要额外的输入变量。

而当脚本运行器有先决条件时,情况会变得复杂一些。首先,代码会遍历所有的先决条件,并启动它们的任务。然后,使用 Task.Factory.ContinueWhenAll 方法创建一个新任务,这个新任务会在所有先决条件任务完成后才开始执行。在这个新任务中,会将先决条件任务的结果收集到一个字典对象中,这个字典对象将作为语言特定代码的输入变量。

以下是对 StartTask 方法的详细解释表格:
| 条件 | 操作步骤 | 代码示例 |
| — | — | — |
| 无先决条件 | 1. 调用 Task.Factory.StartNew 启动新任务
2. 创建空字典作为输入变量容器
3. 调用 script.Execute 执行语言特定代码 | csharp<br>task = Task.Factory.StartNew<IDictionary<String, Object>>(() => {<br> IDictionary<String, object> scope = new Dictionary<String, object>();<br> return this.script.Execute(scope);<br>});<br> |
| 有先决条件 | 1. 遍历先决条件并启动它们的任务
2. 使用 Task.Factory.ContinueWhenAll 创建新任务
3. 收集先决条件任务的结果到字典中
4. 调用 script.Execute 执行语言特定代码 | csharp<br>List<Task<IDictionary<String, Object>>> taskList = new List<Task<IDictionary<string, object>>>();<br>foreach (var prerequisite in prerequisites)<br> taskList.Add(prerequisite.StartTask());<br>task = Task.Factory.ContinueWhenAll(taskList.ToArray(),<br> (tasks) => {<br> IDictionary<String, object> scope = new Dictionary<String, object>();<br> foreach (var prerequisiteTask in tasks) {<br> foreach (var item in prerequisiteTask.Result) {<br> if (!scope.ContainsKey(item.Key) &&<br> this.inputParameters.Contains(item.Key))<br> scope.Add(item);<br> }<br> }<br> return this.script.Execute(scope);<br> });<br> |

9. 语言插件的作用与实现

语言插件是Stitch运行时的重要组成部分,它使得Stitch能够支持多种不同的编程语言。Stitch运行时为语言插件提供了 ILanguagePlugin IScript 两个接口。

ILanguagePlugin 接口用于定义语言插件的基本信息和功能,而 IScript 接口则负责执行特定语言的代码。对于基于DLR的语言,Stitch运行时内置了 DlrScript 类来实现 IScript 接口。对于非DLR语言,如PowerShell,我们可以通过实现这两个接口来扩展Stitch运行时的支持。

以下是实现语言插件的一般步骤:
1. 实现 ILanguagePlugin 接口 :定义语言插件的名称、支持的文件扩展名等信息。
2. 实现 IScript 接口 :实现 Execute 方法,该方法负责执行特定语言的代码。
3. 注册语言插件 :将实现好的语言插件注册到Stitch运行时中。

以下是一个简单的示例,展示如何为PowerShell实现一个语言插件:

// 实现ILanguagePlugin接口
public class PowerShellLanguagePlugin : ILanguagePlugin
{
    public string Name => "PowerShell";
    public string[] FileExtensions => new[] { ".ps1" };

    public IScript CreateScript(Ast.IFunction function)
    {
        return new PowerShellScript(function);
    }
}

// 实现IScript接口
public class PowerShellScript : IScript
{
    private readonly Ast.IFunction _function;

    public PowerShellScript(Ast.IFunction function)
    {
        _function = function;
    }

    public IDictionary<string, object> Execute(IDictionary<string, object> scope)
    {
        // 执行PowerShell脚本的逻辑
        // 这里可以使用PowerShell的API来执行脚本
        // 并返回执行结果
        return new Dictionary<string, object>();
    }
}
10. 总结与展望

通过前面的介绍,我们深入了解了Stitch语言从语法测试到并行执行的整个流程。语法测试是确保语言正确性的重要步骤,通过GUI和C#代码两种方式可以有效地进行测试。Stitch运行时是执行Stitch代码的核心组件,它与客户端应用程序和语言插件进行交互,实现了代码的解析和执行。

函数执行协调器和脚本运行器在管理和执行Stitch函数方面发挥了重要作用。并行扩展和TPL的使用使得Stitch能够实现高效的并行执行,大大提高了代码的执行效率。

未来,我们可以进一步扩展Stitch语言的功能。例如,可以添加更多的语言插件,支持更多的编程语言;可以优化并行执行的算法,提高并行执行的性能;还可以开发更友好的开发工具,方便开发者使用Stitch语言进行开发。

以下是Stitch语言开发的未来发展方向表格:
| 发展方向 | 具体内容 |
| — | — |
| 语言支持扩展 | 添加更多语言插件,支持Python、Ruby等更多编程语言 |
| 性能优化 | 优化并行执行算法,减少任务调度的开销 |
| 开发工具改进 | 开发可视化的开发工具,提供代码自动补全、调试等功能 |

总之,Stitch语言为开发者提供了一种强大的方式来编写和执行多语言脚本。通过深入理解其原理和机制,开发者可以更好地利用Stitch语言的优势,开发出高效、灵活的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值