所以我正在制作一种“玩具语言”,并且还使用“玩具编译器”来执行代码。
基本上,我在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
,但不带名称。
答案 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以获得全面的概述。您的解析器解决了非常自定义的情况。添加新功能可能是痛苦且冒险的,例如,考虑添加嵌套表达式将花费多少?我不怪你的实现,但是已经写了很多东西,随时学习和使用:)