首页 > 解决方案 > 已设置通用操作后如何更新它?

问题描述

所以我正在制作一种“玩具语言”并使用“玩具编译器”来执行代码。

基本上,我在 C# 中设计所有东西,并简单地说它是如何工作的,只是从源文件中制作标记,循环它们并使用 C# Action 列表设计指令。

我已经尝试向后“解析/编译”所有内容,然后在执行时反转操作列表,考虑到问题的结构,这是非常愚蠢的。

这是“玩具语言”的部分源代码

printl("Hello, What is your name?")
string Name = inline()

printl("Oh, hello there " + Name)

而我的 C#“玩具编译器”是如何通过添加操作来完成的

printl("Hello, what is your name?") 将函数内的字符串作为具有值的标记给出以下解析代码:

Actions.Add(new Action(() => Console.WriteLine(CurrentTok.Value)));

尽管在代码的最后一部分中具有多个值,但它只需要一个空对象并通过将值转换为字符串来将所有值添加到循环中,直到当前标记变为')' RightParen标记。生成一个对象,其中包含使用该ToString()函数打印的所有值。

对于我拥有该inline()函数的那个​​,还请记住,我有一个Dictionary类型<string, object>来存储所有变量。

Actions.Add(new Action(() => Variables[Var_name] = Console.ReadLine()));

现在问题出现在解析应该写出该值的最后一行时,因为它已经被“编译”并且变量没有值。inline()命令执行后。该变量不会更新它的值,因为它在一个列表中。

这是“编译器”代码源的简化版本,用于更好地解释问题,注意。Current = Tokens[Index]

While(Index < Tokens.Count - 1) 
{ // Index a simple int
    if(Ignore.Contains(CurrentTok.Type)) // Type = Type of Token
        Index++ // if it's a  { or a }.. and so on
    if(CurrentTok.Type == TokenType.String) // TokenType = enum
    { 
        if(Current.Value == "inline()") 
        {
            Variables[Current.Symbol] = " "; // so it's not undefined 
            Actions.Add(new Action(() => Variables[Current.Symbol] = Console.ReadLine()
            )); // Current.Symbol being the variable name
        } else {
            Variables[Current.Symbol] = Current.Value;
        }
    }
    if(Current.Type == TokenType.Function) {
        if(Current.Symbol == "printl") {
            Index++;
            if(Current.Type == TokenType.LParen) { // '('
                Index++;
                object ToPrint = " "; // init an object
                While(Current.Type != TokenType.RParen) { // ')'
                    if(Current.Type == TokenType.Plus)
                        Index++;
                    if(Current.Type == TokenType.PrintString) {
                        // PrintString being a string inside a function
                        // that has not been declared as an variable.
                        object ToAdd = Current.Value;
                        ToPrint += Convert.ToString(ToAdd);
                    }
                    if(Current.Type == TokenType.String) {
                        object ToAdd = GetVar(Current.Symbol); 
                       //GetVar = object that returns the value if the dictionary contains it
                        ToPrint += Convert.ToString(ToAdd);
                    }
                    Index++;
                }
                Actions.Add(new Action(() => Console.WriteLine(ToPrint)));
            } else {
                // errors...
            }
        }
    }
    index++;
}

从我上面列出的源代码中它可以正常工作,它打印文本Hello, What is your name并使用 readline 打开输入流。但返回时Oh, heloo there没有名字。

标签: c#

解决方案


问题是您ToPrint在解析脚本时构造 value 并且当您调用时GetVar(Current.Symbol), value 还不存在,因为它将在执行期间被初始化。最简单的选择就是将评估推迟ToPrint到执行。

有很多方法可以做到这一点,例如将其转换为Func

Func<String> ToPrint = ()=> " "; // // this is func, evaluation is deferred to invocation
  While(Current.Type != TokenType.RParen) { // ')'
     if(Current.Type == TokenType.Plus)
        Index++;
     if(Current.Type == TokenType.PrintString) {
        var ToAdd = Current.Value;
        var currentValue = ToPrint; // you cannot use ToPrint in the clausure :(
        ToPrint = () => currentValue() + Convert.ToString(ToAdd);
     }
     if(Current.Type == TokenType.String) {
        var ToAddSymbol = Current.Symbol; 
        //GetVar = object that returns the value if the dictionary contains it
        var currentValue = ToPrint 
        ToPrint = () => currentValue() + Convert.ToString(GetVar(ToAddSymbol)); // GetVar will be called during execution
     }
     Index++;
  }
Actions.Add(new Action(() => Console.WriteLine(ToPrint())));

从堆栈使用的角度(如果字符串中有很多标记怎么办?)和内存的角度(这里有多少个闭包?),这个解决方案都不好。您可能会玩弄实现,但主要思想是将GetVar调用推迟到执行时间。

编写解析器并不是一项新任务,有很多框架可以做到这一点,您可以阅读此线程以获得广泛的概述。您的解析器解决了非常自定义的场景。添加新功能可能会很痛苦且有风险,例如,想一想添加嵌套表达式的成本是多少?我不怪你的实现,但是已经写了这么多,随意学习和使用:)


推荐阅读