2 type命令改良(harib16b)
下面我们就来实现对换行的支持吧。这次需要支持的特殊字符编码如下。
0x09
……制表符:显示空格直到x被4整除为止
0x0a
……换行符:换行
0x0d
……回车符:忽略
针对上面几个特殊字符,我们来详细讲解一下。
制表符原本是用来对齐字符显示位置的,因此“遇到制表符就显示4个空格”这种做法是不对的,制表符的功能应该是在当前位置到下一个制表位之间填充空格。这里我们将制表位设定在第0、4、8、12……个字符这样4的倍数的位置(这是笔者的偏好),而在Windows等环境中,制表位则是8的倍数。
再介绍个小知识,我们这里所说的制表符也称为水平制表符(horizonal tab),因为对齐字符位置是在水平方向上移动。相对的,还有一种垂直制表符(vertical tab),不过因为这次我们遇不到(其实在一般的文本文件中也不会出现),所以大家可以先不用理会它了。
换行符其实也有一段有趣的身世。在Windows中换行的字符编码为“0x0d 0x0a”两个字节,而Linux中只有“0x0a”一个字节。也就是说,同样一篇文章,在Windows中保存下来文件尺寸会比Linux中要大。那么为什么Windows要用两个字节的换行符呢?我们来追本溯源一下吧。
字符编码0x0a原本代表折行(line feed)的意思,即只是移动到下一行。因此,如果只用0x0a作为换行符的,以前有些打印机就会印成这个样子:
- one
- two
- three
- four
而另一方面,0x0d,也就是回车符的文字编码,代表“让打印头(或者打字机的辊筒)回到行首”的意思,因此才被称为“回车”(carriage return)。回车符其实不会换行,而只是让文字显示的位置回到最左端。
因此,如果使用“0x0d 0x0a”两个字节作为换行符的话,即便是最古老的打印机,也会正确打印成下面这样。
- one
- two
- three
- four
正是因为如此,Windows中的换行符编码才会被规定为“0x0d 0x0a”吧。
另外,如果反过来利用0x0d的这一性质,在以前的打印机上可以实现文字的叠加打印,不过现在的打印机上会如何,那就不得而知了。
那么我们的“纸娃娃系统”该怎么办呢?我们就先将0x0a作为换行加回车,将0x0d忽略掉吧。因为这样一来,无论是在Windows中还是Linux中所保存的文件,都可以正确显示出来。
■■■■■
所以我们仅将type命令的字符显示部分进行一些改写。
本次的bootpack.c节选
void console_task(struct SHEET *sheet, unsigned int memtotal)
{
(中略)
for (;;) {
io_cli();
if (fifo32_status(&task->fifo) == 0) {
(中略)
} else {
(中略)
if (256 <= i && i <= 511) { /*键盘数据(通过任务A) */
if (i == 8 + 256) {
(中略)
} else if (i == 10 + 256) {
/*回车键*/
(中略)
/*执行命令*/
if (strcmp(cmdline, "mem") == 0) {
(中略)
} else if (strcmp(cmdline, "cls") == 0) {
(中略)
} else if (strcmp(cmdline, "dir") == 0) {
(中略)
/*这里!*/ } else if (strncmp(cmdline, "type ", 5) == 0) {
/* type命令*/
(中略)
if (x < 224 && finfo[x].name[0] != 0x00) {
/*找到文件的情况*/
(中略)
for (x = 0; x < y; x++) {
/*逐字输出*/
s[0] = p[x];
s[1] = 0;
/*从此开始*/ if (s[0] == 0x09) { /*制表符*/
for (;;) {
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
cursor_x += 8;
if (cursor_x == 8 + 240) {
cursor_x = 8;
cursor_y = cons_newline(cursor_y, sheet);
}
if (((cursor_x - 8) & 0x1f) == 0) {
break; /*被32整除则break */
}
}
} else if (s[0] == 0x0a) { /*换行*/
cursor_x = 8;
cursor_y = cons_newline(cursor_y, sheet);
} else if (s[0] == 0x0d) { /*回车*/
/*这里暂且不进行任何操作*/
} else { /*一般字符*/
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF,
COL8_000000, s, 1);
cursor_x += 8;
if (cursor_x == 8 + 240) {
cursor_x = 8;
cursor_y = cons_newline(cursor_y, sheet);
}
/*到此结束*/ }
}
} else {
/*没有找到文件的情况*/
(中略)
}
cursor_y = cons_newline(cursor_y, sheet);
} else if (cmdline[0] != 0) {
(中略)
}
(中略)
} else {
(中略)
}
}
(中略)
}
}
}
我们来讲解一下吧。首先是这里:
} else if (strncmp(cmdline, "type ", 5) == 0) {
这个部分我们前面因为不能用strcmp,而只好写成了很难看的if语句,这次我们使用strncmp这个函数,又把代码给改整洁了。最后的5这个参数,代表“只比较前面5个字符,后面的部分即便不一致,也要判定为一致”的意思。这样一来,这里的代码就干净多了!
接下来是制表符的处理,这个貌似有点难懂。
if (((cursor_x - 8) & 0x1f) == 0) {
break; /* 被32整除则break */
}
首先,为什么要将cursor_x减8呢?因为命令行窗口的边框有8个像素,所以要把那部分给去掉。然后,1个字符的宽度是8个像素,每个制表位相隔4个字符,也就是说,cursor_x – 8应该为0、32、64、96、128、160、192、224其中之一,即被32除后余数为0即可。
这里我们回想一下笔者在10.1节中所讲过的内容。32这个数字对于2进制来说是一个“很整的数”,因此用&就可以计算余数了——说起来我们还真是很走运嘛。
换行处理的部分没有什么难度,那我们的讲解就到这里吧。
■■■■■
好,来“make run”了哦,心情好紧张啊。按Tab键切换到命令行窗口,输入“type ipl10.nas”。
这次显示出来了哦!
虽然里面的日文部分还是乱码,不过剩下的字符都已经能够正常显示了。太好啦,成功了!
仔细看看程序,我们发现bootpack.c越来越长,console_task也很长了,不过整理起来太麻烦,所以我们就暂时让它再乱一会阵子吧,实在不好意思。
话说,观察像ipl10.nas这种比较长的文件,会发现全部显示出来需要花上一段时间(尤其在模拟器环境中感觉很明显),这时我们可以按Tab键切换到task_a的窗口中输入字符试试看。也就是说,在命令执行的过程中,系统还是可以进行其他的工作。看上去这应该是理所当然的,因为我们已经实现了多任务嘛。不过一个自制的操作系统能达到这样的程度,大家不觉得很开心吗?笔者心里已经乐开花了哟。