前言

開始學習編譯原理了耶~

關於編譯原理的所有練習,按照老規矩,還是用我最喜歡的C#語言來實現,運行在.NetCore平台上~關於這個系列的所有代碼已經上傳到github了,項目主頁:

https://github.com/Deali-Axy/CompilerConstructionLearning

本次題目

對C或C++等高級程序設計語言編寫的源程序中的//注釋和//注釋進行刪除,保留刪除後的源程序。要求以文件形式進行保存。

思路分析

程序主要功能就是消除已經編寫好的源程序中的注釋。在源程序中注釋有兩種形式,一種是單行注釋,用「//」表示,另一種是多行注釋,用「//」表示。針對這兩種形式,程序中用了if..else..語句加以判斷,並做出相應的處理。在這裡還有可能出現另一種情況,上述兩種注釋符號可能出現在引號中,出現在引號中的注釋符號並沒有注釋功能,因此在引號中出現的注釋符號不應該被消除。所以,這次編寫的程序將要分三種情況分析。

第一種情況,單行注釋:

if (ch != temp)
{
// 這裡就是單行注釋
ofile.put(ch);
ch = ifile.get();
}

或者

if (ch != temp)
{
/* 這裡就是單行注釋 */
ofile.put(ch);
ch = ifile.get();
}

第二種情況,塊注釋:

if (ifile.fail() || ofile.fail())
{
cerr << "open file fail
";
return EXIT_FAILURE;
/*返回值EXIT_FAILURE(在cstdlib庫中定義),用於向操作系統報*
告打開文件失敗*/
}

第三種情況,行後注釋:

ifile.close(); // 關閉文件
ofile.close();
cout << "/////*////ret/rtr////";
system("pause");
return 0;

還有一個關鍵的注意點

可以看到這一行

cout << "/////*////ret/rtr////";

這個字元串用雙引號包起來的代碼中有很多斜杠,所以要避免將這些斜杠識別為注釋。

這裡我用的方法是在處理注釋前先把包含注釋符號的字元串替換掉,等注釋刪除之後,再添加回去。

實現代碼

注釋寫得很詳細啦,配合上面的思路分析,我就不再繼續分析代碼了~

var sReader = new StreamReader(filePath);
var newSource = "";
var inBlock = false;
var replaceFlag = false;
var tempLine = ""; // 用於保存被替換的特殊行代碼
while (!sReader.EndOfStream)
{
var line = sReader.ReadLine();
if (line.Length == 0) continue; // 去除空行

var quotationPattern = "^(.*?)".*//.*"";
var quotationResult = Regex.Match(line, quotationPattern);
if (quotationResult.Success)
{
System.Console.WriteLine("替換特殊代碼,雙引號中包裹注釋斜杠");
tempLine = quotationResult.Groups[0].Value;
replaceFlag = true;
line = Regex.Replace(line, quotationPattern, REPLACEMENT);
}

// 單行注釋
if (line.Trim().StartsWith(@"//"))
continue;
if (line.Trim().StartsWith(@"/*") && line.EndsWith(@"*/"))
continue;

// 注釋塊
if (Regex.Match(line.Trim(), @"^/*").Success)
inBlock = true;
if (Regex.Match(line.Trim(), @"*/$").Success)
{
inBlock = false;
continue;
}

// 行後注釋
// 使用非貪婪模式(.+?)匹配第一個//
var pattern = @"^(.*?)//(.*)";
// var pattern = @"[^(.*?)//(.*)]|[^(.*?)/*(.*)*/]";
var result = Regex.Match(line, pattern);
if (result.Success)
{
System.Console.WriteLine("發現行後注釋:{0}", result.Groups[2]);
line = result.Groups[1].Value;
}

// 還原被替換的代碼
if (replaceFlag)
{
System.Console.WriteLine("還原特殊代碼");
line = line.Replace(REPLACEMENT, tempLine);
replaceFlag = false;
}

if (inBlock) continue;
newSource += line + Environment.NewLine;
}

var outputPath = "output/exp1.src";
System.Console.WriteLine("去除注釋完成,創建新文件。");
using (var sWriter = new StreamWriter(outputPath))
{
sWriter.Write(newSource);
}
System.Console.WriteLine("操作完成!文件路徑:{0}", outputPath);

結果測試

源文件

#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
using namespace std;

int main()
{
cout << /;
ifstream ifile; //建立文件流對象
ofstream ofile;
ifile.open("f:\上機實驗題\C++\ConsoleApplication2\ConsoleApplication2\源.cpp"); //打開F盤根目錄下的fileIn.txt文件
ofile.open("f:\上機實驗題\C++\ConsoleApplication2\ConsoleApplication2\源.obj");
if (ifile.fail() || ofile.fail())
{ //測試打開操作是否成功
cerr << "open file fail
";
return EXIT_FAILURE;
/*返回值EXIT_FAILURE(在cstdlib庫中定義),用於向操作系統報*
告打開文件失敗*/
}
char ch;
ch = ifile.get(); //進行讀寫操作
while (!ifile.eof())
{
if (ch == 34)
{ //雙引號中若出現「//」,雙引號中的字元不消除
char temp = ch; //第一個雙引號
ofile.put(ch);
ch = ifile.get();
while (!ifile.eof())
{
if (ch != temp)
{ //尋找下一個雙引號
ofile.put(ch);
ch = ifile.get();
}
else
{
ofile.put(ch);
break;
}
}
ch = ifile.get();
continue; //雙引號情況結束,重新新一輪判斷
}
if (ch == 47)
{ //出現第一個斜杠
char temp2 = ch;
ch = ifile.get();
if (ch == 47)
{ //單行注釋情況
ch = ifile.get();
while (!(ch ==
))
ch = ifile.get();
}
else if (ch == *)
{ //多行注釋情況
while (1)
{
ch = ifile.get();
while (!(ch == *))
ch = ifile.get();
ch = ifile.get();
if (ch == 47)
break;
}
ch = ifile.get();
}
else
{
ofile.put(temp2); //temp2保存第一個斜杠,當上述兩種情況都沒有時,將此斜杠輸出
}
//ch = ifile.get();
}
//cout << ch << endl;
ofile.put(ch); //將字元寫入文件流對象中
ch = ifile.get(); //從輸入文件對象流中讀取一個字元
}
ifile.close(); //關閉文件
ofile.close();
cout << "/////*////ret/rtr////";
system("pause");
return 0;
}

處理後的結果

#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
using namespace std;
int main()
{
cout << /;
ifstream ifile;
ofstream ofile;
ifile.open("f:\上機實驗題\C++\ConsoleApplication2\ConsoleApplication2\源.cpp");
ofile.open("f:\上機實驗題\C++\ConsoleApplication2\ConsoleApplication2\源.obj");
if (ifile.fail() || ofile.fail())
{
cerr << "open file fail
";
return EXIT_FAILURE;
}
char ch;
ch = ifile.get();
while (!ifile.eof())
{
if (ch == 34)
{
char temp = ch;
ofile.put(ch);
ch = ifile.get();
while (!ifile.eof())
{
if (ch != temp)
{
ofile.put(ch);
ch = ifile.get();
}
else
{
ofile.put(ch);
break;
}
}
ch = ifile.get();
continue;
}
if (ch == 47)
{
char temp2 = ch;
ch = ifile.get();
if (ch == 47)
{
ch = ifile.get();
while (!(ch ==
))
ch = ifile.get();
}
else if (ch == *)
{
while (1)
{
ch = ifile.get();
while (!(ch == *))
ch = ifile.get();
ch = ifile.get();
if (ch == 47)
break;
}
ch = ifile.get();
}
else
{
ofile.put(temp2);
}
}
ofile.put(ch);
ch = ifile.get();
}
ifile.close();
ofile.close();
cout << "/////*////ret/rtr////";
system("pause");
return 0;
}

完整代碼

https://github.com/Deali-Axy/CompilerConstructionLearning/blob/master/code/exp/exp1/Exp1.cs

參考資料:

  • 正則表達式元字元:https://www.runoob.com/regexp/regexp-metachar.html
  • JavaScript去除注釋:https://segmentfault.com/a/1190000015611632

推薦閱讀:

相关文章