package Inline::Ruby; use strict; use warnings; use Carp; require Inline; require DynaLoader; require Exporter; use vars qw(@ISA $VERSION @EXPORT_OK); $VERSION = '0.04'; @ISA = qw(Inline DynaLoader Exporter); @EXPORT_OK = qw(rb_eval rb_call_function rb_iter rb_call_class_method rb_new_object rb_call_instance_method rb_bind_class rb_bind_func ); # Prevent Inline's import from complaining sub import { Inline::Ruby->export_to_level(1, @_); } sub dl_load_flags { 0x01 } Inline::Ruby->bootstrap($VERSION); eval_support_code(); #============================================================================== # Register Ruby.pm as a valid Inline language #============================================================================== sub register { return { language => 'Ruby', aliases => ['rb', 'ruby', 'RUBY'], type => 'interpreted', suffix => 'rbdat', }; } #============================================================================== # Validate the Ruby config options #============================================================================== sub validate { my $o = shift; $o->{ILSM} ||= {}; $o->{ILSM}{FILTERS} ||= []; $o->{ILSM}{AUTO_INCLUDE} ||= {}; $o->{ILSM}{built} ||= 0; $o->{ILSM}{loaded} ||= 0; $o->{ILSM}{bindto} = [qw(classes modules functions)]; $o->{ILSM}{ITER} ||= 'iter'; while (@_) { my ($key, $value) = (shift, shift); if ($key eq 'REGEX' or $key eq 'REGEXP') { $o->{ILSM}{regexp} = qr/$value/; } elsif ($key eq 'BIND_TYPE' or $key eq 'BIND_TYPES') { $o->add_list($o->{ILSM}, 'bindto', $value, []); } elsif ($key eq 'ITER') { $o->{ILSM}{$key} = $value; } elsif ($key eq 'FILTERS') { next if $value eq '1' or $value eq '0'; # ignore ENABLE, DISABLE $value = [$value] unless ref($value) eq 'ARRAY'; my %filters; for my $val (@$value) { if (ref($val) eq 'CODE') { $o->add_list($o->{ILSM}, $key, $val, []); } else { eval { require Inline::Filters }; croak "'FILTERS' option requires Inline::Filters to be installed." if $@; %filters = Inline::Filters::get_filters($o->{API}{language}) unless keys %filters; if (defined $filters{$val}) { my $filter = Inline::Filters->new($val, $filters{$val}); $o->add_list($o->{ILSM}, $key, $filter, []); } else { croak "Invalid filter $val specified."; } } } } else { croak "$key is not a valid config option for Ruby"; } next; } } sub usage_validate { return "Invalid value for config option $_[0]"; } sub add_list { my $o = shift; my ($ref, $key, $value, $default) = @_; $value = [$value] unless ref $value; croak usage_validate($key) unless ref($value) eq 'ARRAY'; foreach my $val (@$value) { if (defined $val) { push @{$ref->{$key}}, $val; } else { $ref->{$key} = $default; } } } sub add_string { my $o = shift; my ($ref, $key, $value, $default) = @_; $value = [$value] unless ref $value; croak usage_validate($key) unless ref($value) eq 'ARRAY'; foreach my $val (@$value) { if (defined $val) { $ref->{$key} .= " $val"; } else { $ref->{$key} = $default; } } } sub add_text { my $o = shift; my ($ref, $key, $value, $default) = @_; $value = [$value] unless ref $value; croak usage_validate($key) unless ref($value) eq 'ARRAY'; for my $val (@$value) { if (defined $val) { chomp($val); $ref->{$key} .= $val . "\n"; } else { $ref->{$key} = $default; } } } #========================================================================== # Print a short information section if PRINT_INFO is enabled. #========================================================================== sub info { my $o = shift; my $info = ""; $o->build unless $o->{ILSM}{built}; my @functions = @{$o->{ILSM}{namespace}{functions}||[]}; $info .= "The following Ruby functions have been bound to Perl:\n" if @functions; for my $function (sort @functions) { $info .= "\tdef $function()\n"; } my %classes = %{$o->{ILSM}{namespace}{classes}||{}}; $info .= "The following Ruby classes have been bound to Perl:\n"; my $i = ' ' x 4; for my $class (sort keys %classes) { $info .= "${i}class $class\n"; $i .= $i; for my $method (sort @{$classes{$class}{imethods}}) { next unless $method =~ /^\w+$/; $info .= "${i}def $method(...)\n"; } for my $method (sort @{$classes{$class}{methods}}) { next unless $method =~ /^\w+$/; $info .= "${i}def $class.$method(...)\n"; } } return $info; } sub eval_support_code { rb_eval(<<'END'); def inline_ruby_class_grokker(*classes) if classes == [] ObjectSpace.each_object(Class) do |x| yield ['classes', x.name] end ObjectSpace.each_object(Module) do |x| yield ['modules', x.name] end Kernel.methods.each do |x| yield ['functions', x] end else classes.each do |k| n = {} begin n['methods'] = eval "#{k}.methods" n['imethods'] = eval "#{k}.instance_methods" rescue Exception p "Exception: " + $! end yield [k, n] end end end END } #========================================================================== # Run the code, study the main namespace, and cache the results. #========================================================================== sub build { my $o = shift; return if $o->{ILSM}{built}; # Filter the code $o->{ILSM}{code} = $o->filter(@{$o->{ILSM}{FILTERS}}); # Get the namespace before & after evaluating the code: my (%pre, %post, %n); rb_iter(undef, sub {my ($type, $name) = @_; $pre{$type}{$name}++}) ->inline_ruby_class_grokker; rb_eval($o->{ILSM}{code}); rb_iter(undef, sub {my ($type, $name) = @_; $post{$type}{$name}++}) ->inline_ruby_class_grokker; # Select those things which sprang into existence after running the code: my @skip_clas = qw(PerlException PerlProc); my @skip_func = qw(inline_ruby_class_grokker); delete @{ $post{classes} }{@skip_clas, keys(%{$pre{classes}})}; delete @{ $post{functions} }{@skip_func, keys(%{$pre{functions}})}; delete @{ $post{modules} }{ keys(%{$pre{modules}}), keys(%{$post{classes}}) }; # Filter the results according to the {bindto} and {REGEXP} selections: for my $type (qw(classes modules functions)) { if ($o->{ILSM}{bindto}) { delete $post{$type} unless grep { $_ eq $type } @{$o->{ILSM}{bindto}}; } if ($o->{ILSM}{regexp}) { for my $k (keys %{$post{$type}}) { delete $post{$type}{$k} unless $k =~ $o->{ILSM}{regexp}; } } } # Get more details about the classes and modules: rb_iter(undef, sub { $n{$_[0]} = $_[1] }) ->inline_ruby_class_grokker(keys %{$post{classes}}) if (%{$post{classes} || {}}); rb_iter(undef, sub { $n{$_[0]} = $_[1] }) ->inline_ruby_class_grokker(keys %{$post{modules}}) if (%{$post{modules} || {}}); # And the namespace is: my %namespace = ( classes => \%n, functions => [keys %{$post{functions} || {}}], ); if ((! @{$namespace{functions}}) and (! %{$namespace{classes}})) { warn "No functions or classes found!"; } # Cache the results require Inline::denter; my $namespace = Inline::denter->new->indent( *namespace => \%namespace, *filtered => $o->{ILSM}{code}, *itername => $o->{ILSM}{ITER}, ); $o->mkpath("$o->{API}{install_lib}/auto/$o->{API}{modpname}"); { open my $rbdat_fh, '>', $o->{API}{location} or croak "Inline::Ruby couldn't write parse information!"; print {$rbdat_fh} $namespace; close($rbdat_fh); } $o->{ILSM}{namespace} = \%namespace; $o->{ILSM}{built}++; } sub _slurp { my $filename = shift; open my $in, '<', $filename or croak "Cannot open '$filename' for slurping - $!"; local $/; my $contents = <$in>; close($in); return $contents; } #============================================================================== # Load the code, run it, and bind everything to Perl #============================================================================== sub load { my $o = shift; return if $o->{ILSM}{loaded}; my $rbdat = _slurp($o->{API}{location}); require Inline::denter; my %rbdat = Inline::denter->new->undent($rbdat); $o->{ILSM}{namespace} = $rbdat{namespace}; $o->{ILSM}{code} = $rbdat{filtered}; $o->{ILSM}{ITER} = $rbdat{itername}; $o->{ILSM}{loaded}++; # Run it rb_eval($o->{ILSM}{code}); # Bind it all for my $func (@{ $o->{ILSM}{namespace}{functions} || [] }) { rb_bind_func("$o->{API}{pkg}::$func", $func); } for my $class (keys %{ $o->{ILSM}{namespace}{classes} || {} }) { rb_bind_class("$o->{API}{pkg}::$class", $class, $o->{ILSM}{ITER}, %{$o->{ILSM}{namespace}{classes}{$class}}); } # Bind the global function 'iter': eval <{API}{pkg}::$o->{ILSM}{ITER} { unshift \@_, undef; return &Inline::Ruby::rb_iter; } END croak $@ if $@; } #============================================================================== # Wrap a Ruby function with a Perl sub which calls it. #============================================================================== sub rb_bind_func { my $perlfunc = shift; # The fully-qualified Perl sub name to create my $function = shift; # The fully-qualified Ruby sub name to wrap my $bind = < \&to_str; sub new { my ($cls, $obj) = @_; die bless $obj, ref($cls) || $cls; } sub to_str { $_[0]->inspect . "\n"; } 1;