コンパイル原理字句アナライザーの C++ 実装

1. テーマの理解と説明

コンパイル原則のコースは、コンピュータ サイエンス専攻の中心コースの 1 つで、ソフトウェアとは何か、なぜ動作するのか、どのように動作するのかを研究する科目です。コンパイルシステムの改善は、上位アプリケーションプログラムの実行効率や実行原理に直接大きな影響を与えます。コンパイル原則の目的は、ソース言語をターゲット言語に翻訳することです。このプロセスは、字句解析、構文解析、意味解析中間コード生成、中間コード最適化、コード生成の 5 つの段階に分かれています。この実験で解決される問題は、プログラムのコンパイルの最初の段階である字句解析です。

このラボでは、プログラミング言語を分析できる構文アナライザーを実装する必要があります。C/C++ 言語の文法を例として、C/C++ 言語上で字句解析を実行できるアナライザーを実装するプログラムを C/C++ を使用して行います。

字句解析の目的は、C 言語のソース プログラムを処理し、無駄なシンボルを除去し、ソース プログラム内の単語の正当性を判断し、正しい単語を分解して、バイナリ グループの形式でファイルに保存することです。

字句解析ツールで行われる作業は、主にソース プログラムのコンパイルと前処理 (コメント、不要なキャリッジ リターンとライン フィードを削除して、インクルードされたファイルを見つけるなど) を行った後、ソース プログラム全体を単語に分解することです。カテゴリは、識別子、予約語、定数、演算子、区切り文字の 5 つだけです。

字句解析の対象は単一の文字であり、それを有効な単語(文字列)に整形することが目的であり、文法解析は字句解析の結果を入力として文法規則に適合しているかどうかを解析して実行することです。文法の指導の下で意味解析を行い、最後に 4 つのコード (中間コード) を生成し、最適化 (オプション) を行って、最終的にターゲット コードを生成します。したがって、字句解析はコンパイルプログラム全体の基礎となるものであり、字句解析がうまく行われないと、その後のコンパイル段階での影響が大きくなります。

2. プログラムの機能と枠組み

1. 単語のクラスを認識する

認識する必要がある単語の合計は、識別子、予約語、定数、演算子、区切り文字の 5 つのカテゴリに分類されます。C 言語のさまざまなステートメントをより広範囲に扱うために、これら 5 つのタイプの単語を次のように定義します。 最初のタイプ
: 識別子の文字 (文字 | 数字) * 無限集合
2 番目のタイプ: 定数 (数字) + 無限集合
3 番目のカテゴリ: 予約語 (32)
auto Break case char const continue
default do double else enum extern
float for goto if int long
register return short signed sizeof static
struct switch typedef Union unsigned void
volatile 一方、

4 番目のカテゴリ: 区切り文字 '/ '、 '//'、() { } [ ] " " ' など。
5 番目のカテゴリ: 演算子 <、<=、>、>=、=、+、-、 、
/、^ など。
次に、すべての可算記号用です。 :
<$,0>
<auto,1>

<while,32><+,33><-,34><*,35></,36><<,37><<=,38> <> ,39><>=,40><=,41>
<==,
<&&,53><|,54><||,55><%,56><~,57><<<,58>左シフト<>>,59>右シフト<[,60> <]
、 61><{,62><},63><,64><.,65><?,66><:,67><!,68>"[", "]", "{", "} "
<定数 99、値><識別子 100、識別子ポインタ>

上記のバイナリグループでは、左側が単語の記号、右側がカテゴリコードですが、このうち定数と識別子は少し特殊で、無限集合なので定数は次のように表されます。それ自体、カテゴリ コードは 99、識別子は識別記号を使用します。時計の指針が示します (もちろん、単独で表示することもでき、その方が観察しやすいです)、カテゴリ コードは 100 です。上記の取り決めによれば、カテゴリコード syn=63 を見ると、単語「}」は一意に決定されます。

2. プログラムフレームワークの設計

単語の種類を決定した後、プログラムは次の機能を実装する必要があります。
予約語認識機能:int SearchRWord(char RW[][20],char s[])
文字判別機能:bool IsLetter(char letter)
数字判別機能:bool IsDigit(char digit)
前処理プログラム:void PreProcessing(char r[], int pProject)
スキャナー (アルゴリズム コア):void Scanner(int &syn, char resourceProject[], char token[], int &pProject)

上記の機能のうち、中核となるのがスキャナーです。スキャナの実現は主に DFA 理論に基づいています。読み込まれて前処理された文字ストリームで文字ごとのスキャンと識別を実現し、実現された有限オートマトンアルゴリズムでスペースで区切られた単語を1つずつ認識し、それに関連する2タプルシーケンスを見つけるように設計されています: (自己値、型コード) 単語の種類を識別する目的を達成します。

プログラムの基本的な処理の流れは次のとおりです。
(1) 字句解析プログラムはソース ファイルを開き、ファイルの終端文字「$」に到達するまでファイルの内容を読み取り、読み取りを終了します。
(2) 読み取ったファイルを前処理し、最初から最後までスキャンし、// および /* */ の内容と、プログラムの実行に影響を与えるいくつかの無駄な記号 (改行、復帰、タブ待ちなど) を削除します。ただし、このときスペースを削除しないように注意してください。スペースは字句解析に便利なので、 int i=3; このステートメントでは、スペースを削除すると「inti=3」となり、プログラムの本来の意図が失われます。したがって、現時点ではスペースを削除することはできません。
(3) ソース ファイルを最初から最後までスキャンし、最初からスキャンを開始するには、次のオプションを選択します。このとき、スキャン プログラムはまず現在の文字がスペースかどうかを尋ねます。スペースの場合はスキャンを続けます。スペースでなくなるまで次の文字を入力し、その文字が文字であるかどうかを尋ね、文字である場合は識別子と予約語を特定し、文字が数字である場合はその数字を決定します。それ以外の場合は、この文字の起こり得る状況を順番に判断し、すべての可能性を検討してもまだ分からない場合は、エラー記号とみなし、エラー記号を出力して終了します。単語が正常に認識されるたびに、その単語は token[] に保存されます。そして、この単語のカテゴリーコードを決定し、最後に次の単語の認識を行います。これがスキャンプログラムの役割であり、識別子の識別や数値の識別など、有限オートマトンを決定する一定の機能を完全に実現しているといえる。簡単にするために、ここでの数値は単なる整数です。
(4) メイン制御プログラムは主に、各認識のタイプ コード syn を判断し、識別子を識別子テーブルに挿入するなど、単語の種類に応じて異なる応答を行います。予約語の場合は、予約語のカテゴリコードやニーモニックなどを出力します。syn=0 に到達するまで、プログラムは終了します。

3. 設計の説明

1. 変数ストレージ

レポートの後半では、識別子、予約語、定数、演算子、区切り文字の 5 種類の単語を紹介しました。
プログラムの実装では、上記の 5 種類の単語をそれぞれ格納するために次のデータ構造を設定します。

/*******保留字表*******/
static char RWord[32][20] = {
    
    	
"auto", "break", "case", "char",	
"const", "continue","default", "do",	
"double", "else", "enum", "extern",	
"float", "for", "goto", "if",	
"int", "long","register", "return",	
"short", "signed", "sizeof", "static",	
"struct", "switch", "typedef", "union",	
"unsigned", "void","volatile", "while" };
/*******保留字表*******/
/*******界符与运算符表*******/
static char OandD[36][10] = {
    
    	
"+", "-", "*", "/", "<", "<=",	
">", ">=", "=", "==","!=", ";",	
"(", ")", "^", ",", "\"", "\'",	
"#", "&","&&", "|", "||", "%",	
"~", "<<", ">>", "[", "]", "{",	
"}", "\\", ".", "\?", ":", "!" };
/*******界符与运算符表*******/
/*******标识符表*******/
static char IDtable[1000][50] = {
    
     "" };//初始为空
/*******标识符表*******/

2. 基本機能の実現

/*******识别保留字*******/
int SearchRWord(char RW[][20],char s[]) {
    
    
 for (int i = 0;i<32;i++) {
    
    
  if (strcmp(RW[i], s) == 0) {
    
    
   //所识别的单词和保留字表中;
   //存储的保留字比较;
   //正确则返回其种别码;
   return i + 1;
  }
 }
 //不匹配则返回-1
 //即:该单词可能是标识符或者错别字
 return -1;
}
/*******识别保留字*******/
/*******字母判别*******/
bool IsLetter(char letter){
    
    
 //C/c++语言中允许下划线也为标识符的一部分可以放在首部或其他地方
 if (letter >= 'a'&&letter <= 'z' || letter >= 'A'&&
 letter <= 'Z' || letter == '_')
  return true;
 else
  return false;
}
/*******字母判别*******/
/*******数字判别*******/
bool IsDigit(char digit){
    
    
 if (digit >= '0'&&digit <= '9')
  return true;
 else
  return false;
}
/*******数字判别*******/
/*******预处理程序,去除错误字符和注释*******/
void PreProcessing(char r[], int pProject){
    
    
 char tempString[10000];
 int count = 0;
 for (int i = 0; i <= pProject; i++){
    
    
  if (r[i] == '/'&&r[i + 1] == '/'){
    
    
   //若为单行注释“//”,则去除注释后面的东西,直至遇到回车换行
   while (r[i] != '\n'){
    
    
    i++;//向后扫描
   }
  }
  if (r[i] == '/'&&r[i + 1] == '*'){
    
    
   //若为多行注释“/*......*/”则去除该内容
   i += 2;
   while (r[i] != '*' || r[i + 1] != '/'){
    
    
    i++;//继续扫描
    if (r[i] == '$'){
    
    
     printf("注释出错,没有找到 */,程序结束!!!\n");
     exit(0);
    }
   }
   i += 2;//跨过“*/”
  }
  if (r[i] != '\n'&&r[i] != '\t'&&r[i] != '\v'&&r[i] != '\r'){
    
    
   //若出现无用字符,则过滤;否则加载
   tempString[count++] = r[i];
  }
 }
 tempString[count] = '\0';
 strcpy(r, tempString);//产生净化之后的源程序,将处理后的源程序字符串重新返回
}
/*******预处理程序,去除错误字符和注释*******/

前処理プロセスでは、入力された文字ストリームをフィルタリングし、コメント、改行、無効な文字、エラー文字などを純化および削除して、スペースのみのプログラム文字ストリームを取得する必要があります。注: スペースは 5 種類の単語を区別するための重要な分解記号であるため、浄化することはできません。

3. スキャナーの実装

字句解析の中核機能として、スキャナーの主な機能は、精製された文字ストリーム内の各単語の分類を実現し、ファイルに書き込む単語の対応する 2 つのタプルを生成することです。主な理論は DFA 理論を使用して構築されます。

/*******分析模块,词法分析器的核心*******/
//该模块主要的原理支撑是DFA的状态转换图的设计
void Scanner(int &syn, char resourceProject[], char token[], int &pProject){
    
    
 int i, count = 0;//count用来做token[]的指示器,收集有用字符
 char ch;//作为判断使用
 ch = resourceProject[pProject];
 while (ch == ' '){
    
    //过滤空格,防止程序因识别不了空格而结束
  pProject++;
  ch = resourceProject[pProject];
 }
 for (i = 0; i < 20; i++){
    
    //每次收集前先清零
  token[i] = '\0';
 }
 if (IsLetter(resourceProject[pProject])){
    
    //开头为字母
  token[count++] = resourceProject[pProject];//收集
  pProject++;//下移
  while (IsLetter(resourceProject[pProject]) || IsDigit(resourceProject[pProject])){
    
    //后跟字母或数字
   token[count++] = resourceProject[pProject];//收集
   pProject++;//下移
  }//多读了一个字符既是下次将要开始的指针位置
  token[count] = '\0';
  syn = SearchRWord(RWord, token);//查表找到种别码
  if (syn == -1){
    
    //若不是保留字则是标识符
   syn = 100;//标识符种别码
  }
  return;
 }
 else if (IsDigit(resourceProject[pProject])){
    
    //首字符为数字
  while (IsDigit(resourceProject[pProject])){
    
    //后跟数字
   token[count++] = resourceProject[pProject];//收集
   pProject++;
  }//多读了一个字符既是下次将要开始的指针位置
  token[count] = '\0';
  syn = 99;//常数种别码
 }
 else if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == ';' || ch == '(' || ch == ')' || ch == '^'
  || ch == ',' || ch == '\"' || ch == '\'' || ch == '~' || ch == '#' || ch == '%' || ch == '['
  || ch == ']' || ch == '{' || ch == '}' || ch == '\\' || ch == '.' || ch == '\?' || ch == ':'){
    
    
  //若为运算符或者界符,查表得到结果
  token[0] = resourceProject[pProject];
  token[1] = '\0';//形成单字符串
  for (i = 0; i < 36; i++)
  {
    
    //查运算符界符表
   if (strcmp(token, OandD[i]) == 0){
    
    
    syn = 33 + i;//获得种别码,使用了一点技巧,使之呈线性映射
    break;//查到即推出
   }
  }
  pProject++;//指针下移,为下一扫描做准备
  return;
 }
 else  if (resourceProject[pProject] == '<'){
    
    //<,<=,<<
  pProject++;//后移,超前搜索
  if (resourceProject[pProject] == '='){
    
    
   syn = 38;
  }
  else if (resourceProject[pProject] == '<'){
    
    //左移
   pProject--;
   syn = 58;
  }
  else{
    
    
   pProject--;
   syn = 37;
  }
  pProject++;//指针下移
  return;
 }
 else  if (resourceProject[pProject] == '>'){
    
    //>,>=,>>
  pProject++;
  if (resourceProject[pProject] == '=')
   syn = 40;
  else if (resourceProject[pProject] == '>')
   syn = 59;
  else{
    
    
   pProject--;
   syn = 39;
  }
  pProject++;
  return;
 }
 else  if (resourceProject[pProject] == '='){
    
    //=.==
  pProject++;
  if (resourceProject[pProject] == '=')
   syn = 42;
  else{
    
    
   pProject--;
   syn = 41;
  }
  pProject++;
  return;
 }
 else  if (resourceProject[pProject] == '!'){
    
    //!,!=
  pProject++;
  if (resourceProject[pProject] == '=')
   syn = 43;
  else{
    
    
   syn = 68;
   pProject--;
  }
  pProject++;
  return;
 }
 else  if (resourceProject[pProject] == '&'){
    
    //&,&&
  pProject++;
  if (resourceProject[pProject] == '&')
   syn = 53;
  else{
    
    
   pProject--;
   syn = 52;
  }
  pProject++;
  return;
 }
 else  if (resourceProject[pProject] == '|'){
    
    //|,||
 pProject++;
 if (resourceProject[pProject] == '|')
  syn = 55;
 else{
    
    
  pProject--;
  syn = 54;
 }
 pProject++;
 return;
 }
 else  if (resourceProject[pProject] == '$')//结束符
  syn = 0;//种别码为0
 else{
    
    //不能被以上词法分析识别,则出错。
  printf("error:there is no exist %c \n", ch);
  exit(0);
 }
}
/*******分析模块,词法分析器的核心*******/

主な処理の流れとしては、まず先頭からスキャンを開始します。このとき、スキャンプログラムはまず現在の文字がスペースかどうかを確認し、スペースであればスペースでなくなるまで次の文字をスキャンし、文字かどうかを判定し、識別子と予約語を識別し、文字が数字であればその数字を判定します。それ以外の場合は、この文字の起こり得る状況を順番に判断し、すべての可能性を検討してもまだ分からない場合は、エラー記号とみなし、エラー記号を出力して終了します。単語が正常に認識されるたびに、その単語は token[] に保存されます。そして、この単語のカテゴリーコードを決定し、最後に次の単語の認識を行います。これがスキャンプログラムの役割であり、このプログラムは、識別子の識別、数値の識別など、有限オートマトンを決定するある種の機能を完全に実現していると言える。簡単にするために、ここでの数値は単なる整数です。

4. メイン機能の動作

主な機能は主に必要な変数の初期化とファイルの読み書きです。txt テキストから読み取った文字ストリームを格納する resourceProject 変数を設定します。便宜上、読み込む文字ストリームの最大値を char 型の 10,000 文字にのみ設定します。認識された各単語を保存し、対応するカテゴリ コードを見つけるためにトークン変数を設定します。syn は現在の単語カテゴリ コードのストレージです。読み込み txt テキストのスキャン終了識別子として ' ' を設定します。読み込み ' ' が読み取りテキストのスキャン終了識別子である場合、その syn 値は 0 です。 -in txt テキスト、読み取られたときの syn 値は 0 です'は、読み取られたt x t textスキャン終了の識別子でありそのs y nは 0 です。 ' 'が読み取られたときそれはスキャンされた文字ストリームが最後に到達したこと、つまり syn= のときを意味します。 =0、スキャナを終了、スキャンの終了、字句解析の終了。pProject 変数はソース プログラム ポインターであり、常に現在認識されている文字位置を指します。main 関数の動作プロセスは次のとおりです。まず、プリセットされた txt テキストを開き、その中のすべての文字を resourceProject 変数に読み込みます。次に、前処理プログラムを呼び出して精製された文字ストリームを取得し、それを resourceProject 変数に上書きして保存します。次に、スキャナを呼び出して各単語を認識します。このとき、最初は syn=-1、pProject=0 です。処理後、処理結果をtxtファイルに格納して出力します。

int main(){
    
    
 //打开一个文件,读取其中的源程序
 char resourceProject[10000];
 char token[20] = {
    
     0 };
 int syn = -1, i;//初始化
 int pProject = 0;//源程序指针
 FILE *fp, *fp1;
 if ((fp = fopen("F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_rc.txt", "r")) == NULL){
    
    //打开源程序
  cout << "can't open this file";
  exit(0);
 }
 resourceProject[pProject] = fgetc(fp);
 while (resourceProject[pProject] != '$'){
    
    //将源程序读入resourceProject[]数组
  pProject++;
  resourceProject[pProject] = fgetc(fp);
 }
 resourceProject[++pProject] = '\0';
 fclose(fp);
 cout << endl << "源程序为:" << endl;
 cout << resourceProject << endl;
 //对源程序进行过滤
 PreProcessing(resourceProject, pProject);
 cout << endl << "过滤之后的程序:" << endl;
 cout << resourceProject << endl;
 pProject = 0;//从头开始读
 if ((fp1 = fopen("F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_compile.txt", "w+")) == NULL){
    
    //打开源程序
  cout << "can't open this file";
  exit(0);
 }//F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_compile.txt
 while (syn != 0){
    
    
  //启动扫描
  Scanner(syn, resourceProject, token, pProject);
  if (syn == 100){
    
    //标识符
   for (i = 0; i < 1000; i++){
    
    //插入标识符表中
    if (strcmp(IDtable[i], token) == 0)//已在表中
     break;  
    if (strcmp(IDtable[i], "") == 0){
    
    //查找空间
     strcpy(IDtable[i], token);
     break;
    }
   }//F:\大三下课程\编译原理(必修)\词法分析器\zyr_rc.txt
   printf("(标识符  ,%s)\n", token);
   fprintf(fp1, "(标识符   ,%s)\n", token);
  }
  else if (syn >= 1 && syn <= 32){
    
    //保留字
   printf("(%s   ,  --)\n", RWord[syn - 1]);
   fprintf(fp1, "(%s   ,  --)\n", RWord[syn - 1]);
  }
  else if (syn == 99){
    
    //const 常数
   printf("(常数   ,   %s)\n", token);
   fprintf(fp1, "(常数   ,   %s)\n", token);
  }
  else if (syn >= 33 && syn <= 68){
    
    
   printf("(%s   ,   --)\n", OandD[syn - 33]);
   fprintf(fp1, "(%s   ,   --)\n", OandD[syn - 33]);
  }
 }
 for (i = 0; i < 100; i++){
    
    //插入标识符表中
  if (strcmp(IDtable[i],"")==0)
   break;
  printf("第%d个标识符:  %s\n", i + 1, IDtable[i]);
  fprintf(fp1, "第%d个标识符:  %s\n", i + 1, IDtable[i]);
 }
 fclose(fp1);
 return 0;
}

4. テストデータと走行結果

テスト内容:
ファイルパスとそのファイル名:F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_rc.txt
ファイル内容:

int main(){
    
    
 //打开一个文件,读取其中的源程序
 char resourceProject[10000];
 char token[20] = {
    
     0 };
 int syn = -1, i;//初始化
 int pProject = 0;//源程序指针
 FILE *fp, *fp1;
 if ((fp = fopen("F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_rc.txt", "r")) == NULL){
    
    //打开源程序
  cout << "can't open this file";
  exit(0);
 }
 resourceProject[pProject] = fgetc(fp);
 while (resourceProject[pProject] != '$'){
    
    //将源程序读入resourceProject[]数组
  pProject++;
  resourceProject[pProject] = fgetc(fp);
 }
 resourceProject[++pProject] = '\0';
 fclose(fp);
 cout << endl << "源程序为:" << endl;
 cout << resourceProject << endl;
 //对源程序进行过滤
 PreProcessing(resourceProject, pProject);
 cout << endl << "过滤之后的程序:" << endl;
 cout << resourceProject << endl;
 pProject = 0;//从头开始读
 if ((fp1 = fopen("F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_compile.txt", "w+")) == NULL){
    
    //打开源程序
  cout << "can't open this file";
  exit(0);
 }
 $
 

出力結果には、ファイル パスとそのファイル名が保存されます。F:\\大三下课程\\编译原理(必修)\\词法分析器\\zyr_compile.txt
実行結果は次のようになります:
(int , --)
(identifier,main)
(( , --)
() , --)
({ , --)
(char , - -)
(識別子,リソースプロジェクト)
([ , --)
(定数, 10000)
(] , --)
(; , --)
(char , --)
(識別子, トークン)
([ , --)
(定数, 20)
(] , --)
(= , --)
({ , --)
(定数, 0)
(} , --)
(; , --)
(int , --)
(識別子, syn)
( = , --)
(- , --)
(定数, 1)
(, , --)
(識別子,i)
(; , --)
(int , --)
(識別子, pProject)
(= , - -)
(定数、0)
(; , --)
(識別子,FILE)
(* , --)
(識別子,fp)
(, , --)
(* , --)
(識別子,fp1)
(; , --)
(if , --)
(( , --) )
(( , --)
(識別子,fp)
(= , --)
(識別子,fopen)
(( , --)
(" , --)
(識別子,D)
(: , - -)
(\ , - -)
(\ , --)
(識別子,zyr_rc)
(. , --)
(識別子,txt)
(" , --)
(, , --)
(" , --)
(識別子,r)
(" , --)
() 、 --)
() 、 --)
(== 、 --)
(識別子、NULL)
() 、 --)
({ 、 --)
(識別子、cout)
(<< 、 -- )
(< , --)
(" , --)
(識別子,can)
(' , --)
(識別子,t)
(識別子,open)
(識別子,this)
(識別子,ファイル)
(" , --)
(; , --)
(識別子文字, 終了)
( ( , --)
(定数、0)
() , --)
(; , --)
(} , --)
(識別子、resourceProject)
([ , --)
(識別子、pProject )
(] , --)
(= , --)
(識別子,fgetc)
(( , --)
(識別子,fp)
() , --)
(; , --)
(while , --)
( ( , --)
(識別子,リソースプロジェクト)
([ , --)
(識別子,pProject)
(] , --)
(!= , --)
(' , --)
最初の識別子:
main 2 つの識別子: resourceProject
3 番目の識別子: トークン
4番目の識別子:syn
5番目の識別子:i
6番目の識別子:pProject 7番目の
識別子:FILE 8番目の
識別子:fp
9番目の識別子:fp1
10番目の識別子:
fopen 11番目の識別子:D
12番目の識別子:zyr_rc 13番目の識別子
:txt 14番目の識別子
:r
15番目の識別子:NULL 16番目識別子:
cout 17 番目の
識別子: can
18 番目の識別子: t
識別子 19: オープン
識別子 20: この
識別子 21: ファイル
識別子 22: 終了
識別子 23: fgetc

V. まとめ

この実験では、アルゴリズム部分はそれほど難しいものではなく、DFA にある程度の習熟度があれば、スキャナ モジュールは簡単に作成できます。さらに厄介なのは、5種類の文字数が増えるとプログラムが長くなってしまうことです。ただし、ほとんどのプログラムを識別できるようにするために、比較的大きなサブセットを選択し、ある程度の労力を費やしましたが、最終的には、この字句アナライザの処理能力は依然としてほとんどの C/C++ プログラムを処理できます。同時にキャラクターへの理解も深まりました。プログラムの可読性も悪くありません。私のプログラムが実装していないのは、すべての複合演算の分離ですが、原理は同じです。「+=」など、「+」のロジックの後に前方にスキャンするだけなので、追加はありません。最も実感しているのは、コンパイルの原理を学ぶには、実験をしたりプログラムを書いたりする必要があるため、実践能力が向上し、困難に対する理解が深まるということです。この実験に基づいて、コンパイラーについての基本的な理解が得られ、その後の文法解析実験のためのいくつかのアイデアも得られました。将来のより良い学習と実践のために完全に準備が整っています。

おすすめ

転載: blog.csdn.net/weixin_42529594/article/details/105622166