利用lex和yacc做词法、语法分析

最近在一直做一个东西。设计一种脚本语言,再写一个翻译器,将这种脚本语言翻译成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

接下来编译并执行,操作如下图:





  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值