123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- #!/usr/bin/perl
- use strict;
- use warnings;
- use Data::Dumper;
- use lib "/home/swatson/perl5/lib/perl5";
- use GraphViz2;
- my $SOURCE_FILE = $ARGV[0];
- sub read_to_var($) {
- my $file_path = shift;
- my $content;
- open(my $fh, '<', $file_path) or die "cannot open file $file_path";
- {
- local $/;
- $content = <$fh>;
- }
- close($fh);
- return $content;
- }
- my $src_content = read_to_var($SOURCE_FILE);
- sub trim($) {
- my $str = shift;
- $str =~ s/^\s+|\s+$//g;
- return $str;
- }
- my @module_library_paths = (
- ".",
- );
- my %modules;
- sub parse_module_file {
- my $src = shift;
- my %module;
- my $last_proc_type = "null";
- my $last_input = "null";
- my $last_output = "null";
- foreach my $line ( split("\n", $src) ) {
- chomp $line;
- if ( $line =~ m/^#/ ) {
- next;
- }
- if ( $line =~ m/^Manufacturer:(.*)/ ) {
- my $manu = $1;
- $manu = trim($manu);
- $module{'Manufacturer'} = $manu;
- }
- if ( $line =~ m/^Module:(.*)/ ) {
- my $mod = $1;
- $mod = trim($mod);
- $module{'Module'} = $mod;
- }
- if ( $line =~ m/^Revision:(.*)/ ) {
- my $rev = $1;
- $rev = trim($rev);
- $module{'Rev'} = $rev;
- }
- if ( $line =~ m/^-\ / ) {
- if ( $line =~ m/^-\ Input:(.*)/ ) {
- my $input = $1;
- $input = trim($input);
- my %input_chars;
- $last_input = $input;
- $last_proc_type = "input";
- $module{'Inputs'}->{$input} = \%input_chars;
- }
- if ( $line =~ m/^-\ Knob:(.*)/ ) {
- my $knob = $1;
- $knob = trim($knob);
- my %knob_chars;
- $last_input = $knob;
- $last_proc_type = "input";
- $module{'Inputs'}->{$knob} = \%knob_chars;
- }
- if ( $line =~ m/^-\ Output:(.*)/ ) {
- my $output = $1;
- $output = trim($output);
- my %output_chars;
- $last_output = $output;
- $last_proc_type = "output";
- $module{'Outputs'}->{$output} = \%output_chars;
- }
- if ( $line =~ m/^-\ Button:(.*)/ ) {
- my $button = $1;
- $button = trim($button);
- my %button_chars;
- $last_input = $button;
- $last_proc_type = "input";
- $module{'Inputs'}->{$button} = \%button_chars;
- }
-
- }
- if ( $line =~ m/^--\ / ) {
- if ( $line =~ m/^--\ Position:(.*)/ ) {
- my $pos_args = $1;
- $pos_args = trim($pos_args);
- if ( $last_proc_type eq "input" ) {
- $module{'Inputs'}->{$last_input}->{'pos'} = $pos_args;
- } elsif ( $last_proc_type eq "output" ) {
- $module{'Outputs'}->{$last_output}->{'pos'} = $pos_args;
- }
- }
- }
- }
- return \%module;
- }
- my %AST;
- my %PARSE_TABLE = (
- 'comment' => '^#.*$',
- 'title' => '^Title: (.*)$',
- 'mod_path' => '^ModuleDir\ "(.*)"$',
- 'import' => '^import (Module)::([a-zA-Z0-9]{1,})::([a-zA-Z0-9]{1,})(.*$)',
- 'set' => '^set\ (.*)$',
- 'connect' => '^connect(.*)$',
- );
- my %PARSE_RULES = (
- 'comment' => sub {
- # Do nothing, throw this line out
- },
- 'title' => sub {
- my $title = shift;
- $AST{'Title'} = $title;
- },
- 'mod_path' => sub {
- my $file_path = shift;
- if ( ! -d $file_path ) {
- die "Path: $file_path doesn't look like a directory, exiting";
- }
- push(@module_library_paths, $file_path);
- },
- 'import' => sub {
- my $module_import = shift;
- my $import_manu = shift;
- my $import_mod = shift;
- my $import_as = shift;
- my @module_files = sub {
- my @files;
- foreach my $path ( @module_library_paths ) {
- my @f = split("\n", `find $path`);
- foreach my $file ( @f ) {
- if ( $file =~ m/.module$/ ) {
- my $f_bn = `basename $file`;
- chomp $f_bn;
- if ( ! grep(/$f_bn/, @files) ) {
- push(@files, $file);
- }
- }
- }
- }
- return @files;
- }->();
- foreach my $mod_file ( @module_files ) {
- my $mod_file_content = read_to_var($mod_file);
- my $mod_ref = parse_module_file($mod_file_content);
- if ( $import_mod eq $$mod_ref{'Module'} ) {
- if ( defined $AST{'Modules'} ) {
- my $r = grep { $import_mod eq $_->{'Module'} } @{$AST{'Modules'}};
- if ( $r == 0 ) {
- push(@{$AST{'Modules'}}, $mod_ref);
- }
- } else {
- push(@{$AST{'Modules'}}, $mod_ref);
- }
- } else {
- next;
- }
- }
- },
- 'set' => sub {
- my $set_line = shift;
- my $mod_to_set;
- my $attr_to_set;
- my $attr_param;
- my $value;
- my $setter = sub {
- my $mod_to_set = shift;
- my $attr_to_set = shift;
- my $attr_param = shift;
- if ( $attr_param eq "position" ) {
- $attr_param = "pos";
- }
- my $value = shift;
- my %set_params = (
- 'Param' => $attr_param,
- 'Value' => $value,
- );
- # Check values against mod definition
- # Pull mod ref out of AST for straight forward checking
- my $mod_ref;
- # Check we have module in AST
- my $r = grep { $mod_to_set eq $_->{'Module'} } @{$AST{'Modules'}};
- if ( $r eq 0 ) {
- die "Can't set value on module that is not imported: $mod_to_set\n";
- } else {
- foreach my $module_ref ( @{$AST{'Modules'}} ) {
- if ( $mod_to_set eq $$module_ref{'Module'} ) {
- $mod_ref = $module_ref;
- last;
- }
- }
- }
- # Check that module has param we want to set
- if ( ! $attr_to_set eq $$mod_ref{'Inputs'}->{$attr_to_set} ) {
- die "Can't set a param that doesn't existing in the module spec: $attr_to_set";
- }
- # If the set has an attr param, check that it's in the allowed range on the attr
- if ( $attr_param ne "null" ) {
- my $attr_range = $$mod_ref{'Inputs'}->{$attr_to_set}->{'pos'};
- if ( $attr_range =~ m/([0-9]{1,2})\-([0-9]{1,2})/ ) {
- my $r_begin = $1;
- my $r_end = $2;
- if ( $value > $r_end || $value < $r_begin ) {
- die "Parse error: attr_param value: $value for $attr_to_set : $attr_param is outside of range: $r_begin $r_end";
- }
- } else {
- die "Somehow encountered parse error in setter for module file $$mod_ref{'Module'}\n";
- }
- }
- $AST{'Sets'}->{$mod_to_set}->{$attr_to_set} = \%set_params;
- };
- if ( $set_line =~ m/(^[A-Z]{1}[A-Za-z0-9]{1,})\.{1}([A-Za-z0-9]{1,})\ \=\ (.*)$/ ) {
- $mod_to_set = $1;
- $attr_to_set = $2;
- $value = $3;
- } elsif ( $set_line =~ m/(^[A-Z]{1}[A-Za-z]{1,})\.{1}([A-Za-z0-9]{1,})\.([A-Za-z0-9]{1,})\ \=\ (.*)$/ ) {
- $mod_to_set = $1;
- $attr_to_set = $2;
- $attr_param = $3;
- $value = $4;
- } else {
- die "Parse error at $set_line";
- }
- if ( ! defined $attr_param || $attr_param eq "" ) {
- $attr_param = "null",
- };
- $setter->($mod_to_set,$attr_to_set,$attr_param,$value);
- },
- 'connect' => sub {
- my $connect_line = shift;
- $connect_line = trim($connect_line);
- 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,})/ ) {
- my $output_mod = $1;
- my $output_mod_port = $2;
- my $input_mod = $3;
- my $input_mod_dst = $4;
- # Check we have modules defined
- if ( ! defined $AST{'Modules'} ) {
- die "Parse error: Encountered connection but no modules are imported: $connect_line"
- } else {
- # Check that connect references imported modules and get module refs
- my $output_mod_ref;
- my $input_mod_ref;
- foreach my $mod_ref ( @{$AST{'Modules'}} ) {
- if ( $$mod_ref{'Module'} eq $output_mod ) {
- $output_mod_ref = $mod_ref;
- } elsif ( $$mod_ref{'Module'} eq $input_mod ) {
- $input_mod_ref = $mod_ref;
- }
- }
- # If we reach the end of the loop and input/output refs are not set
- # we didn't find the module, and it's a parse error
- if ( ! defined $output_mod_ref ||
- $output_mod_ref eq "" ||
- ! defined $input_mod_ref ||
- $input_mod_ref eq "" ) {
- die "Parse error, couldn't find $output_mod or $input_mod in AST"
- }
- # Check src/dst ports
- if ( ! defined $$output_mod_ref{'Outputs'}->{$output_mod_port} ) {
- die "Parse error: $output_mod_port is not defined in module $output_mod"
- } elsif ( ! defined $$input_mod_ref{'Inputs'}->{$input_mod_dst} ) {
- die "Parse error: $input_mod_dst is not defined in module $input_mod"
- }
- # Everything looks good, make connection
- my $get_conn_id = sub {
- if ( ! defined $AST{'Connections'} ) {
- return 0;
- } else {
- my $c = 0;
- foreach my $conn_id ( keys %{$AST{'Connections'}} ) {
- $c++;
- }
- return $c;
- }
- };
- my $conn_id = $get_conn_id->();
- my %conn_map = (
- 'Output_Module' => $output_mod,
- 'Output_Port' => $output_mod_port,
- 'Input_Module' => $input_mod,
- 'Input_Mod_Dst' => $input_mod_dst,
- );
- $AST{'Connections'}->{$conn_id} = \%conn_map;
-
- }
- } else {
- die "Parse error at $connect_line";
- }
- },
-
- );
- # Basic line parser
- sub line_parse($) {
- my $line = shift;
- my $line_type = "null";
- my @line_caps;
- foreach my $key ( keys %PARSE_TABLE ) {
- if ( $line =~ m/$PARSE_TABLE{$key}/ ) {
- $line_type = $key;
- }
- }
- if ( $line_type ne "null" && $line =~ m/$PARSE_TABLE{$line_type}/) {
- if ( defined $1 && ! defined $2 ) {
- $PARSE_RULES{$line_type}->($1);
- } elsif ( defined $1 && defined $2 && defined $3 && defined $4 ) {
- # This is for `import`
- $PARSE_RULES{$line_type}->($1,$2,$3,$4);
- } else {
- $PARSE_RULES{$line_type}->();
- }
- } else {
- print("$line\n");
- }
- }
- # For testing module definitions
- if ( defined $ARGV[1] && $ARGV[1] eq "--import-test" ) {
- my $mod_ref = parse_module_file($src_content);
- print Dumper $mod_ref;
- exit 0;
- }
-
- # MAIN starts here - split the input and parse it
- foreach my $line ( split("\n", $src_content) ) {
- chomp $line;
- if ( $line eq "" ) {
- next;
- }
- line_parse($line);
- }
- # Dump the AST for now. This is where we'd render the output
- print Dumper %AST;
- my $graph = GraphViz2->new(
- edge => {color => 'grey'},
- global => {directed => 1},
- graph => {label => $AST{'Title'}, rankdir => 'TB'},
- node => {shape => 'square'},
- );
- # Given a mod_ref, construct a graph object
- # that represents a module
- sub module_node_constructor($$) {
- my $graph = shift;
- my $mod_ref = shift;
- $graph->push_subgraph(
- name => "cluster_$$mod_ref{'Module'}",
- graph => {label => "$$mod_ref{'Module'}"},
- node => {color => 'black', shape => 'circle'},
- );
- # $graph->push_subgraph(
- # name => "cluster_mod_info",
- # graph => {label => "mod_info"},
- # node => {color => 'grey', shape => 'square'},
- # );
- # foreach my $node_name ( keys %{$mod_ref} ) {
- # if ( $node_name =~ m/Module|Manufacturer|Rev/ ) {
- # $graph->add_node(name => "$node_name\n: $$mod_ref{$node_name}", shape => 'square');
- # }
- # }
- # $graph->pop_subgraph;
- if ( defined $AST{'Sets'}->{$$mod_ref{'Module'}} ) {
- my $sets_ref = $AST{'Sets'}->{$$mod_ref{'Module'}};
- foreach my $set ( keys %{$sets_ref} ) {
- my $name = "cluster" . "_" . "$set";
- my $label = "$set" . "_" . "settings";
- $graph->push_subgraph(
- name => $name,
- graph => {label => $label},
- node => {color => "green", shape => 'square'},
- );
- $graph->add_node(name => "$set : $$sets_ref{$set}->{'Param'} : $$sets_ref{$set}->{'Value'}");
- $graph->pop_subgraph;
- }
- }
- # Handle connections
- if ( defined $AST{'Connections'} ) {
- my $conn_ref = $AST{'Connections'};
- foreach my $conn ( keys %{$conn_ref} ) {
- if ($$mod_ref{'Module'} eq $$conn_ref{$conn}->{'Output_Module'} ) {
- my $name = "cluster" . "_" . "$$conn_ref{$conn}->{'Output_Port'}_$conn";
- my $label = "$$conn_ref{$conn}->{'Output_Port'}_$conn";
- $graph->push_subgraph(
- name => $name,
- graph => {label => $label},
- node => {color => "blue"},
- );
- $graph->add_node(name => "$$conn_ref{$conn}->{'Output_Port'}");
- $graph->pop_subgraph;
- } elsif ($$mod_ref{'Module'} eq $$conn_ref{$conn}->{'Input_Module'} ) {
- my $name = "cluster" . "_" . "$$conn_ref{$conn}->{'Input_Mod_Dst'}_$conn";
- my $label = "$$conn_ref{$conn}->{'Input_Mod_Dst'}_$conn";
- $graph->push_subgraph(
- name => $name,
- graph => {label => $label},
- node => {color => 'purple'},
- );
- $graph->add_node(name => "$$conn_ref{$conn}->{'Input_Mod_Dst'}");
- $graph->pop_subgraph;
- }
-
- }
- }
- $graph->pop_subgraph;
- }
- # Draw modules
- foreach my $mod_ref ( @{$AST{'Modules'}} ) {
- module_node_constructor($graph,$mod_ref);
- }
- # Draw connections
- foreach my $conn_ref ( keys %{$AST{'Connections'}} ) {
- my $from = $AST{'Connections'}->{$conn_ref}->{'Output_Port'};
- my $to = $AST{'Connections'}->{$conn_ref}->{'Input_Mod_Dst'};
- $graph->add_edge(color => 'red', from => $from, to => $to);
- }
- my $format = 'svg';
- $graph->run(format => $format, output_file => "test.svg");
|