博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
表达式剖析器(THE EXPRESSION PARSER)
阅读量:2299 次
发布时间:2019-05-09

本文共 5064 字,大约阅读时间需要 16 分钟。

THE LITTLE C INTERPRETER

表达式剖析器(THE EXPRESSION PARSER)

   读取和分析表达式的这部分代码叫做表达式剖析器。毫无疑问,表达式剖析器是C解释器中单一的最重要的部分。因为C语言定义表达式的方式比其他语言更加粗鄙,所以用大量的代码组成的C源文件来实现表达式剖析器。
   有几种不同的方式来设计C的表达式剖析器。许多商业的编译器用一种由parser-generator创建的table-driven parser。尽管table-driven parser一般来说要快过其他方式,但却很难手工构建。为了开发简易的C解释器,在这里我们使用递归-继承剖析器(recursive-descent parser.)
  
   一个递归-继承剖析器本质上是一大堆处理表达式的互相递归的函数。如果是在编译器里,那么剖析器将被用来生成与源码相符的标准的目标代码。无论如何,在解释器中,剖析器的目的就是对给定的表达式求值。
  
将源代码变成他们的组成元素
 
   所有解释器的基础是一个读取源码然后返回下一个逻辑符号的特殊的函数。基于历史原因,这些逻辑标记一般与标记(token)相关联。一般而言,计算机语言,特别是C语言,依据标记来定义程序。你可以想象标记是一个不可见的程序单元。比如说,相等运算符==就是一个标记。这两个等于号分开后意义将彻底改变。同理,if 也是一个标记。在C语言中无论是i还是f都没有其他意思。
  
   在ANSI C标准中,标记被定义为属于下列几组:
   keywords    identifiers    constants   
   strings    operators    pinctuation
  
   keywords(关键字)是那些如while的构成C语言的标记。Identifier(识别符)是变量、函数、用户定义类型的名字。而Constants(常量)和 Strings(字符串)是不解自明的,就像operators(运算符)一样。最后Punctuation(标点符号)包括了几个项目,像分号,逗号,大括号,小括号等。
  
   在简易C解释器中,这个被称作get_token()的函数可以从源码中读取标记。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*Get a token*/
get_token()
{
    
register
char
*temp;
    
token_type=0;tok=0;
    
temp=token;
    
*temp=
'\0'
;
 
    
/* skip over white space*/
    
while
(iswhite(*prog)&&*prog)
        
++prog;
 
    
if
(*prog==
'\r'
)
    
{
        
++prog;
        
++prog;
        
while
(iswhite(*prog)&&*prog)
            
++prog;
    
}
 
    
if
(*prog==
'\0'
)
    
{
        
*token=
'\0'
;
        
tok=FINISHED;
        
return
(token_type=DELIMITER);
    
}
 
    
if
(
strchr
(
"{}"
,*prog))
    
{
        
*temp=*prog;
        
temp++;
        
*temp=
'\0'
;
        
prog++;
        
return
(token_type=BLOCK);
    
}
 
    
/* look for comments*/
    
if
(*prog==
'/'
)
        
if
(*(prog+1)==
'*'
)
        
{
            
prog+=2;
            
do
            
{
                
while
(*prog!=
'*'
) prog++;
                
prog++;
            
}
while
(*prog!=
'/'
);
            
prog++;
        
}
 
    
if
(
strchr
(
"!<>="
,*prog))
    
{
        
switch
(*prog)
        
{
            
case
'='
:
                
if
(*(prog+1)==
'='
)
                
{
                    
prog++;prog++;
                    
*temp=EQ;
                    
temp++; *temp=EQ; temp++;
                    
*temp=
'\0'
;
                
}
                
break
;
            
case
'!'
:
                
if
(*(prog+1)==
'='
)
                
{
                    
prog++;prog++;
                    
*temp=NE;
                    
temp++; *temp=NE; temp++;
                    
*temp=
'\0'
;
                
}
                
break
;
            
case
'<'
:
                
if
(*(prog+1)==
'='
)
                
{
                    
prog++;prog++;
                    
*temp=LE;temp++;*temp=LE;
                
}
                
else
                
{
                    
prog++;
                    
*temp=LT;
                
}
                
temp++;
                
*temp=
'\0'
;
                
break
;
            
case
'>'
:
                
if
(*(prog+1)==
'='
)
                
{
                    
prog++;prog++;
                    
*temp=GE;temp++;*temp=GE;
                
}
                
else
                
{
                    
prog++;
                    
*temp=GT;
                
}
                
temp++;
                
*temp=
'\0'
;
                
break
;
        
}
        
if
(*token)
return
(token_type=DELIMITER);
    
}
 
    
if
(
strchr
(
"+-*^/%=;(),'"
,*prog))
    
{
        
*temp=*prog;
        
prog++;
        
temp++;
        
*temp=
'\0'
;
        
return
(token_type=DELIMITER);
    
}
 
    
if
(*prog==
'"'
)
    
{
        
prog++;
        
while
(*prog!=
'"'
&& *prog!=
'\r'
)
            
*temp++=*prog++;
        
if
(*prog==
'\r'
) sntx_err(SYNTAX);
        
prog++;*temp=
'\0'
;
        
return
(token_type=STRING);
    
}
 
    
if
(
isdigit
(*prog))
    
{
        
while
(!isdelim(*prog))
            
*temp++=*prog++;
        
*temp=
'\0'
;
        
return
(token_type=NUMBER);
    
}
 
    
if
(
isalpha
(*prog))
    
{
        
while
(!isdelim(*prog))
            
*temp++=*prog++;
        
token_type=TEMP;
    
}
 
    
*temp=
'\0'
;
 
    
/*see if a string is a command or variable*/
    
if
(token_type==TEMP)
    
{
        
tok=look_up(token);
        
if
(tok) token_type=KEYWORD;
        
else
token_type=IDENTIFIER;
    
}
    
return
token_type;
}

get_token()函数用到了以下的全局变量和枚举类型:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extern
char
*prog;
extern
char
*p_buf;
extern
char
token[80];
extern
char
token_type;
extern
char
tok;
 
 
enum
tok_types {DELIMITER,IDENTIFIER,NUMBER,
                
KEYWORD,TEMP,STRING,BLOCK};
                 
enum
double_ops {LT=1,LE,GT,GE,EQ,NE};
 
enum
error_msg{
    
SYNTAX,UNBAL_PARENS,NO_EXP,EQUALS_EXPECTED,
    
NOT_VAR,PARAM_ERR,SEMI_EXPECTED,
    
UNBAL_BRACES,FUNC_UNDEF,TYPE_EXPECTED,
    
NEST_FUNC,RET_NOCALL,PAREN_EXPECTED,
    
WHILE_EXPECTED,QUOTE_EXPECTED,NOT_TEMP,
    
TOO_MANY_LVARS};

   在源码中当前位置由prog指定。p_buf指针无法被解释器所改变而且经常指向正在被解析的程序的开头。get_token()函数开始跳过所有的空白,包括回车和空行。直到没有C标记包含空格,所以的空格必须被跳过。当然get_token()函数也要跳过注释。接下来,将每个标记的字符串表现形式归类到token,它的类型用token_type表达,而且,如果标记是一个关键字,它的内部表示将通过look_up()函数分配给tok。如你所见的get_token()函数,它将C的双字关系运算符转换成对应的枚举值。虽然不是技术需要,但这一步可以简化parser剖析器的实现。最后,如果parser剖析器遇到语法错误,将会调用sntx_err()函数。sntx_err()函数如下:

   

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*Display an error message*/
void
sntx_err(
int
error)
{
    
char
*p,*temp;
    
int
linecount=0;
    
register
int
i;
 
    
static
char
*e[]={
        
"Syntax error"
,
        
"unbalanced parentheses"
,
        
"no expression present"
,
        
"equals sign expected"
,
        
"not a variable"
,
        
"parameter error"
,
        
"semicolon expected"
,
        
"unbalanced braces"
,
        
"function undefined"
,
        
"type specifier expected"
,
        
"too many nested function calls"
,
        
"return without call"
,
        
"parentheses expected"
,
        
"while expected"
,
        
"closing quoto expected"
,
        
"not a string"
,
        
"too many local variable"
    
};
    
printf
(
"%s"
,e[error]);
    
p=p_buf;
    
while
(p!=prog)
    
{
        
p++;
        
if
(*p==
'\r'
)
            
linecount++;
    
}
    
printf
(
" in line %d\n"
,linecount);
    
temp=p;
    
for
(i=0;i<20 && p>p_buf && *p!=
'\n'
;i++,p--);
    
for
(i=0;i<30 && p<=temp;i++,p++)
        
printf
(
"%c"
,*p);
    
longjmp
(e_buf,1);
}

   值得注意的是sntx_err()也会显示出错语句的行号,最后要注意的是sntx_err()调用longjmp()结束。因为语法错误会在嵌套或递归程序中频繁地出现,最简单的控制错误的方法就是跳到一个安全的地方。

 

   

 

转载地址:http://wyuib.baihongyu.com/

你可能感兴趣的文章
日期操作
查看>>
iOS的三种弹框
查看>>
UIApplication和程序启动过程
查看>>
cocoaPods安装2017 以及遇到的坑
查看>>
Android中自定义可以选择中文的NumberPicker屏蔽弹出软键盘
查看>>
Scrapy教程——搭建环境、创建项目、爬取内容、保存文件(txt)
查看>>
SQL SERVER 2012 附加数据AdventureWorks2012失败解决方案
查看>>
C++内联函数(inline)的工作原理与例子
查看>>
Eclipse中使用svn主要命令的详细介绍
查看>>
MS第二题解题思路
查看>>
第一个mpi程序in linux
查看>>
epoll 详解
查看>>
Hadoop 面试题
查看>>
【Day22】mysql数据库的优化(一版)
查看>>
【Day23】几道值得研究注意的php相关问题(一)
查看>>
【Day24】几道值得研究注意的php相关问题(二)
查看>>
php源码之路第三章第六节( 变量的生命周期之变量的赋值和销毁)
查看>>
【Day35】浅谈PHP拦截器之__set()与__get()的理解与使用方法
查看>>
php源码之路第四章第一节( 函数的内部结构)
查看>>
【Day36】PHP定时任务获取微信access_token
查看>>