--- loncom/homework/lonr.pm 2009/06/19 14:03:19 1.5 +++ loncom/homework/lonr.pm 2014/11/19 21:14:47 1.11 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Interface routines to R CAS # -# $Id: lonr.pm,v 1.5 2009/06/19 14:03:19 www Exp $ +# $Id: lonr.pm,v 1.11 2014/11/19 21:14:47 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -33,7 +33,8 @@ use IO::Socket; use Apache::lonnet; use Apache::response(); use LONCAPA; -### Commented out for now: use Tie::IxHash::Easy; # autoties all subhashes to keep index order +use Tie::IxHash::Easy; # autoties all subhashes to keep index order +use Data::Dumper; # used to output hash contents my $errormsg=''; @@ -52,18 +53,18 @@ sub Rcroak { # sub Rpeel { my $x = $_[0]; # the string containing the serialized R object(s) - if ($x =~ /^((?:i|d):(.+?);)(.*)$/) { + if ($x =~ /^N\;(.*)$/) { + return ('',$1); + } elsif ($x =~ /^((?:i|d):(.+?);)(.*)$/) { return ($1, $+); # x starts with a number - } - elsif ($x =~ /^s:(\d+):/) { + } elsif ($x =~ /^s:(\d+):/) { my $n = $1; # x starts with a string of length n if ($x =~ /^(s:\d+:\"(.{$n})\";)(.*)$/) { return ($1, $+); # x starts with a valid string } else { &Rcroak('invalid string detected'); } - } - elsif ($x =~ /^a:/) { + } elsif ($x =~ /^a:/) { # x starts with an array -- need to find the closing brace my $i = index $x, '{', 0; # position of first opening brace if ($i < 0) { @@ -101,82 +102,103 @@ sub Rpeel { # Rreturn accepts a string containing a serialized R object # and returns either the object's value (if it is scalar) or a reference # to a hash containing the contents of the object. Any null keys in the hash -# are replaced by 'capaNNN' where NNN is the index of the entry in the original +# are replaced by 'resultNNN' where NNN is the index of the entry in the original # R array. # sub Rreturn { - my $x = $_[0]; # the string containing the serialized R object(s) - $errormsg=''; - if ($x =~ /^(?:i|d):(.+?);$/) { - return $1; # return the value of the number - } elsif ($x =~ /^s:(\d+):\"(.*)\";$/) { - # string -- verify the length - if (length($2) eq $1) { - return $2; # return the string - } else { - return 'mismatch in string length'; - } - } elsif ($x =~ /^a:(\d+):\{(.*)\}$/) { - # array - my $dim = $1; # array size - $x = $2; # array contents - tie(my %h,'Tie::IxHash::Easy'); # start a hash - keys(%h) = $dim; # allocate space for the hash - my $key; - my $y; - for (my $i = 0; $i < $dim; $i++) { - ($y, $x) = &Rpeel($x); # strip off the entry for the key - if ($y eq '') { - &Rcroak('ran out of keys'); - } - $key = &Rreturn($y); - if ($key eq '') { - $key = "capa$i"; # correct null key - } - ($y, $x) = &Rpeel($x); # strip off the value - if ($y eq '') { - &Rcroak('ran out of values'); - } - if ($y =~ /^a:/) { - $h{$key} = \&Rreturn($y); # array value: store as reference - } else { - $h{$key} = &Rreturn($y); # scalar value: store the entry in the hash - } - } - if ($errormsg) { return $errormsg; } - return \%h; # return a reference to the hash + my $x = $_[0]; # the string containing the serialized R object(s) + $x=~s/^\"//; + $x=~s/\"$//; + $x=~s/\\\"/\"/g; + $errormsg=''; + if ($x =~ /^(?:i|d):(.+?);$/) { + return $1; # return the value of the number + } elsif ($x =~ /^s:(\d+):\"(.*)\";$/) { + # string -- verify the length + if (length($2) eq $1) { + return $2; # return the string + } else { + return 'mismatch in string length'; + } + } elsif ($x =~ /^a:(\d+):\{(.*)\}$/) { + # array + my $dim = $1; # array size + $x = $2; # array contents + tie(my %h,'Tie::IxHash::Easy'); # start a hash + keys(%h) = $dim; # allocate space for the hash + my $key; + my $y; + for (my $i = 0; $i < $dim; $i++) { + ($y, $x) = &Rpeel($x); # strip off the entry for the key + if ($y eq '') { + &Rcroak('ran out of keys'); + } + $key = &Rreturn($y); + if ($key eq '') { + $key = "result$i"; # correct null key + } + ($y, $x) = &Rpeel($x); # strip off the value + if ($y eq '') { + &Rcroak('ran out of values'); + } + if ($y =~ /^a:/) { + $h{$key} = \&Rreturn($y); # array value: store as reference + } else { + $h{$key} = &Rreturn($y); # scalar value: store the entry in the hash + } } + if ($errormsg) { return $errormsg; } + return \%h; # return a reference to the hash + } elsif ($x eq '') { + return ''; + } else { + return 'Unrecognized output'; + } } # --- end Rreturn --- -# -# Rentry takes a list of indices and gets the entry in a hash generated by Rreturn. -# Call: Rentry(Rvalue, index1, index2, ...) where Rvalue is a hash returned by Rreturn. -# Rentry will return the first scalar value it encounters (ignoring excess indices). -# If an invalid key is given, Rentry returns undef. -# sub Rentry { - my $hash = shift; # pointer to hash - my $x; - my $i; - if (ref($hash) ne 'HASH') { - &Rcroak('argument to Rentry is not a hash'); - } - while ($i = shift) { - if (exists $hash->{$i}) { - $hash = $hash->{$i}; - } else { - return undef; - } - if (ref($hash) eq 'REF') { - $hash = $$hash; # dereference one layer - } elsif (ref($hash) ne 'HASH') { - return $hash; # drilled down to a scalar - } - } + my $hash = shift; # pointer to tied hash + my $i; + if (ref($hash) ne 'HASH') { + return 'Argument to cas_hashref_entry is not a hash!'; + } + while ($i = shift) { + if (exists($hash->{$i})) { + $hash = $hash->{$i}; + } else { + return undef; + } + if (ref($hash) eq 'REF') { + $hash = $$hash; # dereference one layer + } elsif (ref($hash) ne 'HASH') { + return $hash; # drilled down to a scalar + } + } } -# --- end Rentry --- +sub Rarray { + my $hash = shift; # pointer to tied hash + my $i; + if (ref($hash) ne 'HASH') { + return 'Argument to cas_hashref_array is not a hash!'; + } + while ($i = shift) { + if (exists($hash->{$i})) { + $hash = $hash->{$i}; + } else { + return undef; + } + if (ref($hash) eq 'REF') { + $hash = $$hash; # dereference one layer + } + } + my @returnarray=(); + foreach my $key (keys(%{$hash})) { + $returnarray[$key-1]=$$hash{$key}; + } + return @returnarray; +} sub connect { return IO::Socket::UNIX->new(Peer => $Apache::lonnet::perlvar{'lonSockDir'}.'/rsock', @@ -221,9 +243,9 @@ sub blacklisted { } sub r_allowed_libraries { - return ('boot','class','cluster','datasets','KernSmooth','MASS', - 'methods','mgcv','nlme','nnet','rpart','spatial', - 'splines','stats','stats4','survival'); + return ('alr3','boot','car','class','cluster','datasets','Hmisc','KernSmooth','leaps','lmtest', + 'MASS','methods','mgcv','nlme','nnet','qAnalyst','quadprog','rpart','SuppDists','spatial', + 'splines','stats','stats4','survival','tseries','zoo'); } sub r_is_allowed_library { @@ -237,29 +259,62 @@ sub r_is_allowed_library { sub runscript { my ($socket,$fullscript,$libraries)=@_; if (&blacklisted($fullscript)) { return 'Error: blacklisted'; } - my $reply; + my $reply=''; $fullscript=~s/[\n\r\l]//gs; if ($libraries) { foreach my $library (split(/\s*\,\s*/,$libraries)) { unless ($library=~/\w/) { next; } if (&r_is_allowed_library($library)) { - $reply=&rreply($socket,'library('.$library.');'."\n"); + $reply=&rreply($socket,'library('.$library.');'); if ($reply=~/^Error\:/) { return $reply; } } else { return 'Error: blacklisted'; } } } - foreach my $line (split(/\;/s,$fullscript)) { - if ($line=~/\w/) { $reply=&rreply($socket,$line.";\n"); } - if ($reply=~/^Error\:/) { return $reply; } - } + $fullscript=~s/\;+\s*$//s; + if ($fullscript=~/\w/) { $reply=&rreply($socket,$fullscript.';'); } + if ($reply=~/^Error\:/) { return $reply; } $reply=~s/^\s*//gs; $reply=~s/\s*$//gs; &Apache::lonxml::debug("r $fullscript \n reply $reply"); return $reply; } +sub runserializedscript { + my ($socket,$fullscript,$libraries)=@_; + if (&blacklisted($fullscript)) { return 'Error: blacklisted'; } + my $reply; + $fullscript=~s/[\n\r\l]//gs; + if ($libraries) { + foreach my $library (split(/\s*\,\s*/,$libraries)) { + unless ($library=~/\w/) { next; } + if (&r_is_allowed_library($library)) { + $reply=&rreply($socket,'library('.$library.');'); + if ($reply=~/^Error\:/) { return($reply,$reply); } + } else { + return 'Error: blacklisted'; + } + } + } + $fullscript=~s/\;+\s*$//s; + my $lastline=''; + my $firstpart=''; + if ($fullscript=~/\;/) { + ($firstpart,$lastline)=($fullscript=~/^(.*\;)([^\;]+)$/); + } else { + $lastline=$fullscript; + } + if ($firstpart) { + $firstpart=~s/\;+\s*$//s; + $reply=&rreply($socket,$firstpart.';'); + if ($reply=~/^Error\:/) { return($reply,$reply); } + } +# The last line needs to be serialized + $reply=&Rreturn(&rreply($socket,"phpSerialize($lastline);")); + return($reply,&Dumper($reply)); +} + sub r_cas_formula_fix { my ($expression)=@_; return &Apache::response::implicit_multiplication($expression); @@ -288,11 +343,17 @@ sub r_run { } sub r_eval { - my ($script,$libraries) = @_; + my ($script,$libraries,$hashflag) = @_; my $socket=&connect(); - my $reply=&runscript($socket,$script,$libraries); + my $reply; + my $dump=''; + if ($hashflag) { + ($reply,$dump)=&runserializedscript($socket,$script,$libraries); + } else { + $reply=&runscript($socket,$script,$libraries); + } &disconnect($socket); - return $reply; + return ($reply,$dump); }