现在还不打算实现真真意义的语法分析,况且这玩意我也不太了解~
反正没看过编译原理,对这些概念上的东西很模糊。那我们从哪里开始呢?
先说说上面最后进行的测试吧,有这么一句
@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是正数还是负数,可以确定是“(”还是“)”多了
具体就大家自己动手了,挺简单。
下一步做什么呢?恩,定义变量,并且可以用于表达式处理。