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 = " \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"); 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 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, " " " ", 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 = (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 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, "\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); }