blob: fb6f50c1acfd09df7f4b2544776d23ff88967942 [file] [log] [blame] [edit]
#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>
/* Globals */
struct veri_Includes veri_includes;
struct veri_Defines veri_defines;
/* Function declarations */
FILE* open_source_file(char* filename, std::string parent_path);
FILE *remove_comments(FILE *source);
/*
* Initialize the preprocessor by allocating sufficient memory and setting sane values
*/
const char symbol_char[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
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;
}
/*
* Return an open file handle
* if the file is not in the pwd try the paths indicated by char* list_of_file_names int current_parse_file
*
* Return NULL if unable to find and open the file
*/
FILE* open_source_file(char* filename, std::string path)
{
auto loc = path.find_last_of('/');
if (loc != std::string::npos) /* No other path to try to find the file */
path = path.substr(0,loc+1);
path += filename;
// look in the directory where the file with the include directory resides in
FILE* src_file = fopen(path.c_str(), "r");
if (src_file != NULL)
return src_file;
// else Look for the file in the PWD as a last resort TODO: this should go away... not standard behavior
src_file = fopen(filename, "r");
if (src_file != NULL)
{
fprintf(stderr, "Warning: Unable to find %s, opening in current working directory instead\n",
path.c_str());
return src_file;
}
return NULL;
}
/*
* 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;
/* Was going to use filename to prevent duplication but the global var isn't used in the case of a config value */
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;
}
preproc_producer = tmpfile();
preproc_producer = freopen(NULL, "r+", preproc_producer);
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;
}
/*
* Returns a new temporary file with the comments removed.
* Preserves the line numbers by keeping newlines in place.
*/
FILE *remove_comments(FILE *source)
{
FILE *destination = tmpfile();
destination = freopen(NULL, "r+", destination);
rewind(source);
char *line = NULL;
long line_size = 0;
int in_multiline_comment = FALSE;
while ((line = get_line(line, &line_size, source)) != nullptr)
{
unsigned int i;
for (i = 0; i < strnlen(line, line_size); i++)
{
if (!in_multiline_comment)
{
// For a single line comment, skip the rest of the line.
if (line[i] == '/' && line[i+1] == '/')
{
break;
}
// For a multi-line comment, set the flag and skip over the *.
else if (line[i] == '/' && line[i+1] == '*')
{
i++; // Skip the *.
in_multiline_comment = TRUE;
}
else
{
if (line[i] != '\n')
fputc(line[i], destination);
}
}
else
{
// If we're in a multi-line comment, search for the */
if (line[i] == '*' && line[i+1] == '/')
{
i++; // Skip the /
in_multiline_comment = FALSE;
}
}
}
fputc('\n', destination);
vtr::free(line);
}
rewind(destination);
return destination;
}
void veri_preproc_bootstraped(FILE *original_source, FILE *preproc_producer, veri_include *current_include)
{
// Strip the comments from the source file producing a temporary source file.
FILE *source = remove_comments(original_source);
int line_number = 1;
veri_flag_stack *skip = (veri_flag_stack *)vtr::calloc(1, sizeof(veri_flag_stack));;
char *line = NULL;
long line_size = 0;
char *token;
veri_include *new_include = NULL;
while ((line = get_line(line, &line_size, source)) != NULL)
{
//fprintf(stderr, "%s:%ld\t%s", current_include->path,line_number, line);
// advance past all whitespace
line = trim(line);
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)
{
std::string symbol = proc_line.substr(pch+1, (end_pch-pch)-1);
// 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());
line_size = proc_line.size();
}
//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] == '`')
{
token = strtok(line, " \t");
/* If we encounter an `included directive we want to recurse using included_file and
* new_include in place of source and current_include
*/
if (top(skip) < 1 && strcmp(token, "`include") == 0)
{
token = strtok(NULL, "\"");
FILE *included_file = open_source_file(token,current_include->path);
/* 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(token, 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. */
}
/* If we encounter a `define directive we want to add it and its value if any to our
* symbol table.
*/
else if (top(skip) < 1 && strcmp(token, "`define") == 0)
{
char *value = NULL;
/* strtok is destructive to the original string which we need to retain unchanged, this fixes it. */
// fprintf(preproc_producer, "`define %s\n", line + 1 + strnlen(line, line_size));
token = strtok(NULL, " \t");
size_t len = strlen(token);
value = &(token[len+1]);
// symbol value can potentially be to the end of the line!
add_veri_define(token, value, line_number, current_include);
}
/* If we encounter a `undef preprocessor directive we want to remove the corresponding
* symbol from our lookup table.
*/
else if (top(skip) < 1 && strcmp(token, "`undef") == 0)
{
int is_defined = 0;
/* strtok is destructive to the original string which we need to retain unchanged, this fixes it. */
// fprintf(preproc_producer, "`undef %s", line + 1 + strnlen(line, line_size));
token = strtok(NULL, " \t");
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;
}
}
else if (strcmp(token, "`ifdef") == 0)
{
// if parent is not skipped
if ( top(skip) < 1 ) {
int is_defined = 0;
token = strtok(NULL, " \t");
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 ) ;
}
}
else if (strcmp(token, "`ifndef") == 0)
{
// if parent is not skipped
if ( top(skip) < 1 ) {
int is_defined = 0;
token = strtok(NULL, " \t");
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 ) ;
}
}
else if (strcmp(token, "`else") == 0)
{
// 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
}
}
else if (strcmp(token, "`endif") == 0)
{
pop(skip);
}
/* Leave unhandled preprocessor directives in place. */
else if (top(skip) < 1)
{
fprintf(preproc_producer, "%s %s", line, line + 1 + strnlen(line, line_size));
}
}
else if(top(skip) < 1)
{
if(strlen(line) > 0 && fprintf(preproc_producer, "%s", line) < 0)//fputs(line, preproc_producer)
{
/* There was an error writing to the stream */
}
}
fprintf(preproc_producer, "\n");
line_number++;
token = NULL;
vtr::free(line);
}
fclose(source);
vtr::free(skip);
}
/* General Utility methods ------------------------------------------------- */
#define UPPER_BUFFER_LIMIT 32728
bool is_whitespace(const char in)
{
return (in == ' ' || in == '\t' || in == '\n' || in == '\r');
}
/**
* the trim function remove consequent whitespace and remove trailing whitespace
*/
char *trim(char *input_str)
{
return trim(input_str, UPPER_BUFFER_LIMIT);
}
char* trim(char *input_string, size_t n)
{
size_t upper_limit = (n < 1)? UPPER_BUFFER_LIMIT: (n > UPPER_BUFFER_LIMIT)? UPPER_BUFFER_LIMIT: n;
if (!input_string)
return input_string;
int head = 0;
int tail = 0;
char current = ' ';
char previous = ' ';
// trim head and compress
while( tail < upper_limit && input_string[tail] != '\0' )
{
previous = current;
current = input_string[tail];
input_string[tail] = '\0';
tail += 1;
if( is_whitespace(current) )
{
if( ! is_whitespace(previous) )
{
input_string[head] = ' ';
head += 1;
}
}
else
{
input_string[head] = current;
head += 1;
}
}
input_string[head] = '\0';
// trim tail end
while( head >= 0
&& (input_string[head] == '\0' || is_whitespace(input_string[head]) ) )
{
input_string[head] = '\0';
head -= 1;
}
return input_string;
}
/*
* Reads a line from a file stream character-by-character
* to dynamically build a string.
*/
char *get_line(char *line, long *size, FILE *source)
{
long length = 0;
line = (char *) vtr::malloc(sizeof(char) * 1);
char next = 0;
while (next != '\n')
{
next = (char) fgetc(source);
if (next != EOF)
{
line = (char*) vtr::realloc(line, sizeof(char) * (length + 2));
line[length] = next;
length++;
}
else
{
break;
}
}
if (length == 0)
{
vtr::free(line);
return NULL;
}
line[length] = '\0';
if (size != NULL) *size = length;
return line;
}
/* ------------------------------------------------------------------------- */
/* 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;
}
}
/* ------------------------------------------------------------------------- */