实验一 词法分析设计
实验学时:4 实验类型:综合 实验要求:必修
一、实验目的
通过本实验的编程实践,使学生了解词法分析的任务,掌握词法分析程序设 计的原理和构造方法,使学生对编译的基本概念、原理和方法有完整的和清楚的 理解,并能正确地、熟练地运用。
二、实验内容
用 VC++/VB/JAVA 语言实现对 C 语言子集的源程序进行词法分析。通过输 入源程序从左到右对字符串进行扫描和分解,依次输出各个单词的内部编码及单 词符号自身值;若遇到错误则显示“Error”,然后跳过错误部分继续显示 ;同时 进行标识符登记符号表的管理。
以下是实现词法分析设计的主要工作:
(1)从源程序文件中读入字符。
(2)统计行数和列数用于错误单词的定位。
(3)删除空格类字符,包括回车、制表符空格。
(4)按拼写单词,并用(内码,属性)二元式表示。(属性值——token 的机内 表示)
(5)如果发现错误则报告出错
6
(6)根据需要是否填写标识符表供以后各阶段使用。 单词的基本分类:
关键字:由程序语言定义的具有固定意义的标识符。也称为保留字例如 if、 for、while、printf ; 单词种别码为 1。
标识符:用以表示各种名字,如变量名、数组名、函数名;
常数: 任何数值常数。如 125, 1,0.5,3.1416;
运算符:+、-、*、/;
关系运算符: <、<=、= 、>、>=、<>; 分界符: ;、,、(、)、[、];
三、词法分析实验设计思想及算法 1、主程序设计考虑:
程序的说明部分为各种表格和变量安排空间。 在具体实现时,将各类单词设计成结构和长度均相同的形式,较短的关键字后面 补空。
k 数组------关键字表,每个数组元素存放一个关键字(事先构造好关键字表)。
s 数组------存放分界符表(可事先构造好分界符表)。为了简单起见,分界符、 算术运算符和关系运算符都放在 s 表中(编程时,应建立算术运算符表和关系运 算符表,并且各有类号),合并成一类。
id 和 ci 数组分别存放标识符和常数。
instring 数组为输入源程序的单词缓存。
outtoken 记录为输出内部表示缓存。
还有一些为造表填表设置的变量。
主程序开始后,先以人工方式输入关键字,造 k 表;再输入分界符等造 p 表。
主程序的工作部分设计成便于调试的循环结构。每个循环处理一个单词; 接收键盘上送来的一个单词;调用词法分析过程;输出每个单词的内部码。 例如,把每一单词设计成如下形式: (type,pointer)
其中 type 指明单词的种类,例如:Pointer 指向本单词存放处的开始位置。
7
还有一些为造表填表设置的变量。
主程序开始后,先以人工方式输入关键字,造 k 表;再输入分界符等造 p 表。
主程序的工作部分设计成便于调试的循环结构。每个循环处理一个单词; 接收键盘上送来的一个单词;调用词法分析过程;输出每个单词的内部码。 例如,把每一单词设计成如下形式: (type,pointer)
其中 type 指明单词的种类,例如:Pointer 指向本单词存放处的开始位置。
8
词法分析设计流程图
2、词法分析过程考虑
根据输入单词的第一个字符(有时还需读第二个字符),
判断单词类,产生类号:以字符 k 表示关键字;id 表示标识符;
ci 表示常数;s 表示分界符。
对于标识符和常数,需分别与标识符表和常数表中已登记的元素相比较, 如表中已有该元素,则记录其在表中的位置,如未出现过,将标识符按顺序填入 数组 id 中,将常数变为二进制形式存入数组中 ci 中,并记录其在表中的位置。 lexical 过程中嵌有两个小过程:一个名为 getchar,其功能为从 instring 中按 顺序取出一个字符,并将其指针 pint 加 1 ;另一个名为 error,当出现错误时, 调用这个过程,输出错误编号。
要求:所有识别出的单词都用两个字节的等长表示,称为内部码。第 一个字节为 t ,第二个字节为 i 。 t 为单词的种类。关键字的 t=1;分界符 的 t=2;算术运算符的 t=3;关系运算符的 t=4;无符号数的 t=5;标识符 的 t=6。i 为该单词在各自表中的指针或内部码值。表 1 为关键字表;表 2 为
9
分界符表;表 3 为算术运算符的 i 值;表 4 为关系运算符的 i 值。
10
四、实验要求
取字符和统计字符行列位置子程序
1、编程时注意编程风格:空行的使用、注释的使用、缩进的使用等。 2、将标识符填写的相应符号表须提供给编译程序的以后各阶段使用。 3、根据测试数据进行测试。测试实例应包括以下三个部分:
全部合法的输入。
各种组合的非法输入。 由记号组成的句子。
4、词法分析程序设计要求输出形式: 例:输入 VC++语言的实例程序:
If i=0 then n++; a<= 3b %); 输出形式为:
单词 二元序列 类型 位置(行,列)
for i
=
(单词种别,单词属性)
(1,for ) 关键字
( 6,i ) 标识符
( 4,= ) 关系运算符
(1,1) (1,2) (1,3)
11
0 then n ++ ;
a <= 3b %
)
;
( 5,0 )
( 1,then)
(6,n ) Error ( 2, ; )
(6,a ) (4,<= ) Error Error
( 2, ) ) ( 2, ; )
常数 (1,4) 关键字 (1,5) 标识符 (1,6) Error (1,7) 分界符 (1,8) 标识符 (2,1) 关系运算符 (2,2) Error (2,4) Error (2,4)
分界符 (2,5) 分界符 (2,6)
五、实验步骤
1、根据流程图编写出各个模块的源程序代码上机调试。
2、 编制好源程序后,设计若干用例对系统进行全面的上机测试,并通过所设计 的词法分析程序;直至能够得到完全满意的结果。
3、书写实验报告 ;实验报告正文的内容:
功能描述:该程序具有什么功能?
程序结构描述:函数调用格式、参数含义、返回值描述、函数功能;函数 之间的调用关系图。
详细的算法描述(程序总体执行流程图)。
给出软件的测试方法和测试结果。
实验总结 (设计的特点、不足、收获与体会)。
import java.io.IOException;
import java.io.InputStreamReader;
public class Q1{
private char ch;
private StringBuilder inString = new StringBuilder();
private StringBuilder outToken = new StringBuilder();
private int i;
private int j;
private int row;
private int line;
public void startAnalyze() {
while(j < inString.length()){
getChar(); getBC();
if(isLetter()){
while(isLetter() || isDigit()){
concat(); getChar();
}
retract();
if(reserve()){
formatPrint(1);
}else{
// insertId();
formatPrint(6);
}
}else if(isDigit()){
while(isDigit()){
concat(); getChar();
}
if (isLetter()){
while (isLetter()){
concat(); getChar();
}
retract();
formatPrint(0);
continue;
}
retract();
// insertCount();
formatPrint(5);
}else{
if(isOther()){
concat();
getChar();
if(ch != '\0'){
if(isOther()){
concat();
}else {
retract();
}
}
formatPrint(getType());
}else{
concat();
formatPrint(0);
}
}
}
}
private int getType() {
int i = 0;
while (i < p.length){
if(p[i].equals(outToken.toString())){
break;
}
i++;
}
if(i < 6){
return 2;
}else if(i < 10){
return 3;
}else if(i < 16){
return 4;
}else{
boolean flag1 = false;
boolean flag2 = false;
for(int k = 0; k < 6; k++){
if(p[k].charAt(0) == outToken.toString().charAt(0)){
flag1 = true;
}
if(p[k].charAt(0) == outToken.toString().charAt(1)){
flag2 = true;
}
}
if(flag1 && flag2){
retract();
outToken.delete(1, 2);
return 2;
}
return 0;
}
}
public void getChar() {
if(j < inString.length()){
ch = inString.charAt(j++);
line++;
}else{
ch = '\0';
}
}
public void getBC(){
while(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'){
if(ch == '\n'){
row++;
line = 1;
}
getChar();
}
}
public boolean reserve(){
for(int i = 0; i < k.length; i++){
if(k[i].equals(outToken.toString())) return true;
}
return false;
}
public void concat() {
outToken.append(ch);
}
public void retract(){
// if(j <= inString.length()){
j--;
line--;
// }
}
public void insertId(){
}
public void insertCount(){
}
public boolean isDigit(){
return Character.isDigit(ch);
}
public boolean isLetter(){
return Character.isLetter(ch);
}
public boolean isOther(){
for(int i = 0; i < p.length; i++){
if(p[i].equals(String.valueOf(ch))){
return true;
}
}
return false;
}
public void formatPrint(int type){
String s = outToken.toString();
switch (type){
case 0:
System.out.printf("%s\tError\tError\t(%d, %d)\n", s, row, line - s.length());
break;
case 1:
System.out.printf("%s\t(1, %s)\t关键字\t(%d, %d)\n", s, s, row, line - s.length());
break;
case 2:
System.out.printf("%s\t(2, %s)\t分界符\t(%d, %d)\n", s, s, row, line - s.length());
break;
case 3:
System.out.printf("%s\t(3, %s)\t算数运算符\t(%d, %d)\n", s, s, row, line - s.length());
break;
case 4:
System.out.printf("%s\t(4, %s)\t关系运算符\t(%d, %d)\n", s, s, row, line - s.length());
break;
case 5:
System.out.printf("%s\t(5, %s)\t常数\t(%d, %d)\n", s, s, row, line - s.length());
break;
case 6:
System.out.printf("%s\t(6, %s)\t标识符\t(%d, %d)\n", s, s, row, line - s.length());
break;
default:
break;
}
outToken.delete(0, outToken.length());
}
// private HashSet id = new HashSet<>();
// private HashSet ci = new HashSet<>();
private String[] k;
private String[] p;
public Q1 () throws IOException {
k = new String[] {"do", "end", "for", "if", "printf", "scanf", "then", "while"};
p = new String[] {",", ";", "(", ")", "[", "]", "+", "-", "*", "/", "<", "<=", "=", ">", ">=", "<>"};
InputStreamReader reader = new InputStreamReader(Q1.class.getResourceAsStream("Q1"));
for(int ch; (ch = reader.read()) != -1; ){
inString.append((char)ch);
}
row = 1; line = 1;
}
public static void main(String[] args) throws IOException {
Q1 main = new Q1();
main.startAnalyze();
}
}