Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Parsing and converting Fortran expression [solved]

by kikuchiyo (Hermit)
on Aug 25, 2015 at 20:09 UTC ( [id://1139915]=perlquestion: print w/replies, xml ) Need Help??

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

I need to parse Fortran expressions and emit their C equivalents, e.g from this:

.not.foo(1,bar(2)+1,3).and.baz(4,5)

produce

! foo[2][bar[1]][0] && baz[4][3]
  • foo, bar, baz and the like are variable names which have to be preserved.
  • .not., .and., .or. are boolean operators which have to be translated into their C equivalents.
  • Fortran array indices start from 1 while in C they start from 0, so all indices have to be decreased by one. Additionally, the order of indices for multidimensional arrays have to be reversed, and the comma-separated list must be converted to a sequence of [] subscripts.

Is there something readily usable for this?

Replies are listed 'Best First'.
Re: Parsing and converting Fortran expression
by afoken (Chancellor) on Aug 25, 2015 at 20:23 UTC
    I need to parse Fortran expressions and emit their C equivalents

    f2c?

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      "Use the source, Luke!"

      If f2c can translate a bunch of Fortran expressions (a "Fortran program") to a bunch equivalent C expressions (a "C program"), f2c must contain code to convert a single Fortran expression into an equivalent C expression.

      Either find and extract that, or construct a workaround: Wrap the Fortran expression in a dummy Fortran program that surrounds the expression in the C output with easy to find markers. Page 8 of the very verbose documentation suggests that Fortran labels may survive as C comments:

      Unused labels are retained in comments for orienteering purposes.

      So: Place an unused label "AAAAA" before the expression, a second unused label "ZZZZZ" after the expression, then search the generated C code for "/* AAAAA: */" and "/* ZZZZZ: */". Whatever you find in between should be the translated expression.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        I've experimented it with a bit, but apparently it's not that easy: f2c seems to make all sorts of assumptions about variables, functions and expected output, many of which turn out to be wrong (e.g. by default, it interprets the statement "a = foo(1,2)" as if foo were a function, so it turns that into "a = foo_(&c__1, &c__2)" which is useless for me). To coax it into generating usable output I'd have to declare variables with their correct dimensions and sizes and so on, so in the end I'd have to replace the original non-trivial problem with several more, even more non-trivial problems.

        Extracting the expression parsing part from the source might be an avenue worth exploring, but then there is this comment in the linked pdf:

        "The program f2c is a horror, based on ancient code and hacked unmercifully. Users are only supposed to look at its C output, not at its appalling inner workings."

      Heh, that's thinking outside the particular box I happened to be in.

      However, it's not really suitable after all, because it wants to translate complete programs, not just expressions.

        Then wrap your Fortran expressions into a dummy Fortran program and you should be able to find your ways. It might even be possible to make the process more or less automatic (using Perl, perhaps). But it has been quite a long time since I last used Fortran and f2c, I think it was back in 1996, so I don't really remember the details.

        Update: afoken was faster with that idea.

Re: Parsing and converting Fortran expression
by Anonymous Monk on Aug 26, 2015 at 02:16 UTC

    Fun problem, I enjoyed it, thank you.

    This passes your test case ( should teach you to give good test cases :)

    #!/usr/bin/perl # http://perlmonks.org/?node_id=1139915 use strict; # recursive descent parser using /\G/gc and C generator use warnings; my $had = $_ = @ARGV ? "@ARGV" : '.not.foo(1,bar(2)+1,3).and.baz(4,5)' +; my $want = '! foo[2][bar[1]][0] && baz[4][3]'; my $answer = expr(); /\G\s* \z /gcx or error(); # verify complete parse #use YAML; print Dump $answer; print "had $had\nwant $want\ngot ", $answer->C, "\n"; sub AND::C { '((' . $_[0][0]->C . ') && (' . $_[0][1]->C . '))' } sub PLUS::C { '((' . $_[0][0]->C . ') + (' . $_[0][1]->C . '))' } sub NOT::C { '( !(' . $_[0][0]->C . '))' } sub NUMBER::C { $_[0][0] } sub ARRAY::C { $_[0][0] . $_[0][1]->C } sub SUBS::C { return join '', map { '[(' . $_->C . ')-1]' } reverse @{ $_[0] }; } sub expr # left associative => term ([+-] term)* { my $left = term(); $left = bless [ $left, term() ], 'AND' while /\G\s* \.and\. /gcx; return $left; } sub term # left associative => item ([+] item)* { my $left = item(); $left = bless [ $left, item() ], 'PLUS' while /\G\s* \+ /gcx; return $left; } sub list { my $left = bless [ expr() ], 'SUBS'; push @$left, expr() while /\G\s* , /gcx; return $left; } sub item # number, unary minus or (expr) { /\G\s* (\d+) /gcx and return bless [$1], 'NUMBER'; /\G\s* \.not\. /gcx and return bless [ item() ], 'NOT'; /\G\s* (\w+) \s* \( /gcx and do{ # array my $arr = $1; my $value = list(); /\G\s* \) /gcx or error(); return bless [ $arr, $value ], 'ARRAY'; }; /\G\s* \( /gcx and # parens do{ my $value = expr(); /\G\s* \) /gcx or error(); return $value } +; error(); } sub error { die s/\G/ SYNTAX ERROR->/r, "\n" }

    produces

    had .not.foo(1,bar(2)+1,3).and.baz(4,5) want ! foo[2][bar[1]][0] && baz[4][3] got ((( !(foo[(3)-1][(((bar[(2)-1]) + (1)))-1][(1)-1]))) && (baz[(5)- +1][(4)-1]))

    There are some (hehehe) extra parens and a few expressions that are not reduced, but any good C compiler will handle that for you. :)

      Alternate version without intermediate parse tree, just building the C code directly.

      #!/usr/bin/perl # http://perlmonks.org/?node_id=1139915 use strict; # recursive descent parser using /\G/gc and C generator use warnings; my $had = $_ = @ARGV ? "@ARGV" : '.not.foo(1,bar(2)+1,3).and.baz(4,5)' +; my $want = '! foo[2][bar[1]][0] && baz[4][3]'; my $answer = expr(); /\G\s* \z /gcx or error(); # verify complete parse print "had $had\nwant $want\ngot $answer\n"; sub expr # left associative => term (.and. term)* { my $left = term(); $left = "(($left) && (" . term() . '))' while /\G\s* \.and\. /gcx; return $left; } sub term # left associative => item ([+] item)* { my $left = item(); $left = "(($left)+(" . item() . '))' while /\G\s* \+ /gcx; return $left; } sub list { my $left = '[(' . expr() . ')-1]'; $left = '[(' . expr() . ")-1]$left" while /\G\s* , /gcx; return $left; } sub item # number, .not. minus, array, or (expr) { /\G\s* (\d+) /gcx and return $1; /\G\s* \.not\. /gcx and return '!(' . item() . ')'; /\G\s* (\w+) \s* \( /gcx and do{ # array my $arr = $1; my $value = list(); /\G\s* \) /gcx or error(); return $arr . $value; }; /\G\s* \( /gcx and # parens do{ my $value = expr(); /\G\s* \) /gcx or error(); return $value } +; error(); } sub error { die s/\G/ SYNTAX ERROR->/r, "\n" }

        Thanks, this is almost perfect!

        With a few modifications I could use it right away. (Those modifications were required mostly because my original specs were not complete enough. I've added this to handle all logical operators I needed:

        sub op { { '.and.' => '&&', '.or.' => '||', '.eq.' => '==', '.ne.' => '!=', '.eqv.' => '==', '.neqv.' => '!=', '.lt.' => '<', '.gt.' => '>', '.le.' => '<=', '.ge.' => '>=', }->{$_[0]} // $_[0]; } # left associative => term (.and. term)* sub expr { my $left = term(); $left = "(($left) " . op($1) . " (" . term() . '))' while /\G\s* (\. +\w+\.) /gcx; return $left; }

        )

        (I'm the OP, I just can't log in from here, but I'll upvote when I get the chance)

        Thanks again!

Re: Parsing and converting Fortran expression
by sundialsvc4 (Abbot) on Aug 26, 2015 at 04:34 UTC

    There are many full-fledged parsing tools in CPAN ... a favorite is Parse::RecDescent but there are many more like Parse::Yapp.

    So, another approach to the problem is to use an existing parser to create a parse-tree (AST) from the source, then to generate the “C” equivalent output.   These approaches push the complexity into a grammar, resulting in a simpler program for you to write.   I have achieved rather-miracles with these tools in the past, if I may say so myself.   ;-)

    Which is “best?”   That’s up to you.   Anonymous Monk has very generously presented you with two “hand-crafted” recursive-descent routines that (especially since they have just been handed to you by the PerlMonks Code-Writing Service™) might be just the ticket.   They do look very, very good ... but they are hand-made, vis-a-vis what Parse::RecDescent would generate, which of course “might be an issue, or might not.”   It very much depends on just how complex the Fortran expressions might be, whether they might contain syntax errors, and just how much this project might “scope-creep” in the future.   The very nice thing is that Perl gives you so many powerful alternatives to choose from.

      I'm a big fan of Parse::Yapp myself, but I thought I'd try to convert a simple arithmetic parser I had to build a parse tree and then see what it took to generate the C code. It was just a fun little project.

      After submitting it, I realized the parse tree, though cool, was not needed for such a simple problem, so the second one just generates C directly.

      A possible choice is to use Parse::Yapp to generate the parse tree, and then my XXX::C subs to produce the code.

      To kikuchiyo, I'd also suggest reading the "Which is "best"? paragraph above several times, it's a very good analysis of the choices and problems they present.

      Btw, *all* parsers are sneaky and complicated and have many tricks and traps for the unwary. :(

        I've tried to use Parse::RecDescent in the past, but IIRC the initial cost of trying to understand that module was quite high.

        In the meantime I've recalled that I've solved a similar problem with Parse::Lex earlier at $work, but by that time the solution earlier in the thread appeared and it was too good not to use.

        I agree that parsing is tricky, no wonder that there is a library's worth of theoretical work related to it, and several generation's worth of tools invented and re-invented by people who don't know said theoretical work (me included).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1139915]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (5)
As of 2024-03-29 07:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found