1 命令行计算器(harib27a)

这一章我们要编写一些“高级的应用程序”,不过其实也没那么高级,说不定还是昨天那个invader.hrb比较高级呢,因此大家可别抱太大的期望哦(笑)。

那到底做点什么样的应用程序好呢?现在我们的系统中已经有noodle.hrb可以帮我们做泡面,还有invader.hrb可以用来玩游戏,应该再找一些其他能派上用场的功能……于是笔者想到了计算器。

Windows中附带了一个计算器软件,不过做那样一个好看的计算器太麻烦了,我们就做一个在命令行中输入算式来进行计算的应用程序吧。

■■■■■

我们来看程序。

calc.c

  1. #include "apilib.h"
  2. #include <stdio.h> /* sprintf */
  3. #define INVALID -0x7fffffff
  4. int strtol(char *s, char **endp, int base); /*标准函数(stdlib.h) */
  5. char *skipspace(char *p);
  6. int getnum(char **pp, int priority);
  7. void HariMain(void)
  8. {
  9. int i;
  10. char s[30], *p;
  11. api_cmdline(s, 30);
  12. for (p = s; *p > ' '; p++) { } /*一直读到空格为止*/
  13. i = getnum(&p, 9);
  14. if (i == INVALID) {
  15. api_putstr0("error!\n");
  16. } else {
  17. sprintf(s, "= %d = 0x%x\n", i, i);
  18. api_putstr0(s);
  19. }
  20. api_end();
  21. }
  22. char *skipspace(char *p)
  23. {
  24. for (; *p == ' '; p++) { } /*将空格跳过去*/
  25. return p;
  26. }
  27. int getnum(char **pp, int priority)
  28. {
  29. char *p = *pp;
  30. int i = INVALID, j;
  31. p = skipspace(p);
  32. /*单项运算符*/
  33. if (*p == '+') {
  34. p = skipspace(p + 1);
  35. i = getnum(&p, 0);
  36. } else if (*p == '-') {
  37. p = skipspace(p + 1);
  38. i = getnum(&p, 0);
  39. if (i != INVALID) {
  40. i = - i;
  41. }
  42. } else if (*p == '~') {
  43. p = skipspace(p + 1);
  44. i = getnum(&p, 0);
  45. if (i != INVALID) {
  46. i = ~i;
  47. }
  48. } else if (*p == '(') { /*括号*/
  49. p = skipspace(p + 1);
  50. i = getnum(&p, 9);
  51. if (*p == ')') {
  52. p = skipspace(p + 1);
  53. } else {
  54. i = INVALID;
  55. }
  56. } else if ('0' <= *p && *p <= '9') { /*数值*/
  57. i = strtol(p, &p, 0);
  58. } else { /*错误 */
  59. i = INVALID;
  60. }
  61. /*二项运算符*/
  62. for (;;) {
  63. if (i == INVALID) {
  64. break;
  65. }
  66. p = skipspace(p);
  67. if (*p == '+' && priority > 2) {
  68. p = skipspace(p + 1);
  69. j = getnum(&p, 2);
  70. if (j != INVALID) {
  71. i += j;
  72. } else {
  73. i = INVALID;
  74. }
  75. } else if (*p == '-' && priority > 2) {
  76. p = skipspace(p + 1);
  77. j = getnum(&p, 2);
  78. if (j != INVALID) {
  79. i -= j;
  80. } else {
  81. i = INVALID;
  82. }
  83. } else if (*p == '*' && priority > 1) {
  84. p = skipspace(p + 1);
  85. j = getnum(&p, 1);
  86. if (j != INVALID) {
  87. i *= j;
  88. } else {
  89. i = INVALID;
  90. }
  91. } else if (*p == '/' && priority > 1) {
  92. p = skipspace(p + 1);
  93. j = getnum(&p, 1);
  94. if (j != INVALID && j != 0) {
  95. i /= j;
  96. } else {
  97. i = INVALID;
  98. }
  99. } else if (*p == '%' && priority > 1) {
  100. p = skipspace(p + 1);
  101. j = getnum(&p, 1);
  102. if (j != INVALID && j != 0) {
  103. i %= j;
  104. } else {
  105. i = INVALID;
  106. }
  107. } else if (*p == '<' && p[1] == '<' && priority > 3) {
  108. p = skipspace(p + 2);
  109. j = getnum(&p, 3);
  110. if (j != INVALID && j != 0) {
  111. i <<= j;
  112. } else {
  113. i = INVALID;
  114. }
  115. } else if (*p == '>' && p[1] == '>' && priority > 3) {
  116. p = skipspace(p + 2);
  117. j = getnum(&p, 3);
  118. if (j != INVALID && j != 0) {
  119. i >>= j;
  120. } else {
  121. i = INVALID;
  122. }
  123. } else if (*p == '&' && priority > 4) {
  124. p = skipspace(p + 1);
  125. j = getnum(&p, 4);
  126. if (j != INVALID) {
  127. i &= j;
  128. } else {
  129. i = INVALID;
  130. }
  131. } else if (*p == '^' && priority > 5) {
  132. p = skipspace(p + 1);
  133. j = getnum(&p, 5);
  134. if (j != INVALID) {
  135. i ^= j;
  136. } else {
  137. i = INVALID;
  138. }
  139. } else if (*p == '|' && priority > 6) {
  140. p = skipspace(p + 1);
  141. j = getnum(&p, 6);
  142. if (j != INVALID) {
  143. i |= j;
  144. } else {
  145. i = INVALID;
  146. }
  147. } else {
  148. break;
  149. }
  150. }
  151. p = skipspace(p);
  152. *pp = p;
  153. return i;
  154. }

如果以前没有编写过类似的计算器程序的话,估计不太容易看懂上面这段程序。这段程序本来就已经偏离了“编写操作系统”这个主题,因此笔者也没打算逼着大家看懂,不过如果你对这段程序很感兴趣,不妨先运行一下玩玩看,然后再仔细阅读下面的讲解,一定能慢慢看明白的。

在这段程序中使用了strtol(s, endp, base)这样一个函数,这个函数的功能基本上和sprintf是相反的,它可以将字符串形式的数值转换为整数。字符串的地址由s指定,base表示进制,例如10代表十进制,16代表十六进制,0则代表自动识别(以0x开头则识别为十六进制)。

endp一般可以指定为0,也可以指定一个变量的地址,如果指定了地址,则函数会返回在转换字符串时所读取到的字符串末尾地址。字符串末尾地址是一个char 型的变量,这个变量的地址由endp指定,地址的地址,也就是一个char *型的变量。

strtol是一个只要include了就可以使用的标准函数,不过笔者在“纸娃娃系统”用的tolset中没有包含,因此在程序中直接进行了声明。

getnum中也使用了char **型的参数,和strtol一样,也是为了将当前解析到算式中的位置返回给相应的变量。

函数getnum的功能是将字符串形式的算式进行解释,并获取一个数值。除了进入字符串开始地址的变量地址外,还需要指定计算到哪个等级的运算符(“+”、“/”等用于表示各种运算的符号)。在HariMain中我们指定了9,这代表“无论多么低优先级的运算符,全部需要计算出来”的意思。

用作运算符的字符包括+ - / % & | ^ ~ << >> ( ),结果同时显示十进制和十六进制。不过计算都是以整数进行,比如:10 / 3 = 3,可以使用负数。计算的优先级和C语言的规定相同,因此1 + 2 3 + 4 = 11,如果单纯从左往右按顺序计算的话结果应该是13,但我们的计算器不会这样计算,当然,如果输入( 1 + 2 ) * 3 + 4的话就可以计算出13了哦。

由于使用了和C语言相同的语法规则,因此^是代表XOR运算的运算符,2^3可不是“2的3次方”的意思,而是“2 XOR 3”的意思哦。另外,和C语言一样,我们可以直接输入十六进制的数字进行计算,只要在数字前面加上0x就可以了。

■■■■■

我们来“make run”看看吧,关于使用方法,可以输入如 “calc 1+2”。

1 命令行计算器(harib27a) - 图1 1 命令行计算器(harib27a) - 图2
尝试进行了各种计算 又尝试了更多种类的计算

怎么样,还挺有意思的吧?这样一来,一些比较简单的计算用“纸娃娃系统”就可以胜任了哦,可喜可贺。对了,calc.hrb只有1688字节哦,短小精悍吧!