Возьмем конкретный пример: я решил задокументировать вариант MIPSfpga который я сделал осенью для семинаров в России, и для этой документации мне нужны картинки иерархии модулей. Рисовать картинки в графическом редакторе мне лень, так как вариантов иерархии несколько сотен, и в картинке должны быть HTML-ные ссылки на гитхаб.
Посему я вчера написал вот такую программку, которая генерит HTML тексты http://github.com/MIPSfpga/mipsfpga-plus/tree/master/documentation/figure_generator
Прошу не вопить про использование коротких идентификаторов, препроцессора и передачу параметров через глобальные переменные - от решения требуется краткость для чтения и быстрота написания, а не scalability и verifiability. Я осознаю, что данным кодом можно троллировать
А как бы решили такую задачу вы? Особенно интересует мнение пуристов функциональных языков.
Примеры картинок, генерируемых в зависимости от параметров (всех вариантов генерируемых скриптом - несколько сотен):



Моя программка на Си, которая генерирует картинки:
http://github.com/MIPSfpga/mipsfpga-plus/tree/master/documentation/figure_generator
https://github.com/MIPSfpga/mipsfpga-plus/blob/master/documentation/figure_generator/utilities.cpp # include "stdio.h" # include "string.h" char const mipsfpga_plus_github_location [] = "http://github.com/MIPSfpga/mipsfpga-plus/blob/master"; char const mipsfpga_download_instruction [] = "http://www.silicon-russia.com/2015/12/11/mipsfpga-download-instructions"; const char * module_names [1000]; int n_module_names; const char * current_module_name; int current_module_is_already_described; int level = 0; //---------------------------------------------------------------------------- #define p printf #define header p ("<font size=2 face=\"Verdana, Arial, Helvetica, sans_serif\">\n"); #define footer p ("</font>\n"); #define tr p ("<tr>\n"); #define td p ("<td valign=top>\n"); #define _tr p ("</tr>\n"); #define _td p ("</td>\n"); #define hbreak _td td #define vbreak _td _tr tr td #define ellipsis p (". . . . . . . . . .<br><br>\n"); //---------------------------------------------------------------------------- void module ( const char * file_url = NULL, const char * module_name = NULL, const char * instance_name = NULL, const char * description = NULL, int colspan = 1 ) { level ++; if (current_module_name == NULL && module_name != NULL) module_names [n_module_names ++] = module_name; int the_module_is_current = current_module_name != NULL && module_name != NULL && strcmp (module_name, current_module_name) == 0; // if (! the_module_is_current || current_module_is_already_described) // description = NULL; if (the_module_is_current) current_module_is_already_described = 1; char buf [BUFSIZ]; if (file_url != NULL && strstr (file_url, "http") == NULL) { sprintf (buf, "%s/%s", mipsfpga_plus_github_location, file_url); file_url = buf; } p ("<table width=%s", level == 1 ? "1000" : "100%%"); char const * level_colors [] = { "A3FFD1", "A3FFFF", "A3D1FF" }; p (" bgcolor=#%s", the_module_is_current ? "FFFF29" : level_colors [level % (sizeof (level_colors) / sizeof (* level_colors))]); if (module_name != NULL || instance_name != NULL || description != NULL) p (" border=2"); p (" cellpadding=10 cellspacing=0 rules=none>\n"); if (module_name != NULL || instance_name != NULL || description != NULL) { // p ("<tr><td valign=top nowrap=nowrap"); p ("<tr><td valign=top"); if (colspan != 1) p (" colspan=%d", colspan); p (">\n"); if (module_name != NULL || instance_name != NULL) p ("<b>"); if (module_name != NULL) { if (file_url != NULL) p ("<a href=\"%s\">", file_url); p ("%s", module_name); if (file_url != NULL) p ("</a>"); } if (module_name != NULL && instance_name != NULL) p (" "); if (instance_name != NULL) p ("%s", instance_name); if (module_name != NULL || instance_name != NULL) p ("</b>"); if ((module_name != NULL || instance_name != NULL) && description != NULL) p ("<br><br>\n"); if (description != NULL) p ("%s", description); p ("\n</td></tr>\n"); } p ("<tr><td valign=top>\n"); colspan = 1; } //---------------------------------------------------------------------------- #define _module p ("</td></tr>\n</table>\n"), level --; #define group module (); #define _group _module //---------------------------------------------------------------------------- void leaf ( const char * file_url = NULL, const char * module_name = NULL, const char * instance_name = NULL, const char * description = NULL ) { module ( file_url, module_name, instance_name, description ); _module } https://github.com/MIPSfpga/mipsfpga-plus/blob/master/documentation/figure_generator/generator.cpp #include "utilities.cpp" struct board_descriptor { char const * module_name; int testbench; int clock_frequency; int altera; int static_7_segment_display; int dynamic_7_segment_display; char const * description; } boards [] = { { "mfp_testbench" , 1 , 0 , 0 , 0 , 0 , "Testbench for RTL simulation" }, { "nexys4_ddr" , 0 , 100 , 0 , 0 , 1 , "Wrapper for Digilent Nexys 4 DDR board with Xilinx Artix-7 FPGA" }, { "de0_cv" , 0 , 50 , 1 , 1 , 0 , "Wrapper for Terasic DE0-CV board with Altera Cyclone V FPGA" }, { "de0_nano" , 0 , 50 , 1 , 0 , 0 , "Wrapper for Terasic DE0-Nano board with Altera Cyclone IV FPGA" }, { "basys3" , 0 , 100 , 0 , 0 , 1 , "Wrapper for Digilent Basys 3 board with Xilinx Artix-7 FPGA" }, { "marsohod3" , 0 , 50 , 1 , 0 , 0 , "Wrapper for Marsohod 3 board with Altera MAX10 FPGA" } }; int i_board; int narrow_write_support; int switchable_clock; int light_sensor; int serial_loader; void print_hierarchy () { current_module_is_already_described = 0; board_descriptor * b = & boards [i_board]; char buf [BUFSIZ]; sprintf ( buf, "../run/hierarchy_%s%s%s%s%s%s%s.html", b -> module_name, narrow_write_support ? "__narrow_write_support" : "", switchable_clock ? "__switchable_clock" : "", light_sensor ? "__light_sensor" : "", serial_loader ? "__serial_loader" : "", current_module_name != NULL ? "__" : "", current_module_name != NULL ? current_module_name : "" ); freopen (buf, "w", stdout); char const * file_url = NULL; if (! b -> testbench) { sprintf (buf, "boards/%s/%s.v", b -> module_name, b -> module_name); file_url = buf; } header module ( file_url, b -> module_name, NULL, b -> description ); if (switchable_clock | b -> static_7_segment_display | b -> dynamic_7_segment_display) { group if (switchable_clock) { group module ("mfp_switch_and_button_debouncers.v", "mfp_multi_switch_or_button_sync_and_debouncer"); leaf ("mfp_switch_and_button_debouncers.v", "mfp_switch_or_button_sync_and_debouncer", NULL, "Debouncer for the switches that control the clock"); _module vbreak if (b -> clock_frequency == 50) module ("mfp_clock_dividers.v", "mfp_clock_divider_50_MHz_to_25_MHz_12_Hz_0_75_Hz"); else if (b -> clock_frequency == 100) module ("mfp_clock_dividers.v", "mfp_clock_divider_100_MHz_to_25_MHz_12_Hz_0_75_Hz"); leaf ("mfp_clock_dividers.v", "mfp_clock_divider"); _module vbreak if (b -> altera) leaf (NULL, "global", "gclk", "Needed for the divided clock"); else leaf (NULL, "BUFG", NULL, "Needed for the divided clock"); _group } hbreak if (b -> static_7_segment_display) { group leaf ("mfp_seven_segment_displays.v", "mfp_single_digit_seven_segment_display", "digit_0"); vbreak leaf ("mfp_seven_segment_displays.v", "mfp_single_digit_seven_segment_display", "digit_1"); vbreak leaf ("mfp_seven_segment_displays.v", "mfp_single_digit_seven_segment_display", "digit_2"); vbreak ellipsis _group } if (b -> dynamic_7_segment_display) { group leaf ("mfp_clock_dividers.v", "mfp_clock_divider_100_MHz_to_763_Hz", NULL, "Clock for 7-segment display"); vbreak leaf ("mfp_seven_segment_displays.v", "mfp_multi_digit_display"); _group } _group vbreak } module ("mfp_system.v", "mfp_system", NULL, NULL, 2 + light_sensor); group leaf (mipsfpga_download_instruction, "m14k_top", NULL, "The CPU core"); vbreak leaf ("mfp_system.v", "mfp_ejtag_reset"); _group hbreak if (serial_loader) { module ("mfp_ahb_lite_matrix_with_loader.v", "mfp_ahb_lite_matrix_with_loader", NULL, NULL, 2); group leaf ("mfp_uart_receiver.v", "mfp_uart_receiver", NULL, "Receives data bytes from the PC via UART"); vbreak leaf ("mfp_srec_parser.v", "mfp_srec_parser", NULL, "Parses data received via UART as text in Motorola S-Record format and issues transactions to fill the system memory with this data"); vbreak leaf ("mfp_srec_parser_to_ahb_lite_bridge.v", "mfp_srec_parser_to_ahb_lite_bridge", NULL, "Converts the transactions from S-Record parser into AHB-Lite protocol. Also converts virtual addresses into physical using fixed mapping"); _group hbreak } module ("mfp_ahb_lite_matrix.v", "mfp_ahb_lite_matrix" ); leaf ("mfp_ahb_lite_matrix.v", "mfp_ahb_lite_decoder" ); vbreak module ("mfp_ahb_ram_slave.v", "mfp_ahb_ram_slave", "reset_ram"); if (narrow_write_support) { leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram", "i0"); vbreak leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram", "i1"); vbreak leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram", "i2"); vbreak leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram", "i3"); } else { leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram"); } _module vbreak module ("mfp_ahb_ram_slave.v", "mfp_ahb_ram_slave", "ram"); if (narrow_write_support) { leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram", "i0"); vbreak leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram", "i1"); vbreak ellipsis } else { leaf ("mfp_dual_port_ram.v", "mfp_dual_port_ram"); } _module vbreak leaf ("mfp_ahb_gpio_slave.v", "mfp_ahb_gpio_slave", "gpio"); vbreak leaf ("mfp_ahb_lite_matrix.v", "mfp_ahb_lite_response_mux"); _module if (serial_loader) _module if (light_sensor) { hbreak leaf ("mfp_pmod_als_spi_receiver.v", "mfp_pmod_als_spi_receiver", NULL, "Receives data from the light sensor using a version of SPI protocol"); } _module _module footer } //---------------------------------------------------------------------------- int main () { for (i_board = 0; i_board < sizeof (boards) / sizeof (* boards); i_board ++) for (narrow_write_support = 0; narrow_write_support <= 1; narrow_write_support ++) for (switchable_clock = 0; switchable_clock <= 1; switchable_clock ++) for (light_sensor = 0; light_sensor <= 1; light_sensor ++) for (serial_loader = 0; serial_loader <= 1; serial_loader ++) { int features = narrow_write_support + switchable_clock + light_sensor + serial_loader; if ( i_board == 0 && (switchable_clock + light_sensor + serial_loader) != 0 || i_board != 0 && narrow_write_support == 0 || i_board > 2 ) { continue; } // module_names and n_module_names are extracted when current_module_name is NULL n_module_names = 0; current_module_name = NULL; print_hierarchy (); for (int i = 0; i < n_module_names; i ++) { current_module_name = module_names [i]; if (current_module_name != NULL) print_hierarchy (); } } return 0; }
Пример кода на HTML, автоматически сгенеренного программкой:
<font size=2 face="Verdana, Arial, Helvetica, sans_serif"> <table width=1000 bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b>mfp_testbench</b><br><br> Testbench for RTL simulation </td></tr> <tr><td valign=top> <table width=100%% bgcolor=#A3D1FF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top colspan=2> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_system.v">mfp_system</a></b> </td></tr> <tr><td valign=top> <table width=100%% bgcolor=#A3FFD1 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://www.silicon-russia.com/2015/12/11/mipsfpga-download-instructions">m14k_top</a></b><br><br> The CPU core </td></tr> <tr><td valign=top> </td></tr> </table> </td> </tr> <tr> <td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_system.v">mfp_ejtag_reset</a></b> </td></tr> <tr><td valign=top> </td></tr> </table> </td></tr> </table> </td> <td valign=top> <table width=100%% bgcolor=#A3FFD1 border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_ahb_lite_matrix.v">mfp_ahb_lite_matrix</a></b> </td></tr> <tr><td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_ahb_lite_matrix.v">mfp_ahb_lite_decoder</a></b> </td></tr> <tr><td valign=top> </td></tr> </table> </td> </tr> <tr> <td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_ahb_ram_slave.v">mfp_ahb_ram_slave</a> reset_ram</b> </td></tr> <tr><td valign=top> <table width=100%% bgcolor=#A3D1FF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_dual_port_ram.v">mfp_dual_port_ram</a></b> </td></tr> <tr><td valign=top> </td></tr> </table> </td></tr> </table> </td> </tr> <tr> <td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_ahb_ram_slave.v">mfp_ahb_ram_slave</a> ram</b> </td></tr> <tr><td valign=top> <table width=100%% bgcolor=#A3D1FF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_dual_port_ram.v">mfp_dual_port_ram</a></b> </td></tr> <tr><td valign=top> </td></tr> </table> </td></tr> </table> </td> </tr> <tr> <td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_ahb_gpio_slave.v">mfp_ahb_gpio_slave</a> gpio</b> </td></tr> <tr><td valign=top> </td></tr> </table> </td> </tr> <tr> <td valign=top> <table width=100%% bgcolor=#A3FFFF border=2 cellpadding=10 cellspacing=0 rules=none> <tr><td valign=top> <b><a href="http://github.com/MIPSfpga/mipsfpga-plus/blob/master/mfp_ahb_lite_matrix.v">mfp_ahb_lite_response_mux</a></b> </td></tr> <tr><td valign=top> </td></tr> </table> </td></tr> </table> </td></tr> </table> </td></tr> </table> </font>
А как бы эту задачу решили вы?