YAMLScript/lib/YAMLScript.pm
# Copyright 2023-2025 Ingy dot Net
# This code is licensed under MIT license (See License for details)
use strict;
use warnings;
package YAMLScript;
use FFI::CheckLib ();
use FFI::Platypus;
use Cpanel::JSON::XS ();
our $VERSION = '0.2.7';
our $libys_version = $VERSION;
#------------------------------------------------------------------------------
# libys FFI setup:
#------------------------------------------------------------------------------
# Find the proper libys version:
my $libys = find_libys();
# Set up FFI functions:
my $ffi = FFI::Platypus->new(
api => 2,
lib => $libys,
);
my $graal_create_isolate = $ffi->function(
graal_create_isolate =>
['opaque', 'opaque*', 'opaque*'] => 'int',
);
my $graal_tear_down_isolate = $ffi->function(
graal_tear_down_isolate =>
['opaque'] => 'int',
);
#------------------------------------------------------------------------------
# YAMLScript object constructor and destructor:
#------------------------------------------------------------------------------
# YAMLScript object constuctor. Creates and saves a graal isolate thread:
sub new {
my ($class, $config) = (@_, {});
my ($isolatethread);
$graal_create_isolate->(undef, undef, \$isolatethread) == 0
or die 'Failed to create graal isolate';
bless {
isolatethread => \$isolatethread,
}, $class;
}
# Tear down the graal isolate when the YAMLScript object goes out of scope:
sub DESTROY {
my ($self) = @_;
$graal_tear_down_isolate->(${$self->{isolatethread}}) == 0
or die "Failed to tear down graal isolate";
}
#------------------------------------------------------------------------------
# YAMLScript API methods:
#------------------------------------------------------------------------------
sub load;
# "load" method wrapper for FFI.
# It calls the libys load_ys_to_json function.
$ffi->attach(
[load_ys_to_json => 'load'] =>
['sint64', 'string'] => 'string' =>
sub {
my ($xsub, $self, $ys) = @_;
$self->{error} = undef;
my $resp = Cpanel::JSON::XS::decode_json(
$xsub->(${$self->{isolatethread}}, $ys)
);
return $resp->{data} if exists $resp->{data};
if ($self->{error} = $resp->{error}) {
die "libys: $self->{error}{cause}";
}
die "Unexpected response from 'libys'";
},
);
#------------------------------------------------------------------------------
# Helper functions:
#------------------------------------------------------------------------------
# Look for the local libys first, then look for the Alien version:
sub find_libys {
my $vers = $libys_version;
my $so = $^O eq 'darwin' ? 'dylib' : 'so';
my $name = "libys.$so.$vers";
my @paths;
if (my $path = $ENV{LD_LIBRARY_PATH}) {
@paths = split /:/, $path;
}
push @paths, qw(
/usr/local/lib
/usr/local/lib64
/usr/lib
/usr/lib64
), "$ENV{HOME}/.local/lib";
for my $path (@paths) {
if (-e "$path/$name") {
return "$path/$name";
}
}
require Alien::YAMLScript;
for my $path (Alien::YAMLScript->dynamic_libs) {
if ($path =~ /\Q$name\E$/ && -r $path) {
return $path;
}
}
die <<"..."
Shared library file $name not found
Try: curl https://yamlscript.org/install | VERSION=$vers LIB=1 bash
See: https://github.com/yaml/yamlscript/wiki/Installing-YAMLScript
...
}
1;