#include #include #include #include #include "utlist.h" #include "../vm/vm.h" // /home/scott/joey/rpgengine/dbltw/test.dbl /home/scott/joey/rpgengine/dbltw/test.bin /home/scott/joey/rpgengine/dbltw/test.lst #define MAX_KEYWORD_ARGS 8 #define MAX_ARG_SIZE 1024 #define MAX_LINE_SIZE 8192 #define STATE_KEYWORD 1 #define STATE_ARGUMENT 2 #define QUOTE_NONE 1 #define QUOTE_SINGLE 2 #define QUOTE_DOUBLE 3 typedef int (*keywordHandler)(int argc, char *argv[], char *file, int line); typedef struct { char *keyword; int argc; // Negative values represent the minimum required keywordHandler handler; } KeywordT; typedef struct { char *opcode; int argc; int stackPop; int stackPush; unsigned char value; } OpcodeT; typedef struct { int16_t value; int labelLine; char *labelFile; char *text; } StackT; typedef struct variableT_ { int16_t index; unsigned char exported; char *name; struct variableT_ *next; } VariableT; typedef struct labelT_ { uint16_t addr; unsigned char exported; int line; char *name; char *file; struct labelT_ *next; } LabelT; typedef struct defineT_ { char *name; char *value; struct defineT_ *next; } DefineT; typedef struct bitstreamT_ { uint16_t addr; unsigned char opcode; StackT *argument; struct bitstreamT_ *next; } BitstreamT; int argIsNumber(char *arg); int argIsString(char *arg); int argIsVariable(char *arg); void emitOpcode(char *opcode); void emitStackItem(StackT *item); int16_t findVariable(char *varName); int kw_add(int argc, char *argv[], char *file, int line); int kw_call(int argc, char *argv[], char *file, int line); int kw_define(int argc, char *argv[], char *file, int line); int kw_do(int argc, char *argv[], char *file, int line); int kw_end(int argc, char *argv[], char *file, int line); int kw_export(int argc, char *argv[], char *file, int line); int kw_goto(int argc, char *argv[], char *file, int line); int kw_if(int argc, char *argv[], char *file, int line); int kw_include(int argc, char *argv[], char *file, int line); int kw_return(int argc, char *argv[], char *file, int line); int kw_set(int argc, char *argv[], char *file, int line); int kw_subtract(int argc, char *argv[], char *file, int line); void op_add(void); void op_boc(void); void op_exe(void); void op_jmp(void); void op_hlt(void); void op_pop(char *varName); void op_puc(StackT *arg); void op_puv(int16_t varIndex); void op_sub(void); int parse(char *sourceIn); void push(char *arg, unsigned char isLabel, char *file, int line); void strReplace(char *target, const char *needle, const char *replacement); static KeywordT keywords[] = { { "add", 2, kw_add }, { "call", 1, kw_call }, { "define", 2, kw_define }, { "do", -1, kw_do }, { "end", 0, kw_end }, { "export", 2, kw_export }, { "goto", 1, kw_goto }, { "if", 4, kw_if }, { "include", 1, kw_include }, { "return", 0, kw_return }, { "set", 2, kw_set }, { "subtract", 2, kw_subtract } }; static OpcodeT opcodes[] = { // Note: There is no zero opcode! { "ADD", 0, 2, 1, OPCODE_ADD }, // Add { "BOC", 0, 4, 0, OPCODE_BOC }, // Branch On Condition { "EXE", 0, -1, -1, OPCODE_EXE }, // Execute VM Function (Unknown stack results) ***TODO*** Change to be entirely stack based { "JMP", 0, 1, 0, OPCODE_JMP }, // Jump to address { "HLT", 0, 0, 0, OPCODE_HLT }, // Halt Execution { "POP", 1, 1, 0, OPCODE_POP }, // Pop into Variable { "PUC", 1, 0, 1, OPCODE_PUC }, // Push Constant { "PUV", 1, 0, 1, OPCODE_PUV }, // Push Variable { "SUB", 0, 2, 1, OPCODE_SUB } // Subtract }; static LabelT *labels; static DefineT *defines; static VariableT *variables; static uint16_t variableCount; // Variables declared static uint16_t pc; // Program Counter static int sloc; // Source Lines of Code static int files; // Files compiled static int opCount; // Operands emitted static int labelCount; // Labels found static BitstreamT *bitstream; int argIsNumber(char *arg) { if (((arg[0] >= '0') && (arg[0] <= '9')) || (arg[0] == '-')) { return 1; } return 0; } int argIsString(char *arg) { if ((arg[0] == '\'') || (arg[0] == '"')) { return 1; } return 0; } int argIsVariable(char *arg) { return !argIsNumber(arg) && !argIsString(arg); } void emitOpcode(char *opcode) { BitstreamT *bits; for (int i=0; i<(int)(sizeof(opcodes)/sizeof(OpcodeT)); i++) { if (strcmp(opcode, opcodes[i].opcode) == 0) { bits = (BitstreamT *)malloc(sizeof(BitstreamT)); bits->addr = pc; bits->opcode = opcodes[i].value; bits->argument = NULL; LL_APPEND(bitstream, bits); pc++; // Opcodes are a single byte opCount++; } } } void emitStackItem(StackT *item) { BitstreamT *bits; bits = (BitstreamT *)malloc(sizeof(BitstreamT)); bits->addr = pc; bits->opcode = 0; bits->argument = (StackT *)malloc(sizeof(StackT)); bits->argument->value = item->value; if (item->text == NULL) { bits->argument->text = NULL; pc++; // Non string values still get a 0 written here } else { // Remove enclosing quotes bits->argument->text = strdup(&item->text[1]); bits->argument->text[strlen(bits->argument->text) - 1] = 0; bits->argument->value = (jint16)strlen(bits->argument->text); // Is this a string or label? if (item->labelLine >= 0) { // Label. Just add 1 because it will end up a null string. pc++; } else { // String arguments add length pc += strlen(bits->argument->text); } } if (item->labelFile == NULL) { bits->argument->labelFile = NULL; } else { bits->argument->labelFile = strdup(item->labelFile); } bits->argument->labelLine = item->labelLine; LL_APPEND(bitstream, bits); pc += 2; // All arguments have a 16 bit integer value } int16_t findVariable(char *varName) { VariableT *variable; int16_t found = -1; LL_FOREACH(variables, variable) { if (strcmp(variable->name, varName) == 0) { found = variable->index; } } if (found < 0) { variable = (VariableT *)malloc(sizeof(VariableT)); variable->exported = 0; variable->name = strdup(varName); variable->index = variableCount++; found = variable->index; LL_APPEND(variables, variable); } return found; } int kw_add(int argc, char *argv[], char *file, int line) { (void)argc; if (!argIsVariable(argv[0])) { printf("Keyword 'add' requires first argument to be a variable\n"); return 1; } push(argv[1], 0, file, line); push(argv[0], 0, file, line); op_add(); op_pop(argv[0]); return 0; } int kw_call(int argc, char *argv[], char *file, int line) { uint16_t addr; char tempAddr[8]; char *temp; unsigned long len; (void)argc; // We need to calculate the address of the opcode *after* the JMP. addr = pc + 9; /* addr = pc + 4; // +1 for the JMP opcode, +3 for the value we're pushing. if (argIsString(argv[0])) { addr += strlen(argv[0]); // Strings are still quoted at this point so the extra two bytes make up for not adding the length bytes } else { addr += 3; // Three bytes for a numeric PUC or a PUV } */ snprintf(tempAddr, 8, "%d", addr); push(tempAddr, 0, file, line); // Label (expected to be quoted, but isn't at this point) len = strlen(argv[0]) + 3; temp = (char *)malloc(len * sizeof(char)); snprintf(temp, len, "'%s'", argv[0]); push(temp, 1, file, line); free(temp); op_jmp(); return 0; } int kw_define(int argc, char *argv[], char *file, int line) { DefineT *define; (void)argc; (void)file; (void)line; define = (DefineT *)malloc(sizeof(DefineT)); define->name = strdup(argv[0]); if (argIsString(argv[1])) { // Remove Quotes define->value = strdup(&argv[1][1]); define->value[strlen(define->value) - 1] = 0; } else { define->value = strdup(argv[1]); } LL_APPEND(defines, define); return 0; } int kw_do(int argc, char *argv[], char *file, int line) { char temp[8]; // Arguments for EXE for (int i=argc; i>0; i--) { push(argv[i - 1], 0, file, line); } // Number of arguments snprintf(temp, 8, "%d", argc); push(temp, 0, file, line); op_exe(); return 0; } int kw_end(int argc, char *argv[], char *file, int line) { (void)argc; (void)argv; (void)file; (void)line; op_hlt(); return 0; } int kw_export(int argc, char *argv[], char *file, int line) { VariableT *variable; (void)argc; if (!argIsVariable(argv[0])) { printf("Keyword 'export' requires first argument to be a variable\n"); return 1; } push(argv[1], 0, file, line); op_pop(argv[0]); LL_FOREACH(variables, variable) { if (strcmp(variable->name, argv[0]) == 0) { variable->exported = 1; } } return 0; } int kw_goto(int argc, char *argv[], char *file, int line) { char *temp; unsigned long len; (void)argc; // Label (expected to be quoted, but isn't at this point) len = strlen(argv[0]) + 3; temp = (char *)malloc(len * sizeof(char)); snprintf(temp, len, "'%s'", argv[0]); push(temp, 1, file, line); free(temp); op_jmp(); return 0; } int kw_if(int argc, char *argv[], char *file, int line) { char *temp; unsigned long len; (void)argc; // Label (expected to be quoted, but isn't at this point) len = strlen(argv[3]) + 3; temp = (char *)malloc(len * sizeof(char)); snprintf(temp, len, "'%s'", argv[3]); push(temp, 1, file, line); free(temp); push(argv[2], 0, file, line); // Second argument switch(argv[1][0]) { case '=': push("0", 0, file, line); break; case '!': push("1", 0, file, line); break; case '<': push("2", 0, file, line); break; case '>': push("3", 0, file, line); break; default: printf("Unknown conditional operation '%s'\n", argv[1]); return 1; } push(argv[0], 0, file, line); // First argument op_boc(); return 0; } int kw_include(int argc, char *argv[], char *file, int line) { (void)argc; (void)file; (void)line; return parse(argv[0]); } int kw_return(int argc, char *argv[], char *file, int line) { (void)argc; (void)argv; (void)file; (void)line; // The return address *should* be on the stack at this point op_jmp(); return 0; } int kw_set(int argc, char *argv[], char *file, int line) { (void)argc; (void)file; (void)line; if (!argIsVariable(argv[0])) { printf("Keyword 'set' requires first argument to be a variable\n"); return 1; } push(argv[1], 0, file, line); op_pop(argv[0]); return 0; } int kw_subtract(int argc, char *argv[], char *file, int line) { (void)argc; if (!argIsVariable(argv[0])) { printf("Keyword 'subtract' requires first argument to be a variable\n"); return 1; } push(argv[1], 0, file, line); push(argv[0], 0, file, line); op_sub(); op_pop(argv[0]); return 0; } void op_add(void) { // Add // Add two stack items and push result emitOpcode("ADD"); } void op_boc(void) { // Branch On Condition // Use four stack items to determine if we should branch emitOpcode("BOC"); } void op_exe(void) { // Execute // Calls native function number. Unknown stack state. emitOpcode("EXE"); } void op_jmp(void) { // Jump // Jumps to address on the stack emitOpcode("JMP"); } void op_hlt(void) { // Halt emitOpcode("HLT"); } void op_pop(char *varName) { // Pop // Pop top stack value into variable StackT item; item.labelLine = -1; item.labelFile = NULL; item.text = NULL; item.value = (int16_t)findVariable(varName); emitOpcode("POP"); emitStackItem(&item); } void op_puc(StackT *arg) { // Push Constant // Pushes the constant value of 'arg' to the stack emitOpcode("PUC"); emitStackItem(arg); } void op_puv(int16_t varIndex) { // Push Variable // Pushes address of variable to use onto stack StackT item; item.labelLine = -1; item.labelFile = NULL; item.text = NULL; item.value = varIndex; emitOpcode("PUV"); emitStackItem(&item); } void op_sub(void) { // Subtract // Subtract two stack items and push result emitOpcode("SUB"); } int parse(char *sourceIn) { char line[MAX_LINE_SIZE]; char *token; int tokenLength; int state; char **argv; int argc; char quote[MAX_ARG_SIZE]; LabelT *found; LabelT *label; DefineT *define; char buffer[MAX_ARG_SIZE]; int inQuote = QUOTE_NONE; int keyword = -1; int lineNumber = 0; int bailOut = 0; FILE *in = fopen(sourceIn, "rt"); if (!in) { printf("Unable to open input file '%s'.\n", sourceIn); return 1; } files++; argv = (char **)malloc(MAX_KEYWORD_ARGS * sizeof(char *)); for (int i=0; iname, define->value); } //printf("Line: %s\n", line); // First token token = strtok(line, " "); state = STATE_KEYWORD; argc = 0; keyword = -1; while ((token != NULL) && (!bailOut)) { tokenLength = (int)strlen(token); if (tokenLength > 0) { // Copy token to buffer so we don't screw up strtok(). strcpy(buffer, token); // Handle Comments if (buffer[0] == ';') { break; } // Handle EOLs if ((buffer[tokenLength - 1] == 10) || (buffer[tokenLength - 1] == 13)) { tokenLength--; buffer[tokenLength] = 0; } // Handle Keywords & Arguments if (tokenLength > 0) { // Handle keywords if (state == STATE_KEYWORD) { // Handle labels if (buffer[tokenLength - 1] == ':') { buffer[tokenLength - 1] = 0; // Do we know this label already? found = NULL; LL_FOREACH(labels, label) { if (strcmp(buffer, label->name) == 0) { found = label; } } if (found) { // Yes. Die. printf("Label '%s' already declared in '%s' at line %d.\n", buffer, found->file, found->line); bailOut = 1; } else { // Add it. label = (LabelT *)malloc(sizeof(LabelT)); label->addr = pc; // Is this marked exported? if (buffer[0] == '>') { // Yes! label->name = strdup(&buffer[1]); label->exported = 1; } else { // No label->name = strdup(buffer); label->exported = 0; } label->line = lineNumber; label->file = strdup(sourceIn); LL_APPEND(labels, label); labelCount++; //printf("Label: %s\n", buffer); } } else { // Not a label, search for known keyword keyword = -2; for (int i=0; i<(int)(sizeof(keywords)/sizeof(KeywordT)); i++) { if (strcmp(buffer, keywords[i].keyword) == 0) { // Found our keyword keyword = i; quote[0] = 0; inQuote = QUOTE_NONE; state = STATE_ARGUMENT; //printf("Keyword: %s\n", keywords[i].keyword); } } } // Is a label? } else { // Handle argument quoting if (inQuote == QUOTE_NONE) { // Are we entering a quoted string? if ((buffer[0] == '"') || (buffer[0] == '\'')) { // Does this token also end with quotes? If so, we don't need to enter quote mode. if (!( (((buffer[tokenLength - 1] == '"') && (buffer[0] == '"')) || ((buffer[tokenLength - 1] == '\'') && (buffer[0] == '\''))) && (buffer[tokenLength - 2] != '\\') ) || (tokenLength == 1)) { // Enter quote mode inQuote = buffer[0] == '"' ? QUOTE_DOUBLE : QUOTE_SINGLE; } } } else { // Look to leave quote mode if ( (((buffer[tokenLength - 1] == '"') && (inQuote == QUOTE_DOUBLE)) || ((buffer[tokenLength - 1] == '\'') && (inQuote == QUOTE_SINGLE))) && (buffer[tokenLength - 2] != '\\') ) { inQuote = QUOTE_NONE; } } if (strlen(quote) > 0) { strcat(quote, " "); } strcat(quote, buffer); // Can we process this argument yet? if (inQuote == QUOTE_NONE) { strcpy(argv[argc], quote); argc++; //printf("Argument: [%s]\n", quote); quote[0] = 0; } } // state } // tokenLength > 0 } // tokenLength > 0 // Next token token = strtok(NULL, " "); // If this is the end of the line and we found a keyword, process it. if ((!token) && (keyword >= 0) && (!bailOut)) { if (keywords[keyword].argc < 0) { if (argc < abs(keywords[keyword].argc)) { printf("Keyword '%s' requires at least %d argument(s)\n", keywords[keyword].keyword, abs(keywords[keyword].argc)); bailOut = 1; } } else { if (argc != keywords[keyword].argc) { printf("Keyword '%s' requires %d argument(s)\n", keywords[keyword].keyword, keywords[keyword].argc); bailOut = 1; } } if (!bailOut) { if (keywords[keyword].handler(argc, argv, sourceIn, lineNumber) != 0) { bailOut = 1; } } } else { if (keyword == -2) { printf("Watchu talkin' bout Willus?\n"); bailOut = 1; } } } // token != NULL } // read line if (bailOut) { printf("Location %d in '%s'\n", lineNumber, sourceIn); } for (int i=0; iargument != NULL) { if (bits->argument->labelFile != NULL) { labelS = NULL; LL_FOREACH(labels, label) { if (strcmp(bits->argument->text, label->name) == 0) { labelS = label; } } if (labelS) { //printf("Fixing label '%s' as %04x\n", bits->argument->text, labelS->addr); bits->argument->value = (int16_t)labelS->addr; free(bits->argument->text); bits->argument->text = NULL; } else { printf("Unable to locate label '%s' in '%s' at %d\n", bits->argument->text, bits->argument->labelFile, bits->argument->labelLine); bailOut = 1; result = 1; } } } } } else { bailOut = 1; result = 1; } // If everything was okay, produce listing and binary if (bailOut == 0) { listing = fopen(listingFile, "wt"); if (listing) { binary = fopen(binFile, "wb"); if (binary) { // Write header fputc('D', binary); fputc('B', binary); fputc('L', binary); fputc(0, binary); // Version // Write exported jump table for (int pass=0; pass<2; pass++) { LL_FOREACH(labels, label) { if (label->exported) { if (pass == 0) { // First pass, just count exportedLabels++; } else { // Second pass, write it out fwrite(&label->addr, sizeof(uint16_t), 1, binary); fputc((char)strlen(label->name), binary); fwrite(label->name, sizeof(char), strlen(label->name), binary); } } } // End of first pass, write out header size if (pass == 0) { fwrite(&exportedLabels, sizeof(uint16_t), 1, binary); } } // Write exported variable table for (int pass=0; pass<2; pass++) { count = 0; LL_FOREACH(variables, variable) { if (variable->exported) { if (pass == 0) { // First pass, just count exportedVariables++; } else { // Second pass, write it out fwrite(&count, sizeof(uint16_t), 1, binary); fputc((char)strlen(variable->name), binary); fwrite(variable->name, sizeof(char), strlen(variable->name), binary); } } count++; } // End of first pass, write out header size if (pass == 0) { fwrite(&exportedVariables, sizeof(uint16_t), 1, binary); } } // Write number of variables used fwrite(&variableCount, sizeof(uint16_t), 1, binary); headerSize = (uint16_t)ftell(binary); // Write program bitstream LL_FOREACH(bitstream, bits) { // At this point we should always have a valid opcode if ((bits->opcode > 0) && (!bailOut)) { for (int i=0; i<(int)(sizeof(opcodes)/sizeof(OpcodeT)); i++) { // Find opcode data if (bits->opcode == opcodes[i].value) { // Sanity check if (ftell(binary) - headerSize != bits->addr) { printf("Bitstream addresses out of sync at %04x\n", (uint16_t)ftell(binary) - headerSize); bailOut = 1; result = 1; break; } fprintf(listing, "(%04x) %04x %s (%02x)", (uint16_t)ftell(binary), bits->addr, opcodes[i].opcode, i + 1); fputc(opcodes[i].value, binary); // Does this opcode have arguments? if (opcodes[i].argc > 0) { // Move on to the argument data bits = bits->next; // Should NOT have an opcode if (bits->opcode > 0) { printf("Unexpected opcode at address %04x\n", bits->addr); bailOut = 1; result = 1; break; } else { // MUST have an argument if (bits->argument == NULL) { printf("No argument data at address %04x\n", bits->addr); bailOut = 1; result = 1; break; } else { // String data? if (bits->argument->text != NULL) { // Yes! fprintf(listing, " [%s]", bits->argument->text); fwrite(&bits->argument->value, sizeof(int16_t), 1, binary); fwrite(bits->argument->text, sizeof(char), strlen(bits->argument->text), binary); } else { // Nope. fprintf(listing, " %04x", bits->argument->value); fwrite(&bits->argument->value, sizeof(int16_t), 1, binary); fputc(0, binary); } } } } fprintf(listing, "\n"); break; } } } else { printf("Invalid opcode at address %04xd\n", bits->addr); result = 1; } } fclose(binary); } else { // binary != null printf("Unable to create object file '%s'\n", binFile); result = 1; } fclose(listing); } else { printf("Unable to create listing file '%s'\n", listingFile); result = 1; } } // Stats if (result == 0) { printf(" Files Compiled: %d\n", files); printf(" Lines Compiled: %d\n", sloc); printf(" Labels declared: %d\n", labelCount); printf(" Labels Exported: %d\n", exportedLabels); printf("Variables declared: %d\n", variableCount); printf("Variables Exported: %d\n", exportedVariables); printf(" Operands Emitted: %d\n", opCount); printf(" Bytes Used: %d\n", pc); } // Shut down LL_FOREACH_SAFE(bitstream, bits, bitsS) { LL_DELETE(bitstream, bits); if (bits->argument != NULL) { if (bits->argument->labelFile != NULL) { free(bits->argument->labelFile); } if (bits->argument->text != NULL) { free(bits->argument->text); } free(bits->argument); } } LL_FOREACH_SAFE(variables, variable, variableS) { LL_DELETE(variables, variable); free(variable->name); } LL_FOREACH_SAFE(defines, define, defineS) { LL_DELETE(defines, define); free(define->name); free(define->value); } LL_FOREACH_SAFE(labels, label, labelS) { LL_DELETE(labels, label); free(label->name); free(label->file); } return result; }