设计一门脚本语言——(四)简单表达式的处理

现在还不打算实现真真意义的语法分析,况且这玩意我也不太了解~

反正没看过编译原理,对这些概念上的东西很模糊。那我们从哪里开始呢?

先说说上面最后进行的测试吧,有这么一句

@zzzzz=1@@@@@22aaa;这么怪异的句子,大家都知道肯定错的,但确能通过词法分析。

因为词法分析不检查脚本语法,仅仅看句子是否符合Token的定义

@  zzzzz = 1 这些都是符合的。。所以可以通过

但显然,1@@@@@22aaa;这并不是一个值,不能赋值给变量zzzzz。所以我们先讨论什么是一个有意义的值。

说到值,第一个想到的就是1、5、999这些数据了。但脚本很多时候执行的不只是这些简单的字面量,比如

@x = 1+2;

这就涉及到表达式的分析了。

分析表达式:

1+2 就是一个简单的表达式,分析它,可能大家第一个想到如下代码

public int ParseExp()

{

lexel.GetToken();

string val1 = lexel.CurrToken;

lexel.GetToken();

string op = lexel.CurrToken;

lexel.GetToken();

string val2 = lexel.CurrToken;

int result = 0;

switch ( op )

{

case “+”:

result = int.Parse(val1) + int.Parse(val2);

break;

case “-”:

result = int.Parse(val1) - int.Parse(val2);

break;

case “*”:

result = int.Parse(val1) * int.Parse(val2);

break;

case “/”:

result = int.Parse(val1) / int.Parse(val2);

break;

default:

break;

}

return result;

}

先不论脚本如果拼写错误怎么处理。就是脚本完全正确,对于1+2完全能够处理

如果1+2+3呢?可能你又要急着动手呢,或许你构思着,用一个while语句进行处理。没错,完全可以实现。而且对于型如:1+2-3+4等等的表达式,都能成功。更有如1*2+3都可以正常分析

或许你开心的就要用于项目中了,但我们不得不做最后一次测试

如果是1+2*3+4这样的表达式呢?

这涉及到优先级的问题,上面的函数根本没有实现。

我们仔细看这个表达式

用人类的思维来看,我们首先做2*3=6的乘法,然后1+6=7 7+4=11这就完成了。

对于表达式的分析,我们一般把1 2 3 4还有以后用到的变量x y函数调用$z()等,都看做“因子”,就是最小的表达式成分了,你不能对他们进行更一步的运算了

接下来我们把2*3  5*6/7  x*y*2等等看做“一项”,优先级是大于+ -运算的。

最后就是一个表达式了,是由+和-连接的一连串的“项目”

有了上面的分析,那么1+2*3+4就可以看做是

1

2*3

4

这三个“项”最加法,而其中2*3这个“项”又是由2和3这两个因子最乘法组成的

接下来我们开始编码实现了

首先需要几个函数

private void ParseExp();//分析表达式的函数

private void ParseTerm(); // 分析项的函数

private void ParseFactor(); // 分析因子的函数

另外在定义一个变量

private string expValue;

用来保存表达式每一步运算的值(暂时用string来表示我们脚本中的值,因为我还没有专门写一个类来实现“值”)

private void ParseExp()

{

try

{

string value = string.Empty;

ParseTerm();

value = expValue;

while (true)

{

lexel.GetToken();

if (lexel.CurrToken == ";" )

{

return;

}

switch (lexel.CurrToken)

{

case "+":

ParseTerm();

value += expValue;

break;

case "-":

ParseTerm();

value -= expValue;

break;

default:

throw new Exception("Unknown Operator " + lexel.CurrToken);

}

}

expValue = value;

if (expLayer != 0)

{

throw new Exception("Mismatching Paren");

}

}

catch (Exception ex)

{

throw ex;

}

}

private void ParseTerm()

{

try

{

string value = string.Empty;

ParseFactor();

value = expValue;

while (true)

{

lexel.GetToken();

switch (lexel.CurrToken)

{

case "*":

ParseFactor();

value *= expValue;

break;

case "/":

ParseFactor();

value /= expValue;

break;

default:

lexel.Rewind();

expValue = value;

return;

}

expValue = value;

}

}

catch (Exception ex)

{

throw ex;

}

}

private void ParseFactor()

{

try

{

switch (lexel.GetToken())

{

case TokenType.Real:

expValue = lexel.CurrToken;

break;

default:

throw new Exception("Unknown Value " + lexel.CurrToken);

}

}

catch (Exception ex)

{

throw ex;

}

}

有几个地方需要说明

lexel.Rewind();是lexel中的一个函数,用于当我们获取了不用的Token,回滚Token流。简单一点就是index--;

public void Rewind()

{

if (index > 0)

{

index--;

currToken = prevToken;

currType = prevType;

}

else

{

throw new Exception("Can't Rewind()");

}

}

prevToken和prevType是lexel中的两个变量,储存上一个Token和Type。

在GetToken()的最开始加入

prevToken = currToken;

prevType = currType;

好了,忍不住运行一下,在test.txt中输入1+2*3+4;

运行咦,提示Unkown value 1+2*3+4

怎么没有分割呢?对了,我们还没有加入分隔符嘛

因为+ - * /都是运算符

所以在Lexel加入

private static readonly char[] Operators = new char[]

{

 '+''-''*''/''%'

};

然后再Lexel.Start函数中,把

if (splitSigns.Contains<char>(c))

{

if (line.Length != 0)

{

Tokens.Add(line.ToString());

line.Clear();

}

Tokens.Add(c.ToString());

continue;

}

if语句改为

if (splitSigns.Contains<char>(c) || Operators.Contains<char>(c))

另外,还有在TokenType中加入一个Operator

TokenType.Operator表示运算符

最后,为了设别它,我们需要在Lexel.GetTokenType的开头加入

if (token.Length == 1)

{

if (Operators.Contains<char>(token[0]))

{

return TokenType.Operator;

}

}

好了,完成测试下。打印expValue的值,看到是11。

接下来加入()。

使我们的分析器能认识1+2*(3+4)

因为我们是先算的括号里的,而括号里又可以是一个表达式,所以我们可以递归的调用

首先加入TokenType.Left_Small_Paren和TokenType.Right_Small_Paren表示“(”和“)”

然后在GetTokenType中加入

if (token == "(")

{

return TokenType.Left_Small_Paren;

}

if (token == ")")

{

return TokenType.Right_Small_Paren;

}

然后在Parse.ParseFactor中加入一项

switch (lexel.GetToken())

{

case TokenType.Left_Small_Paren:

ParseExp();

return;

最后在ParseExp的

if (lexel.CurrToken == ";")

{

lexel.Rewind();

break;

}

后面加上

if (lexel.CurrToken == ")")

{

expValue = value;

return;

}

好了,完成。是不是比较容易的实现了。

另外,这里说明下,我们没有考虑错误处理

比如(1+2)),多打了一个括号怎么办?

这个简单,可以添加变量int expLayer;

当遇到“(”的时候expLayer++;

当遇到“)”的时候expLayer--;

最后当表达式处理完了后,检查expLayer是不是为0

不为0,那肯定是括号不匹配了。

而且,还可以根据expLayer是正数还是负数,可以确定是“(”还是“)”多了

具体就大家自己动手了,挺简单。

下一步做什么呢?恩,定义变量,并且可以用于表达式处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值