proc visualize_lisp_machine() -> void { struct Drawn_Area { int x; int y; int width; int height; }; fprintf(stderr, "Drawing visualization..."); defer { fprintf(stderr, "Done!\n"); }; const int padding = 40; const int margin = 20; const char* draw_text_template = " \n %s%s%s\n \n"; const char* draw_integer_template = " \n %d\n \n"; const char* draw_float_template = " \n %012.6f\n \n"; FILE *f = fopen("visualization.svg", "w"); defer { fclose(f); }; if (f == NULL) { create_generic_error("The file for writing the visualization" "could not be opened for writing"); return; } 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 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(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.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); default: 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, " " " ", write_x, write_y, write_x+50, write_y, write_x+50, write_y+50); // arrow to first fprintf(f, " ", 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, " ", 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, " ", 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 = 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(); Int_Array_List* indices_to_filter = create_Int_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 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.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(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, "\n" "\n\n", -padding, -padding, 0, 0); draw_header(); draw_symbols_keywords_and_numbers(); // draw_text("DoEun", "#00aaaa", true); fprintf(f, "\n\n"); // fill in the correct viewBox rewind(f); fprintf(f, "\n" "", -padding, -padding, max_x + 2*padding, max_y + 2*padding); }