Close

How this all adds up.

A project log for Rubidium 2.0

This is an all in one spectrum and logic analyzer, robotics control platform, and modular synthesizer with audio in and sheet music out!

glgormanglgorman 10/08/2021 at 11:370 Comments

O.K., that's not what this log entry is about either; but it does seem relevant.  Would you prefer "Where this train is really headed?  And is the light at the end of the tunnel another oncoming train?"  I do try however to begin every log entry with a catchy title.  In any case, I threw together a quick example in C++ of how to implement a function table, which is also better described as a "table of function pointers" which can be used, as in the example I am about to discuss as a part of a simple programmable calculator application, or the techniques can be applied in such a way as to make for a very elegant emulator for any CPU platform, or else this is how we finally will be routing commands from the debug stream to the appropriate oscilloscope, debugging, or interactive windows in the Propeller Debug Terminal, and it is also a very useful technique for compiler writing, as we will see later on. 

#define PI (3.14159265358979323)
typedef enum { add=0, sub=1, mult=2, divi=3, }  opcode;
typedef double (*ftable[])(const double&, const double&);

class calculator
{
protected:
    double reg[16];
    static double (*ftab[4])(const double&, const double&);
    static double fadd(const double &, const double &);
    static double fsub(const double &, const double &);
    static double fmult(const double &, const double &);
    static double fdiv(const double &, const double &);

public:
    static double exec(opcode, const double &arg1, const double &arag2);
    static int main();
};

 It looks simple enough, with lots of static member functions that do all of the heavy lifting for us, along with a "set of registers" that some other functions, which are not provided in this example might use to implement a complete calculator.  Now let's look at the functions, that we do have:

#include "stdafx.h"
#include "calculator_test.h"

ftable calculator::ftab =
{
  &calculator::fadd,
  &calculator::fsub,
  &calculator::fmult,
  &calculator::fdiv,
};

double calculator::fadd(const double &arg1, const double &arg2)
{
    double result;
    result = arg1+arg2;
    return result;
}

double calculator::fsub(const double &arg1, const double &arg2)
{
    double result;
    result = arg1-arg2;
    return result;
}

double calculator::fmult(const double &arg1, const double &arg2)
{
     double result;
    result = arg1*arg2;
    return result;
}

double calculator::fdiv(const double &arg1, const double &arg2)
{
    double result;
    result = arg1/arg2;
    return result;
}

double calculator::exec(opcode op, const double &arg1, const double &arg2)
{ 
    double result;
    result = (ftab[op])(arg1,arg2);
    return result;
}

int calculator::main()
{
  double val1, val2;
  val1 = exec(mult,2,PI);
  val2 = exec(mult,exec(add,3,4),exec(add,4,6));
  return 0;
}

Now if you have never seen anything like this before, what typedef enum { add=0, sub=1, mult=2, divi=3, }  opcode and typedef double (*ftable[])(const double&, const double&) do is exactly as described, they create a new type of variable that we will call an "opcode", and then another type of variable that we are going to use later to map opcodes onto specific functions, and thus what the "executive" does is to (when presented with an opcode and appropriate parameters) -- look up the function that is responsible for carrying out the operation described by that particular opcode or instruction.  With no need for 256 if-then-else statements, the equivalent case statements in Pascal or switch statements in C/C++.

And thus:

ftable calculator::ftab =
{
  &calculator::fadd,
  &calculator::fsub,
  &calculator::fmult,
  &calculator::fdiv,
};

--- actually creates the function table, and this is how we call the desired function with one line of code, instead of sometimes thousands.  Seriously.

result = (ftab[op])(arg1,arg2);

 Now, this is where things get surreal.  I have so far converted about 2500 lines of the UCSD Pascal p-system compiler from Pascal to C++, trying to be as strict as possible in converting the original source line by line and keeping as near an exact style as the process will allow, and then I ran "Code Factor" which is a code quality analysis tool, to try to identify issues with the code.  So are you ready for the results, so far?

Yep, "Code Factor" gave the University of California at San Diego an "F" for code quality, or at least that's the grade that their compiler got.  Hmmm? Of course, if older versions of Pascal didn't support function pointers or jump tables, then that might explain some things.  Otherwise feeling pretty good that nearly every piece of code I have ever written seems to be getting an "A".  At least with that particular tool.  Kind of makes me wonder what it might say about GCC.

One of the issues with UCSD Pascal that I would like to mention, however, is the quite astonishing use, in at least one place, of a CASE statement that has a separate CASE for each and every expected ASCII symbol.  Converting the original Pascal source of part of a function called INSYMBOL to C++ directly, that is to say, without making any gratuitous  changes gives us something like this:

switch (CH)
    {
    case (int)'"':
        STRING();
        break;

    case (int)'0': case (int)'1': case (int)'2': case (int)'3': case (int)'4': 
    case (int)'5': case (int)'6': case (int)'7': case (int)'8': case (int)'9':
        NUMBER();
        break;

    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
    case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
    case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
    case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b':
    case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i':
    case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p':
    case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w':
    case 'x': case 'y': case 'z':
        SEARCH::IDSEARCH(SYMCURSOR.val,(char*&)(*SYMBUFP)); /* MAGIC PROC */
        break;
....etc  

 How sad!

Imagine going through all of that for every character read by an application. Why not - if you really wanted to so something in a proper, but classic Pascal style - try this:

namespace chartype
{
    SET digits(10,'0','1','2','3','4','5','6','7','8','9');
    SET whitespace(3,' ','\t','\n');
    SET alpha(52,'A','B','C','D','E','F','G','H','I','J','K','L','M',
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        'a','b','c','d','e','f','g','h','i','j','k','l','m',
        'n','o','p','q','r','s','t','u','v','w','x','y','z');
    SET punct1(10,'.',',',':','\'','(',')','{','}','[',']');
    SET operat(11,'+','-','*','/','%','|','~','=','&','>','<');
}

It does seem to compile and pass some simple tests, and it should be Unicode compatible when using suitable codepage parameters and methods elsewhere, for those people who insist on writing their applications in Arabic, Greek, or APL.  I am aware of course that Pascal is a descendant of Algol-60, and as both Pascal and C were created in the 70's, it is understandable that the creators didn't use C functions like "isalpha()", or "isnum()", and maybe even Pascal SETS weren't quite working yet.  But seriously!

 Yes - seriously, there are likely hundreds, if not possibly thousands of so-called "zero-day" vulnerabilities out there, lurking in major applications that are in use every day because of misuse of the CASE or SWITCH statements.  Maybe I will explain just why that is so in another post.   

Discussions