SEP课程代码分享(2022SPRING)-lab2



首先,奉上lab的具体要求:

Lab2: MiniEd

Ed是一个非常古老的行文本编辑器,曾经被用于文本文件的创建、显示、更改和其他一些操作。

Ed有两种模式,刚运行时Ed处于命令模式(command mode)。此时输入到Ed的文本会被当做命令进行处理。通过一些特殊命令,Ed可以被切换到输入模式(input mode),在输入模式下输入的文本会被当做文件内容进行保存。

本次Lab的任务是使用C++通过面向对象的方法完成一个简化版的Ed编辑器MiniEd。在MiniEd中,仅需实现Ed命令的一个子集。

命令与符号解释

在以下列出的Ed命令中,每个?为一个数字n,表示第n行。其值应该在第一行到最后一行之间(包含第一行和最后一行)。

每个?,?表示一个区间,其中?的含义同上。如1,2表示第一行到第二行;5,11表示第五行到第十一行;6,6表示第六行。应保证区间起点不大于终点。

每个命令为一行输入,以下为需要实现的命令说明。

  • a

    Appends text to the buffer after the current address line. If the current address is zero, the entered text is placed at the beginning of the buffer. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, the current address is not changed.

    在当前地址行后将文本追加到缓冲区。如果当前地址为零,输入的文本被放置在缓冲区的开头。文本在输入模式下输入。当前地址被设置为输入的最后一行的地址,当前地址不变。

  • ?,?d

    Deletes the addressed lines from the buffer. The current address is set to the new address of the line after the last line deleted; if the lines deleted were originally at the end of the buffer, the current address is set to the address of the new last line; if no lines remain in the buffer, the current address is set to zero.

    从缓冲区中删除寻址行。当前地址被设置为删除最后一行之后的行的新地址;如果删除的行最初位于缓冲区的末尾,则将当前地址设置为新的最后一行的地址;如果缓冲区中没有剩余行,则当前地址设置为零。

  • i

    Inserts text in the buffer before the current addressed line. If the current address is zero, the entered text is placed at the beginning of the buffer. Text is entered in input mode. The current address is set to the address of the last line entered or, if there were none, the current address is not changed.

    在缓冲区中当前地址行之前插入文本。如果当前地址为零,则输入的文本将放在缓冲区的开头。文本在输入模式下输入。当前地址设置为最后一行输入的地址,如果没有,则当前地址不变。

  • ?,?n

    Number command. Prints the addressed lines, preceding each line by its line number and a \ ('\t'). The current address is set to the address of the last line printed. Specially, 1,$n prints all lines in the buffer.

    数字命令。打印已寻址的行,在每行前面按行号和(“\t”)。当前地址设置为打印的最后一行的地址。特别是,1$n打印缓冲区中的所有行。

  • Q

    Quits ed unconditionally. Unwritten changes are discarded.

    无条件退出。放弃未写入的更改

  • w file

    Writes all lines to file and prints the number of bytes written to the file. Print an error if no filename is specified. The current address is unchanged.

    将所有行写入文件并打印写入文件的字节数。如果未指定文件名,则打印错误。当前地址不变。

  • ?

    Null command. An address alone prints the addressed line. The current address is set to the address of the printed line.

    空命令。地址单独打印地址行。当前地址设置为打印行的地址。

对于无效指令或错误指令,请输出一个以问号开头的错误提示信息:

1
? Bad/Unknown command

程序大体逻辑

MiniEd的部分代码已经给出,共五个文件:Buffer.ccBuffer.hEditor.ccEditor.hed.cc。其中Buffer.cc/hEditor.cc/h分别声明和实现了两个类。ed.cc中包含了main函数,并在其中建造并运行了一个Editor实例。在Editor::run中,程序会不断读入命令,对命令进行解析并调用相应的函数(Editor::cmdXXX)。Buffer类用于保存所有输入的文本,其中每行文本保存在一个string类型的字符串中,所有的行保存在一个链表中。Buffer类有各种用于操作和展示文本的函数,供Editor调用。

任务

代码

  1. 实现一个链表,并作为Buffer的成员,用于保存所有行。
  2. 实现Buffer::writeToFile,将当前所有的文本保存到指定文件中,并打印所保存的字节数。
  3. 实现Buffer::showLines,将相应的文本行以规定格式打印出来。
  4. 实现Buffer::insertLine,将指定文本插入到当前行之前。
  5. 实现Editor::cmdAppend,完成对a命令的处理。
  6. Editor::dispatchCmd中,完成对cmdWrite的调用。
  7. Editor::dispatchCmd中,完成对1,$n情况的判断,以及后续的处理。
  8. 完成Editor的析构函数。
  9. 添加对各种异常情况的处理。异常情况如指定了错误的行号区间、指定了超出范围的行号等。

链表请自主实现,不可使用任何第三方库的实现。

除ed.cc外,可以随意更改代码,实现了要求的功能即可。

回答问题

  1. 请解释在Editor::dispatchCmd中下述代码的逻辑,尤其注意对stringstream的使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int start, end;
char comma, type = ' ';
stringstream ss(cmd);
ss >> start;
if (ss.eof()) {
cmdNull(start);
return;
}
ss >> comma >> end >> type;
if (ss.good()) {
if (type == 'n') {
cmdNumber(start, end);
return;
} else if (type == 'd') {
cmdDelete(start, end);
return;
}
}

使用样例

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
cmd> a  
It's input mode now. Quit with a line with a single dot(.)
#include <iostream>

int main()
{
}
.
cmd> 1,$n
1 #include <iostream>
2
3 int main()
4 {
5 }
cmd> 3
int main()
cmd> i
It's input mode now. Quit with a line with a single dot(.)
using namespace std;

#includ
.
cmd> 1,$n
1 #include <iostream>
2
3 using namespace std;
4
5 #includ
6 int main()
7 {
8 }
cmd> 4,5d
cmd> 1,$n
1 #include <iostream>
2
3 using namespace std;
4 int main()
5 {
6 }
cmd> 1,3c
? Bad/Unknown command
cmd> 1,5n
1 #include <iostream>
2
3 using namespace std;
4 int main()
5 {
cmd> a
It's input mode now. Quit with a line with a single dot(.)
cout << "Hello my editor" << endl;

return 0;
.
cmd> 1,$n
1 #include <iostream>
2
3 using namespace std;
4 int main()
5 {
6 cout << "Hello my editor" << endl;
7
8 return 0;
9 }
cmd> write
? Bad/Unknown command
cmd> 1,999n
? Line number out of range
cmd> 9,1n
? Number range error
cmd> 4,2d
? Delete range error
cmd> 2019
? Line number out of range
cmd> w hello.cc
114 byte(s) written
cmd> Q

输出格式规范

  • 你的程序应该从标准输入stdin中获取输入,输出到标准输出stdout

  • 输出行时,行号和内容之间用一个制表符(\t)隔开

  • Write指令成功后输出写入的字节数:%d byte(s) written

    • 统计输出字节数需要统计可见的字符和所有不可见的字符(包括所有空格和每行末尾的换行符),按照ASCII编码每个字符占一字节。一般来说,使用流输出或者C语言的输出函数时,可以通过相关函数或者输出函数返回值得知成功输出的字节数。自己计算字符串长度统计也可以。
  • 错误处理:

    • 指令错误:? Bad/Unknown command
    • 行号越界:? Line number out of range
    • Number指令中范围错误:? Number range error
    • Delete指令中范围错误:? Delete range error
    • Write指令中未指定文件名:? Filename not specified

    补充说明:

    非数字的范围判定为指令错误。例:1,xn判定为指令错误,而非范围错误

    范围错误优先于行号越界。例:999,1n判定为范围错误,而非行号越界

  • 提示性的输出不作规定。例如样例中的cmd>以及It's input mode now. Quit with a line with a single dot(.)

提交

提交时,请将

  • 你完成的MiniEd源代码和你对上述问题的回答lab2-XXX.pdf放在同一个文件夹下,该文件夹名应为lab2
  • lab2文件夹压缩成7z压缩包,并重命名为lab2-XXX.7z

上传到 canvas 中。

文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
lab2-XXX.7z
└── lab2
   ├── Buffer.cc
   ├── Buffer.h
   ├── CMakeLists.txt
   ├── ed.cc
   ├── Editor.cc
   ├── Editor.h
   ├── lab2-XXX.pdf
   └── spec
   ├── lab2.md
   └── lab2.pdf

(其中XXX为学号,如lab2-518037910001.7zlab2-518037910001.pdf

如果需要更改,请在文件名后加版本号,最终以最高版本号为准。如第二次提交可用lab2-518037910001-2.7z

其余要求同 lab1。

注1:未按照格式提交可能会引起评测错误,导致零分

注2:建议将代码写入提供的框架代码文件中。将代码写入新建的源代码文件可能会导致评测时编译失败

注3:本次实验可以多次提交,每30分钟将会进行一次自动评测


要求结束

奉上代码(自己写的,可能不够完美)

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
//Buffer.cc
#include <fstream>
#include <iostream>
#include "Buffer.h"

//TODO: your code here
//first we can implement the functions in list;
edlist::edlist()
{
head=new node;
currentLength=0;
}

void edlist::clear() {
node*p=head->next,*q;
head->next=NULL;
while(p!=NULL)
{
q=p->next;
delete p;
p=q;
}
currentLength=0;
}

edlist::node *edlist::move(int i) const {
node*p=head;
while(i-->0)
{
p=p->next;
}
return p;
}

void edlist::insert(int i, const std::string &x) {
node* pos;
pos=move(i-1);
pos->next=new node(x,pos->next);
++currentLength;
}

void edlist::remove(int i) {
node *pos,*delp;
pos=move(i-1);
delp=pos->next;
pos->next=delp->next;
delete delp;
--currentLength;
}

int edlist::search(const std::string &x) {
node*p=head->next;
int i=0;
while(p!=NULL&&p->data!=x)
{
p=p->next;
i++;
}
if(p==NULL)
return -1;
else return i;
}

void edlist::traverse() const {
node*p=head->next;
int i=0;
while(p!=NULL)
{
i++;
std::cout<<i<<'\t';
std::cout<<p->data<<std::endl;
p=p->next;
}
}

int edlist::length() const {
return currentLength;
}

string edlist::visit(int i) {
node* tmp= move(i);
std::cout<<tmp->data;
std::cout<<std::endl;
}

//implement the functions in ListBuffer
Buffer::Buffer() {
list=new edlist;
}

Buffer::~Buffer() {
delete list;
}

void Buffer::writeToFile(const string &filename) const
{
int allByte=0;
std::ofstream afile(filename);
edlist::node *p=list->head->next;
while(p!=NULL)
{
allByte+=p->data.size();
afile<<p->data;
p=p->next;
allByte++;
}
std::cout<<allByte<<" byte(s) written"<<std::endl;
afile.close();
}

void Buffer::showLines(int from, int to) const
{
for(int i=from;i<=to;i++)
{
edlist::node* tmp=list->move(i);
std::cout<<i<<'\t';
std::cout<<tmp->data<<std::endl;
}
}

void Buffer::deleteLines(int from, int to)
{
for(int i=0;i<=to-from;i++)
{
list->remove(from);
}

}

void Buffer::insertLine(const string &text,int a)
{
list->insert(a,text);
currentLineNum=a;
}

void Buffer::appendLine(const string &text)
{
insertLine(text,currentLineNum);
}

const string &Buffer::moveToLine(int idx) const
{
edlist::node* tmp=list->move(idx);
return tmp->data;

}

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
//Buffer.h
#pragma once

#include <string>

using std::string;
class edlist
{
public:
void clear();
int length() const;
void insert(int i,const string &x);
void remove(int i);
int search(const string &x);
string visit(int i);
void traverse()const;
edlist();
~edlist(){};
struct node
{
string data;
node* next;
node(const string &x,node* n=NULL)
{
data=x;next=n;
}
node():next(NULL){}
~node(){};
};
node* head;
int currentLength;
node* move(int i)const;
};

class Buffer {
private:
int currentLineNum;
// TODO: add a List here for storing the input lines

public:
Buffer();
~Buffer();
edlist* list;

void writeToFile(const string &filename) const;

const string &moveToLine(int idx) const;

void showLines(int from, int to) const;

void deleteLines(int from, int to);
void insertLine(const string &text,int a);
void appendLine(const string &text);
};

1
2
3
4
5
6
7
8
9
//ed.cc
#include "Editor.h"

int main()
{
Editor ed;
ed.run();
return 0;
}
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//Editor.cc
#include <iostream>
#include <sstream>
#include "Editor.h"
#include "string"

using namespace std;

Editor::Editor()
{
buffer = new Buffer();
}
Editor::~Editor()
{
// TODO: Implement destructor
delete buffer;
}

void Editor::run()
{
string cmd;
while (true)
{
cout << "cmd> ";
cout.flush();
getline(cin, cmd);
if (cmd == "Q")
break;
try {
dispatchCmd(cmd);
} catch (const char *e) {
cout << "? " << e << endl;
} catch (const out_of_range &oor) {
cout << "? " << oor.what() << endl;
} catch (const range_error &re) {
cout << "? " << re.what() << endl;
}
}
}
void Editor::cmdAppend()
{
//it's function:
//Appends text to the buffer after the current address line. If the current address is zero,
//the entered text is placed at the beginning of the buffer. Text is entered in input mode.
//The current address is set to the address of the last line entered or, if there were none,
//the current address is not changed.
cout << "It's input mode now. Quit with a line with a single dot(.)" << endl;
// TODO: finish cmdAppend.

while(true)
{
string text;
getline(cin,text);
if(text==".")
break;
if (currentLine==0) {
buffer->insertLine(text,currentLine+1);
currentLine++;
}
else {
buffer->insertLine(text,currentLine+1);
currentLine++;
}

}

}

void Editor::cmdInsert()
{
cout << "It's input mode now. Quit with a line with a single dot(.)" << endl;
bool firstLine = true;
while (true)
{
string text;
getline(cin, text);
if (text == ".")
break;
/*if (firstLine) {
buffer->insertLine(text,currentLine);
firstLine = false;
} else {
buffer->insertLine(text,currentLine+1);
currentLine++;
}*/
if(currentLine==0)
{
firstLine= false;

buffer->insertLine(text,currentLine+1);
currentLine++;
currentLine++;
}
else
{
firstLine= false;
buffer->insertLine(text,currentLine);
currentLine++;
}
}
if(firstLine== false)
{
currentLine--;
}

}

void Editor::cmdDelete(int start, int end)
{
if(end<start)
throw "Delete range error";
if(start<=0||end>buffer->list->currentLength)
throw "Line number out of range";
bool flag= true;
bool anot= true;
if(end==buffer->list->currentLength)
flag=false;

buffer->deleteLines(start, end);
if(flag== false)
currentLine=start-1;



else
{
currentLine=start;
}



//cout<<currentLine<<endl;
}

void Editor::cmdNull(int line)
{
if(line<=0||line>buffer->list->currentLength)
throw "Line number out of range";
cout << buffer->moveToLine(line) << endl;
currentLine=line;
}

void Editor::cmdNumber(int start, int end)
{
if(end<start)
throw "Number range error";
if(start<=0||end>buffer->list->currentLength)
throw "Line number out of range";
buffer->showLines(start, end);
currentLine=end;
}

void Editor::cmdWrite(const string &filename)
{
buffer->writeToFile(filename);

}

void Editor::dispatchCmd(const string &cmd)
{
/*if(cmd=="r")
{
cout<<returnCurrentLine()<<endl;
return;
}*/
if (cmd == "a") {
cmdAppend();
return;
}

if (cmd[0] == 'w' && cmd[1] == ' ') {
// TODO: call cmdWrite with proper arguments
string a;
a=cmd.substr(2);
if(a=="")
throw "Filename not specified";
cmdWrite(a);
return;
}
// TODO: handle special case "1,$n".
if(cmd=="1,$n")
{
if(buffer->list->currentLength==0)
throw "Number range error";
cmdAll();
currentLine=buffer->list->currentLength;
//cout<<currentLine<<endl;
return;
}

int start, end;
char comma, type = ' ';
stringstream ss(cmd);
ss >> start;
if (ss.eof()) {
cmdNull(start);
return;
}
ss >> comma >> end >> type;
if (ss.good()) {
if (type == 'n') {
cmdNumber(start, end);
return;
} else if (type == 'd') {
cmdDelete(start, end);
return;
}
}
if (cmd == "i") {
cmdInsert();
return;
}
throw "Bad/Unknown command";
}

void Editor::cmdAll() {
buffer->list->traverse();
}

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
//Editor.h
#include <string>
#include "Buffer.h"

class Editor {
private:
Buffer *buffer;
int currentLine=0;

void dispatchCmd(const string &cmd);
void cmdAppend();
void cmdInsert();
void cmdDelete(int start, int end);
void cmdNumber(int start, int end);
void cmdWrite(const string &filename);
void cmdNull(int line);
void cmdAll();
int returnCurrentLine()
{
return currentLine;
}
public:
Editor();
~Editor();
void run();
};


下面是样例以及自己写的一些解题结构

样例:

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
cmd> a  
It's input mode now. Quit with a line with a single dot(.)
#include <iostream>

int main()
{
}
.
cmd> 1,$n
1 #include <iostream>
2
3 int main()
4 {
5 }
cmd> 3
int main()
cmd> i
It's input mode now. Quit with a line with a single dot(.)
using namespace std;

#includ
.
cmd> 1,$n
1 #include <iostream>
2
3 using namespace std;
4
5 #includ
6 int main()
7 {
8 }
cmd> 4,5d
cmd> 1,$n
1 #include <iostream>
2
3 using namespace std;
4 int main()
5 {
6 }
cmd> 1,3c
? Bad/Unknown command
cmd> 1,5n
1 #include <iostream>
2
3 using namespace std;
4 int main()
5 {
cmd> a
It's input mode now. Quit with a line with a single dot(.)
cout << "Hello my editor" << endl;

return 0;
.
cmd> 1,$n
1 #include <iostream>
2
3 using namespace std;
4 int main()
5 {
6 cout << "Hello my editor" << endl;
7
8 return 0;
9 }
cmd> write
? Bad/Unknown command
cmd> 1,999n
? Line number out of range
cmd> 9,1n
? Number range error
cmd> 4,2d
? Delete range error
cmd> 2019
? Line number out of range
cmd> w hello.cc
111 byte(s) written
cmd> Q


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
class Editor {
private:
Buffer *buffer;
int currentLine=0;

void dispatchCmd(const string &cmd);
void cmdAppend();
void cmdInsert();
void
cmdDelete(int start, int end);
void cmdNumber(int start, int end);
void cmdWrite(const string &filename);
void cmdNull(int line);
void cmdAll();
public:
Editor();
~Editor();
void run();
};





class Buffer {
private:
int currentLineNum;
public:
Buffer();
~Buffer();
edlist* list;

void writeToFile(const string &filename) const;

const string &moveToLine(int idx) const;

void showLines(int from, int to) const;

void deleteLines(int from, int to);//删除从from行到to行
void insertLine(const string &text,int a);//在第a行之前插入text(即插入后的内容为第a行)
void appendLine(const string &text,int a);
};




class edlist
{
public:
void clear();

int length() const;
void insert(int i,const string &x);//在第i行之前插入x(即插入后的内容为第i行)
void remove(int i);//删除第i行
int search(const string &x);
string visit(int i);
void traverse()const;
edlist();
~edlist(){};
node* head;
int currentLength;
node* move(int i)const;//转移到第i-1行
};

本次分享到这啦,如果你有更好的解题方法,欢迎与作者交流。

作者vx:w2386181363


文章作者: Wang Yixiao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wang Yixiao !
  目录