//ABC.c A minimal interpreted human readable bytecode language
//http://t...content-available-to-author-only...d.org/techref/idea/minimalcontroller.htm
#include <stdio.h>
#define NUM_PORTS 3
//number of IO ports to support.
#define VAR_SIZE 4
//bytes of storage in each variable
#define STACK_SIZE 10
#define NUM_VARS ( (VAR_SIZE*('z'-'a')) + STACK_SIZE + 2 )
//number of memory spaces needed for variables.
//some number of bytes per variable address (a-z) plus stack size after that.
#define START_STACK ( NUM_VARS - STACK_SIZE + 1 )
//stack starts inside the variable sapce, but at the end, after 'z'
#define NUM_LCDS 40
#define START_PORTS ( NUM_VARS + 1 )
//memory reserved to shadow IO ports
#define START_LCD ( START_PORTS + NUM_PORTS + 1 )
//memory reserved to shadow local display
#define NUM_MEMS ( START_LCD + NUM_LCDS )
//amount of RAM memory required.
//TODO: add a check to verify this uC has that much available.
#define NUM_FLASH 2048
//amount of FLASH or EEPROM memory
//use the first line for debug output, the second to remove it from any optimizing compiler
#define DEBUG if(1)
//#define DEBUG if(0)
char abc; //the incomming character
int skip_lines=0; //count of the number of lines to skip in branches.
unsigned char dst,src;
/* SRC and DST reference up to 128 8 bit "registers" or, when the high bit is set,
one of the devices (LCD/Keys, Serial, EEPROM, IO). Defaults to LCD/Keys. */
bool src_dst=false; //set when the SRC register is to be loaded, clear for DST
/* The DST is set first, and the SRC/DST flag toggled so that SRC is loaded next at
which point the current operation and comparison are performed. */
int num=0; //numeric accumulator. Hex digits (0-9, A-F) are added into this
/* If SRC is not loaded (zero), NUM is the SRC. */
unsigned char op=0; //Operation
/* OP stores the operation to perform once we have a SRC (we should already have
a DST) or when the line ends. After each operation SRC is cleared.
At the end of each line, SRC/DST and DST is cleared. Since there is only one SRC
at a time, binary operations like addition always accumulate into DST. i.e. DST
is the implied second source in binary operations. The default operation, when
no other is specified, is copy. i.e. if only a DST, then a SRC are specified,
the value of the SRC is simply written to the DST. OP is not cleared so that "
can escape ". In other words, so that quote can escape quote. */
bool true_flag; //set when the comparison finds truth, clear when false.
unsigned char vars[NUM_MEMS];
unsigned char flash[NUM_FLASH];
int flashptr=1;
//can't start at address 0 in flash because a call to that address looks like console
int pcptr=0; //just a temp var to hold the current PC pointer
unsigned char pcp=START_STACK;
//program counter pointer starts at TOS, in var space, after 'z'
void doop() {
if (dst && op && (src || num) ) { //got enough to work with
if ('"'==op) { //Quote? but don't update vars[dst] if it's a variable.
if (NUM_VARS>dst) { //'cause it's a value quoted to a variable ptr
flash[flashptr]=src; //so actually write it to flash instead
DEBUG printf("\nflash[%i]=%c",flashptr,src);
flashptr++; //and move to next space in flash
return; //and that's it.
}
}
else if (src>=START_PORTS) {
//on a real uC, we would update vars[src] with the value of the port
//so add code here to actually read the port.
DEBUG printf(" reading %i from port %i",vars[src],src-NUM_VARS);
};
num += vars[src];
switch(op) {
case ':': vars[dst]=num; break;
case '+': vars[dst]+=num; break;
case '-': vars[dst]-=num; break;
case '*': vars[dst]*=num; break;
case '/': vars[dst]/=num; break;
case '=': true_flag=(vars[dst]==num); break;
case '<': true_flag=(vars[dst]<num); break;
case '>': true_flag=(vars[dst]>num); break;
case '{': true_flag=(vars[dst]<=num); break;
case '}': true_flag=(vars[dst]>=num); break;
case '|': true_flag=(vars[dst]|=num); break;
case '&': true_flag=(vars[dst]&=num); break;
case '`': true_flag=(vars[dst]!=num); break;
}
DEBUG {
switch(op) {
case ':': case '+': case '-': case '*': case '/':
printf("\n vars\t[dst]\t%c=\tvars\t[src]\t+num;",op);
printf("\n \\\\%i\t[%i]\t%c=\t%i\t[%i]\t+%i",vars[dst],dst,op,vars[src],src,num);
break;
case '"':
break;
default:
printf("\n flag=\t(vars\t[dst]\t%c=\tvars\t[src]\t+num);",op);
printf("\n \\\\%c\t %i\t[%i]\t%c=\t%i\t[%i]\t+%i",true_flag?'Y':'N',vars[dst],dst,op,vars[src],src,num);
break;
}
}
if (dst>=NUM_VARS && dst<NUM_LCDS) {
DEBUG printf(" writing %i to port %i",src,dst-START_PORTS);
//on a real uC, we would update the port with the value of vars[dst]
//so add code here to actually write to the port.
};
if (dst>=NUM_LCDS) {
DEBUG printf(" writing %c to LCD @ %i",src,dst-START_LCD);
//on a real uC, we would actually write the character to the LCD
dst++; //move to the next character on the LCD.
};
num=0;
DEBUG printf("\n num=0;src=0;");
src = 0; //clear source leave dst and op.
}
}
int main () {
printf("Ready %i\n", NUM_MEMS);
vars[pcp+0] = 0;
vars[pcp+1] = 0;
vars[pcp+2] = 0;
//initialize the current PC to zero...
pcptr = 0;
//which flags that we are executing from the console
while ( (pcptr=((vars[pcp+2]<<16) + (vars[pcp+1]<<8) + (vars[pcp+0]<<0))) //Not executing from FLASH?
|| EOF != (abc=getchar()) //get a character from the console, until EOF
) {
//DEBUG printf("pcptr=%u, abc=%c",pcptr,abc);
if (!abc) { //we didn't get a character from the console
abc = flash[pcptr];
//get abc from flash at PC, and increment PC
//DEBUG printf("GOT %c from %u via pcp %u",abc,vars[pcp],pcp);
if(!(++vars[pcp+0])) if (!(++vars[pcp+1])) if (!(++vars[pcp+2]));
//increment must roll over to pcp+1, etc... Note: pcptr updated next time around
}
//can we process a ready operation here first? Helps with quote. TODO: test.
if (src) doop(); // if we already had a source, process it first
if ('\n'==abc) {
// DEBUG printf("\n ");
doop();
src_dst=false; //reset back to looking for destinations at EOL
num=op=0;
if (skip_lines) {
skip_lines--;
DEBUG printf("\n----------------");
continue;
}
}
else printf("\n%c ",abc);
if (skip_lines) {
DEBUG printf("skipped");
continue;
}
if ('\"'==abc) {
DEBUG printf("quote, op==%c",op);
if ('\"'==op) {//ending quote?
op=' '; //secret op means we were quoting.
abc=' ';
DEBUG printf(" ending quote");
}
else if (' '==op) { //double quote
op='"'; //just pickup where we left off
DEBUG printf(" double quote");
}
else if (NUM_VARS>dst && ':'==op) {
vars[dst+0]=flashptr>> 0 & 0xFF;
vars[dst+1]=flashptr>> 8 & 0xFF;
vars[dst+2]=flashptr>>16 & 0xFF;
//This may be a 3 or 4 byte value. Could be more efficient.
DEBUG printf("vars[%i]=%i //assign variable to flashptr", dst, flashptr);
}
//continue;
} //done with input was a quote
if ('"'==op) { //If we are quoting
src=abc; //value of input in source, just pass it on
DEBUG printf(" src=%i;",src);
}
else if ((abc>='A' && abc<='F') || (abc>='0' && abc<='9')) { //number
num *= 16;
num += (abc>'9' ? abc-'A'+10 : abc-'0');
DEBUG printf(" num=%i;",num);
}
else if (('a'<=abc && 'z'>=abc) || (abc>='G' && abc<='Z')) { //variable or function
switch(abc) {
case 'P': //Port and pin.
num+=START_PORTS; //correct P to past the last variable
DEBUG printf(" port=%i;",num);
break;
case 'L': //Local display
DEBUG printf("local display @%i",num);
num+=START_LCD;
break;
default: //it's a variable
num += ((abc-'a')*VAR_SIZE)+1; //spread out in var space by VAR_SIZE
break;
}
//variables or functions can be sources or destinations.
if (!src_dst) { //looking for a destination
dst = num;
DEBUG printf(" dst=%i;vars[dst]=%i",dst,vars[dst]);
src_dst=true;
}
else { //looking for a source
src = num;
DEBUG printf(" src=%i;",src);
//src_dst=true; //stay on sources until EOL.
}
num = 0;
}
else switch(abc) { //must be an operator
case '@':
if (src) { //if we have a source
num+=vars[src];
src=0;
DEBUG printf(" num=%i;src=0;",num);
}
else {
num+=vars[dst];
dst=0;
DEBUG printf(" num=%i;dst=0;",num);
src_dst=false;//look for another destination.
};
break;
case '?':
doop();
if (!true_flag) {
skip_lines=1;
DEBUG printf(" FALSE! (skip the line)");
}
else {
DEBUG printf(" TRUE! {continue}");
};
src_dst=false; //reset back to looking for destinations at EOL
break;
case ')': //call. PCP++, (DST)->var(PCP)
//Check if dst is a variable.
if (NUM_VARS < dst) {
DEBUG printf("ERROR: Call to other than variable address!");
return 1;
}
pcp+=VAR_SIZE; //increment PCP to the next variable space
// check if PCP > NUM_MEMS. Stack overflow
if (NUM_VARS < pcp) {
DEBUG printf("ERROR: Stack Overflow!");
return 1;
}
//copy the value of dst to the new pcp
vars[pcp+0]=vars[dst+0];
vars[pcp+1]=vars[dst+1];
vars[pcp+2]=vars[dst+2];
src_dst=false;
DEBUG printf(" CALL %c at %u, via pcp %u", dst+'a'-1, ((vars[pcp+2]<<16)+(vars[pcp+1]<<8)+(vars[pcp+0]<<0)), pcp);
break;
case '.': //return. 0!=var(PCP)? PCP--
//TODO: check if we are already on the console
//Check if PCP >= VAR_SIZE
DEBUG printf(" RETURN to %u, via pcp %u",vars[pcp],pcp);
if (START_STACK >= pcp) {
DEBUG printf("ERROR: Return stack underflow. Return while not in subroutine?");
break;
}
doop(); //finish up whatever was done in the subroutine
pcp-=VAR_SIZE; //decrement PCP to the prior variable space
break;
case '\n':
DEBUG printf("\n----------------");
break;
default:
doop(); //in case we already have a destination, operation and source.
if ('='==abc && op) { // an equal might come after < or >
abc=op+('{'-'<');
// add 123 - 60 or 63. This maps <, =, > to {, |, }
// <= is {, == is |, >= is }, ?= is ~, != is ` (back single quote)
// so don't use {} in the future! () and [] are ok.
}
op=abc;
DEBUG printf(" op=\'%c\';",op);
break;
}
//putchar(abc);
abc=0; //done with this character.
}
}