Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

#!/usr/bin/env perl
# suppress 'WEXRP00: Found multiple rperl executables' due to blib/ & pre-existing installation(s),
#BEGIN { $ENV{RPERL_WARNINGS} = 0; }
# [[[ HEADER ]]]
#use RPerl; # disabled here & RPERL_WARNINGS above & RPerl::diag() below, only re-enable for RPerl::diag() debugging use
use strict;
our $VERSION = 0.006_000;
# [[[ CRITICS ]]]
## no critic qw(ProhibitUselessNoCritic ProhibitMagicNumbers RequireCheckedSyscalls) # USER DEFAULT 1: allow numeric values & print operator
## no critic qw(ProhibitStringyEval) # SYSTEM DEFAULT 1: allow eval()
# [[[ INCLUDES ]]]
use Test::More tests => 16;
use Carp;
use English qw(-no_match_vars);
# [[[ OPERATIONS ]]]
BEGIN {
if ( $ENV{RPERL_VERBOSE} ) {
Test::More::diag('[[[ Beginning Inline::CPP Pre-Test Loading ]]]');
}
}
if ( $ENV{RPERL_VERBOSE} ) {
Test::More::diag('[[[ Beginning Entirety Of Tests From The Inline::CPP POD ]]]');
}
# Inline::CPP examples from: http://search.cpan.org/~davido/Inline-CPP-0.44/lib/Inline/CPP.pod
# Note: I did not exclude any Inline::CPP examples, they are all suitable for testing.
# class Farmer, AKA "Farmer Bob"
#RPerl::diag('in 03_inline_cpp.t, starting Farmer Bob...' . "\n");
my $farmer_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
class Farmer {
public:
Farmer(char *name, int age);
~Farmer();
int how_tired() { return tiredness; }
int how_long() { return howlong; }
void do_chores(int howlong);
private:
char *name;
int age;
int tiredness;
int howlong;
};
Farmer::Farmer(char *name, int age) {
this->name = strdup(name);
this->age = age;
tiredness = 0;
howlong = 0;
}
Farmer::~Farmer() { free(name); }
void Farmer::do_chores(int hl) {
howlong += hl;
tiredness += (age * hl);
}
END_OF_CPP_CODE
EOF
#RPerl::diag('in 03_inline_cpp.t, building Farmer Bob...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval $farmer_define_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $EVAL_RETVAL, undef, q{Inline::CPP, define class Farmer returns correct value} );
},
q{Inline::CPP, define class Farmer lives}
);
my $farmer_call_eval_string = <<'EOF';
my $farmer = new Farmer("Ingy", 42);
my $slavedriver = 1;
while($farmer->how_tired < 420) {
$farmer->do_chores($slavedriver);
$slavedriver <<= 1;
}
return("Wow! The farmer worked " . $farmer->how_long . " hours!");
EOF
#RPerl::diag('in 03_inline_cpp.t, running Farmer Bob...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval $farmer_call_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $EVAL_RETVAL, 'Wow! The farmer worked 15 hours!', q{Inline::CPP, call Farmer methods returns correct value} );
},
q{Inline::CPP, call Farmer methods lives}
);
#RPerl::diag('in 03_inline_cpp.t, finished Farmer Bob!' . "\n");
# class Airplane, AKA "Plane and Simple"
#RPerl::diag('in 03_inline_cpp.t, starting Airplane...' . "\n");
my $airplane_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
using namespace std;
//#include <string.h> // DEV NOTE: uncomment for possible solution to Github Issue #34 https://github.com/wbraswell/rperl/issues/34
#include <sstream>
/* Abstract class (interface) */
class Object {
public:
virtual void print() { cout << "Object (" << this << ")" << endl; }
virtual char* nonprint() { ostringstream oretval; oretval << "Object (" << this << ")"; return((char*)oretval.str().c_str()); }
// virtual string nonprint() { ostringstream oretval; oretval << "Object (" << this << ")"; string ret = oretval.str(); return ret; } // DEV NOTE: uncomment for possible solution to Github Issue #34
virtual void info() = 0;
virtual bool isa(char *klass) = 0;
virtual bool can(char *method) = 0;
};
class Airplane : public Object {
public:
Airplane() {}
~Airplane() {}
virtual void info() { print(); }
virtual bool isa(char *klass) { return strcmp(klass, "Object")==0; }
virtual bool can(char *method) {
bool yes = false;
yes |= strcmp(method, "print")==0;
yes |= strcmp(method, "info")==0;
yes |= strcmp(method, "isa")==0;
yes |= strcmp(method, "can")==0;
return yes;
}
};
END_OF_CPP_CODE
EOF
#RPerl::diag('in 03_inline_cpp.t, building Airplane...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval $airplane_define_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $EVAL_RETVAL, undef, q{Inline::CPP, define class Airplane returns correct value} );
},
q{Inline::CPP, define class Airplane lives}
);
my $airplane_call_eval_string = <<'EOF';
my $plane = new Airplane;
my $plane_retval1 = '';
#my $plane_retval1 = $plane->nonprint; # DEV NOTE: uncomment when solution found for Github Issue #34 https://github.com/wbraswell/rperl/issues/34
my $plane_retval2 = '';
if ($plane->isa("Object")) { $plane_retval2 .= "Plane is an Object!"; }
unless ($plane->can("fly")) { $plane_retval2 .= " This plane sucks!"; }
return($plane_retval1, $plane_retval2);
EOF
#RPerl::diag('in 03_inline_cpp.t, running Airplane...' . "\n");
lives_and(
sub {
my ( $airplane_retval1, $airplane_retval2 ) = eval $airplane_call_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
# like( $airplane_retval1, '/Object\ \(0x\w*\)/', 'Inline::CPP, call Airplane methods, return correct value 1' ); # DEV NOTE: uncomment when solution found for Github Issue #34 https://github.com/wbraswell/rperl/issues/34
is( $airplane_retval2, 'Plane is an Object! This plane sucks!', 'Inline::CPP, call Airplane methods, return correct value 2' );
},
q{Inline::CPP, call Airplane methods lives}
);
#RPerl::diag('in 03_inline_cpp.t, finished Airplane!' . "\n");
# average() VS avg(), AKA "The Ellipsis Abridged"
#RPerl::diag('in 03_inline_cpp.t, starting Elipses Abridged...' . "\n");
my $average_define_eval_string = <<'EOF';
sub average {
my $average = 0;
for (my $i=0; $i<@_; $i++) {
$average *= $i;
$average += $_[$i];
$average /= $i + 1;
}
return $average;
}
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
double avg(...) {
Inline_Stack_Vars;
double avg = 0.0;
for (int i=0; i<items; i++) {
avg *= i;
avg += SvNV(ST(i));
avg /= i + 1;
}
return avg;
}
END_OF_CPP_CODE
EOF
#RPerl::diag('in 03_inline_cpp.t, building Elipses Abridged...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval $average_define_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $EVAL_RETVAL, undef, q{Inline::CPP, define average() and avg() returns correct value} );
},
q{Inline::CPP, define average() and avg() lives}
);
my $average_call_eval_string = <<'EOF';
my @numbers = map { rand } (1 .. 10000);
my $average_retval = average(@numbers);
my $avg_retval = avg(@numbers);
my $average_retval_string = "The Perl average of 10000 random numbers is: " . $average_retval;
my $avg_retval_string = "The C/C++ average of 10000 random numbers is: " . $avg_retval;
return($average_retval, $avg_retval, $average_retval_string, $avg_retval_string);
EOF
#RPerl::diag('in 03_inline_cpp.t, running Elipses Abridged...' . "\n");
lives_and(
sub {
my ( $average_retval, $avg_retval, $average_retval_string, $avg_retval_string ) = eval $average_call_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
like(
$average_retval_string,
'/The Perl average of 10000 random numbers is: \d+.\d+/',
q{Inline::CPP, call average() and avg() returns correct value 1}
);
like(
$avg_retval_string,
'/The C\/C\+\+ average of 10000 random numbers is: \d+.\d+/',
q{Inline::CPP, call average() and avg() returns correct value 2}
);
delta_ok( $average_retval, $avg_retval, q{Inline::CPP, call average() and avg() return values are equivalent} );
},
q{Inline::CPP, call average() and avg() lives}
);
#RPerl::diag('in 03_inline_cpp.t, finished Elipses Abridged!' . "\n");
# classes Queue and Stack, AKA "Stacks and Queues"
#RPerl::diag('in 03_inline_cpp.t, starting Stacks & Queues...' . "\n");
my $queuestack_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
class Queue {
public:
Queue(int sz=0) { q = newAV(); if (sz) av_extend(q, sz-1); }
~Queue() { av_undef(q); }
int size() {return av_len(q) + 1; }
int enqueue(SV* item) { av_push(q, SvREFCNT_inc(item)); return av_len(q)+1; }
SV* dequeue() { return av_shift(q); }
SV* peek() { return size() ? SvREFCNT_inc(*av_fetch(q,0,0)): &PL_sv_undef;}
private:
AV* q;
};
class Stack {
public:
Stack(int sz=0) { s = newAV(); if (sz) av_extend(s, sz-1); }
~Stack() { av_undef(s); }
int size() { return av_len(s) + 1; }
int push(SV* i) { av_push(s, SvREFCNT_inc(i)); return av_len(s)+1; }
SV* pop() { return av_pop(s); }
SV* peek() { return size() ? SvREFCNT_inc(*av_fetch(s,size()-1,0)) : &PL_sv_undef; }
private:
AV* s;
};
END_OF_CPP_CODE
EOF
#RPerl::diag('in 03_inline_cpp.t, building Stacks & Queues...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval $queuestack_define_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $EVAL_RETVAL, undef, q{Inline::CPP, define classes Queue and Stack returns correct value} );
},
q{Inline::CPP, define classes Queue and Stack lives}
);
my $queuestack_call_eval_string = <<'EOF';
my $q = new Queue;
my $queue_retval = '';
$q->enqueue(50);
$q->enqueue("Where am I?");
$q->enqueue("In a queue.");
#RPerl::diag("There are " . $q->size . " items in the queue\n");
while($q->size) {
# RPerl::diag("About to dequeue: " . $q->peek . "\n");
# RPerl::diag("Actually dequeued: " . $q->dequeue . "\n");
$queue_retval .= $q->dequeue . ' ';
}
my $s = new Stack;
my $stack_retval = '';
$s->push(42);
$s->push("What?");
#RPerl::diag("There are " . $s->size . " items on the stack\n");
while($s->size) {
# RPerl::diag("About to pop: " . $s->peek . "\n");
# RPerl::diag("Actually popped: " . $s->pop . "\n");
$stack_retval .= $s->pop . ' ';
}
return($queue_retval, $stack_retval);
EOF
#RPerl::diag('in 03_inline_cpp.t, running Stacks & Queues...' . "\n");
lives_and(
sub {
my ( $queue_retval, $stack_retval ) = eval $queuestack_call_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $queue_retval, '50 Where am I? In a queue. ', q{Inline::CPP, call Queue and Stack methods returns correct value 1} );
is( $stack_retval, 'What? 42 ', q{Inline::CPP, call Queue and Stack methods returns correct value 2} );
},
q{Inline::CPP, call Queue and Stack methods lives}
);
#RPerl::diag('in 03_inline_cpp.t, finished Stacks & Queues!' . "\n");
# multiadd(), AKA "Elipses Revisited (and Overloading or Templates)"
#RPerl::diag('in 03_inline_cpp.t, starting Elipses Revisited...' . "\n");
my $multiadd_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
#include <stdexcept>
// Inline::CPP won't create predictable bindings to overloaded functions.
int add ( int a, int b ) { return a + b; }
int add ( int a, int b, int c ) { return a + b + c; }
// But a function call with elipses can dispatch to overloaded functions since
// no Perl binding is required in reaching those functions.
int multiadd ( SV* a, ... ) {
dXSARGS; // Creates a variable 'items' that contains a paramater count.
SV* saved_error_message = NULL; // NEW LINE
try{
switch ( items ) {
case 1: return SvIV(ST(0));
case 2: return add( SvIV(ST(0)), SvIV(ST(1)) );
case 3: return add( SvIV(ST(0)), SvIV(ST(1)), SvIV(ST(2)) );
default: throw std::runtime_error( "multiadd() - Too many args in function call" );
}
}
// catch ( std::runtime_error msg ) { croak( msg.what() ); } // Perl likes croak for exceptions. THIS DOES NOT WORK IN WINDOWS!
catch ( std::runtime_error msg ) { saved_error_message = sv_2mortal(newSVpv(msg.what(), 0)); } // Perl likes croak for exceptions. NEW LINE
// if (saved_error_message) { croak_sv( saved_error_message ); } // this does not work in Perl < v5.13.1 NEW LINE
if (saved_error_message) { croak( SvPV_nolen(saved_error_message) ); } // NEW LINE
}
END_OF_CPP_CODE
EOF
#RPerl::diag('in 03_inline_cpp.t, building Elipses Revisited...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval $multiadd_define_eval_string;
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
is( $EVAL_RETVAL, undef, q{Inline::CPP, define multiadd() returns correct value} );
},
q{Inline::CPP, define multiadd() lives}
);
#RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 1/4...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval 'return(multiadd(1)); # No dispatch; just return the value';
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
cmp_ok( $EVAL_RETVAL, q{==}, 1, q{Inline::CPP, call multiadd(1) returns correct value} );
},
q{Inline::CPP, call multiadd(1) lives}
);
#RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 2/4...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval 'multiadd(1, 2); # Dispatch add(int, int)';
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
cmp_ok( $EVAL_RETVAL, q{==}, 3, q{Inline::CPP, call multiadd(1, 2) returns correct value} );
},
q{Inline::CPP, call multiadd(1, 2) lives}
);
#RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 3/4...' . "\n");
lives_and(
sub {
my $EVAL_RETVAL = eval 'multiadd(1, 2, 3); # Dispatch add(int, int, int)';
if ( $EVAL_ERROR ne q{} ) {
croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
}
cmp_ok( $EVAL_RETVAL, q{==}, 6, q{Inline::CPP, call multiadd(1, 2, 3) returns correct value} );
},
q{Inline::CPP, call multiadd(1, 2, 3) lives}
);
# NEED FIX: get Elipses Revisited part 4/4 working in Windows
if ( $OSNAME eq 'MSWin32' ) {
# RPerl::diag('in 03_inline_cpp.t, skipping Elipses Revisited part 4/4, Windows detected...' . "\n");
if ( $ENV{RPERL_VERBOSE} ) {
Test::More::diag("[[[ MS Windows OS Detected, Inline::CPP Exception Temporarily Disabled, Skipping Elipses Revisited Part 4/4 Test, RPerl Inline System ]]]");
}
ok(1, q{Inline::CPP, call multiadd(1, 2, 3, 4) skipped on MS Windows OS});
}
else {
# RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 4/4...' . "\n");
lives_and( # can't use throws_ok() because we are trapping the exception inside of eval
sub {
my $EVAL_RETVAL = eval 'multiadd(1, 2, 3, 4); # No dispatch; throw an exception';
like( $EVAL_ERROR, '/^multiadd\(\) \- Too many args in function call at/', q{Inline::CPP, call multiadd(1, 2, 3, 4) throws correct exception} );
},
q{Inline::CPP, call multiadd(1, 2, 3, 4) lives}
);
}
#RPerl::diag('in 03_inline_cpp.t, finished Elipses Revisited!' . "\n");
done_testing();