* (c) 2019 Aaron Taylor <ataylor at subgeniuskitty dot com>
#define HEAPSIZE 65536 /* Size of heap in words */
#define DATASTACKSIZE 65536 /* Size of stack in words */
#define RETURNSTACKSIZE 65536 /* Max subroutine call depth */
print_usage(char ** argv
)
printf( "VVhitespace Interpreter v%d (www.subgeniuskitty.com)\n"
" -h Help (prints this message)\n"
" -i <file> Specify a VVhitespace source file to interpret.\n"
FD_SET(STDIN_FILENO
, &read_fds
);
int retval
= select(1, &read_fds
, NULL
, NULL
, &timeout
);
/* retval could be -1. Ignoring that for now. */
if (retval
> 0) return 0;
tcgetattr(STDIN_FILENO
, &options
);
/* Create a cbreak-like environment through the following options. */
options
.c_lflag
&= ~ECHO
; /* Disable echoing of input characters. */
options
.c_lflag
&= ~ICANON
; /* Disable cooked/line-oriented mode. */
tcsetattr(STDIN_FILENO
, TCSANOW
, &options
);
unset_terminal_mode(void)
tcgetattr(STDIN_FILENO
, &options
);
/* Undo the changes made in set_terminal_mode(). */
options
.c_lflag
|= ECHO
; /* Enable echoing of input characters. */
options
.c_lflag
|= ICANON
; /* Enable cooked/line-oriented mode. */
options
.c_cc
[VMIN
] = 1; /* Default value from /usr/src/sys/sys/ttydefaults.h */
options
.c_cc
[VTIME
] = 0; /* Default value from /usr/src/sys/sys/ttydefaults.h */
tcsetattr(STDIN_FILENO
, TCSANOW
, &options
);
ws_die(size_t * pc
, char * msg
)
printf("SIM_ERROR @ PC %lu: %s\n", *pc
, msg
);
stack_push(int64_t ** sp
, int64_t word
)
stack_peek(int64_t ** sp
, size_t offset
)
/* offset=0 peeks TOS, offset=1 peeks NOS, etc. */
return *((*sp
)-offset
-1);
next_code_byte(uint8_t * code
, size_t * pc
)
* In addition to returning the parsed label, this function advances the PC to
parse_label(uint8_t * code
, size_t * pc
)
while ((c
= code
[(*pc
)++]) != '\n') {
check_label(size_t * labels
, uint16_t label
, size_t * pc
)
fprintf(stderr
, "Trying to process label 0x%X.\n", label
);
ws_die(pc
, "uninitialized label (forgot an include?)");
populate_labels(size_t * labels
, uint8_t * code
, size_t code_size
)
while (cp
<= code_size
) {
if (code
[cp
++] == '\v') {
uint16_t temp_label
= parse_label(code
, &cp
);
process_imp_stack(uint8_t * code
, size_t * pc
, int64_t ** sp
)
switch (next_code_byte(code
,pc
)) {
/* Push number onto TOS. */
/* First, pick off the sign */
switch (next_code_byte(code
,pc
)) {
case ' ' : sign
= 1; break;
case '\t': sign
= -1; break;
default : ws_die(pc
, "expected sign"); break;
/* Now, construct the number and push to TOS. */
/* I'm assuming the numbers are read MSb first. */
uint64_t number
= 0; /* Unsigned to accomodate magnitude of most negative number. */
while ((temp
= next_code_byte(code
,pc
)) != '\n') {
if (temp
== '\v') ws_die(pc
, "non-binary digit in number");
if (temp
== '\t') number
++;
/* Without temporarily casting to something >64-bit, the most negative */
/* number will overflow when performing 'number*sign'. Instead, we */
/* pick off the most negative number as a special case. */
if (number
== (1ULL << 63) && sign
== -1) {
/* C parses negative integer literals first as signed positive */
/* integer literals, then applying a unary negation operator. */
/* Thus, the most negative value is unreachable directly. */
int64_t number_temp
= -9223372036854775807LL; /* First store -((2^63)-1) */
number_temp
--; /* Now turn it into -(2^63) */
stack_push(sp
, number_temp
);
stack_push(sp
, number
*sign
);
switch (next_code_byte(code
,pc
)) {
stack_push(sp
, stack_peek(sp
,0));
int64_t t1
= stack_pop(sp
);
int64_t t2
= stack_pop(sp
);
ws_die(pc
, "malformed stack IMP");
default: ws_die(pc
, "malformed stack IMP"); break;
process_imp_arithmetic(uint8_t * code
, size_t * pc
, int64_t ** sp
)
switch (next_code_byte(code
,pc
)) {
switch (next_code_byte(code
,pc
)) {
stack_push(sp
, stack_pop(sp
)+stack_pop(sp
));
stack_push(sp
, stack_pop(sp
)-temp
);
stack_push(sp
, stack_pop(sp
)*stack_pop(sp
));
ws_die(pc
, "malformed arithmetic IMP");
switch (next_code_byte(code
,pc
)) {
stack_push(sp
, stack_pop(sp
)/temp
);
stack_push(sp
, llabs(stack_pop(sp
) % llabs(temp
)));
default: ws_die(pc
, "malformed arithmetic IMP"); break;
default: ws_die(pc
, "malformed arithmetic IMP"); break;
process_imp_flowcontrol(uint8_t * code
, size_t * pc
, int64_t ** sp
, size_t * labels
,
switch (next_code_byte(code
,pc
)) {
/* Technically another LF is required but we ignore it. */
switch (next_code_byte(code
,pc
)) {
/* Mark a location in the program. */
if (next_code_byte(code
,pc
) != '\v') ws_die(pc
,"expected vtab, "
"perhaps a whitespace program, rather than vvhitespace?");
/* Jump to next instruction since labels were parsed during startup. */
temp_pc
= labels
[check_label(labels
, parse_label(code
, pc
), pc
)];
/* Jump unconditionally to a label. */
*pc
= labels
[check_label(labels
, parse_label(code
, pc
), pc
)];
ws_die(pc
, "malformed flow control IMP");
switch (next_code_byte(code
,pc
)) {
/* Jump to a label if TOS == 0 */
temp_pc
= labels
[check_label(labels
, parse_label(code
, pc
), pc
)];
if (stack_pop(sp
) == 0) *pc
= temp_pc
;
/* Jump to a label if TOS < 0. */
temp_pc
= labels
[check_label(labels
, parse_label(code
, pc
), pc
)];
if (stack_pop(sp
) < 0) *pc
= temp_pc
;
/* Return from subroutine. */
ws_die(pc
, "malformed flow control IMP");
ws_die(pc
, "malformed flow control IMP");
process_imp_heap(uint8_t * code
, size_t * pc
, int64_t ** sp
, int64_t ** hp
)
switch (next_code_byte(code
,pc
)) {
int64_t value
= stack_pop(sp
);
int64_t addr
= stack_pop(sp
);
stack_push(sp
, *(*hp
+ stack_pop(sp
)));
ws_die(pc
, "malformed heap IMP");
process_imp_io(uint8_t * code
, size_t * pc
, int64_t ** sp
, int64_t ** hp
)
switch (next_code_byte(code
,pc
)) {
switch (next_code_byte(code
,pc
)) {
case ' ' : /* Output char from TOS */ printf("%c", (uint8_t) stack_pop(sp
)); break;
case '\t': /* Output digit from TOS */ printf("%c", (uint8_t) stack_pop(sp
)+'0'); break;
default : ws_die(pc
, "malformed output IMP"); break;
while (stdin_empty()) continue;
switch (next_code_byte(code
,pc
)) {
case '\t': /* Input digit */ c
-= '0'; /* fallthrough */
case ' ' : /* Input character */ *(*hp
+ *(--(*sp
))) = c
; break;
default : ws_die(pc
, "malformed input IMP"); break;
default: ws_die(pc
, "malformed i/o IMP"); break;
main(int argc
, char ** argv
)
* Process command line arguments
while ((c
= getopt(argc
,argv
,"i:h")) != -1) {
if ((input
= fopen(optarg
, "r")) == NULL
) {
fprintf(stderr
, "ERROR: %s: %s\n", optarg
, strerror(errno
));
fprintf(stderr
, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
* Read just the VVhitespace source code into memory, stripping comment characters.
* We will use the array indices as addresses for the virtual PC when jumping to labels.
while (fread(&temp_byte
, 1, 1, input
)) {
if (temp_byte
== ' ' || temp_byte
== '\t' || temp_byte
== '\n' || temp_byte
== '\v') {
uint8_t * ws_code_space
= malloc(ws_code_size
);
while (fread(&temp_byte
, 1, 1, input
)) {
if (temp_byte
== ' ' || temp_byte
== '\t' || temp_byte
== '\n' || temp_byte
== '\v') {
ws_code_space
[ws_code_size
++] = temp_byte
;
* Setup a stack and heap.
int64_t * hp
= malloc(HEAPSIZE
*sizeof(int64_t));
int64_t * sp
= malloc(DATASTACKSIZE
*sizeof(int64_t));
* Setup the return stack and the label array.
size_t * rsp
= malloc(RETURNSTACKSIZE
*sizeof(size_t));
size_t labels
[65536] = {0}; /* 65536 = 2^16 => Holds all possible labels. */
/* Default value of zero indicates an uninitialized label. */
populate_labels(labels
, ws_code_space
, ws_code_size
);
size_t pc
= 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
if (pc
>= ws_code_size
) {
fprintf(stderr
, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n",
switch (ws_code_space
[pc
++]) {
process_imp_stack(ws_code_space
, &pc
, &sp
);
process_imp_flowcontrol(ws_code_space
, &pc
, &sp
, labels
, &rsp
);
/* Arithmetic, Heap Access, or I/O */
switch (ws_code_space
[pc
++]) {
process_imp_arithmetic(ws_code_space
, &pc
, &sp
);
process_imp_heap(ws_code_space
, &pc
, &sp
, &hp
);
process_imp_io(ws_code_space
, &pc
, &sp
, &hp
);
default: ws_die(&pc
, "unexpected byte"); break;