pl_proto.pl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use Data::Dumper;
  5. use lib "/home/swatson/perl5/lib/perl5";
  6. use GraphViz2;
  7. my $SOURCE_FILE = $ARGV[0];
  8. sub read_to_var($) {
  9. my $file_path = shift;
  10. my $content;
  11. open(my $fh, '<', $file_path) or die "cannot open file $file_path";
  12. {
  13. local $/;
  14. $content = <$fh>;
  15. }
  16. close($fh);
  17. return $content;
  18. }
  19. my $src_content = read_to_var($SOURCE_FILE);
  20. sub trim($) {
  21. my $str = shift;
  22. $str =~ s/^\s+|\s+$//g;
  23. return $str;
  24. }
  25. my @module_library_paths = (
  26. ".",
  27. );
  28. my %modules;
  29. sub parse_module_file {
  30. my $src = shift;
  31. my %module;
  32. my $last_proc_type = "null";
  33. my $last_input = "null";
  34. my $last_output = "null";
  35. foreach my $line ( split("\n", $src) ) {
  36. chomp $line;
  37. if ( $line =~ m/^#/ ) {
  38. next;
  39. }
  40. if ( $line =~ m/^Manufacturer:(.*)/ ) {
  41. my $manu = $1;
  42. $manu = trim($manu);
  43. $module{'Manufacturer'} = $manu;
  44. }
  45. if ( $line =~ m/^Module:(.*)/ ) {
  46. my $mod = $1;
  47. $mod = trim($mod);
  48. $module{'Module'} = $mod;
  49. }
  50. if ( $line =~ m/^Revision:(.*)/ ) {
  51. my $rev = $1;
  52. $rev = trim($rev);
  53. $module{'Rev'} = $rev;
  54. }
  55. if ( $line =~ m/^-\ / ) {
  56. if ( $line =~ m/^-\ Input:(.*)/ ) {
  57. my $input = $1;
  58. $input = trim($input);
  59. my %input_chars;
  60. $last_input = $input;
  61. $last_proc_type = "input";
  62. $module{'Inputs'}->{$input} = \%input_chars;
  63. }
  64. if ( $line =~ m/^-\ Knob:(.*)/ ) {
  65. my $knob = $1;
  66. $knob = trim($knob);
  67. my %knob_chars;
  68. $last_input = $knob;
  69. $last_proc_type = "input";
  70. $module{'Inputs'}->{$knob} = \%knob_chars;
  71. }
  72. if ( $line =~ m/^-\ Output:(.*)/ ) {
  73. my $output = $1;
  74. $output = trim($output);
  75. my %output_chars;
  76. $last_output = $output;
  77. $last_proc_type = "output";
  78. $module{'Outputs'}->{$output} = \%output_chars;
  79. }
  80. }
  81. if ( $line =~ m/^--\ / ) {
  82. if ( $line =~ m/^--\ Position:(.*)/ ) {
  83. my $pos_args = $1;
  84. $pos_args = trim($pos_args);
  85. if ( $last_proc_type eq "input" ) {
  86. $module{'Inputs'}->{$last_input}->{'pos'} = $pos_args;
  87. } elsif ( $last_proc_type eq "output" ) {
  88. $module{'Outputs'}->{$last_output}->{'pos'} = $pos_args;
  89. }
  90. }
  91. }
  92. }
  93. return \%module;
  94. }
  95. my %AST;
  96. my %PARSE_TABLE = (
  97. 'comment' => '^#.*$',
  98. 'title' => '^Title: (.*)$',
  99. 'mod_path' => '^ModuleDir\ "(.*)"$',
  100. 'import' => '^import (Module)::([a-zA-Z0-9]{1,})::([a-zA-Z0-9]{1,})(.*$)',
  101. 'set' => '^set\ (.*)$',
  102. 'connect' => '^connect(.*)$',
  103. );
  104. my %PARSE_RULES = (
  105. 'comment' => sub {
  106. # Do nothing, throw this line out
  107. },
  108. 'title' => sub {
  109. my $title = shift;
  110. $AST{'Title'} = $title;
  111. },
  112. 'mod_path' => sub {
  113. my $file_path = shift;
  114. if ( ! -d $file_path ) {
  115. die "Path: $file_path doesn't look like a directory, exiting";
  116. }
  117. push(@module_library_paths, $file_path);
  118. },
  119. 'import' => sub {
  120. my $module_import = shift;
  121. my $import_manu = shift;
  122. my $import_mod = shift;
  123. my $import_as = shift;
  124. my @module_files = sub {
  125. my @files;
  126. foreach my $path ( @module_library_paths ) {
  127. my @f = split("\n", `find $path`);
  128. foreach my $file ( @f ) {
  129. if ( $file =~ m/.module$/ ) {
  130. my $f_bn = `basename $file`;
  131. chomp $f_bn;
  132. if ( ! grep(/$f_bn/, @files) ) {
  133. push(@files, $file);
  134. }
  135. }
  136. }
  137. }
  138. return @files;
  139. }->();
  140. foreach my $mod_file ( @module_files ) {
  141. my $mod_file_content = read_to_var($mod_file);
  142. my $mod_ref = parse_module_file($mod_file_content);
  143. if ( $import_mod eq $$mod_ref{'Module'} ) {
  144. if ( defined $AST{'Modules'} ) {
  145. my $r = grep { $import_mod eq $_->{'Module'} } @{$AST{'Modules'}};
  146. if ( $r == 0 ) {
  147. push(@{$AST{'Modules'}}, $mod_ref);
  148. }
  149. } else {
  150. push(@{$AST{'Modules'}}, $mod_ref);
  151. }
  152. } else {
  153. next;
  154. }
  155. }
  156. },
  157. 'set' => sub {
  158. my $set_line = shift;
  159. my $mod_to_set;
  160. my $attr_to_set;
  161. my $attr_param;
  162. my $value;
  163. my $setter = sub {
  164. my $mod_to_set = shift;
  165. my $attr_to_set = shift;
  166. my $attr_param = shift;
  167. if ( $attr_param eq "position" ) {
  168. $attr_param = "pos";
  169. }
  170. my $value = shift;
  171. my %set_params = (
  172. 'Param' => $attr_param,
  173. 'Value' => $value,
  174. );
  175. # Check values against mod definition
  176. # Pull mod ref out of AST for straight forward checking
  177. my $mod_ref;
  178. # Check we have module in AST
  179. my $r = grep { $mod_to_set eq $_->{'Module'} } @{$AST{'Modules'}};
  180. if ( $r eq 0 ) {
  181. die "Can't set value on module that is not imported: $mod_to_set\n";
  182. } else {
  183. foreach my $module_ref ( @{$AST{'Modules'}} ) {
  184. if ( $mod_to_set eq $$module_ref{'Module'} ) {
  185. $mod_ref = $module_ref;
  186. last;
  187. }
  188. }
  189. }
  190. # Check that module has param we want to set
  191. if ( ! $attr_to_set eq $$mod_ref{'Inputs'}->{$attr_to_set} ) {
  192. die "Can't set a param that doesn't existing in the module spec: $attr_to_set";
  193. }
  194. # If the set has an attr param, check that it's in the allowed range on the attr
  195. if ( $attr_param ne "null" ) {
  196. my $attr_range = $$mod_ref{'Inputs'}->{$attr_to_set}->{'pos'};
  197. if ( $attr_range =~ m/([0-9]{1,2})\-([0-9]{1,2})/ ) {
  198. my $r_begin = $1;
  199. my $r_end = $2;
  200. if ( $value > $r_end || $value < $r_begin ) {
  201. die "Parse error: attr_param value: $value for $attr_to_set : $attr_param is outside of range: $r_begin $r_end";
  202. }
  203. } else {
  204. die "Somehow encountered parse error in setter for module file $$mod_ref{'Module'}\n";
  205. }
  206. }
  207. $AST{'Sets'}->{$mod_to_set}->{$attr_to_set} = \%set_params;
  208. };
  209. if ( $set_line =~ m/(^[A-Z]{1}[a-z]{1,})\.{1}([A-Za-z0-9]{1,})\ \=\ (.*)$/ ) {
  210. $mod_to_set = $1;
  211. $attr_to_set = $2;
  212. $value = $3;
  213. } elsif ( $set_line =~ m/(^[A-Z]{1}[a-z]{1,})\.{1}([A-Za-z0-9]{1,})\.([A-Za-z0-9]{1,})\ \=\ (.*)$/ ) {
  214. $mod_to_set = $1;
  215. $attr_to_set = $2;
  216. $attr_param = $3;
  217. $value = $4;
  218. } else {
  219. die "Parse error at $set_line";
  220. }
  221. if ( ! defined $attr_param || $attr_param eq "" ) {
  222. $attr_param = "null",
  223. };
  224. $setter->($mod_to_set,$attr_to_set,$attr_param,$value);
  225. },
  226. 'connect' => sub {
  227. my $connect_line = shift;
  228. $connect_line = trim($connect_line);
  229. if ( $connect_line =~ m/([A-Z]{1}[A-Za-z0-9]{1,})\.([A-Za-z0-9]{1,})\ ([A-Z]{1}[A-Za-z0-9]{1,})\.([A-Za-z0-9]{1,})/ ) {
  230. my $output_mod = $1;
  231. my $output_mod_port = $2;
  232. my $input_mod = $3;
  233. my $input_mod_dst = $4;
  234. # Check we have modules defined
  235. if ( ! defined $AST{'Modules'} ) {
  236. die "Parse error: Encountered connection but no modules are imported: $connect_line"
  237. } else {
  238. # Check that connect references imported modules and get module refs
  239. my $output_mod_ref;
  240. my $input_mod_ref;
  241. foreach my $mod_ref ( @{$AST{'Modules'}} ) {
  242. if ( $$mod_ref{'Module'} eq $output_mod ) {
  243. $output_mod_ref = $mod_ref;
  244. } elsif ( $$mod_ref{'Module'} eq $input_mod ) {
  245. $input_mod_ref = $mod_ref;
  246. }
  247. }
  248. # If we reach the end of the loop and input/output refs are not set
  249. # we didn't find the module, and it's a parse error
  250. if ( ! defined $output_mod_ref ||
  251. $output_mod_ref eq "" ||
  252. ! defined $input_mod_ref ||
  253. $input_mod_ref eq "" ) {
  254. die "Parse error, couldn't find $output_mod or $input_mod in AST"
  255. }
  256. # Check src/dst ports
  257. if ( ! defined $$output_mod_ref{'Outputs'}->{$output_mod_port} ) {
  258. die "Parse error: $output_mod_port is not defined in module $output_mod"
  259. } elsif ( ! defined $$input_mod_ref{'Inputs'}->{$input_mod_dst} ) {
  260. die "Parse error: $input_mod_dst is not defined in module $input_mod"
  261. }
  262. # Everything looks good, make connection
  263. my $get_conn_id = sub {
  264. if ( ! defined $AST{'Connections'} ) {
  265. return 0;
  266. } else {
  267. my $c = 0;
  268. foreach my $conn_id ( @{$AST{'Connections'}} ) {
  269. $c++;
  270. }
  271. return $c;
  272. }
  273. };
  274. my $conn_id = $get_conn_id->();
  275. my %conn_map = (
  276. 'Output_Module' => $output_mod,
  277. 'Output_Port' => $output_mod_port,
  278. 'Input_Module' => $input_mod,
  279. 'Input_Mod_Dst' => $input_mod_dst,
  280. );
  281. $AST{'Connections'}->{$conn_id} = \%conn_map;
  282. }
  283. } else {
  284. die "Parse error at $connect_line";
  285. }
  286. },
  287. );
  288. # Basic line parser
  289. sub line_parse($) {
  290. my $line = shift;
  291. my $line_type = "null";
  292. my @line_caps;
  293. foreach my $key ( keys %PARSE_TABLE ) {
  294. if ( $line =~ m/$PARSE_TABLE{$key}/ ) {
  295. $line_type = $key;
  296. }
  297. }
  298. if ( $line_type ne "null" && $line =~ m/$PARSE_TABLE{$line_type}/) {
  299. if ( defined $1 && ! defined $2 ) {
  300. $PARSE_RULES{$line_type}->($1);
  301. } elsif ( defined $1 && defined $2 && defined $3 && defined $4 ) {
  302. # This is for `import`
  303. $PARSE_RULES{$line_type}->($1,$2,$3,$4);
  304. } else {
  305. $PARSE_RULES{$line_type}->();
  306. }
  307. } else {
  308. print("$line\n");
  309. }
  310. }
  311. # MAIN - split the input and parse it
  312. foreach my $line ( split("\n", $src_content) ) {
  313. chomp $line;
  314. if ( $line eq "" ) {
  315. next;
  316. }
  317. line_parse($line);
  318. }
  319. # Dump the AST for now. This is where we'd render the output
  320. print Dumper %AST;
  321. my $graph = GraphViz2->new(
  322. edge => {color => 'grey'},
  323. global => {directed => 1},
  324. graph => {label => $AST{'Title'}, rankdir => 'TB'},
  325. node => {shape => 'square'},
  326. );
  327. # Given a mod_ref, construct a graph object
  328. # that represents a module
  329. sub module_node_constructor($$) {
  330. my $graph = shift;
  331. my $mod_ref = shift;
  332. $graph->push_subgraph(
  333. name => "cluster_$$mod_ref{'Module'}",
  334. graph => {label => "$$mod_ref{'Module'}"},
  335. node => {color => 'black', shape => 'circle'},
  336. );
  337. foreach my $node_name ( keys %{$mod_ref} ) {
  338. if ( $node_name =~ m/Module|Manufacturer|Rev/ ) {
  339. $graph->add_node(name => "$node_name : $$mod_ref{$node_name}", shape => 'oval');
  340. }
  341. }
  342. if ( defined $AST{'Sets'}->{$$mod_ref{'Module'}} ) {
  343. my $sets_ref = $AST{'Sets'}->{$$mod_ref{'Module'}};
  344. foreach my $set ( keys %{$sets_ref} ) {
  345. my $name = "cluster" . "_" . "$set";
  346. my $label = "$set" . "_" . "settings";
  347. $graph->push_subgraph(
  348. name => $name,
  349. graph => {label => $label},
  350. node => {color => "red", shape => 'square'},
  351. );
  352. $graph->add_node(name => "$set : $$sets_ref{$set}->{'Param'} : $$sets_ref{$set}->{'Value'}");
  353. $graph->pop_subgraph;
  354. }
  355. }
  356. # Handle connections
  357. if ( defined $AST{'Connections'} ) {
  358. my $conn_ref = $AST{'Connections'};
  359. foreach my $conn ( keys %{$conn_ref} ) {
  360. if ($$mod_ref{'Module'} eq $$conn_ref{$conn}->{'Output_Module'} ) {
  361. my $name = "cluster" . "_" . "$$conn_ref{$conn}->{'Output_Port'}";
  362. my $label = "$$conn_ref{$conn}->{'Output_Port'}";
  363. $graph->push_subgraph(
  364. name => $name,
  365. graph => {label => $label},
  366. node => {color => "blue"},
  367. );
  368. $graph->add_node(name => "$$conn_ref{$conn}->{'Output_Port'}");
  369. $graph->pop_subgraph;
  370. } elsif ($$mod_ref{'Module'} eq $$conn_ref{$conn}->{'Input_Module'} ) {
  371. my $name = "cluster" . "_" . "$$conn_ref{$conn}->{'Input_Mod_Dst'}";
  372. my $label = "$$conn_ref{$conn}->{'Input_Mod_Dst'}";
  373. $graph->push_subgraph(
  374. name => $name,
  375. graph => {label => $label},
  376. node => {color => 'purple'},
  377. );
  378. $graph->add_node(name => "$$conn_ref{$conn}->{'Input_Mod_Dst'}");
  379. $graph->pop_subgraph;
  380. }
  381. }
  382. }
  383. $graph->pop_subgraph;
  384. }
  385. sub connection_node_constructor($) {
  386. my $graph = shift;
  387. }
  388. foreach my $mod_ref ( @{$AST{'Modules'}} ) {
  389. module_node_constructor($graph,$mod_ref);
  390. }
  391. # Draw connections
  392. foreach my $conn_ref ( keys %{$AST{'Connections'}} ) {
  393. my $from = $AST{'Connections'}->{$conn_ref}->{'Output_Port'};
  394. my $to = $AST{'Connections'}->{$conn_ref}->{'Input_Mod_Dst'};
  395. $graph->add_edge(from => $from, to => $to);
  396. }
  397. my $format = 'svg';
  398. $graph->run(format => $format, output_file => "test.svg");