Calcc, A Programmers' Calculator

15:21 Fri, 13 Jan 2012

Calcc is a useful command line calculator for programmers, with a full range of bit and byte operators.

I have been going through James Malloy's tutorial on writing your own operating system1 , which involves some assembly and quite a bit of bit twiddling with shifts and masks in C.

I haven't written any assembly or done any serious bit manipulation for years. I can do simple (i.e. 1 byte) hexadecimal addition in my head, but nothing more complex than that, so I needed a quick calculator that showed me the results from doing things like bit shifts, rotates, masking, and so on.

A bit of googling found one contributor to Stackoverflow recommending calcc by Luigi Auriemma. A quick download of a small zip file and there it was, GPL'ed source code and pre-built binaries for unix and MS Windows.

It is quick and simple to use and has the usual unary and binary operators you can think of such as integer maths, logical connectives and bit manipulation.

Input can be any of the usual bases of 2, 4, 8, 10 and 16, plus percentages, floats, datetime and IP dotted quads. You can mix and match within the one command. To see a number conversion, just enter a number and the list will display. Enter multiple numbers in different formats and with different operators to see the result.

Most valuable for me is that the output provides everything at once: hex, unsigned and signed decimal, octal, binary, ascii, and so on. You don't have to specify the output like 'print/x' (gdb's way of specifying hex output); it is all there in one list. Very useful.

64 bit original version

Example input shows addition of decimal + binary + octal + hex

You can build either a 32 bit or a 64 bit binary by using a compile option. I prefer the 32 bit one because I find 8 byte words hard to parse. It's probably what I am used to; those of you with 64 bit machines will probably want the 64 bit version.

I really like this tool. I have had it open in a spare xterm constantly while I go through the aforementioned tutorial. Very much recommended for programmers.

Changes

I would like to change a couple of things. Calcc uses fgets to get its input, which means unix users won't get command-line editing nor, importantly, a command history buffer (I hate having to retype a command with only one small change). Also, sometimes I want to find the hex for an ASCII char, so an ASCII table would be nice.

I modified calcc to use the GNU readline library. It comes with various command line editing shortcuts and has a history buffer built in, with which any Bash user is familiar. I also added an ASCII table and a 'quit' command, plus I tidied up the output to suit my personal style.

32 bit version with my changes

Output

Let's look at the output (for the 32 bit version) and see what it provides.

unsigned dec hex octal binary
signed dec ascii exponential base 4 I.P. address
base32 base64 date/time
float double

Notice the IP address and datetime fields. You can do arithmetic on those types too. See the help for the full list.

Grab the diff

You can grab the diff file here. Put it in the same directory as the source code and patch it with patch < calcc_changes.diff

Drop me a line if you find it useful. Thanks to Luigi for producing a useful calculator for programmers.

Colourized diff

Here is the diff with syntax highlighting for those who want to read it first.

--- calcc.c
+++ calcc.c
@@ -18,8 +18,28 @@
     http://www.gnu.org/licenses/gpl-2.0.txt
 */
 
+/* NJC 01/11/12  
+* ====== Compilation ==========
+*
+* The printed output can be in words of either 4 bytes or 8 bytes, depending on compile-time options.
+* If you have a 32 bit machine, you may prefer the 4 byte output as it takes less screen space
+* and is easier to scan.
+* 
+* Change "my_calcc" to whatever you want to call the binary.
+*
+* 32 bit output
+* gcc calcc.c -DBITS32 -O2 -lreadline -lncurses -o my_calcc
+*
+* 64 bit output
+* gcc calcc.c -DBITS64 -O2 -lreadline -lncurses -o my_calcc
+*
+*/
+
 #include <stdio.h>
 #include <stdlib.h>
+// NJC 01/10/12   Changed input from fgets to use the readline library
+#include <readline/readline.h>
+#include <readline/history.h>
 #include <string.h>
 #include <stdint.h>
 #include <ctype.h>
@@ -30,8 +50,6 @@
 typedef uint32_t    u32;
 typedef uint64_t    u64;
 
-
-
 // #define BITS32   // uncomment to enable 32 bits
 // #define DEBUG    // uncomment to enable the step-by-step visualization
 
@@ -80,13 +98,17 @@
 char *showtime(NTI num);
 char *showchar(NTI num);
 char *showbase(NTI num, int size, int sign);
+// NJC 01/11/12 
+char * rl_gets ();
+void print_ascii();
 
 
 
 static const u8 b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 static const u8 b32_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
 static const u8 hex_table[] = "0123456789abcdef";
-
+// NJC 01/11/12 
+static char *line_read = (char *)NULL;
 
 
 int main(int argc, char *argv[]) {
@@ -99,7 +121,7 @@
     int     op,
             len,
             slen;
-    char    buff[READSZ * 9],   // should be enough
+	    char    buff[READSZ * 9],   // should be enough
             tmp[NTSZ + 1],
             *data,
             *pl,
@@ -109,12 +131,12 @@
 
     setbuf(stdout, NULL);
 
-    printf("\n"
-        "Calcc "VER" (%d bits version)\n"
-        "by Luigi Auriemma\n"
-        "e-mail: aluigi@autistici.org\n"
-        "web:    aluigi.org\n"
-        "\n", NTSZ);
+    // NJC 01/10/12 removed extra \n's to make vanity header appear on one line
+    printf( "Calcc "VER" (%d bits version) "
+        "by Luigi Auriemma "
+        "e-mail: aluigi@autistici.org "
+        "web:    aluigi.org "
+        "\nType help or ?\n", NTSZ);
 
     if(argc > 1) {
         p = buff;
@@ -127,7 +149,10 @@
         goto do_it;
     }
 
-    while(fgets(buff, READSZ, stdin)) {
+    // NJC 01/10/12  while(fgets(buff, READSZ, stdin)) {
+    while(line_read = rl_gets()) {
+	if (line_read)
+	    memccpy(buff, line_read, '\0', READSZ - 1);		// copy readline's buffer to ours, stopping at first \0 (EOString for readline),  or sizeof buff.
 
 do_it:
         mytolower(buff);
@@ -159,6 +184,12 @@
             help();
             *buff = 0;
         }
+	else if (buff[0] == 'a') {
+	    print_ascii();
+	    *buff = 0;
+	}
+	else if (buff[0] == 'q' || buff[0] == 'Q')
+	    goto quit;
 
         for(;;) {
             data = buff;
@@ -314,12 +345,13 @@
             showbase(tot, 32, 0), showbase(tot, 64, 0), showtime(tot),
             totfl, totdb);
     }
-
+quit:
+    putchar('\n');
     return(0);
 }
 
 
-
+// NJC 01/12/12 added a, q/Q/ctrl-D/ctrl-C
 void help(void) {
     fputs("\n"
         "Input types:\n"
@@ -331,7 +363,8 @@
         "  y  = year                            t  = time hh:mm:ss (0 for current date)\n"
         "  ,. = float/double numbers (is not possible to make operations on them)\n"
         "     = decimal (default)\n"
-        "\n"
+        "  a  = print ASCII table               q,Q,ctrl-D,ctrl-C = quit\n"
+	"\n"
         "Operators and priority order:\n"
         "  ~  = complement                      !  = not\n"
         "  << = shift left                      >> = shift right\n"
@@ -667,7 +700,7 @@
 char *showtime(NTI num) {
     struct  tm  *tmx;
     time_t  datex;
-    static  char    buff[32];
+    static  char    buff1[32];
     static const char   *day[] =
             { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" },
             *months[] =
@@ -677,14 +710,14 @@
     tmx = gmtime(&datex);
     if(!tmx) return("none");
     sprintf(
-        buff,
+        buff1,
         "%s %02d %s %d %02d:%02d:%02d",
         day[tmx->tm_wday],
         tmx->tm_mday,
         months[tmx->tm_mon],
         1900 + tmx->tm_year,
         tmx->tm_hour, tmx->tm_min, tmx->tm_sec);
-    return(buff);
+    return(buff1);
 }
 
 
@@ -746,4 +779,80 @@
     return(o + i);
 }
 
-
+// NJC 01/11/12  from the readline library guide at gnu.org.
+/* Read a string, and return a pointer to it.
+   Returns NULL on EOF. */
+char *
+rl_gets ()
+{
+  /* If the buffer has already been allocated,
+     return the memory to the free pool. */
+  if (line_read)
+    {
+      free (line_read);
+      line_read = (char *)NULL;
+    }
+
+  /* Get a line from the user. */
+  line_read = readline (" > ");
+
+  /* If the line has any text in it,
+     save it on the history. */
+  if (line_read && *line_read)
+    add_history (line_read);
+
+  return (line_read);
+}
+
+// NJC 01/11/12 Print the ASCII table
+void
+print_ascii() {
+    u8 i, j, x;
+    char  a[10] = "" ;					// wide enough to hold ascii representation of non-keyboard chars like DEL etc.
+    for (i = 0 ; i < 20 ; i++  ) {			// print in columns, 10 cols wide == 20 rows to hold 127 data points.
+	for ( j = 0 ; j < 128 ; j += 20 ) {		// each new column is +20 from the one to the left.
+	    x = i + j;
+	    if ( x > 127 )
+		break ;					// no more ASCII chars after decimal 127
+	    switch (x) {
+		case   0: strcpy(a, "NUL"); break;
+		case   1: strcpy(a, "SOH"); break;
+		case   2: strcpy(a, "STX"); break;
+		case   3: strcpy(a, "ETX"); break;
+		case   4: strcpy(a, "EOT"); break;
+		case   5: strcpy(a, "ENQ"); break;
+		case   6: strcpy(a, "ACK"); break;
+		case   7: strcpy(a, "BEL \\a"); break;
+		case   8: strcpy(a, "BS \\b"); break;
+		case   9: strcpy(a, "HT \\t"); break;
+		case  10: strcpy(a, "LF \\n"); break;
+		case  11: strcpy(a, "VT \\t"); break;
+		case  12: strcpy(a, "FF \\f"); break;
+		case  13: strcpy(a, "CR \\r"); break;
+		case  14: strcpy(a, "SO"); break;
+		case  15: strcpy(a, "SI"); break;
+		case  16: strcpy(a, "DLE"); break;
+		case  17: strcpy(a, "DC1"); break;
+		case  18: strcpy(a, "DC2"); break;
+		case  19: strcpy(a, "DC3"); break;
+		case  20: strcpy(a, "DC4"); break;
+		case  21: strcpy(a, "NAK"); break;
+		case  22: strcpy(a, "SYN"); break;
+		case  23: strcpy(a, "ETB"); break;
+		case  24: strcpy(a, "CAN"); break;
+		case  25: strcpy(a, "EM"); break;
+		case  26: strcpy(a, "SUB"); break;
+		case  27: strcpy(a, "ESC"); break;
+		case  28: strcpy(a, "FS"); break;
+		case  29: strcpy(a, "GS"); break;
+		case  30: strcpy(a, " RS"); break;
+		case  31: strcpy(a, " US"); break;
+		case  32: strcpy(a, "space"); break;
+		case 127: strcpy(a, "DEL"); break;
+		default:  a[0] = x ; a[1] = '\0';
+	    }
+	    printf("%6s %3.0d %2X\t",a,x,x);
+	}
+	putchar('\n');
+    }
+}


Categories: programming, unix

Comments

  1. Artem     91.219.xxx.xxx
    2013-08-20 07:36

    WOW. Looks great! But, unfortunately, I can't find origianl version and can't download modified.

Leave a comment

Your email address will not be published. Required fields are marked *

Plain text only please, any < or > are removed.