设计一门脚本语言——(六)函数

本文介绍了一种函数解析方法及局部变量的管理方案,通过定义Function类存储函数相关信息,并实现函数添加与查找功能。此外,文章还探讨了如何在运行时环境中处理局部变量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

既然有了变量,那肯定想到函数了

老规矩,定义一个类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;

            }

        }

由于分析函数时,我们的处理方式是直接跳过了函数体,所以变量只能在运行时候添加,这更符合参数的传递。所以这里只是给出函数,还没有实现添加。

下一节准备实现函数调用的问题,这个要涉及到“运行时环境”的入栈和出栈问题,需要保持调用函数时的现场。以实现递归调用

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值