【问题标题】:How to update a generic Action after it's been set already?已设置通用操作后如何更新它?
【发布时间】:2019-12-16 15:51:07
【问题描述】:

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

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

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

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

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() 函数的那个​​,给出以下 还要记住,我有一个<string, object> 类型的Dictionary 来存储所有变量。

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

【问题讨论】:

  • 我不明白你为什么不能再次查找Variables[Var_name]。你想让编译器检查变量是否存在?
  • @hugo 你能详细说明一下,你的意思是抬头吗?一切都存储在一个动作列表中,然后在解析完成后,它执行每个动作。给出我所说的问题,因为变量是在没有值的操作中给出的。是的,我有一个函数可以查找变量是否存在,如果它不可用则返回“未定义”
  • 创建另一个标记列表'List'(或HashSet,whatevs),您在编译时更新。在编译期间的任何步骤,您都可以使用它来了解声明是否与此标记匹配。
  • @hugo 现在又一次我不明白这会如何解决?由于所有内容都已在列表中,因此在执行之前的操作以更新它之后,我如何转到该特定索引?有没有办法将事件添加到值分配中?
  • 您的问题缺少太多信息...您应该提供实际显示问题的代码。

标签: c#


【解决方案1】:

在编译时跟踪您的变量:

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
}

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

【讨论】:

  • 这似乎是一个很好的答案,可以作为解决方案的良好基础。尽管在实施它之后,它仍然没有做我认为它会做的事情。因为仍然,在执行操作之前,变量不会被添加到scopeVars,这意味着用于添加值的其他解析已经完成,导致变量仍然处于空/无值状态那应该被打印出来......
  • 由于您更新了答案,我可以看到发生了什么。查看我的编辑。
【解决方案2】:

问题是你在解析脚本时构造了ToPrint值,当你调用GetVar(Current.Symbol)时,值还不存在,因为它将在执行期间被初始化。最简单的选择是将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 调用推迟到执行时间。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-31
    • 2016-10-24
    • 2017-02-23
    • 1970-01-01
    • 2023-03-18
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多