555 lines
20 KiB
C++
555 lines
20 KiB
C++
proc visualize_lisp_machine() -> void {
|
|
|
|
// save the current working directory
|
|
char* cwd = get_cwd();
|
|
|
|
// get the direction of the exe
|
|
char* exe_path = get_exe_dir();
|
|
// switch to the exe directory for loading pre.slime
|
|
change_cwd(exe_path);
|
|
|
|
defer {
|
|
// switch back to the users directory
|
|
change_cwd(cwd);
|
|
free(cwd);
|
|
free(exe_path);
|
|
};
|
|
|
|
struct Drawn_Area {
|
|
int x;
|
|
int y;
|
|
int width;
|
|
int height;
|
|
};
|
|
|
|
log_message(Log_Level::Info, "Drawing visualization...");
|
|
|
|
defer {
|
|
log_message(Log_Level::Info, "Done drawing visualization!");
|
|
};
|
|
|
|
const int padding = 40;
|
|
const int margin = 20;
|
|
|
|
const char* draw_text_template = " <text x='%d' y='%d' font-size='20' font-family='monospace' style='fill: %s'>\n %s%s%s\n </text>\n";
|
|
const char* draw_integer_template = " <text x='%d' y='%d' font-size='20' font-family='monospace'>\n %d\n </text>\n";
|
|
const char* draw_float_template = " <text x='%d' y='%d' font-size='20' font-family='monospace'>\n %012.6f\n </text>\n";
|
|
|
|
|
|
FILE *f = fopen("visualization.svg", "w");
|
|
if (!f) {
|
|
create_generic_error("The file for writing the visualization "
|
|
"could not be opened for writing");
|
|
return;
|
|
}
|
|
defer {
|
|
fclose(f);
|
|
};
|
|
|
|
int max_x = 0,
|
|
max_y = 0,
|
|
write_x = 0,
|
|
write_y = 0;
|
|
|
|
|
|
proc draw_margin = [&](int count = 1) -> Drawn_Area {
|
|
write_x += margin * count;
|
|
return {
|
|
write_x - margin * count,
|
|
write_y,
|
|
margin * count,
|
|
write_y
|
|
};
|
|
};
|
|
proc draw_new_line = [&](int count = 1) {
|
|
write_x = 0;
|
|
write_y += 25 * count;
|
|
};
|
|
proc draw_text = [&](const char* text, const char* color = "#000000", bool draw_quotes = false, int max_length = 200) -> Drawn_Area {
|
|
// take care of escaping sensitive chars
|
|
int text_length = 0;
|
|
int extra_needed_chars = draw_quotes ? 10 : 0;
|
|
char* new_text = nullptr;
|
|
char char_at_max_length = 0;
|
|
|
|
char source;
|
|
while ((source = text[text_length++]) != '\0') {
|
|
switch (source) {
|
|
case '\n':
|
|
extra_needed_chars += 1;
|
|
case '<':
|
|
case '>':
|
|
extra_needed_chars += 3;
|
|
break;
|
|
case '&':
|
|
extra_needed_chars += 4;
|
|
break;
|
|
case '\'':
|
|
case '"':
|
|
extra_needed_chars += 5;
|
|
}
|
|
}
|
|
// last char was \0 but we don't count it
|
|
--text_length;
|
|
|
|
if (text_length > max_length) {
|
|
char_at_max_length = ((char*)text)[max_length];
|
|
((char*)text)[max_length] = '\0';
|
|
text_length = max_length;
|
|
}
|
|
defer {
|
|
if (char_at_max_length)
|
|
((char*)text)[max_length] = char_at_max_length;
|
|
};
|
|
|
|
// if we need to replace some chars
|
|
if (extra_needed_chars > 0) {
|
|
new_text = (char*)malloc((text_length + extra_needed_chars) * sizeof(char));
|
|
|
|
int index_in_text = 0,
|
|
index_in_new_text = 0;
|
|
|
|
char source;
|
|
while ((source = text[index_in_text++]) != '\0') {
|
|
switch (source) {
|
|
case '\n': new_text[index_in_new_text++] = '\\'; new_text[index_in_new_text++] = 'n'; break;
|
|
case '<': new_text[index_in_new_text++] = '&'; new_text[index_in_new_text++] = 'l'; new_text[index_in_new_text++] = 't'; new_text[index_in_new_text++] = ';'; break;
|
|
case '>': new_text[index_in_new_text++] = '&'; new_text[index_in_new_text++] = 'g'; new_text[index_in_new_text++] = 't'; new_text[index_in_new_text++] = ';'; break;
|
|
case '&': new_text[index_in_new_text++] = '&'; new_text[index_in_new_text++] = 'a'; new_text[index_in_new_text++] = 'm'; new_text[index_in_new_text++] = 'p'; new_text[index_in_new_text++] = ';'; break;
|
|
case '"': new_text[index_in_new_text++] = '&'; new_text[index_in_new_text++] = 'q'; new_text[index_in_new_text++] = 'u'; new_text[index_in_new_text++] = 'o'; new_text[index_in_new_text++] = 't'; new_text[index_in_new_text++] = ';'; break;
|
|
case '\'': new_text[index_in_new_text++] = '&'; new_text[index_in_new_text++] = 'a'; new_text[index_in_new_text++] = 'p'; new_text[index_in_new_text++] = 'o'; new_text[index_in_new_text++] = 's'; new_text[index_in_new_text++] = ';'; break;
|
|
default: new_text[index_in_new_text++] = source;
|
|
}
|
|
}
|
|
new_text[index_in_new_text] = '\0';
|
|
}
|
|
|
|
int text_width = 12 * (text_length + (draw_quotes ? 2 : 0));
|
|
if (write_x + text_width > max_x) max_x = write_x + text_width;
|
|
if (write_y + 12 > max_y) max_y = write_y + 12;
|
|
|
|
const char* quote = draw_quotes ? """ : "";
|
|
if (extra_needed_chars) {
|
|
fprintf(f, draw_text_template, write_x, write_y+12, color, quote, new_text, quote);
|
|
free(new_text);
|
|
} else {
|
|
fprintf(f, draw_text_template, write_x, write_y+12, color, quote, text, quote, color);
|
|
}
|
|
|
|
// write_x += text_width;
|
|
|
|
return {
|
|
write_x - text_width,
|
|
write_y,
|
|
text_width,
|
|
12
|
|
};
|
|
};
|
|
proc draw_integer = [&](int number) -> Drawn_Area {
|
|
int text_width = 12 * ((int)log10(number)+1);
|
|
|
|
if (write_x + text_width > max_x) max_x = write_x + text_width;
|
|
if (write_y > max_y) max_y = write_y;
|
|
|
|
fprintf(f, draw_integer_template, write_x, write_y+12, number);
|
|
|
|
return {
|
|
write_x,
|
|
write_y,
|
|
text_width,
|
|
12
|
|
};
|
|
};
|
|
proc draw_float = [&](float number) -> Drawn_Area {
|
|
int text_width = 12 * 12;
|
|
|
|
if (write_x + text_width > max_x) max_x = write_x + text_width;
|
|
if (write_y > max_y) max_y = write_y;
|
|
|
|
fprintf(f, draw_float_template, write_x, write_y+12, number);
|
|
|
|
return {
|
|
write_x,
|
|
write_y,
|
|
text_width,
|
|
12
|
|
};
|
|
};
|
|
std::function<Drawn_Area(Lisp_Object*)> draw_pair;
|
|
proc draw_lisp_object = [&](Lisp_Object* obj) -> Drawn_Area {
|
|
switch (Memory::get_type(obj)) {
|
|
case Lisp_Object_Type::T: return draw_text("t");
|
|
case Lisp_Object_Type::Nil: return draw_text("()");
|
|
case Lisp_Object_Type::Pair: return draw_pair(obj);
|
|
case Lisp_Object_Type::Number: return draw_float((float)obj->value.number);
|
|
case Lisp_Object_Type::Symbol: return draw_text(&obj->value.string->data);
|
|
case Lisp_Object_Type::Keyword: {
|
|
Drawn_Area colon = draw_text(":", "#c61b6e");
|
|
write_x += colon.width;
|
|
Drawn_Area text = draw_text(&obj->value.symbol.identifier->data, "#c61b6e");
|
|
write_x -= colon.width;
|
|
return {
|
|
colon.x,
|
|
colon.y,
|
|
colon.width + text.width,
|
|
colon.height
|
|
};
|
|
}
|
|
case Lisp_Object_Type::String: return draw_text(&obj->value.string->data, "#2aa198", true, 20);
|
|
case Lisp_Object_Type::Function: return draw_text("Function", "#aa1100");
|
|
case Lisp_Object_Type::CFunction: return draw_text("CFunction", "#11aa00");
|
|
default:
|
|
fprintf(stderr, "Do not know hot to visualize type %d\n", (int)Memory::get_type(obj));
|
|
return {0};
|
|
}
|
|
};
|
|
draw_pair = [&](Lisp_Object* pair) -> Drawn_Area {
|
|
Drawn_Area ret;
|
|
Drawn_Area child;
|
|
|
|
ret.x = write_x;
|
|
ret.y = write_y;
|
|
ret.width = 100;
|
|
ret.height = 100;
|
|
|
|
fprintf(f,
|
|
" <rect x='%d' y='%d' width='100' height='50' stroke-width='3' stroke='black' fill='white'/>"
|
|
" <line x1='%d' y1='%d' x2='%d' y2='%d' stroke-width='3' stroke='black'/>",
|
|
write_x, write_y, write_x+50, write_y, write_x+50, write_y+50);
|
|
|
|
// arrow to first
|
|
fprintf(f,
|
|
" <line x1='%d' y1='%d' x2='%d' y2='%d' stroke-width='3' stroke='black'/>",
|
|
write_x+25, write_y+25, write_x+25, write_y+100);
|
|
|
|
write_y += 110;
|
|
child = draw_lisp_object(pair->value.pair.first);
|
|
if (ret.width < child.width)
|
|
ret.width = child.width;
|
|
if (ret.height < child.height)
|
|
ret.height = child.height;
|
|
|
|
write_y -= 110;
|
|
|
|
if (pair->value.pair.rest == Memory::nil) {
|
|
fprintf(f,
|
|
" <line x1='%d' y1='%d' x2='%d' y2='%d' stroke-width='3' stroke='black'/>",
|
|
write_x+50, write_y+50, write_x+100, write_y);
|
|
} else {
|
|
// arrow to rest
|
|
int x_offset = 150;
|
|
if (child.width+margin > x_offset)
|
|
x_offset = child.width+margin;
|
|
|
|
fprintf(f,
|
|
" <line x1='%d' y1='%d' x2='%d' y2='%d' stroke-width='3' stroke='black'/>",
|
|
write_x+75, write_y+25, write_x+75+x_offset, write_y+25);
|
|
|
|
write_x += x_offset;
|
|
ret.width += 50;
|
|
|
|
child = draw_lisp_object(pair->value.pair.rest);
|
|
ret.width += child.width;
|
|
if (ret.height < 70 + child.height)
|
|
ret.height = 70 + child.height;
|
|
|
|
write_x -= x_offset;
|
|
}
|
|
|
|
fprintf(f, "\n");
|
|
|
|
if (max_x < ret.x + ret.width)
|
|
max_x = ret.x + ret.width;
|
|
if (max_y < ret.y + ret.height)
|
|
max_y = ret.y + ret.height;
|
|
|
|
return ret;
|
|
};
|
|
proc draw_header = [&]() {
|
|
proc draw_separator = [&]() {
|
|
draw_margin();
|
|
draw_text("|");
|
|
draw_margin();
|
|
};
|
|
|
|
time_t t = time(NULL);
|
|
struct tm tm = *localtime(&t);
|
|
|
|
write_y = 12;
|
|
|
|
// -------------------
|
|
// Date
|
|
// -------------------
|
|
char date[12];
|
|
snprintf(date, 12, "%02d.%02d.%d", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
|
|
|
|
write_x += draw_text("Date: ").width;
|
|
write_x += draw_text(date).width;
|
|
|
|
draw_separator();
|
|
|
|
// -------------------
|
|
// Time
|
|
// -------------------
|
|
char time[12];
|
|
snprintf(time, 12, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
write_x += draw_text("Time: ").width;
|
|
write_x += draw_text(time).width;
|
|
|
|
draw_separator();
|
|
|
|
// -------------------
|
|
// String Memory
|
|
// -------------------
|
|
draw_new_line();
|
|
|
|
int free_string_memory = (int)(Memory::next_free_spot_in_string_memory - Memory::string_memory);
|
|
for (int i = 0; i < Memory::free_spots_in_string_memory.next_index; ++i) {
|
|
free_string_memory += ((String*)(Memory::free_spots_in_string_memory.data[i]))->length;
|
|
}
|
|
int used_string_memory = Memory::string_memory_size - free_string_memory;
|
|
|
|
write_x += draw_text("String Memory:").width;
|
|
draw_margin();
|
|
write_x += draw_text("[allocated chars] ").width;
|
|
write_x += draw_integer(Memory::string_memory_size).width;
|
|
draw_margin();
|
|
write_x += draw_text("[free] ").width;
|
|
write_x += draw_integer(free_string_memory).width;
|
|
draw_margin();
|
|
write_x += draw_text("[used] ").width;
|
|
write_x += draw_integer(used_string_memory).width;
|
|
draw_margin();
|
|
write_x += draw_text("[%free] ").width;
|
|
write_x += draw_float(100.0f * free_string_memory / Memory::string_memory_size).width;
|
|
draw_margin();
|
|
write_x += draw_text("[%used] ").width;
|
|
write_x += draw_float(100.0f * used_string_memory / Memory::string_memory_size).width;
|
|
|
|
draw_separator();
|
|
draw_new_line();
|
|
|
|
// -------------------
|
|
// Object Memory
|
|
// -------------------
|
|
|
|
int free_object_memory_cells = Memory::object_memory_size - (Memory::next_index_in_object_memory - Memory::free_spots_in_object_memory.next_index);
|
|
int used_object_memory_cells = Memory::next_index_in_object_memory - Memory::free_spots_in_object_memory.next_index;
|
|
|
|
write_x += draw_text("Object Memory:").width;
|
|
draw_margin();
|
|
write_x += draw_text("[#allocated] ").width;
|
|
write_x += draw_integer(Memory::object_memory_size).width;
|
|
draw_margin();
|
|
write_x += draw_text("[#free] ").width;
|
|
write_x += draw_integer(free_object_memory_cells).width;
|
|
draw_margin();
|
|
write_x += draw_text("[#used] ").width;
|
|
write_x += draw_integer(used_object_memory_cells).width;
|
|
draw_margin();
|
|
write_x += draw_text("[%free] ").width;
|
|
write_x += draw_float(100.0f * free_object_memory_cells / Memory::object_memory_size).width;
|
|
draw_margin();
|
|
write_x += draw_text("[%used] ").width;
|
|
write_x += draw_float(100.0f * used_object_memory_cells / Memory::object_memory_size).width;
|
|
|
|
draw_separator();
|
|
|
|
draw_new_line(3);
|
|
};
|
|
proc draw_symbols_keywords_and_numbers = [&]() {
|
|
Lisp_Object_Array_List symbols = create_Lisp_Object_array_list();
|
|
Lisp_Object_Array_List keywords = create_Lisp_Object_array_list();
|
|
Lisp_Object_Array_List numbers = create_Lisp_Object_array_list();
|
|
Lisp_Object_Array_List strings = create_Lisp_Object_array_list();
|
|
Lisp_Object_Array_List pairs = create_Lisp_Object_array_list();
|
|
Lisp_Object_Array_List lists = create_Lisp_Object_array_list();
|
|
|
|
// loop over all used memory
|
|
for (int i = 0; i < Memory::next_index_in_object_memory; ++i) {
|
|
for (int j = 0; j < Memory::free_spots_in_object_memory.next_index; ++j) {
|
|
if (i == Memory::free_spots_in_object_memory.data[j])
|
|
goto next;
|
|
}
|
|
|
|
switch (Memory::get_type(Memory::object_memory+i)) {
|
|
case Lisp_Object_Type::Symbol: append_to_array_list(&symbols, Memory::object_memory+i); break;
|
|
case Lisp_Object_Type::String: append_to_array_list(&strings, Memory::object_memory+i); break;
|
|
case Lisp_Object_Type::Keyword: append_to_array_list(&keywords, Memory::object_memory+i); break;
|
|
case Lisp_Object_Type::Number : append_to_array_list(&numbers, Memory::object_memory+i); break;
|
|
case Lisp_Object_Type::Pair : append_to_array_list(&pairs, Memory::object_memory+i); break;
|
|
default: break;
|
|
}
|
|
|
|
next: ;
|
|
}
|
|
|
|
// create the lists-list by filtering the pairs-list.
|
|
Lisp_Object_Array_List pairs_to_filter = create_Lisp_Object_array_list();
|
|
|
|
// helper lambda:
|
|
proc remove_doubles_from_lisp_object_array_list = [&](Lisp_Object_Array_List list) -> void {
|
|
if (list.next_index == 0)
|
|
return;
|
|
|
|
sort_array_list(&list);
|
|
Int_Array_List indices_to_filter = create_Int_array_list();
|
|
|
|
size_t last = (size_t)list.data[0];
|
|
for (int i = 1; i < list.next_index; ++i) {
|
|
if ((size_t)list.data[i] == last)
|
|
append_to_array_list(&indices_to_filter, i);
|
|
else
|
|
last = (size_t)list.data[i];
|
|
}
|
|
|
|
for (int i = indices_to_filter.next_index; i >= 0; --i) {
|
|
remove_index_from_array_list(&list, indices_to_filter.data[i]);
|
|
}
|
|
|
|
// sort again as removing items destroys the order
|
|
sort_array_list(&list);
|
|
};
|
|
|
|
// recursive lambda
|
|
std::function<void(Lisp_Object*)> filter_pair_and_children;
|
|
filter_pair_and_children = [&](Lisp_Object* pair) {
|
|
append_to_array_list(&pairs_to_filter, pair);
|
|
|
|
if (Memory::get_type(pair->value.pair.first) == Lisp_Object_Type::Pair)
|
|
filter_pair_and_children(pair->value.pair.first);
|
|
|
|
if (Memory::get_type(pair->value.pair.rest) == Lisp_Object_Type::Pair)
|
|
filter_pair_and_children(pair->value.pair.rest);
|
|
};
|
|
for (int i = 0; i < pairs.next_index; ++i) {
|
|
if (Memory::get_type(pairs.data[i]->value.pair.first) == Lisp_Object_Type::Pair)
|
|
filter_pair_and_children(pairs.data[i]->value.pair.first);
|
|
|
|
if (Memory::get_type(pairs.data[i]->value.pair.rest) == Lisp_Object_Type::Pair)
|
|
filter_pair_and_children(pairs.data[i]->value.pair.rest);
|
|
|
|
}
|
|
|
|
remove_doubles_from_lisp_object_array_list(pairs_to_filter);
|
|
// fprintf(stderr, "removing %d pairs\n", pairs_to_filter->next_index);
|
|
// okay, so pairs_to_filter now only the pairs once each that
|
|
// we want to filter from the pairs list
|
|
for (int i = 0; i < pairs.next_index; ++i) {
|
|
if (sorted_array_list_find(&pairs_to_filter, pairs.data[i]) == -1) {
|
|
append_to_array_list(&lists, pairs.data[i]);
|
|
}
|
|
}
|
|
|
|
draw_text("Memory Contents:");
|
|
draw_new_line();
|
|
draw_new_line();
|
|
|
|
int start_x = write_x,
|
|
start_y = write_y;
|
|
|
|
write_x += draw_text("Symbols: ").width;
|
|
draw_integer(symbols.next_index);
|
|
draw_new_line();
|
|
write_x = start_x;
|
|
|
|
for (int i = 0; i < symbols.next_index; ++i) {
|
|
draw_new_line();
|
|
write_x = start_x;
|
|
|
|
draw_text(&symbols.data[i]->value.symbol.identifier->data);
|
|
}
|
|
|
|
|
|
write_x = start_x + 300;
|
|
write_y = start_y;
|
|
|
|
write_x += draw_text("Keywords: ").width;
|
|
draw_integer(keywords.next_index);
|
|
draw_new_line();
|
|
write_x = start_x + 300;
|
|
|
|
for (int i = 0; i < keywords.next_index; ++i) {
|
|
draw_new_line();
|
|
write_x = start_x + 300;
|
|
|
|
draw_lisp_object(keywords.data[i]);
|
|
}
|
|
|
|
|
|
|
|
write_x = start_x + 600;
|
|
write_y = start_y;
|
|
|
|
write_x += draw_text("Numbers: ").width;
|
|
draw_integer(numbers.next_index);
|
|
draw_new_line();
|
|
write_x = start_x + 600;
|
|
|
|
for (int i = 0; i < numbers.next_index; ++i) {
|
|
draw_new_line();
|
|
write_x = start_x + 600;
|
|
|
|
draw_float((float)(numbers.data[i]->value.number));
|
|
}
|
|
|
|
write_x = start_x + 900;
|
|
write_y = start_y;
|
|
|
|
write_x += draw_text("Strings: ").width;
|
|
draw_integer(strings.next_index);
|
|
draw_new_line();
|
|
write_x = start_x + 900;
|
|
|
|
for (int i = 0; i < strings.next_index; ++i) {
|
|
draw_new_line();
|
|
write_x = start_x + 900;
|
|
|
|
draw_text(&strings.data[i]->value.string->data, "#2aa198", true, 75);
|
|
}
|
|
|
|
|
|
write_x = start_x + 2000;
|
|
write_y = start_y;
|
|
|
|
write_x += draw_text("Lists, Pairs: ").width;
|
|
write_x += draw_integer(lists.next_index).width;
|
|
draw_margin();
|
|
draw_integer(pairs.next_index);
|
|
draw_new_line();
|
|
write_x = start_x + 2000;
|
|
|
|
for (int i = 0; i < lists.next_index; ++i) {
|
|
draw_new_line(3);
|
|
write_x = start_x + 2000;
|
|
|
|
write_y += draw_pair(lists.data[i]).height;
|
|
}
|
|
};
|
|
|
|
fprintf(f,
|
|
"<?xml version='1.0' encoding='UTF-8'?>\n"
|
|
"<svg xmlns='http://www.w3.org/2000/svg'\n"
|
|
" version='1.1' baseProfile='full'\n"
|
|
" viewBox='%012d %012d %012d %012d'"
|
|
">\n\n", -padding, -padding, 0, 0);
|
|
|
|
draw_header();
|
|
draw_symbols_keywords_and_numbers();
|
|
// draw_text("DoEun", "#00aaaa", true);
|
|
|
|
fprintf(f, "\n\n</svg>");
|
|
|
|
// fill in the correct viewBox
|
|
rewind(f);
|
|
|
|
fprintf(f,
|
|
"<?xml version='1.0' encoding='UTF-8'?>\n"
|
|
"<svg xmlns='http://www.w3.org/2000/svg'\n"
|
|
" version='1.1' baseProfile='full'\n"
|
|
" viewBox='%012d %012d %012d %012d'"
|
|
">", -padding, -padding, max_x + 2*padding, max_y + 2*padding);
|
|
|
|
}
|