For fun, a regexp solution. It would have been much simpler if $compound didn't require an accumulator and wasn't reentrant. (Either is ok. Both makes a mess.) That's the reason behind the whole symtab business.

use strict; use warnings; use List::Util qw( sum ); our %ATOM_WEIGHTS = ( C => 12, O => 16, Pb => 207 ); sub OnDestroyAction(&) { return OnDestroyAction->new($_[0]); } sub OnDestroyAction::new { my ($class, $action) = @_; return bless(\$action, $class); } sub OnDestroyAction::abort { my ($self) = @_; undef $$self; } sub OnDestroyAction::now { my ($self) = @_; my $action = $$self; undef $$self; $action->() if $action; } sub OnDestroyAction::DESTROY { my ($self) = @_; my $action = $$self; $action->() if $action; } { our $rv_compound; our $rv_group; our $rv_factor; our $rv_element; # Used to create local variables # in the classical sense of "local". our @symtab; # To bypass RE bug. no warnings 'regexp'; # Definitions must not be combined with assignment. my $compound; my $group; my $factor; my $element; $compound = qr/ (?{ local $symtab_manager = new_symtab_manager() }) (?{ $symtab[0]->{sum} = 0; }) (?: (??{ $group }) (?{ $symtab[0]->{sum} += $rv_group; }) )+ # Return value: (?{ local $rv_compound = $symtab[0]->{sum}; }) (?{ $symtab_manager->now(); }) /x; $group = qr/ (??{ $element }) (??{ $factor }) # Return value: (?{ local $rv_group = $rv_element * $rv_factor; }) /x; $factor = qr/ (?: (\d+) # Return value (first prod): (?{ local $rv_factor = $1 }) | # Nothing # Return value (second prod): (?{ local $rv_factor = 1 }) ) /x; $element = qr/ (?: ([A-Z][a-z]*(?![a-z])) # Return value (first prod): (?{ local $rv_element = $ATOM_WEIGHTS{$1} }) | \( (??{ $compound }) \) # Return value (second prod): (?{ local $rv_element = $rv_compound; }) ) /x; sub weight { local $rv_compound; local $rv_group; local $rv_factor; local $rv_element; local @symtab; local *new_symtab_manager = sub { unshift(@symtab, {}); return OnDestroyAction { shift(@symtab) }; }; my $rv; shift =~ / # To bypass RE bug. () (??{ $compound }) # Return value: (?{ $rv = $rv_compound; }) /x; return $rv; } } print("The weight of $_ is ", weight($_), ".\n") for 'Pb(CO3)2'; # Prints "The weight of Pb(CO3)2 is 327."

What follows is a simpler solution **that doesn't work**. It prints "The weight of Pb(CO3)2 is 384." (instead of 327) because $rv_group gets clobbered.

use strict; use warnings; use List::Util qw( sum ); our %ATOM_WEIGHTS = ( C => 12, O => 16, Pb => 207 ); { our $rv_compound; our $rv_compound_; our $rv_group; our $rv_factor; our $rv_element; # Definitions must not be combined with assignment. my $compound; my $compound_; my $group; my $factor; my $element; $compound = qr/ (??{ $group }) (??{ $compound_ }) # Return value: (?{ local $rv_compound = $rv_group + $rv_compound_; + }) /x; $compound_ = qr/ (?: (??{ $group }) (??{ $compound_ }) # Return value (first prod): (?{ local $rv_compound_ = $rv_group + $rv_compou +nd_; }) | # Nothing # Return value (second prod): (?{ local $rv_compound_ = 0; }) ) /x; $group = qr/ (??{ $element }) (??{ $factor }) # Return value: (?{ local $rv_group = $rv_element * $rv_factor; }) /x; $factor = qr/ (?: (\d+) # Return value (first prod): (?{ local $rv_factor = $1 }) | # Nothing # Return value (second prod): (?{ local $rv_factor = 1 }) ) /x; $element = qr/ (?: ([A-Z][a-z]*(?![a-z])) # Return value (first prod): (?{ local $rv_element = $ATOM_WEIGHTS{$1} }) | \( (??{ $compound }) \) # Return value (second prod): (?{ local $rv_element = $rv_compound; }) ) /x; sub weight { local $rv_compound; local $rv_compound_; local $rv_group; local $rv_factor; local $rv_element; my $rv; shift =~ / # To bypass RE bug. () (??{ $compound }) # Return value: (?{ $rv = $rv_compound; }) /x; return $rv; } } print("The weight of $_ is ", weight($_), ".\n") for 'Pb(CO3)2';

In reply to Re^2: Regular Expressions and atomic weights by ikegami
in thread Regular Expressions and atomic weights by hokie

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.