539 lines
20 KiB
C
539 lines
20 KiB
C
/*
|
|
================================================================
|
|
This file is the test development area for precompiling and compiling C-syntax VM source files in "code/".
|
|
|
|
It provides, globally, a Table of vm_Function pointers, as well as two additional Tables of vm_Function pointers for tile-type and tile-specific functions.
|
|
|
|
Table *vm_T_global_func
|
|
Table *vm_T_group_func[max_tile_types]
|
|
Table *vm_T_individual_func[max_tile_types][max_tile_ids]
|
|
|
|
Using Tables is a temporary solution as it stands, as it is quite inefficient. I would eventually like for a vm_Function pointer list to be generated, with particular functions being referred to solely through a vm_Function pointer (or, failing that, an index in an array).
|
|
|
|
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
|
Program Structure - file input and initial parsing
|
|
````````````````````````````````
|
|
The program reads in each timesynk-VM C-style syntax file found in the "code/" directory. These files contains the function declarations for all Tiles, Tile types, and specific Tiles. ex.:
|
|
|
|
return_type functionName(param_type param_name, param_type, param_name, . . .) {
|
|
type result = param_name;
|
|
return(param_name);
|
|
}
|
|
TID {
|
|
return_type typeFunct(. . .) {
|
|
. . .
|
|
}
|
|
TID:ID {
|
|
return_type tileFunct(. . .) {
|
|
. . .
|
|
}
|
|
}
|
|
|
|
Categorization of functions into global, group, and individual is accomplished through the following method. During the reading in of a line while depth is equal to 0 (e.g., not in a function declaration): if the first word is solely numeric, it is a group function(s) declaration; if the first word is numeric save for a single colon ':', then it is an individual declaration; if the first word is non-numeric, then it is a global declaration.
|
|
|
|
Once the scope is found, the Parser begins to search for variable and function declarations. During reading a line, if the line does not end with a ';', then it is checked as a possible function declaration - otherwise, it is checked as a variable declaration. During this stage, spaces, tabs and return characters are eaten, allowing for multiple function declaration styles.
|
|
|
|
Once a function section is identified, a mid-level function struct is created and populated with the function name, return data type, and variable structs for the parameters and added to the apropriate scope function Table. When a variable is encountered, it is converted into a mid-level variable struct and added into the appropriate scope variable Table.
|
|
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
|
Program Structure - function parsing and pre-compilation
|
|
````````````````````````````````
|
|
Once the parser is within a function, the source code is reduced to mid-level (precompiled) statements and expressions. At this stage, variables remain referred to via char arrays, with new variables added to the mid-level function's variable Table.
|
|
|
|
For example, the following variable assignment statement:
|
|
|
|
int x = 2;
|
|
|
|
Would be precompiled in the following manner:
|
|
|
|
add variable { name: "x", type: int }
|
|
add variable { name: "2", type: int }
|
|
add statement { type: assign, var_1: "x", var_2: "2" }
|
|
|
|
add statement indicates an added statement to the linked list of statements AND an increment to the line count. add variable indicates adding a variable to the variable Table. Or, for a compound statement:
|
|
|
|
float y;
|
|
if (x > 2) {
|
|
y = 0.0;
|
|
} else if (x < 1) {
|
|
y = 0.5;
|
|
} else {
|
|
y = 1.0;
|
|
}
|
|
|
|
Would precompile:
|
|
|
|
add variable { name: "y", type: float, value: "0.0" }
|
|
add variable { name: "2", type: int, value "0" } // if not existing
|
|
add statement { type: gt, var_1: "x", var_2: "2" }
|
|
add statement { type: jump, pos: undef } ---------------------.
|
|
// jump[depth][pos++] = this jump |
|
|
add variable { name: "0.0", type: float, value: "0.0" } |
|
|
add statement { type: assign, var_1: "y", var_2: "0.0" } |
|
|
add statement { type: jump, pos: undef } =====================|============================
|
|
// jump_to_end[depth][pos++] = this jump | "
|
|
// set jump[depth][pos-1].pos = this line <-------------------' "
|
|
add variable { name: "1", type: int, value: "1" } "
|
|
add statement { type: lt, var_1: "x", var_2: "1" } "
|
|
add statement { type: jump, pos: undef } ---------------------------------. "
|
|
// jump[depth][pos++] = this jump | "
|
|
// set jump[depth][pos-1].pos = this line | "
|
|
add variable { name: "0.5", type: float, value: "0.5" } | "
|
|
add statement { type: assign, var_1: "y", var_2: "0.5" } | "
|
|
add statement { type: jump, pos: undef } =================================|==============="
|
|
// jump_to_end[depth][pos++] = this jump | "
|
|
// set jump[depth][pos-1].pos = this line <-------------------------------' "
|
|
add variable { name: "1.0", type: float, value: "1.0" } "
|
|
"
|
|
// for(int jpos = pos;jpos > 0;jpos++) jump_to_end[depth][jpos] = this line <<=============
|
|
|
|
The basic logic for conditionals is that when a left curly bracket is encountered, a jump is created and added by reference to array[depth++][pos++]. When a right curly bracket is found, array[depth--][pos].pos is set to the current line. If pos >= 1, then another jump is added for end-of-conditional-block-jump and added a jump_to_end[depth][pos]. When a right curly bracket is found and pos >= 0, then every jump in jump_to_end is set to the current line.
|
|
|
|
Or, for a for loop:
|
|
|
|
for(int z = 0;z < x;z++) {
|
|
y += 0.1;
|
|
}
|
|
|
|
Would precompile into:
|
|
|
|
add variable { name: "z", type: int, value: "0" }
|
|
add variable { name: "0", type: int, value: "0" }
|
|
add statement { type: assign, var_1: "z", var_2: "0" }
|
|
add statement { type: lt, var_1: "z", var_2: "x" }
|
|
add statement { type: jump, pos: undef } -------------------------------------------.
|
|
// jump[depth][pos++] = this jump |
|
|
add variable { name: "1", type: int, value: "1" } |
|
|
add statement { type: add, var_1: "z", var_2: "1" } |
|
|
add variable { name: "0.1", type: float, value: "0.1" } |
|
|
add statement { type: add, var_1: "y", var_2: "0.1" } |
|
|
// set jump[depth][pos].pos = this line <-------------------------------------------'
|
|
|
|
Once parsing is complete, as marked by the depth reaching -1, the precompilation process is complete.
|
|
|
|
Functions reserve special locations in memory for return and parameters. For example:
|
|
|
|
int myFunction(int some_value) {
|
|
int i = some_value -1;
|
|
return i;
|
|
}
|
|
|
|
Would precompile into:
|
|
|
|
add variable { name: "i", type: int, value: "0" }
|
|
add statement { type: assign, var_1: "i", var_2: "some_value" }
|
|
add statement { type: min, var_1: "i", var_2: "1" }
|
|
add statement { type: return, var_1: "i" }
|
|
|
|
Then:
|
|
|
|
int i = myFunction(1);
|
|
|
|
precompiles into:
|
|
|
|
add variable { name: "i", type: int, value: "0" }
|
|
add variable { name: "1", type: int, value: "1" }
|
|
add statement { type: call, var_return: "i", var_1: "myFunction", var_2: "1" }
|
|
|
|
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
|
Program Structure - compilation
|
|
````````````````````````````````
|
|
The compilation process begins by iterating over all precompiled variables and functions. First, memory is allocated for all variables, starting from global, creating vm_Stacks for the bytesize necessary to fit all variables. The values are then copied into their appropriate locations in memory, with the precompiled variable's target pointer set to these locations.
|
|
|
|
After this, precompiled statements are converted into bytecode operations. Variables are replaced with their memory addresses and statement types with OP codes.
|
|
|
|
There is more written about the OP code format in my(kts) .plan files for 2014/03, and as such, will not be written here.
|
|
================================================================
|
|
*/
|
|
|
|
#include "vm_compile.h"
|
|
#include "vm.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
char *vm_datatypes[] = {
|
|
"char",
|
|
"int",
|
|
"float",
|
|
"string"
|
|
};
|
|
#define TYPE_CHAR 0
|
|
#define TYPE_INT 1
|
|
#define TYPE_FLOAT 2
|
|
#define TYPE_STRING 3
|
|
#define MAX_TYPES 3
|
|
|
|
char *vm_expressions[] = {
|
|
"return",
|
|
"if",
|
|
"while",
|
|
"for"
|
|
};
|
|
#define EXP_RETURN 0
|
|
#define EXP_IF 1
|
|
#define EXP_WHILE 2
|
|
#define EXP_FOR 3
|
|
#define MAX_EXPS 3
|
|
|
|
char *vm_assignments[] = {
|
|
"=",
|
|
"+=",
|
|
"-=",
|
|
"*=",
|
|
"/="
|
|
};
|
|
#define ASS_EQ 0
|
|
#define ASS_ADD 1
|
|
#define ASS_MIN 2
|
|
#define ASS_MUL 3
|
|
#define ASS_DIV 4
|
|
#define MAX_ASS 4
|
|
|
|
char *vm_operations[] = {
|
|
"+",
|
|
"-",
|
|
"*",
|
|
"/"
|
|
};
|
|
#define OP_ADD 0
|
|
#define OP_MIN 1
|
|
#define OP_MUL 2
|
|
#define OP_DIV 3
|
|
#define MAX_OPS 3
|
|
|
|
int main(int argc, char *argv[]) {
|
|
vm_pc_global_scope.variables = newTable(32);
|
|
vm_pc_global_scope.functions = newTable(32);
|
|
vm_pc_group_scopes = newTable(32);
|
|
vm_pc_local_scopes = newTable(32);
|
|
|
|
char *code_dir = "code/";
|
|
struct LList *code_files = dirToLList(code_dir, F_FILES);
|
|
|
|
struct LList *current_file = code_files;
|
|
while(current_file) {
|
|
if(strstr((char *)current_file->data, ".vmc") != NULL) {
|
|
char temp_string[strlen((char *)current_file->data)+strlen(code_dir)+1];
|
|
sprintf(temp_string, "%s%s", code_dir, (char *)current_file->data);
|
|
printf("-> precompiling %s\n", temp_string);
|
|
switch(vm_precompileFile(temp_string)) {
|
|
case 0:
|
|
printf("-> OK: successfully read %s\n", temp_string);
|
|
break;
|
|
case 1:
|
|
printf("-> ERR: could not open file for reading\n");
|
|
break;
|
|
}
|
|
}
|
|
current_file = current_file->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
================================
|
|
int vm_precompileFile(const char *file_name)
|
|
|
|
This function takes in a given file name, opens it, and begins processing the file text character-by-character. When a valid code block is found, such as:
|
|
|
|
{
|
|
// GLOBAL
|
|
}
|
|
|
|
0 {
|
|
// GROUP
|
|
}
|
|
|
|
0:0 {
|
|
// LOCAL
|
|
}
|
|
|
|
The appropriate scopes are created and passed to vm_precompileCode for actual precompilation.
|
|
================================
|
|
*/
|
|
int vm_precompileFile(const char *file_name) {
|
|
FILE *file = fopen(file_name, "r");
|
|
if (file == NULL) {
|
|
return 1;
|
|
}
|
|
char c; // current char!
|
|
int cpos = 1; // char position
|
|
int lpos = 1; // line position
|
|
char first[128]; // max 128 characters for group or id string
|
|
int fpos = 0; // char pos for ^
|
|
char second[128]; // max 128 for id string
|
|
int spos = -1; // char pos for ^, we cheat by using -1 as a way to use spos and fpos for our mode (e.g., group scope == -1, local scope >= 0)
|
|
int errors = 0; // error count returned by precompileCode
|
|
while(( c = fgetc(file)) != EOF) {
|
|
if (isdigit(c)) {
|
|
if (spos >= 0) {
|
|
second[spos++] = c;
|
|
} else {
|
|
first[fpos++] = c;
|
|
}
|
|
} else if (c == ':') {
|
|
if (spos >= 0) {
|
|
printf("second ':' encountered\n");
|
|
}
|
|
if (fpos == 0) {
|
|
first[fpos++] = '0';
|
|
}
|
|
first[fpos] = '\0';
|
|
spos = 0;
|
|
} else if (c == '{') {
|
|
first[fpos] = '\0';
|
|
if (fpos == 0) {
|
|
printf("[%d,%d]: starting precompile for global code block\n", lpos, cpos);
|
|
if ((errors = vm_precompileCode(file, &lpos, &cpos, &vm_pc_global_scope, NULL, NULL)) != 0) {
|
|
printf("%d ERRORS encountered while compiling global code block!\n", errors);
|
|
}
|
|
printf("[%d,%d]: finished precompile for global code block\n", lpos, cpos);
|
|
} else if (spos == -1) {
|
|
struct vm_pc_Scope *group_scope = getTablePairValue(vm_pc_group_scopes, first);
|
|
if (group_scope == NULL) { // create if does not exist
|
|
group_scope = malloc(sizeof(struct vm_pc_Scope));
|
|
group_scope->functions = newTable(32);
|
|
group_scope->variables = newTable(32);
|
|
addTablePairPointer(vm_pc_group_scopes, first, group_scope, T_TABLE); // FIXME: define VOID_POINTER in data.c/.h
|
|
}
|
|
printf("[%d,%d]: starting precompile for group code block %s\n", lpos, cpos, first);
|
|
if ((errors = vm_precompileCode(file, &lpos, &cpos, &vm_pc_global_scope, group_scope, NULL)) != 0) {
|
|
printf("%d ERRORS encountered while compiling group code block %s!\n", errors, first);
|
|
}
|
|
printf("[%d,%d]: finished precompile for group code block %s\n", lpos, cpos, first);
|
|
fpos = 0;
|
|
first[fpos] = '\0';
|
|
} else {
|
|
second[spos] = '\0';
|
|
// Get/create our scope Table vm_pc_local_scopes[first]
|
|
struct Table *local_scopes = getTablePairValue(vm_pc_local_scopes, first);
|
|
if (local_scopes == NULL) {
|
|
local_scopes = newTable(32);
|
|
addTablePairPointer(vm_pc_local_scopes, first, local_scopes, T_TABLE);
|
|
}
|
|
// Get/create Scope from vm_pc_local_scopes[first][second]
|
|
struct vm_pc_Scope *local_scope = getTablePairValue(local_scopes, second);
|
|
if (local_scope == NULL) {
|
|
local_scope = malloc(sizeof(struct vm_pc_Scope));
|
|
local_scope->functions = newTable(32);
|
|
local_scope->variables = newTable(32);
|
|
addTablePairPointer(local_scopes, second, local_scope, T_TABLE); // FIXME: define VOID_POINTER in data.c/.h
|
|
}
|
|
// Get/create group scope from vm_pc_group_scopes[first]
|
|
struct vm_pc_Scope *group_scope = getTablePairValue(vm_pc_group_scopes, first);
|
|
if (group_scope == NULL) {
|
|
group_scope = malloc(sizeof(struct vm_pc_Scope));
|
|
group_scope->functions = newTable(32);
|
|
group_scope->variables = newTable(32);
|
|
addTablePairPointer(vm_pc_group_scopes, first, group_scope, T_TABLE); // FIXME: define VOID_POINTER in data.c/.h
|
|
}
|
|
printf("[%d,%d]: starting precompile for local code block %s:%s\n", lpos, cpos, first, second);
|
|
if ((errors = vm_precompileCode(file, &lpos, &cpos, &vm_pc_global_scope, group_scope, local_scope)) != 0) {
|
|
printf("%d ERRORS encountered while compiling local code block %s:%s!\n", errors, first, second);
|
|
}
|
|
printf("[%d,%d]: finished precompile for local code block %s:%s\n", lpos, cpos, first, second);
|
|
fpos = 0;
|
|
first[fpos] = '\0';
|
|
spos = -1;
|
|
second[spos] = '\0';
|
|
}
|
|
}
|
|
if (c == '\n') {
|
|
lpos++;
|
|
cpos = 1;
|
|
} else {
|
|
cpos++;
|
|
}
|
|
}
|
|
fclose(file);
|
|
return 0; // success, baby
|
|
}
|
|
|
|
#define DISCOVER 0
|
|
int vm_precompileCode(FILE *file, int *lpos_, int *cpos_, struct vm_pc_Scope *global, struct vm_pc_Scope *group, struct vm_pc_Scope *local) {
|
|
int lpos = *lpos_;
|
|
int cpos = *cpos_;
|
|
int depth = 0;
|
|
int pdepth = 0; // parenthesis depth
|
|
char words[31][128]; // word list [X][Y]
|
|
int wpos = 0; // word position, i.e., X
|
|
int wcpos = 0; // word character position, i.e., Y
|
|
int error = 0;
|
|
struct vm_pc_Scope *current_scope = local; // default scope is local
|
|
memset(&words, '\0', 31*128);
|
|
char c; // current char!
|
|
while(( c = fgetc(file)) != EOF) {
|
|
if (c == '{') {
|
|
depth++;
|
|
} else if (c == '}') {
|
|
depth--;
|
|
}
|
|
if (c == '\n') {
|
|
lpos++;
|
|
cpos = 1;
|
|
} else {
|
|
cpos++;
|
|
}
|
|
if (depth < 0)
|
|
break;
|
|
// actual conversion of lines to words
|
|
// when ( is encountered and parenthesis depth is 0, end the current word, and start new word with '('. Otherwise, add parenthesis to current word.
|
|
if (c == '(') {
|
|
if (pdepth == 0) {
|
|
words[wpos++][wcpos] = '\0';
|
|
wcpos = 0;
|
|
words[wpos][wcpos++] = c;
|
|
} else {
|
|
words[wpos][wcpos++] = c;
|
|
}
|
|
pdepth++;
|
|
// when ) is encountered, decrease depth and add to current word.
|
|
} else if (c == ')') {
|
|
pdepth--;
|
|
words[wpos][wcpos++] = c;
|
|
/*if (pdepth == 0) {
|
|
words[wpos++][wcpos] = '\0';
|
|
wcpos = 0;
|
|
}*/
|
|
// if parenthesis depth is greater than zero, then add spaces or tabs to the current word. If not, end current word and start new word.
|
|
} else if (c == ' ' || c == '\t') {
|
|
if (pdepth > 0) {
|
|
words[wpos][wcpos++] = c;
|
|
} else {
|
|
if (wcpos != 0) {
|
|
//words[wpos][wcpos++] = c;
|
|
words[wpos++][wcpos] = '\0';
|
|
wcpos = 0;
|
|
}
|
|
}
|
|
// consume newlines with greed.
|
|
} else if (c == '\n') {
|
|
|
|
// when a ';' is encountered, this equates to a variable declaration.
|
|
} else if (c == ';') {
|
|
int wwpos = 0;
|
|
int wtypes[wpos];
|
|
for (wwpos=0;wwpos <= wpos;wwpos++) {
|
|
if (wwpos == 0) { // "_int_"
|
|
int wtype = vm_isReserved(words[wwpos]);
|
|
wtypes[wwpos] = wtype;
|
|
} else if (wwpos == 1) {
|
|
if (wtypes[wwpos-1] == 1) { // "int _x_ . . ."
|
|
// check against dbl datatype declaration, i.e., int int
|
|
wtypes[wwpos] = vm_isReserved(words[wwpos]);
|
|
if (wtypes[wwpos] == 0) {
|
|
printf("ADDING %s %s\n", words[wwpos-1], words[wwpos]);
|
|
} else {
|
|
printf("[%d,%d]: ERROR, bad variable name, \"%s\" is reserved\n", lpos, cpos, words[wwpos]);
|
|
error++;
|
|
break;
|
|
}
|
|
}
|
|
} else if (wwpos == 2) {
|
|
wtypes[wwpos] = vm_isReserved(words[wwpos]);
|
|
if (wtypes[wwpos-1] == 0) { // variable name
|
|
if (wtypes[wwpos] == 0) { // "int _int_"
|
|
printf("[%d,%d]: ERROR, \"%s\" given after \"%s\" already provided - perhaps a forgotten assignment op?\n", lpos, cpos, words[wwpos], words[wwpos-1]);
|
|
error++;
|
|
break;
|
|
} else if (wtypes[wwpos] == 4) { // "="
|
|
if (wtypes[wwpos-1] == 0) {
|
|
if (wwpos+1 > wpos || words[wwpos+1][0] == '\0') {
|
|
printf("[%d,%d]: ERROR, right-hand of assignment for \"%s\" empty!\n", lpos, cpos, words[wwpos-1]);
|
|
error++;
|
|
break;
|
|
} else {
|
|
printf("SETTING %s %s %s\n", words[wwpos-1], words[wwpos], words[wwpos+1]);
|
|
}
|
|
} else {
|
|
printf("[%d,%d]: ERROR, \"%s\" after \"%s\" is invalid!\n", lpos, cpos, words[wwpos], words[wwpos-1]);
|
|
error++;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
wtypes[wwpos] = vm_isReserved(words[wwpos]);
|
|
if (wtypes[wwpos] == 4) { // "="
|
|
if (wtypes[wwpos-1] == 0) {
|
|
if (wwpos+1 > wpos) {
|
|
printf("[%d,%d]: ERROR, right-hand of assignment for \"%s\" empty!\n", lpos, cpos, words[wwpos-1]);
|
|
error++;
|
|
break;
|
|
} else {
|
|
printf("SETTING %s %s %s\n", words[wwpos-1], words[wwpos], words[wwpos+1]);
|
|
}
|
|
} else {
|
|
printf("[%d,%d]: ERROR, \"%s\" after \"%s\" is invalid!\n", lpos, cpos, words[wwpos], words[wwpos-1]);
|
|
error++;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
printf("\t\t");
|
|
for(wwpos = 0;wwpos <= wpos;wwpos++) {
|
|
printf("%s ", words[wwpos]);
|
|
}
|
|
printf("\n");
|
|
// RESET
|
|
wpos = 0;
|
|
wcpos = 0;
|
|
memset(&words, '\0', 31*128);
|
|
// when a '{' is encountered, what follows should be a function.
|
|
} else if (c == '{') {
|
|
// TODO: create vm_pc_Function and set some current scope up for statements.
|
|
// TODO: also check for conditional thing and convert to ops, of course
|
|
printf("func, while, etc. baby\n");
|
|
int wwpos = 0;
|
|
for(wwpos = 0;wwpos <= wpos;wwpos++) {
|
|
printf("%d: %s\n", wwpos, words[wwpos]);
|
|
}
|
|
// RESET
|
|
wpos = 0;
|
|
wcpos = 0;
|
|
memset(&words, '\0', 31*128);
|
|
} else if (c == '}') {
|
|
// I guess leave current vm_pc_Function scope here if depth == 0?
|
|
printf("function body\n");
|
|
int wwpos = 0;
|
|
for(wwpos = 0;wwpos <= wpos;wwpos++) {
|
|
printf("%d: %s\n", wwpos, words[wwpos]);
|
|
}
|
|
// RESET
|
|
wpos = 0;
|
|
wcpos = 0;
|
|
memset(&words, '\0', 31*128);
|
|
} else {
|
|
words[wpos][wcpos++] = c;
|
|
}
|
|
}
|
|
*lpos_ = lpos;
|
|
*cpos_ = cpos;
|
|
return error;
|
|
}
|
|
|
|
int vm_isReserved(char *word) {
|
|
int checkpos;
|
|
for (checkpos = 0;checkpos <= MAX_TYPES;checkpos++) {
|
|
if (strcmp(word, vm_datatypes[checkpos]) == 0) {
|
|
return 1;
|
|
}
|
|
}
|
|
for (checkpos = 0;checkpos <= MAX_EXPS;checkpos++) {
|
|
if (strcmp(word, vm_expressions[checkpos]) == 0) {
|
|
return 2;
|
|
}
|
|
}
|
|
for (checkpos = 0;checkpos <= MAX_OPS;checkpos++) {
|
|
if (strcmp(word, vm_operations[checkpos]) == 0) {
|
|
return 3;
|
|
}
|
|
}
|
|
for (checkpos = 0;checkpos <= MAX_ASS;checkpos++) {
|
|
if (strcmp(word, vm_assignments[checkpos]) == 0) {
|
|
return 4;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|