最近在一直做一个东西。设计一种脚本语言,再写一个翻译器,将这种脚本语言翻译成avr-gcc可以执行的C语言程序,再将得到的C语言程序利用avr-gcc编译器编译成Intel的hex文件格式,再写一个类似bootloader的东西,将这个hex文件以无线的方式加载到内存执行。这个类似bootloader的东西就是直接跟单片机芯片的存储器打交道,实现起来确实有点难度。万事开头难,只要做好第一步,慢慢来,总是可以实现的。
本以为这个翻译器很好做,无非就是按照一般编译原理的路子就是词法分析、语法分析、语义分析、中间代码生成等一系列步骤,最终转换成avr-gcc支持的C语言代码。事非经过不知难,结果第一步就卡住了。词法分析就是将输入的源程序分割成一系列的单词(Token),这个过程可以依赖正则表达式来实现。所以需要写一个正则表达式的引擎,解析正则表达式。自己也写了一个,测试了一下结果发现效率不是一般的低,呵呵。只好另辟思路,利用古老而又强大的工具yacc、lex来做。发现这个工具确实很强大,熟练掌握之,再用其来实现各种文本搜索、重新定义一种脚本语言等,开发效率会很快。不扯了,最近也在学lex和yacc。这里给出一个例子,用lex和yacc做一个计算器。
Linux系统下面自带了flex和bison,它们是lex和yacc的增强版。可以在lex中利用正则表达式定义符号。calculator.l的源码如下:
%{
#include <stdio.h>
#include "y.tab.h"
int
yywrap(void)
{
return 1;
}
%}
%%
"+" return ADD;
"-" return SUB;
"*" return MUL;
"/" return DIV;
"\n" return CR;
([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
double temp;
sscanf(yytext, "%lf", &temp);
yylval.double_value = temp;
return DOUBLE_LITERAL;
}
[ \t] ;
. {
fprintf(stderr, "lexical error.\n");
exit(1);
}
%%
程序比较简单,不解释。不懂的可以参考《Lex和Yacc》,很实用的一本书。
然后接下来用yacc生成语法分析器,calculator.y的源码如下:
%{
#include<stdio.h>
#include<stdlib.h>
#define YYDEBUG 1
%}
%union {
int int_value;
double double_value;
}
%token <double_value> DOUBLE_LITERAL
%token ADD SUB MUL DIV CR
%type <double_value> expression term primary_expression
%%
line_list
: line
| line_list line
;
line
: expression CR
{
printf(">>%lf\n",$1);
}
expression
: term
| expression ADD term
{
$$=$1+$3;
}
| expression SUB term
{
$$=$1-$3;
}
;
term
: primary_expression
| term MUL primary_expression
{
$$=$1*$3;
}
| term DIV primary_expression
{
$$=$1/$3;
}
;
primary_expression
: DOUBLE_LITERAL
;
%%
int
yyerror(char const *str)
{
extern char *yytext;
fprintf(stderr,"parser error near %s\n",yytext);
return 0;
}
int main(void)
{
extern int yyparse(void);
extern FILE *yyin;
yyin=stdin;
if(yyparse()){
fprintf(stderr,"Error! Error! Error!\n");
exit(1);
}
}
在第一个%%之前定义了一系列的终结符和非终结符。然后又产生了类似下面结构的巴斯科范式(BackusNormal Form):
line_list/*多行的规则*/
: line
| line_list line
;
line/*单行的规则*/
: expression CR/*一个表达式后面跟一个换行符*/
;
expression/*表达式的规则*/
: term
| expression ADD term/*+得到的项*/
| expression SUB term/*-得到的项*/
;
term/*项的规则*/
: primary_expression
| term MUL primary_expression/*乘得到的项*/
| term DIV primary_expression/*除得到的项*/
;
primary_expression/*一元表达式*/
: DOUBLE_LITERAL/*实数终结符*/
;
上面的一系列规则其实就是定义了一个如下的文法G[line_list]:
line_list → line | line_list line
line → expression CR
expression → term | expression ADD term | expression SUB term
term → primary_expression | term MUL primary_expression | term DIV primary_expression
primary_expression → DOUBLE_LITERAL
接下来编译并执行,操作如下图: