HinM_COMPILER_cale
项目的计划和实现
HinM_COMPILER_cale项目是在学习编译原理的时候结合实际写出的子项目,这里讨论的是用编译的思想来实现一个高效能的计算器。
项目分为两个部分,一个是采用DFA的方式生成一个简单的四则运算的计算器。第二个部分是结合使用yacc和lex生成一个复杂的计算器。项目实现时间计划为一个星期。(2008-6-16—2008-6-22)
一个采用DFA
的方式生成的简单的四则运算的计算器
这个项目我的实现参考了别人的例子,所以还是比较顺利的。
DFA的意思就是有穷自动机,是词法分析的内容,其实用词法分析的方法编写这个程序对于我自己来说教益非常大,可以说获得了智慧。我感觉如果我们把机器的状态用更加形式化的方法表示出来,将更容易控制。这个简单的,只可以处理四则运算的计算器的DFA如下:
态涵涵盖了简单的四则运算计算器可能出现的所有状态,首先能够在头脑中将所有的状态思考出来,然后用图表或其它抽象的方法将其形式化表现,用程序语言叙述,这是计算机程序编写的基本过程。
这样,就可以来编写代码,实现这个DFA,比较简答的实现方法是两层select的嵌套
Select(0)
{
Case 1
……
Case 2
……
Case 3
{
Select(input)
{
Case GOTO1
……
Case GOTO2
……
Case GOTO3
……
……
}
……
}
这样正好对于了状态和状态的转换,而且DFA又只是双层的,比较贴切,这个只是也是积累到的;另一个方面,比较重要的就是对于非常的输入DFA可以
不相应,因为本来在这个输入下DFA本身就没有合适的状态来转移嘛.当然,不响应是对于计算器这个程序的特性来说的,对于其它不同的程序,可以统一导向到一个错误收集区域中去,这样整个程序的架构就合理了。
示例代码采用c+win32api实现,采用api的好处是以后移植到HinM中去的时候,修改相应的api就可以了。下面分析代码
// 各函数参数表含义见其定义处/
//
// DFA模拟函数
//
void MiniDFA(HWND hWnd, UINT uInput);
//
// 向显示区追加一位数字
//
void AppendNumber(HWND hWnd, TCHAR ch);
//
// 在显示区退格删去一位数字
//
void BackSpace(HWND hWnd);
//
// 改变显示区数字的符号
//
void ChangeSign(HWND hWnd);
//
// 显示数字
//
void ShowNumber (HWND hWnd, double dwNumber);
//
// 返回显示的浮点数
//
double DisplayToNumber(HWND hWnd);
//
// 对两个操作数做加减乘除运算,返回运算结果
//
double CalcIt(double dwLeftNum, UINT uOperation, double dwRightNum);
//
// 显示区清屏
//
void Clear(HWND hWnd);
//
// 显示当前运算符
//
void SetOperator(HWND hWnd, UINT uOperator);
//
// 显示记忆标志
//
void SetMemoSign(HWND hWnd, BOOL bSet);
//
//
这里就是这个minicale中用到的一些函数了,可以看到比DFA那个图中的要实际一些,包括下面几个类别
1. 主函数,程序入口 MiNiDFA
2. 数据运算 CalcIt
3. 对当前数据的增加,删除,修改 AppendNumber,BackSpace,ChangeSign
4. 显示存储数据 ShowNumbe,DisplayToNumber,SetOperator,SetMemoSign
5. 其它 Clear
其实只有前面三个比较重要,后面两个不一定需要成为函数。
下面从细节到整体分析程序
//AppendNumber 追加显示数字
// hWnd
编辑控件所在窗口句柄
// ch
读取的数字或小数点
void AppendNumber(HWND hWnd, TCHAR ch)
{
TCHAR szBuffer[40], // 用于接收当前显示数字串
szAppend[2] = {'0', '/0'}; // szAppend[0] 为要添加的数字字符
GetWindowText(GetDlgItem(hWnd, IDC_EDITDISPLAY), szBuffer, 30);
szAppend[0] = ch;
strcat(szBuffer, szAppend);
SetWindowText(GetDlgItem(hWnd, IDC_EDITDISPLAY), szBuffer);
}
//
函数平淡无奇,但是给我编写操作系统的提示是需要有类似于GetWindowText,SetWindowText,和GetDlgitem之类的在class和View 之间传递消息的函数,我认为这是很合理的。
//BackSpace
退格,删去显示中的一位
// hWnd
编辑控件所在窗口句柄
void BackSpace(HWND hWnd)
{
TCHAR szBuffer[40]; // 用于接收当前显示数字串
int length;
length = GetWindowText(GetDlgItem(hWnd, IDC_EDITDISPLAY), szBuffer, 30);
szBuffer[length - 1] = '/0';
}
//ChangeSign 改变符号
// hWnd
编辑控件所在窗口句柄
void ChangeSign(HWND hWnd)
{
TCHAR szBuffer[40]; // 当前显示数字串
int length;
length = GetWindowText(GetDlgItem(hWnd, IDC_EDITDISPLAY), szBuffer, 30);
if(TEXT('0') == szBuffer[0])
return; //无数,所以不变
if(TEXT('-') == szBuffer[0]) // 负数
szBuffer[0] = TEXT(' ');
else // 正数 ,它的length被减小了1
{
strrev(szBuffer);
if(TEXT(' ') == szBuffer[length - 1])
length -= 1;
szBuffer[length] = TEXT('-');
szBuffer[length + 1] = TEXT('/0');
strrev(szBuffer);
}
SetWindowText(GetDlgItem(hWnd, IDC_EDITDISPLAY), szBuffer);
}
计算函数
double CalcIt(double dwLeftNum, UINT uOperation, double dwRightNum)
{
switch (uOperation)
{
case '+': return dwLeftNum + dwRightNum ;
case '-': return dwLeftNum - dwRightNum ;
case '*': return dwLeftNum * dwRightNum ;
case '/': return dwLeftNum / dwRightNum;
default: return 0;
}
}
下面是这个程序的核心,其实就是将那个图程序化的一个东西:计算器的确定有穷自动机(DFA)模拟函数
//MiNiDfa
计算器的确定有穷自动机(DFA)模拟函数
// hWnd
主窗口句柄
// uInput
读取的输入符号
// 状态的转换 (一个状态)-输入->(另一状态)
void MiniDFA(HWND hWnd, UINT uInput)
{
static UINT uState = 0, uOperator = '=';
// 状态,运算符
static double dwLeftNum = 0, dwRightNum = 0;
// 左右操作数
static double dwMemo = 0; // 保存一个数的空间
static UINT uIntegralCount = 0, // 整数位数计数器
uDecCount = 0; // 小数位数计数器
// 在各状态下BackSpace, CE, C, MC, MR, MS, M+, SQRT, %, 1 / X, + / -
// 的处理较为统一, 所以先行处理以简化后面DFA主体的设计
switch(uInput)
{
………………………………
// DFA主体代码
// (OneState)-input->(AnotherState)
switch(uState)
{
case 0:
switch(uInput)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
uIntegralCount = 1;
Clear(hWnd);
AppendNumber(hWnd, uInput);
uState = 1; // (0)-num->(1)
break;
case '.':
uIntegralCount = 0; // 整数位计数器归零
Clear(hWnd);
AppendNumber(hWnd, '0'), AppendNumber(hWnd, '.'); // 显示"0."
uState = 2; // (0)-dot->(2)
break;
case '+':
case '-':
case '*':
case '/':
uIntegralCount = 0; // 整数位计数器归零
uOperator = uInput;
SetOperator(hWnd, uOperator);
uState = 3; // (0)-op->(3)
break;
}
break;
// 其它的都是trival,不可能实现的。
case 1:
switch(uInput)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
uIntegralCount ++;
AppendNumber(hWnd, uInput);
uState = 1; // (1)-num->(1)
break;
case '.':
AppendNumber(hWnd, '.');
uState = 2; // (1)-dot->(2)
break;
case '+':
case '-':
case '*':
case '/':
dwLeftNum = DisplayToNumber(hWnd);
uOperator = uInput;
SetOperator(hWnd, uOperator);
uState = 3; // (1)-op->(3)
break;
}
break;
case 2:
switch(uInput)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
uDecCount++;
AppendNumber(hWnd, uInput);
uState = 2; // (2)-num->(2)
break;
case '+':
case '-':
case '*':
case '/':
uDecCount = 0; // 小数位计数器归零
dwLeftNum = DisplayToNumber(hWnd);
uOperator = uInput;
SetOperator(hWnd, uOperator);
uState = 3; // (2)-op->(3)
break;
}
break;
case 3:
switch(uInput)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
uIntegralCount = 1;
Clear(hWnd);
AppendNumber(hWnd, uInput);
uState = 4; // (3)-num->(4)
break;
case '.':
uIntegralCount = 0; // 整数位计数器归零
Clear(hWnd);
AppendNumber(hWnd, '0'), AppendNumber(hWnd, '.'); // 显示"0."
uState = 5; // (3)-dot->(5)
break;
case '+':
case '-':
case '*':
case '/':
uIntegralCount = 0; // 整数位计数器归零
uOperator = uInput;
SetOperator(hWnd, uOperator);
uState = 3; // (3)-op->(3)
break;
}
break;
case 4:
switch(uInput)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
uIntegralCount ++;
AppendNumber(hWnd, uInput);
uState = 4; // (4)-num->(4)
break;
case '.':
AppendNumber(hWnd, '.');
uState = 5; // (4)-dot->(5)
break;
case '+':
case '-':
case '*':
case '/':
uIntegralCount = 0; // 整数位计数器归零
dwRightNum = DisplayToNumber(hWnd);
dwLeftNum = CalcIt(dwLeftNum, uOperator, dwRightNum);
ShowNumber(hWnd, dwLeftNum);
uOperator = uInput;
SetOperator(hWnd, uOperator);
uState = 3; // (4)-op->(3)
break;
case '=':
uIntegralCount = 0; // 整数位计数器归零
dwRightNum = DisplayToNumber(hWnd);
dwLeftNum = CalcIt(dwLeftNum, uOperator, dwRightNum);
ShowNumber(hWnd, dwLeftNum);
uOperator = '=';
SetOperator(hWnd, ' ');
uState = 0; // (4)-op->(0) ,接受状态
break;
}
break;
case 5:
switch(uInput)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
uDecCount++;
AppendNumber(hWnd, uInput);
uState = 5; // (5)-num->(5)
break;
case '+':
case '-':
case '*':
case '/':
uIntegralCount = 0; // 整数位计数器归零
uDecCount = 0; // 小数位计数器归零
dwRightNum = DisplayToNumber(hWnd);
dwLeftNum = CalcIt(dwLeftNum, uOperator, dwRightNum);
ShowNumber(hWnd, dwLeftNum);
uOperator = uInput;
SetOperator(hWnd, uOperator);
uState = 3; // (5)-op->(3)
break;
case '=':
uIntegralCount = 0; // 整数位计数器归零
uDecCount = 0; // 小数位计数器归零
dwRightNum = DisplayToNumber(hWnd);
dwLeftNum = CalcIt(dwLeftNum, uOperator, dwRightNum);
ShowNumber(hWnd, dwLeftNum);
uOperator = '=';
SetOperator(hWnd, ' ');
uState = 0; // (5)-op->(0)
break;
}
break;
}
}
结合使用yacc
和lex
生成一个复杂的计算器
光是学习yacc就很头疼,但是谁叫它那么有用了,是吧,所以所有努力都是值得的。首先说一下为什么在复杂的情况下DFA失去作用。最为显著的就是括号,这种需要前后匹配的东西,如果采取类似DFA的方法,构造一个DFA,可能是类似于下面这个样子:
其
中符号的含义:
圆圈:状态 (同心圆代表接受状态)
箭头:状态的转换方向
num: 输入数字
dot: 输入小数点
( 和 ): 输入括号
op: 输入运算符号
(
+
、
-
、
*
、
/
)
=
:
输入等于号
I :
括号计数器
那么会存在这个问题,那就是形如 (1+2+(3+4)) 的嵌套的式子的时候,在处理中间一个式子的时候,需要对前面的计算值和符号做保存,而由于嵌套的层数未知,所以这里无法控制,需要用更强的上下文无关文法作为编程思想。
在编译原理(陈意云)的100页就提供了一个计算器的yacc代码,我现在了解到yacc是可以直接嵌入vc6的,既然是这样,就可以更好地将代码移植过去。对这方面的知识我进行了学习后积累了以下知识: