# Strada - Perl XS Module Version 1.0 This Perl XS module allows Perl programs to load and call functions from compiled Strada shared libraries. ## Overview The `Strada` module provides a bridge from Perl to Strada, enabling you to: - Load Strada shared libraries (.so files) - Call Strada functions with automatic type conversion - Pass Perl scalars, arrays, and hashes to Strada (arrays/hashes map to Strada references) - Receive Strada return values as Perl types - Call functions with any number of arguments (with libffi), including variadic functions and constructors - Construct Strada objects and call methods on them (`Strada::Object`) - Catch Strada exceptions as Perl `die` (`eval` / `Try::Tiny`) - Inspect library metadata (version, function signatures) ## Installation ### Prerequisites - Perl 5.10 or later - Strada compiler and runtime (from parent directory) - C compiler (gcc) - libffi (optional) — `libffi-dev` / `libffi-devel`. When present, `call()` accepts any number of arguments; without it, calls are capped at 4. ### Build Steps ```bash cd perl/Strada perl Makefile.PL make make test make install # optional, installs system-wide ``` ### Specifying Strada Location By default, the build looks for the Strada runtime in these locations (in order): 1. `STRADA_ROOT` environment variable 2. `STRADA_ROOT=` command line argument 3. System install (`/usr/local/include/strada` or `/usr/include/strada`) 4. Source tree (`../..` - assumes building from within the Strada repo) If Strada is installed elsewhere, specify the path: ```bash # Via command line argument perl Makefile.PL STRADA_ROOT=/path/to/strada # Via environment variable STRADA_ROOT=/path/to/strada perl Makefile.PL ``` The path should point to either: - The Strada source directory (containing `runtime/strada_runtime.h`) - An installed location (containing `strada_runtime.h` directly) ## Usage ### High-Level API (Recommended) ```perl use Strada; # Load a Strada shared library my $lib = Strada::Library->new('./libmath.so'); # Get library info print "Version: ", $lib->version(), "\n"; print $lib->describe(), "\n"; # Call functions using package::function syntax (recommended) my $sum = $lib->call('math_lib::add', 10, 20); # 30 my $greeting = $lib->call('math_lib::greet', 'Perl'); # "Hello, Perl!" # Or use the C-style name directly my $sum2 = $lib->call('math_lib_add', 10, 20); # 30 # Pass arrays my $total = $lib->call('math_lib::sum_array', [1, 2, 3, 4, 5]); # Pass hashes my $desc = $lib->call('math_lib::describe_person', { name => 'Alice', age => 30 }); # Receive arrays my $nums = $lib->call('math_lib::get_numbers'); # Returns arrayref # Receive hashes my $person = $lib->call('math_lib::get_person'); # Returns hashref # Unload when done $lib->unload(); ``` ### Inspecting Library Functions ```perl use Strada; my $lib = Strada::Library->new('./libmath.so'); # Get formatted description (like strada-soinfo tool) print $lib->describe(); # Output: # # Strada Library: ./libmath.so # # Version: 1.0.0 # # Functions: 3 # # # # func math_lib_add(int $a, int $b) int # # func math_lib_greet(str $a) str # # func math_lib_sum_array(scalar $a) int # Get function details programmatically my $funcs = $lib->functions(); for my $name (sort keys %$funcs) { my $f = $funcs->{$name}; print "Function: $name\n"; print " Returns: $f->{return}\n"; print " Params: ", join(", ", @{$f->{params}}), "\n"; } ``` ### Low-Level API ```perl use Strada; # Load library, get handle my $handle = Strada::load('./libmath.so'); die "Failed to load" unless $handle; # Get function pointer my $func = Strada::get_func($handle, 'math_lib_add'); die "Function not found" unless $func; # Call function my $result = Strada::call($func, 2, 3); print "2 + 3 = $result\n"; # Unload Strada::unload($handle); ``` ## Creating Strada Shared Libraries ### 1. Write a Strada Library ```strada # math_lib.strada package math_lib; version "1.0.0"; func add(int $a, int $b) int { return $a + $b; } func greet(str $name) str { return "Hello, " . $name . "!"; } func sum_array(scalar $arr) int { my int $total = 0; my int $len = length($arr); my int $i = 0; while ($i < $len) { $total = $total + $arr->[$i]; $i = $i + 1; } return $total; } ``` ### 2. Compile as Shared Library ```bash ./strada --shared math_lib.strada # Creates: math_lib.so ``` ### 3. Use from Perl ```perl use Strada; my $lib = Strada::Library->new('./math_lib.so'); print $lib->call('math_lib::add', 1, 2), "\n"; # 3 $lib->unload(); ``` ## Function Naming Convention Strada functions are exported with the naming pattern: ``` _ ``` For example: - `package math_lib` + `func add()` = `math_lib_add` - `package utils` + `func format_date()` = `utils_format_date` When calling from Perl, you can use either format: - `$lib->call('math_lib_add', ...)` - C-style name - `$lib->call('math_lib::add', ...)` - Perl/Strada style (automatically converted) ## Type Conversion | Strada Type | Perl Type | |-------------|-----------| | int | IV (integer) | | num | NV (floating point) | | str | PV (string) | | array | Array reference | | hash | Hash reference | | undef | undef | | ref | Dereferenced value | | blessed object | `Strada::Object` (see below) | ## Objects and Methods A blessed Strada object returned from the runtime comes back as a `Strada::Object` — a wrapper that dispatches method calls back into Strada (rather than flattening to a plain hashref and losing the class). Construct objects with `new_object` (sugar for calling `Class::new`) and call methods directly: ```perl my $lib = Strada::Library->new('./example/math_lib.so'); my $c = $lib->new_object('Counter', count => 10, name => 'hits'); # (equivalent to: my $c = $lib->call('Counter::new', count => 10, name => 'hits')) $c->increment; # method dispatch -> 11 $c->add(5); # method with arguments -> 16 print $c->describe, "\n"; # "hits=16" $c->strada_class; # "Counter" (the Strada package) $c->isa('Counter'); # true (checks the Strada class hierarchy) # A Strada::Object can be passed back into the runtime as an argument: my $d = $lib->new_object('Counter', count => 2); $c->merge($d); # $d is handed straight through to the method ``` The wrapper holds a reference to the underlying value and releases it on `DESTROY`. `isa`/`can` check the Perl class first, then the Strada hierarchy. ## Limitations - Without libffi, a function call is limited to 4 arguments (calls with more `croak`). Build with libffi (see Prerequisites) for unlimited arguments. - Argument conversion is by value: mutations a Strada function makes to an array/hash argument are not reflected back in the Perl data structure. - The deep array/hash converter does not detect reference cycles; converting a self-referential Perl (or Strada) structure will recurse without bound. - Typed numeric widths (`int8`, `uint64`, `float`, …) round-trip as Perl IV/NV; the specific C width is not preserved across the boundary. - A blessed Perl object other than a `Strada::Object` is converted as a plain hash — its Perl class is not carried into Strada. - The Strada runtime is single-threaded; do not call into Strada concurrently from multiple Perl threads. ## Example Directory The `example/` subdirectory contains: - `math_lib.strada` - Example Strada library source - `build.sh` - Script to compile the example library - The compiled `libmath.so` after running build.sh ## API Reference ### Low-Level Functions #### Strada::load($path) Load a Strada shared library. Returns a handle (integer) on success, 0 on failure. #### Strada::unload($handle) Unload a previously loaded library. #### Strada::get_func($handle, $name) Get a function pointer by name. Returns the pointer on success, 0 if not found. #### Strada::call($func, @args) Call a Strada function. With libffi, any number of arguments is supported; without it, 0-4 arguments (more will `croak`). Returns the result converted to Perl. #### Strada::get_export_info($handle) Get the raw export metadata string from a Strada library. Returns empty string for non-Strada libraries. #### Strada::get_version($handle) Get the version string from a Strada library. Returns empty string if not available. ### High-Level OO Interface #### Strada::Library->new($path) Create a Library object, loading the specified shared library. Dies on failure. #### $lib->call($func_name, @args) Call a function by name with arguments. Supports both `package_func` and `package::func` naming styles. Caches function pointers for efficiency. #### $lib->version() Returns the library version string, or empty string if not set. #### $lib->functions() Returns a hash reference describing all exported functions: ```perl { 'math_lib_add' => { return => 'int', param_count => 2, params => ['int', 'int'], }, ... } ``` #### $lib->describe() Returns a formatted string describing all functions (similar to `strada-soinfo` output): ``` # Strada Library: ./math_lib.so # Version: 1.0.0 # Functions: 3 # # func math_lib_add(int $a, int $b) int # func math_lib_greet(str $a) str # func math_lib_sum_array(scalar $a) int ``` #### $lib->unload() Unload the library and clear cached function pointers. ## See Also - [Strada Website](https://strada-lang.github.io/) - Official Strada language website - [Cannoli](https://github.com/strada-lang/cannoli) - Preforking web server and framework for Strada - `lib/perl5/` - The reverse integration (calling Perl from Strada) - `docs/LANGUAGE_GUIDE.md` - Strada language documentation - `docs/RUNTIME_API.md` - Strada runtime API reference - `strada-soinfo` - Command-line tool to inspect Strada shared libraries