gantlord has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to write something in perl that I need badly in order to save time, as doing it manually is just going to take too long. I need something that can expand out a command that is prefixed by a '@' symbol that describes a multiply-accumulate unit. it would appear as a line in a program that is otherwise to all intents and purposes ANSI C (it's actually a subset). Here's the line that the perl program would need to find and replace:

@MAC(Result,A,B,2); would become:

Result =
(A0*B0)+(A1*B1);

@MAC(output,X,Y,8); would become:

output =
(((X0*Y0)+(X1*Y1))+((X2*Y2)+(X3*Y3)))+
(((X4*Y4)+(X5*Y5))+((X6*Y6)+(X7*Y7)));

The nice neat layout is not that important, what is important is getting the brackets in the right places. The 4th argument would always be a power of two. Essentially I'm trying to let a C-to-VHDL compiler get the hint about the structure of hardware I want.
I would be grateful if anyone had any bright ideas.

Replies are listed 'Best First'.
Re: Complicated Search and Replace
by ikegami (Patriarch) on Jun 27, 2005 at 18:24 UTC

    What follows is a recursive solution. The expressions are paired, then the pairs are paired, then the paired pairs are paired, etc. until only one expression is left.

    use strict; use warnings; my $command = '@MAC(output,X,Y,8);'; my ($fields) = $command =~ /\@MAC\((.*?)\)/; my ($output, $var1, $var2, $size) = split(/\s*,\s*/, $fields); my @data = map { "$var1$_*$var2$_"} (0 .. $size-1); while (@data > 1) { my @compressed; push @compressed, sprintf("(%s)+(%s)", shift(@data), shift(@data)) while @data; @data = @compressed; } print("$output = $data[0];\n");
      Wow. That was quick. I was trawling through the man pages trying to come up with something to post so I didn't look like such a freeloader... Your solution works. Try putting in 8192 and you'll appreciate how much time is to be saved!

      Thanks
        Try putting in 8192 and you'll appreciate how much time is to be saved!
        >time < nul & echo. & perl !.pl > output & time < nul & echo. The current time is: 14:46:18.10 Enter the new time: The current time is: 14:46:18.76 Enter the new time:

        I'd say! :)

        output = (((((((((((((X0*Y0)+(X1*Y1))+((X2*Y2)+(X3*Y3)))+...
Re: Complicated Search and Replace
by runrig (Abbot) on Jun 27, 2005 at 18:38 UTC
    With no error checking whatsoever (update: I left out the outermost pair of parens...oh well, ikegami's answer is better anyway):
    use strict; use warnings; while(<DATA>) { s/\@MAC\((\w+),(\w+),(\w+),(\d+)\)/mac($1, $2, $3, $4)/ge; print; } sub mac { my ($res, $x, $y, $n) = @_; $n--; my @mac = map { "($x$_*$y$_)" } 0..$n; while (@mac > 2) { @mac = map { "($mac[$_]+$mac[$_+1])" } grep { !($_ % 2) } 0..$#mac; } "$res = " . join("+", @mac); } __DATA__ @MAC(Output,X,Y,2); @MAC(Result,X,Y,8);
Re: Complicated Search and Replace
by tlm (Prior) on Jun 27, 2005 at 18:50 UTC

    Here's another solution:

    use strict; use warnings; while ( <DATA> ) { my ( $args ) = /\@MAC\((.*)\)/; my ( $lhs, $v0, $v1, $e ) = split /,/, $args; print "$lhs=\n", term( $v0, $v1, [0..$e-1] ), "\n"; } sub term { my $indices = pop; my $n = $#$indices; if ( $n ) { my $m = int($n/2); my @t = map term( @_, $_ ), [ @{$indices}[0..$m] ], [ @{$indices}[$m+1..$n] ]; return "($t[0])+($t[1])"; } else { my ( $v0, $v1 ) = @_; my $first = $indices->[0]; return "$v0$first*$v1$first"; } } __DATA__ @MAC(Result,A,B,2) @MAC(output,X,Y,8) __END__ Result= (A0*B0)+(A1*B1) output= (((X0*Y0)+(X1*Y1))+((X2*Y2)+(X3*Y3)))+(((X4*Y4)+(X5*Y5))+((X6*Y6)+(X7* +Y7)))

    the lowliest monk

Re: Complicated Search and Replace
by TedPride (Priest) on Jun 27, 2005 at 19:38 UTC
    I'm sort of wondering what you need all the parentheses for, given that order of operations makes them unnecessary. But here you are:
    use strict; use warnings; $_ = join '', <DATA>; s/\@MAC\((\w+),(\w+),(\w+),(\d+)\)/"$1 =\n".MAC($2,$3,$4)/eg; print; sub MAC { my ($x, $y, $n) = @_; $_ = ':'; $_ = "($_)+($_)" while ($n /= 2) != .5; $n = 0; s/:/"$x$n*$y".$n++/eg; return $_; } __DATA__ @MAC(Result,X,Y,1); @MAC(Result,X,Y,2); @MAC(Result,X,Y,4); @MAC(Result,X,Y,8);
      Yeah, if I was just running this as ordinary C code the parentheses would be superfluous. What's happening is that the parentheses are used to let a C-to-VHDL compiler get the idea of what kind of adder structure I want. I have a series of floating point multipliers at the base level. The output of two multipliers is summed together in a 2-input floating-point addition unit. The output of these additions are summed again in pairs in a further layer of adders. This continues with the adders halving each time until you finish up with a single adder and your output. This allows all the multiplication and addition to take place in parallel. Sweet, eh?

      A0 B0 A1 B1 A2 B2 A3 B3 \ / \ / \ / \ / (*) (*) (*) (*) \ / \ / \ / \ / (+) (+) \ / \ / \ / \ / \ / \ / (+) | Output
Re: Complicated Search and Replace
by dave_the_m (Monsignor) on Jun 27, 2005 at 18:29 UTC
    print /^\s*\@MAC\((\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\d+)\s*\)\s*;\s +*$/ ? "$1 = (" . join('+',map "($2$_+$3$_)", 0..$4-1) . ");\n" : $_ while (<>);

    Dave.

      That doesn't even work for the examples provided by the OP. "What is important is getting the brackets in the right places", yet there are 4 pairs of brackets missing for n=8. Your solution only works for n=2 and n=4, and it's not even exact for n=2.
        whoops, wasn't reading OP closely enough.

        Dave.