2009-03-17

polyglot: throwing fortran in the mix

reminder: this article is part of a serie.

our polyglot program now supports perl, c, pascal and bash. it wouldn't be complete without some maths. so, let's try to shoe-horn fortran in it, with the help of our test snippet, of course! (note: we're going to use fortran 77, with the help of gfortran 4.3.2)

fortran has some strict formatting rules (at least the -77 version): everything after the 72th column will be ignored. so, an easy way to achieve our result would be to insert 72 spaces in the beginning of each line, and fit our fortran program somewhere where it will be ignored by all the languages. it's not really difficult, as you can see in the resulting program. the most difficult part was to come up with the integer format io so that fortran doesn't prepend spaces.

but it's not really satisfying from an artistic point of view. so let's revert this, and try to be more creative! to that end, we're going to use the standard fortran comment, which is a C character in the first column. that's a better challenge... of course, we're still going to use the 72 columns trick in some cases (the cpp instructions for example, so let's just move them and forget about them), but non-fortran code will also be able to use the first columns too.

so now, let's focus on helping the languages ignore the leading C of each line. let's decoy it: in c, a simple #define will do the trick, while in perl, we're going to define an empty sub after this name, with an empty prototype.


[...]
) if 0; sub C () {} # */ );
#define C
[...]


we can now prepend "C ; " in front of each line. but we cannot do that between the main keyword and the opening curly brace, since only the function arguments can be declared in here... so let's just move the opening brace at 2 different places (one for perl, one for c).


[...]
"*/
main () { /*"; { # */
int $ i;
[...]


now we can prepend the fortran comment before all the mixed perl & c code, including the __END__ token (which, contrary to popular belief, does not need to be at the beginning of a line):


[...]
C ; "*/
C ; main () { /*"; { # */
C ; int $ i;
C ; int $ n1;
C ; int $ n2;
C ; int $ n3;
C ; $ i = 0;
C ; $ n1 = 1;
C ; $ n2 = 1;
C ; printf( "%d\n", $ n1 );
C ; while ( $ i < 9 ) {
C ; $ n3 = $ n1 + $ n2;
C ; $ n1 = $ n2;
C ; $ n2 = $ n3;
C ; printf( "%d\n", $ n1 );
C ; $ i++;
C ; }
C ; }

#define foo /*
C ; __END__
[...]


ok, the middle of the code has been fortran-ized. let's now focus on the top of our file. to please pascal, we don't have much choice than moving the opening comment in column 73. but next lines can be commented out in fortran quite easily - indeed, a star in column 0 also acts as a comment. so instead of dividing by 1337, we'll multiply (and bash will glob in current directory instead of looking at filesystem root). and since the line just after is perl only, we can multiply by 0 to achieve the same result:


(*foo /*bar
*1337#) 2>/dev/null;i=0; a=1; b=1;echo $a;while test $i -lt 9;do c=$((a+b));a=$b;b=$c;echo $a;i=$((i+1));done;exit
*0) if 0; sub C () {} # */ );
[...]


(note: we could have used the C-form comment, by adding some math operators in the mix, but let's vary our weapons! :-) )

so, we're stuck with the pascal part of our file. let's just open a comment in column 73, and close it at column 0 on the following line - since the *) will conveniently open a fortran comment. of course, some parts of the program will be spaces, so let's tighten our program to have fewer, longer lines.


[...]
*) program foo; (*
*) var i, n1, n2, n3 : integer; (*
*) begin i := 0; n1 := 1; n2 := 1; writeln(n1); while i < 9 do begin (*
*) n3 := n1 + n2; n1 := n2; n2 := n3; writeln(n1); i := i + 1; end; end.
[...]


and we just have the end of the file which needs to be made compatible with fortran. this is not difficult using above trick, while creating some space that will be ignored by pascal and c, with some clever comments. we get this final program:


(*foo /*bar
*1337#) 2>/dev/null;i=0; a=1; b=1;echo $a;while test $i -lt 9;do c=$((a+b));a=$b;b=$c;echo $a;i=$((i+1));done;exit
*0) if 0; sub C () {} # */ );

#include <stdio.h>
#include <stdlib.h>
#define C
#define $ /*
C ; "*/
C ; main () { /*"; { # */
C ; int $ i;
C ; int $ n1;
C ; int $ n2;
C ; int $ n3;
C ; $ i = 0;
C ; $ n1 = 1;
C ; $ n2 = 1;
C ; printf( "%d\n", $ n1 );
C ; while ( $ i < 9 ) {
C ; $ n3 = $ n1 + $ n2;
C ; $ n1 = $ n2;
C ; $ n2 = $ n3;
C ; printf( "%d\n", $ n1 );
C ; $ i++;
C ; }
C ; }

#define foo /*
C ; __END__
*) program foo; (*
*) var i, n1, n2, n3 : integer; (*
*) begin i := 0; n1 := 1; n2 := 1; writeln(n1); while i < 9 do begin (*
*) n3 := n1 + n2; n1 := n2; n2 := n3; writeln(n1); i := i + 1; end; end.(*

integer i, n1, n2, n3
n1 = 1
n2 = 1
print '(I0)', n1
do 10 i = 1, 9
n3 = n1 + n2
n1 = n2
n2 = n3
print '(I0)', n1
10 continue
end
*/
#define bar *)


program which passes all out tests:

$ prove -l t
t/bash.......ok
t/c..........fibonacci.c:1: warning: data definition has no type or storage class
t/c..........ok
t/fortran....ok
t/pascal.....ok
t/perl.......ok
All tests successful.
Files=5, Tests=5, 1 wallclock secs ( 0.03 usr 0.00 sys + 0.25 cusr 0.06 csys = 0.34 CPU)
Result: PASS


see? finally, it's not so hard to mess with programming languages, isn't it? i guess you're now capable to continue this exercise - but if you bear with me, i'll continue adding some more! :-)

No comments:

Post a Comment