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作为换行符的,以前有些打印机就会印成这个样子:

  1. one
  2. two
  3. three
  4. four

而另一方面,0x0d,也就是回车符的文字编码,代表“让打印头(或者打字机的辊筒)回到行首”的意思,因此才被称为“回车”(carriage return)。回车符其实不会换行,而只是让文字显示的位置回到最左端。

因此,如果使用“0x0d 0x0a”两个字节作为换行符的话,即便是最古老的打印机,也会正确打印成下面这样。

  1. one
  2. two
  3. three
  4. four

正是因为如此,Windows中的换行符编码才会被规定为“0x0d 0x0a”吧。

另外,如果反过来利用0x0d的这一性质,在以前的打印机上可以实现文字的叠加打印,不过现在的打印机上会如何,那就不得而知了。

那么我们的“纸娃娃系统”该怎么办呢?我们就先将0x0a作为换行加回车,将0x0d忽略掉吧。因为这样一来,无论是在Windows中还是Linux中所保存的文件,都可以正确显示出来。

■■■■■

所以我们仅将type命令的字符显示部分进行一些改写。

本次的bootpack.c节选

  1. void console_task(struct SHEET *sheet, unsigned int memtotal)
  2. {
  3. (中略)
  4. for (;;) {
  5. io_cli();
  6. if (fifo32_status(&task->fifo) == 0) {
  7. (中略)
  8. } else {
  9. (中略)
  10. if (256 <= i && i <= 511) { /*键盘数据(通过任务A) */
  11. if (i == 8 + 256) {
  12. (中略)
  13. } else if (i == 10 + 256) {
  14. /*回车键*/
  15. (中略)
  16. /*执行命令*/
  17. if (strcmp(cmdline, "mem") == 0) {
  18. (中略)
  19. } else if (strcmp(cmdline, "cls") == 0) {
  20. (中略)
  21. } else if (strcmp(cmdline, "dir") == 0) {
  22. (中略)
  23. /*这里!*/ } else if (strncmp(cmdline, "type ", 5) == 0) {
  24. /* type命令*/
  25. (中略)
  26. if (x < 224 && finfo[x].name[0] != 0x00) {
  27. /*找到文件的情况*/
  28. (中略)
  29. for (x = 0; x < y; x++) {
  30. /*逐字输出*/
  31. s[0] = p[x];
  32. s[1] = 0;
  33. /*从此开始*/ if (s[0] == 0x09) { /*制表符*/
  34. for (;;) {
  35. putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
  36. cursor_x += 8;
  37. if (cursor_x == 8 + 240) {
  38. cursor_x = 8;
  39. cursor_y = cons_newline(cursor_y, sheet);
  40. }
  41. if (((cursor_x - 8) & 0x1f) == 0) {
  42. break; /*被32整除则break */
  43. }
  44. }
  45. } else if (s[0] == 0x0a) { /*换行*/
  46. cursor_x = 8;
  47. cursor_y = cons_newline(cursor_y, sheet);
  48. } else if (s[0] == 0x0d) { /*回车*/
  49. /*这里暂且不进行任何操作*/
  50. } else { /*一般字符*/
  51. putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF,
  52. COL8_000000, s, 1);
  53. cursor_x += 8;
  54. if (cursor_x == 8 + 240) {
  55. cursor_x = 8;
  56. cursor_y = cons_newline(cursor_y, sheet);
  57. }
  58. /*到此结束*/ }
  59. }
  60. } else {
  61. /*没有找到文件的情况*/
  62. (中略)
  63. }
  64. cursor_y = cons_newline(cursor_y, sheet);
  65. } else if (cmdline[0] != 0) {
  66. (中略)
  67. }
  68. (中略)
  69. } else {
  70. (中略)
  71. }
  72. }
  73. (中略)
  74. }
  75. }
  76. }

我们来讲解一下吧。首先是这里:

  1. } else if (strncmp(cmdline, "type ", 5) == 0) {

这个部分我们前面因为不能用strcmp,而只好写成了很难看的if语句,这次我们使用strncmp这个函数,又把代码给改整洁了。最后的5这个参数,代表“只比较前面5个字符,后面的部分即便不一致,也要判定为一致”的意思。这样一来,这里的代码就干净多了!

接下来是制表符的处理,这个貌似有点难懂。

  1. if (((cursor_x - 8) & 0x1f) == 0) {
  2. break; /* 被32整除则break */
  3. }

首先,为什么要将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”。

2 type命令改良(harib16b) - 图1

这次显示出来了哦!

虽然里面的日文部分还是乱码,不过剩下的字符都已经能够正常显示了。太好啦,成功了!

仔细看看程序,我们发现bootpack.c越来越长,console_task也很长了,不过整理起来太麻烦,所以我们就暂时让它再乱一会阵子吧,实在不好意思。

话说,观察像ipl10.nas这种比较长的文件,会发现全部显示出来需要花上一段时间(尤其在模拟器环境中感觉很明显),这时我们可以按Tab键切换到task_a的窗口中输入字符试试看。也就是说,在命令执行的过程中,系统还是可以进行其他的工作。看上去这应该是理所当然的,因为我们已经实现了多任务嘛。不过一个自制的操作系统能达到这样的程度,大家不觉得很开心吗?笔者心里已经乐开花了哟。