The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# ------------------------------------------------------------------
# Petal::Hash::Var - Evaluates an expression and returns the result.
# ------------------------------------------------------------------
# Author: Jean-Michel Hiver
# This module is redistributed under the same license as Perl
# itself.
# ------------------------------------------------------------------
package Petal::Hash::Var;

use strict;
use warnings;

use Carp;
use Scalar::Util qw( blessed reftype );


our $STRING_RE_DOUBLE  = qr/(?<!\\)\".*?(?<!\\)\"/;
our $STRING_RE_SINGLE  = qr/(?<!\\)\'.*?(?<!\\)\'/;
our $STRING_RE         = qr/(?:$STRING_RE_SINGLE|$STRING_RE_DOUBLE)/;
our $VARIABLE_RE       = qr/(?:--)?[A-Za-z\_][^ \t]*/;
our $PARAM_PREFIX_RE   = qr/^--/;
our $ESCAPED_CHAR_RE   = qr/(?sm:\\(.))/;
our $BEGIN_QUOTE_RE    = qr/^\"|\'/;
our $END_QUOTE_RE      = qr/\"|\'$/;
our $TOKEN_RE          = qr/(?:$STRING_RE|$VARIABLE_RE)/;
our $PATH_SEPARATOR_RE = qr/(?:\/|\.)/;
our $INTEGER_KEY_RE    = qr/^\d+$/;


sub process
{
    my $class    = shift;
    my $hash     = shift;
    my $argument = shift;
    
    my @tokens = $argument =~ /($TOKEN_RE)/gsm;
    my $path   = shift (@tokens) or confess "bad syntax for $class: $argument (\$path)";
    my @path   = split( /$PATH_SEPARATOR_RE/, $path );
    my @args   = @tokens;

    # replace variable names by their value
    for (my $i=0; $i < @args; $i++)
    {
	my $arg = $args[$i];
	if ($arg =~ /^$VARIABLE_RE$/)
	{
	    $arg =~ s/$ESCAPED_CHAR_RE/$1/gsm;
	    if ($arg =~ $PARAM_PREFIX_RE)
	    {
		$arg =~ s/$PARAM_PREFIX_RE//;
		$args[$i] = $arg;
	    }
	    else
	    {
		$args[$i] = $hash->fetch ($arg);
	    }
	}
	else
	{
	    $arg =~ s/$BEGIN_QUOTE_RE//;
	    $arg =~ s/$END_QUOTE_RE//;
	    $arg =~ s/$ESCAPED_CHAR_RE/$1/gsm;
	    $args[$i] = $arg;
	}
    }
    
    my $current = $hash;
    my $current_path = '';
    while (@path)
    {
	my $next = shift (@path);
	$next = ($next =~ /:/) ? $hash->fetch ($next) : $next;
	
	my $has_path_tokens = scalar @path;
	my $has_args        = scalar @args;
	
	if (blessed $current)
	{
	  ACCESS_OBJECT:
	    goto ACCESS_HASH if ($current->isa('Petal::Hash'));

	    if ($current->can ($next) or $current->can ('AUTOLOAD'))
	    {
		if ($has_path_tokens) { $current = $current->$next ()      }
		else                  { $current = $current->$next (@args) }
	    }
	    else
	    {
		goto ACCESS_HASH  if ((reftype($current) or '') eq 'HASH');
		goto ACCESS_ARRAY if ((reftype($current) or '') eq 'ARRAY');
		confess "Cannot invoke '$next' on '" . ref($current) .
		  "' object at '$current_path' - no such method (near $argument)";
	    }
	}
	elsif (ref($current) eq 'HASH')
	{
	  ACCESS_HASH:
          unless (ref($current->{$next}) eq 'CODE')
          {
	    confess "Cannot access hash at '$current_path' with parameters (near $argument)"
	        if ($has_args and not $has_path_tokens);
          }
	    $current = $current->{$next};
	}
	elsif (ref($current) eq 'ARRAY')
	{
	  ACCESS_ARRAY:
	    # it might be an array, then the key has to be numerical...
	    confess "Cannot access array at '$current_path' with non-integer index '$next' (near $argument)"
	        unless ($next =~ /$INTEGER_KEY_RE/);

	    confess "Cannot access array at '$current_path' with parameters (near $argument)"
	        if ($has_args and not $has_path_tokens);

	    $current = $current->[$next];
	}
	else
	{
	    # ... or we cannot find the next value
	    if ($Petal::ERROR_ON_UNDEF_VAR)
	    {
		# let's croak and return
		my $warnstr = "Cannot find value for '$next' at '$current_path': $next cannot be retrieved\n";
		$warnstr   .= "(current value was ";
		$warnstr   .= (defined $current) ? "'$current'" : 'undef';
		$warnstr   .= ", near $argument)";
		confess $warnstr;
	    }
	    return '';
	}

	$current = (ref($current) eq 'CODE') ? $current->(@args) : $current;
	$current_path .= "/$next";
    }
    
    # return '' unless (defined $current);
    # $current = "$current" if (defined $current);
    return $$current if ref($current) eq 'SCALAR';
    return $current;
}


1;