设置通用动作后如何更新?

时间:2019-08-08 21:42:47

标签: c#

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

基本上,我在C#中设计所有内容,并简单地说,它是如何工作的,只是从源文件中制作令牌,遍历它们并使用C#Action列表设计指令。

我尝试将所有内容“反向解析/编译”,然后在执行时将Action-list反转,考虑到问题的构造方式,这真是愚蠢。

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

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,但不带名称。

2 个答案:

答案 0 :(得分:0)

在编译时跟踪您的var:

HashSet<string> scopeVars = new HashSet<string>()

解析分配时,请执行以下操作:

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

当您在表达式中遇到该标记(被识别为var)时,请执行以下操作:

if (!scopeVars.Contains(Var_name)) {
    // Compile error!
} else {
    Actions.Add(...);
}

您可能想使用`Dictionary <,>ˋ来存储变量的类型或其他类型,但是我没有对您的语言做任何假设。


响应您的编辑:

您不能在编译时执行此操作,因为您的var仅在运行时获取值:

object ToAdd = GetVar(Current.Symbol); 
//GetVar = object that returns the value if the dictionary contains it
ToPrint += Convert.ToString(ToAdd);

这是一个简单的解决方案-首先,您需要在开始解析“ printl”时初始化此字符串列表:

var VarsToPrint = new List<string>();

为变量添加占位符,而不是计算其值(在编译过程中不能这样做!)。将我首先引用的代码替换为:

var toAdd = "{" + VarsToPrint.Count + "}"; // will add {0} for the 1st var, {1} the next time, and so on
VarsToPrint.Add(Current.Symbol);
ToPrint += toAdd;

因此,您将在操作内部进行实际的变量评估

Actions.Add(new Action(() => Console.WriteLine(ReplaceVars(ToPrint, VarsToPrint))));

string ReplaceVars(string s, IEnumerable<string> vars) {
    var values = VarsToPrint.Select(varname => Variables[varname]); // fetch the values associated with the variable names (uses System.Linq)
    return string.Format(s, values.ToArray()); // replace {0}, {1}, {2}... with the values fetched
}

您将很快了解这种方法的局限性,但希望它将帮助您理解问题并构建更强大的功能。

答案 1 :(得分:0)

问题在于解析脚本时构造了ToPrint值,并且调用GetVar(Current.Symbol)时,该值尚不存在,因为它将在执行期间初始化。最简单的选择就是推迟对ToPrint的评估才能执行。 有很多方法可以做到这一点,例如将其转换为Func

Func<String> ToPrint = ()=> " "; // // this is func, evaluation is deffered 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调用延迟到执行时间。

编写解析器不是一项新任务,有许多框架可以做到这一点,您可以阅读this thread以获得全面的概述。您的解析器解决了非常自定义的情况。添加新功能可能是痛苦且冒险的,例如,考虑添加嵌套表达式将花费多少?我不怪你的实现,但是已经写了很多东西,随时学习和使用:)