blob: 34153f60993434cc7802f6357170d3e7d8610549 [file] [log] [blame]
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "verilog_preprocessor.h"
#include "odin_types.h"
#include "vtr_util.h"
#include "vtr_memory.h"
#include "odin_util.h"
#include <stdbool.h>
#include <regex>
#include <string>
#include "odin_buffer.hpp"
#define DefaultSize 20
/* Structs */
struct veri_include
{
char *path;
struct veri_include *included_from;
int line;
};
struct veri_define
{
char *symbol;
char *value;
int line;
veri_include *defined_in;
};
struct veri_Includes
{
veri_include **included_files;
int current_size;
int current_index;
};
struct veri_Defines
{
veri_define **defined_constants;
int current_size;
int current_index;
};
/* Stack for tracking conditional branches --------------------------------- */
struct veri_flag_node
{
int flag;
struct veri_flag_node *next;
};
struct veri_flag_stack
{
veri_flag_node *top;
} ;
void clean_veri_define(veri_define *current);
void clean_veri_include(veri_include *current);
/* Adding/Removing includes or defines */
int add_veri_define(char *symbol, char *value, int line, veri_include *included_from);
char* ret_veri_definedval(const char *symbol);
int veri_is_defined(const char * symbol);
veri_include* add_veri_include(const char *path, int line, veri_include *included_from);
/* Preprocessor ------------------------------------------------------------- */
void veri_preproc_bootstraped(FILE *source, FILE *preproc_producer, veri_include *current_include);
/* stack methods */
int top(veri_flag_stack *stack);
int pop(veri_flag_stack *stack);
void push(veri_flag_stack *stack, int flag);
/* General Utility methods ------------------------------------------------- */
static char* get_line(FILE *source, bool *eof, bool *multiline_comment, const char *one_line_comment, const char *n_line_comment_ST, const char *n_line_comment_END);
/* Globals */
struct veri_Includes veri_includes;
struct veri_Defines veri_defines;
const char symbol_char[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
/*
* Initialize the preprocessor by allocating sufficient memory and setting sane values
*/
int init_veri_preproc()
{
veri_includes.included_files = (veri_include **) vtr::calloc(DefaultSize, sizeof(veri_include *));
if (veri_includes.included_files == NULL)
{
perror("veri_includes.included_files : vtr::calloc ");
return -1;
}
veri_includes.current_size = DefaultSize;
veri_includes.current_index = 0;
veri_defines.defined_constants = (veri_define **) vtr::calloc(DefaultSize, sizeof(veri_define *));
if (veri_defines.defined_constants == NULL)
{
perror("veri_defines.defined_constants : vtr::calloc ");
return -1;
}
veri_defines.current_size = DefaultSize;
veri_defines.current_index = 0;
return 0;
}
/*
* Cleanup allocated memory
*/
int cleanup_veri_preproc()
{
//fprintf(stderr, "Cleaning up the verilog preprocessor\n");
veri_define *def_iterator = veri_defines.defined_constants[0];
veri_include *inc_iterator = veri_includes.included_files[0];
int i;
for (i = 0; i < veri_defines.current_index && i < veri_defines.current_size; def_iterator = veri_defines.defined_constants[++i])
{
clean_veri_define(def_iterator);
}
def_iterator = NULL;
veri_defines.current_index = 0;
veri_defines.current_size = 0;
vtr::free(veri_defines.defined_constants);
for (i = 0; i < veri_includes.current_index && i < veri_includes.current_size; inc_iterator = veri_includes.included_files[++i])
{
clean_veri_include(inc_iterator);
}
inc_iterator = NULL;
veri_includes.current_index = 0;
veri_includes.current_size = 0;
vtr::free(veri_includes.included_files);
//fprintf(stderr, " --- Finished\n");
return 0;
}
/*
* Free memory for a symbol in the define table
*/
void clean_veri_define(veri_define *current)
{
if (current != NULL)
{
//fprintf(stderr, "\tCleaning Symbol: %s, ", current->symbol);
vtr::free(current->symbol);
//fprintf(stderr, "Value: %s ", current->value);
vtr::free(current->value);
current->defined_in = NULL;
vtr::free(current);
current=NULL;
//fprintf(stderr, "...done\n");
}
}
/*
* Free memory for a symbol in the include table
*/
void clean_veri_include(veri_include *current)
{
if (current != NULL)
{
//fprintf(stderr, "\tCleaning Include: %s ", current->path);
vtr::free(current->path);
vtr::free(current);
current = NULL;
//fprintf(stderr, "...done\n");
}
}
/*
* add_veri_define returns a non negative value on success, a -1 if creation of the define failed
* due to a lack of memory and -2 if the symbol was previously defined and the values conflict
*/
int add_veri_define(char *symbol, char *value, int line, veri_include *defined_in)
{
int i;
veri_define *def_iterator = veri_defines.defined_constants[0];
veri_define *new_def = (veri_define *)vtr::malloc(sizeof(veri_define));
if (new_def == NULL)
{
perror("new_def : vtr::malloc ");
return -1;
}
/* Check to see if there's enough space in our lookup table and reallocate if not. */
if (veri_defines.current_index == veri_defines.current_size)
{
veri_defines.defined_constants = (veri_define **)vtr::realloc(veri_defines.defined_constants, (size_t)(veri_defines.current_size * 2) * sizeof(veri_define *));
//In a perfect world there is a check here to make sure realloc succeded
veri_defines.current_size *= 2;
}
/* Check previously defined values for collisions. */
for (i = 0; i < veri_defines.current_index && i < veri_defines.current_size; def_iterator = veri_defines.defined_constants[++i])
{
if (0 == strcmp(def_iterator->symbol, symbol))
{
warning_message(PARSE_ERROR, -1, -1, "The constant %s defined on line %d in %s was previously defined on line %d in %s\n",
symbol, line, defined_in->path, def_iterator->line, def_iterator->defined_in->path);
if (value == NULL || (value[0] == '/' && value[1] == '/'))
#ifndef BLOCK_EMPTY_DEFINES
{
warning_message(PARSE_ERROR, -1, -1, "The new value of %s is empty\n\n", symbol);
vtr::free(def_iterator->value);
def_iterator->value =NULL;
}
#else
{
warning_message(PARSE_ERROR, -1, -1, "The new value of %s is empty, doing nothing\n\n", symbol);
return 0;
}
#endif
else if (0 != strcmp(def_iterator->value, value))
{
warning_message(PARSE_ERROR, -1, -1, "The value of %s has been redefined to %s, the previous value was %s\n\n",
symbol, value, def_iterator->value);
vtr::free(def_iterator->value);
def_iterator->value = (char *)vtr::strdup(value);
}
vtr::free(new_def);
return -2;
}
}
/* Create the new define and initalize it. */
new_def->symbol = (char *)vtr::strdup(symbol);
new_def->value = (value == NULL)? vtr::strdup("") : (char *)vtr::strdup(value);
new_def->line = line;
new_def->defined_in = defined_in;
veri_defines.defined_constants[veri_defines.current_index] = new_def;
veri_defines.current_index++;
return 0;
}
/* add_veri_include shall return NULL if it is unable to create a new
* veri_include in the lookup table or an entry for that file already exists.
* Otherwise it wil return a pointer to the new veri_include entry.
*/
veri_include* add_veri_include(const char *path, int line, veri_include *included_from)
{
int i;
veri_include *inc_iterator = veri_includes.included_files[0];
veri_include *new_inc = (veri_include *)vtr::malloc(sizeof(veri_include));
if (new_inc == NULL)
{
perror("new_inc : vtr::malloc ");
return NULL;
}
/* Check to see if there's enough space in our lookup table and reallocate if not. */
if (veri_includes.current_index == veri_includes.current_size)
{
veri_includes.included_files = (veri_include **)vtr::realloc(veri_includes.included_files, (size_t)(veri_includes.current_size * 2) * sizeof(veri_include *));
//In a perfect world there is a check here to make sure realloc succeded
veri_includes.current_size *= 2;
}
/* Scan previous includes to make sure the file wasn't included previously. */
for (i = 0; i < veri_includes.current_index && i < veri_includes.current_size && inc_iterator != NULL; inc_iterator = veri_includes.included_files[++i])
if (0 == strcmp(path, inc_iterator->path))
warning_message(PARSE_ERROR, line, -1, "Warning: including %s multiple times\n", path);
new_inc->path = vtr::strdup(path);
new_inc->included_from = included_from;
new_inc->line = line;
veri_includes.included_files[veri_includes.current_index] = new_inc;
veri_includes.current_index++;
return new_inc;
}
/*
* Retrieve the value associated, if any, with the given symbol. If the symbol is not present or no
* value is associated with the symbol then NULL is returned.
*/
char* ret_veri_definedval(const char *symbol)
{
int is_defined = veri_is_defined(symbol);
if(0 <= is_defined)
{
return veri_defines.defined_constants[is_defined]->value;
}
return NULL;
}
/*
* Returns a non-negative integer if the symbol has been previously defined.
*/
int veri_is_defined(const char * symbol)
{
int i;
veri_define *def_iterator = veri_defines.defined_constants[0];
for (i = 0; (i < veri_defines.current_index) && (i < veri_defines.current_size) && (def_iterator != NULL); i++)
{
def_iterator = veri_defines.defined_constants[i];
if (0 == strcmp(symbol, def_iterator->symbol))
{
return i;
}
}
return -1;
}
/*
* Bootstraps our preprocessor
*/
FILE* veri_preproc(FILE *source)
{
extern global_args_t global_args;
extern config_t configuration;
extern int current_parse_file;
FILE *preproc_producer = NULL;
veri_include *veri_initial = add_veri_include(configuration.list_of_file_names[current_parse_file].c_str(), 0, NULL);
if (veri_initial == NULL)
{
fprintf(stderr, "Unable to store include information returning original FILE pointer\n\n");
return source;
}
std::string file_out = configuration.debug_output_path
+ "/"
+ strip_path_and_ext(configuration.list_of_file_names[current_parse_file]).c_str()
+ "_preproc.v";
preproc_producer = fopen(file_out.c_str(), "w+");
if (preproc_producer == NULL)
{
perror("preproc_producer : fdopen - returning original FILE pointer");
exit(-1);
return source;
}
/* to thread or not to thread, that is the question. Wether yac will block when waitin */
fprintf(stderr, "Preprocessing verilog.\n");
veri_preproc_bootstraped(source, preproc_producer, veri_initial);
rewind(preproc_producer);
return preproc_producer;
}
const char *preproc_symbol_e_STR[] =
{
"include",
"define",
"undef",
"ifdef",
"ifndef",
"else",
"endif",
"preproc_symbol_e_END"
};
enum preproc_symbol_e
{
PREPROC_INCLUDE,
PREPROC_DEFINE,
PREPROC_UNDEF,
PREPROC_IFDEF,
PREPROC_IFNDEF,
PREPROC_ELSE,
PREPROC_ENDIF,
preproc_symbol_e_END
};
static preproc_symbol_e get_preproc_directive(const char *in)
{
preproc_symbol_e to_return = preproc_symbol_e_END;
if(in && strlen(in) > 0)
{
for(int i=0 ; i < (int)preproc_symbol_e_END; i++)
{
if(! strcmp(preproc_symbol_e_STR[i], in) )
{
to_return = (preproc_symbol_e) i;
break;
}
}
}
return to_return;
}
void veri_preproc_bootstraped(FILE *original_source, FILE *preproc_producer, veri_include *current_include)
{
rewind(original_source);
int line_number = 1;
veri_flag_stack *skip = (veri_flag_stack *)vtr::calloc(1, sizeof(veri_flag_stack));
veri_include *new_include = NULL;
char *line = NULL;
buffered_reader_t reader = buffered_reader_t(original_source, "//", "/*", "*/");
while ((line = reader.get_line()))
{
// fprintf(stderr, "%s:%ld\t%s\n", current_include->path,line_number, line);
if( strlen(line) > 0 )
{
std::string proc_line(line);
// start searching for backticks
std::size_t pch = proc_line.find_first_of('`') ;
while ( pch != std::string::npos)
{
std::size_t end_pch = proc_line.find_first_not_of(symbol_char, pch+1);
if (end_pch != std::string::npos)
{
// skip the backtick
std::string symbol = proc_line.substr(pch+1, (end_pch-pch)-1);
// verify that this is not a preprocessor directive
if( get_preproc_directive(symbol.c_str()) == preproc_symbol_e_END)
{
// get value from lookup table
char* value = ret_veri_definedval( symbol.c_str() ) ;
if (value != NULL)
{
proc_line.erase(pch, (end_pch-pch));
proc_line.insert(pch, value);
}
}
}
// find next backtick
pch = proc_line.find_first_of('`', pch+1) ;
}
if(! proc_line.empty())
{
vtr::free(line);
line = vtr::strdup(proc_line.c_str());
// fprintf(stderr, "%s:%ld\t%s\n", current_include->path,line_number, line);
/* Preprocessor directives have a backtick on the first column. */
if (line[0] == '`')
{
char *token = strtok(line, " ");
// skip the backtick to find the type
switch( get_preproc_directive(&token[1]) )
{
case PREPROC_INCLUDE:
{
if(top(skip) < 1)
{
/**
* If we encounter an `included directive we want to recurse using included_file and
* new_include in place of source and current_include
*/
token = strtok(NULL, "\"");
std::string current_path = current_include->path;
auto loc = current_path.find_last_of('/');
if (loc != std::string::npos) /* No other path to try to find the file */
{
current_path = current_path.substr(0,loc+1);
}
else
{
current_path = "";
}
std::string file_path = current_path + token;
FILE* included_file = fopen(file_path.c_str(), "r");
/* If we failed to open the included file handle the error */
if (!included_file)
{
warning_message(PARSE_ERROR, -1, -1, "Unable to open file %s included on line %d of %s\n",
token, line_number, current_include->path);
perror("included_file : fopen");
/*return erro or exit ? */
}
else if (NULL != (new_include = add_veri_include(file_path.c_str(), line_number, current_include)))
{
printf("Including file %s\n", new_include->path);
veri_preproc_bootstraped(included_file, preproc_producer, new_include);
}
fclose(included_file);
/* If last included file has no newline an error could result so we add one. */
}
break;
}
case PREPROC_DEFINE:
{
if(top(skip) < 1)
{
token = strtok(NULL, " ");
size_t len = strlen(token);
char *value = &(token[len+1]);
if(get_preproc_directive(value) != preproc_symbol_e_END)
{
printf("Warning! trying to override preprocessor restricted keyword is not allowed\n");
}
else
{
// symbol value can potentially be to the end of the line!
add_veri_define(token, value, line_number, current_include);
}
}
break;
}
case PREPROC_UNDEF:
{
if(top(skip) < 1)
{
token = strtok(NULL, " ");
int is_defined = veri_is_defined(token);
if(is_defined >= 0)
{
clean_veri_define(veri_defines.defined_constants[is_defined]);
veri_defines.defined_constants[is_defined] = veri_defines.defined_constants[veri_defines.current_index];
veri_defines.defined_constants[veri_defines.current_index--] = NULL;
}
}
break;
}
case PREPROC_IFDEF:
{
// if parent is not skipped
if ( top(skip) < 1 )
{
token = strtok(NULL, " ");
int is_defined = veri_is_defined(token);
if(is_defined < 0) //If we are unable to locate the symbol in the table
{
push(skip, 1);
}
else
{
push(skip, 0);
}
}
// otherwise inherit skip from parent (use 2)
else
{
push( skip, 2 ) ;
}
break;
}
case PREPROC_IFNDEF:
{
// if parent is not skipped
if ( top(skip) < 1 )
{
token = strtok(NULL, " ");
int is_defined = veri_is_defined(token);
if(is_defined >= 0) //If we are able to locate the symbol in the table
{
push(skip, 1);
}
else
{
push(skip, 0);
}
}
// otherwise inherit skip from parent (use 2)
else
{
push( skip, 2 ) ;
}
break;
}
case PREPROC_ELSE:
{
// if skip was 0 (prev. ifdef was 1)
if(top(skip) < 1)
{
// then set to 0
pop(skip) ;
push(skip, 1);
}
// only when prev skip was 1 do we set to 0 now
else if (top(skip) == 1)
{
pop(skip) ;
push(skip, 0);
}
// but if it's 2 (parent ifdef is 1)
else
{
// then do nothing
}
break;
}
case PREPROC_ENDIF:
{
pop(skip);
break;
}
default:
{
/* Leave unhandled preprocessor directives in place. */
if (top(skip) < 1)
{
fprintf(preproc_producer, "%s %s", line, line + 1 + strlen(line));
}
break;
}
}
}
else if(top(skip) < 1)
{
fprintf(preproc_producer, "%s", line);
}
}
}
fprintf(preproc_producer,"\n");
line_number++;
vtr::free(line);
}
vtr::free(skip);
}
/* stack methods ------------------------------------------------------------*/
int top(veri_flag_stack *stack)
{
if(stack != NULL && stack->top != NULL)
{
return stack->top->flag;
}
return 0;
}
int pop(veri_flag_stack *stack)
{
if(stack != NULL && stack->top != NULL)
{
veri_flag_node *top = stack->top;
int flag = top->flag;
stack->top = top->next;
vtr::free(top);
return flag;
}
return 0;
}
void push(veri_flag_stack *stack, int flag)
{
if(stack != NULL)
{
veri_flag_node *new_node = (veri_flag_node *)vtr::malloc(sizeof(veri_flag_node));
new_node->next = stack->top;
new_node->flag = flag;
stack->top = new_node;
}
}