既然有了变量,那肯定想到函数了
老规矩,定义一个类Function来表示函数
public class Function
{
public string Name = string.Empty; 函数名
public int BeginIndex = -1; 函数的入口位置
public int EndIndex = -1; 函数的结束位置
private string[] Params = null; 函数的参数列表(只有保存参数名,参数实际内容,在运行时填充)
private List<Variable> vars = null; 函数局部变量表(包括参数)
}
好了,应该说明的比较详细了,既然GlobalData是存储所有内容的地方,自然包括函数,所以改变下GlobalData的内容
private List<Function> funcs = null; // 其实这里已经舍弃了,之前是考虑有全局函数的,后来觉得,函数都限制在类里比较好,但一开始我确实是实现了全局函数,所以这里给出
另外加入函数AddFunction,用于添加函数
public void AddFunction(string funcName, string[] param, int beginIndex, int endIndex)
{
funcs.Add(new Function(funcName, param, beginIndex, endIndex));
}
加入函数GetFunc,用于查找函数
之所以要用函数名和函数参数个数来查找一个函数,是因为函数可以重载,如果不需要这个功能,设计上可以减少很多的考虑问题。重载要考虑不只是当前类,还有父类。
public Function GetFunc(string funcName, int paramCount, bool allowReturnNull = true)
{
for (int i = 0; i < funcs.Count; i++)
{
if (funcs[i].Name == funcName && funcs[i].ParamCount == paramCount)
{
return funcs[i];
}
}
if (allowReturnNull)
{
return null;
}
else
{
throw new Exception("Can't find Function " + funcName + " in " + Name + " with Parameters " + paramCount.ToString());
}
}
有了这两个函数,我们就可以加入和读取指定函数了,我们暂时还不考虑局部变量的问题(比较简单,以后解决)
然后在Parse加入ParseFunction函数
private void ParseFunction()
{
try
{
lexel.GetToken(TokenType.Ident);
string name = lexel.CurrToken;
lexel.GetToken(TokenType.Left_Small_Paren);
List<string> param = new List<string>();
while (lexel.PeekNext() != TokenType.Right_Small_Paren)
{
lexel.GetToken(TokenType.At);
lexel.GetToken(TokenType.Ident);
param.Add(lexel.CurrToken);
if (lexel.PeekNext() != TokenType.Right_Small_Paren)
{
lexel.GetToken(TokenType.Comma);
}
}
lexel.GetToken(TokenType.Right_Small_Paren);
int beginIndex = lexel.Index;
while (lexel.GetToken() != TokenType.Key_End)
{
if (lexel.CurrType == TokenType.Key_Def)
{
throw new Exception("Parse.ParseFunction : Can't def in Function " + name);
}
}
Ins.GD.AddFunction(name, param.ToArray(), beginIndex, lexel.Index);
}
catch (Exception ex)
{
throw ex;
}
}
代码比较简单,就是读取函数名和参数,然后添加到GD中
而且会循环读取到Key_End表示函数定义结束,并且如果期间检查到def,会报错,应该函数里不能再次定义类和函数(如果需要实现闭包,可以在这里考虑)
好了,现在有了ParseFunction和ParseVariable了,可以对函数和变量进行分析。那怎么调用他们呢
这要提到语法分析的模式了,有自顶向下和自底向上什么的。。。我们这里不管,我是这么想的
首先,所有的脚本,都是由语句组成的,比较def_class、@x=10;这些都是简单的语句,可以说脚本就是由语句组成的。语句是基本的组成单元,和表达式分析中的“因子”一样。比语句高一级的,就是语句块了,一堆语句就是一个语句块,具体点,就是一个函数
def:func
@x =1;
end
这就是一个语句块,可以这样理解:def和end之间括起来起来的就是语句块了
比语句块高一级就是整个脚本了,所以,我们可以根据分析表达式的方法,来实现
首先,需要一个函数ParseCode来分析整个脚本
public void ParseCode(string code)
{
lexel.Start(code);
Ins = Declare; // 这个变量待会儿说
while (lexel.PeekNext() != TokenType.End)
{
ParseStatement();
}
}
然后需要一个函数ParseStatement()分析语句
private bool ParseStatement()
{
try
{
if (lexel.GetToken() == TokenType.End)
{
throw new Exception("Parse.ParseStatement : Unexpect end of file");
}
switch (lexel.CurrType)
{
case TokenType.Key_Def:
ParseDefine();
break;
case TokenType.At: // 变量申明
if (Ins.CurrApply.Name == string.Empty) // 跳过全局变量
{
while (lexel.GetToken() != TokenType.Semicolon) ;
}
break;
case TokenType.Semicolon:
break;
default:
throw new Exception("Parse.ParseStatement : Unexpect " + lexel.CurrToken);
}
return true;
}
catch (Exception ex)
{
throw ex;
}
}
这个函数,读取一个Token如果不是End就是分析
目前我们只分析def和@其余报错
对于def我们只是简单的调用函数ParseDefine来分析,对于变量,由于只是只是分析阶段,没有运行,所以跳过全局变量,对于局部变量也只是简单的添加到变量表中
为什么要跳过全局变量?因为全局变量要在运行阶段才添加,考虑如下代码
@m = $test.new(1,2);
$m.show();
你在分析阶段可能还没有test类,所以还不能分析全局变量,当然你也可以像处理局部变量一样,先添加到变量表~
好了,接下来看看ParseDefine函数
private void ParseDefine()
{
try
{
lexel.GetToken();
switch (lexel.CurrType)
{
case TokenType.Underline:
if (Ins.CurrApply.Name != string.Empty)
{
throw new Exception("Parse.ParseDefine : Can't def Class in Class " + Ins.CurrApply.Name);
}
ParseClass();
break;
case TokenType.Colon:
if (Ins.CurrApply.Name == string.Empty)
{
lexel.GetToken();
throw new Exception("Parse.ParseDefine : Can't def Function " + lexel.CurrToken + " in Global ");
}
ParseFunction();
break;
default:
throw new Exception("Parse.ParseDefine : Unkown Token " + lexel.CurrToken);
}
}
catch (Exception ex)
{
throw ex;
}
}
这里def后面是“_”就调用ParseClass分析类的定义,如果是“:”就调用函数ParseFunction。
由于这里不考虑class。
好了,到这里,分析就完成了。可能有人觉得,这多麻烦,那么多函数,写到一个函数中不好吗?
一个函数也可以,但我觉得每个函数完成自己独有的功能更好一点。尤其以后函数调用阶段可以看到这样做的好处
可以在test.txt中测试下,当然。测试结果只能自己调试看看了。在GD中看看数据是否正常。
现在,处理一下函数变量的问题,因为下面我想实现类,到时候有类变量,不可能都到全局变量处理的。
实现函数的局部变量很简单,因为Function中有List<Variable> vars;
所有,可以在Function中加入函数
public void AddVar(string varName, TValue value, bool aloowReturnNull = true)
{
vars.Add(new Variable(varName, value));
}
注意,这里没有删除变量重名的问题处理,不然总是复制一堆代码,变量重名很简单,就是检查当前的vars列表列表中是否有重名变量,当然,如果你还要考虑局部变量覆盖类变量或者全局变量,或者反过来覆盖等问题,这里都可以搞定。
我们还需要在GlobalData中加入函数
public void AddFunctionVariable(string funcName, int paramCount, string varName, TValue value, bool allowReturnNull = true)
{
try
{
Function func = GetFunc(funcName, paramCount, allowReturnNull);
if (func != null)
{
func.AddVar(varName, value, allowReturnNull);
}
else
{
if (!allowReturnNull)
{
throw new Exception("GlobalData.AddFunctionVariable : Can't find Function " + funcName + " in Class " + applyName);
}
}
}
catch (Exception ex)
{
throw ex;
}
}
由于分析函数时,我们的处理方式是直接跳过了函数体,所以变量只能在运行时候添加,这更符合参数的传递。所以这里只是给出函数,还没有实现添加。
下一节准备实现函数调用的问题,这个要涉及到“运行时环境”的入栈和出栈问题,需要保持调用函数时的现场。以实现递归调用