diff --git a/lib/ZeroPerl/Web.pm b/lib/ZeroPerl/Web.pm new file mode 100644 index 0000000..8b85fe2 --- /dev/null +++ b/lib/ZeroPerl/Web.pm @@ -0,0 +1,53 @@ +package ZeroPerl::Web; + +use strict; +use warnings; +use Exporter 'import'; + +our $VERSION = '0.01'; +our @EXPORT_OK = qw(fetch sleep); + +require XSLoader; +XSLoader::load('ZeroPerl::Web', $VERSION); + +1; + +__END__ + +=head1 NAME + +ZeroPerl::Web - Asynchronous Web APIs for ZeroPerl + +=head1 SYNOPSIS + + use ZeroPerl::Web; + + # Fetch data asynchronously (returns a Promise-like behavior via Asyncify) + my $response = ZeroPerl::Web::fetch("https://api.example.com/data"); + print "Response: $response\n"; + + # Sleep asynchronously without blocking the browser main thread + ZeroPerl::Web::sleep(1000); # Sleep for 1 second + +=head1 DESCRIPTION + +This module provides access to Web APIs within the ZeroPerl WebAssembly environment. +It uses Asyncify to perform asynchronous operations transparently to Perl code. + +=head2 FUNCTIONS + +=over 4 + +=item fetch($url, [\%options]) + +Performs a network request using the Host's `fetch` API. +Returns the response body as a string. + +=item sleep($ms) + +Suspends execution for the specified number of milliseconds. +This yields control back to the browser event loop. + +=back + +=cut diff --git a/pipeline/build-wasm.sh b/pipeline/build-wasm.sh index 1d48753..ebb140e 100644 --- a/pipeline/build-wasm.sh +++ b/pipeline/build-wasm.sh @@ -21,6 +21,7 @@ wasic -flto -O3 -c setjmp_core.S -o setjmp_core.o cd "$WASM_DIR" cp "$REPO_DIR/stubs/zeroperl.c" . +cp "$REPO_DIR/stubs/zeroperl_web.c" . CFLAGS="-c -O3 -flto -DNO_MATHOMS -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_GETPID \ -D_GNU_SOURCE -D_POSIX_C_SOURCE -DBIG_TIME -Wno-implicit-function-declaration \ @@ -30,6 +31,7 @@ CFLAGS="-c -O3 -flto -DNO_MATHOMS -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULAT -I. -I$REPO_DIR/stubs -I$REPO_DIR/gen -cxx-isystem /opt/wasi-sdk/share/wasi-sysroot/include" wasic $CFLAGS zeroperl.c -o zeroperl.o +wasic $CFLAGS zeroperl_web.c -o zeroperl_web.o wasic $CFLAGS "$REPO_DIR/stubs/stubs.c" -o stubs.o CFLAGS_DATA="-c -O0 -std=c23 \ @@ -59,7 +61,7 @@ wasic \ -lwasi-emulated-mman \ -Wl,--strip-all \ -Wl,--allow-undefined \ - zeroperl.o stubs.o zeroperl_data.o \ + zeroperl.o stubs.o zeroperl_data.o zeroperl_web.o \ -Wl,--whole-archive "$REPO_DIR/stubs/libasyncjmp.a" -Wl,--no-whole-archive \ -Wl,--whole-archive libperl.a -Wl,--no-whole-archive \ -Wl,--wrap=fopen -Wl,--wrap=open -Wl,--wrap=close -Wl,--wrap=read \ diff --git a/pipeline/prepare-prefix.sh b/pipeline/prepare-prefix.sh index d7f378d..a567d0a 100644 --- a/pipeline/prepare-prefix.sh +++ b/pipeline/prepare-prefix.sh @@ -19,6 +19,10 @@ if [ "$BUILD_EXIFTOOL" = "true" ]; then cp -R "$SITE_PERL/Image/"* "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/Image/" fi +# Copy internal modules +mkdir -p "/zeroperl/lib/$PERL_VERSION/ZeroPerl" +cp "$REPO_DIR/lib/ZeroPerl/Web.pm" "/zeroperl/lib/$PERL_VERSION/ZeroPerl/" + node "$REPO_DIR/tools/delete.js" "$REPO_DIR/tools/delete.txt" /zeroperl "$PERL_VERSION" if [ "$TRIM" = "true" ]; then diff --git a/stubs/zeroperl.c b/stubs/zeroperl.c index c32bf47..67e2e1f 100644 --- a/stubs/zeroperl.c +++ b/stubs/zeroperl.c @@ -459,97 +459,6 @@ __attribute__((noinline)) int __wrap_fileno(FILE *stream) { return realfd; } -//! Opaque handle to a Perl scalar value -typedef struct zeroperl_value_s { - SV *sv; -} zeroperl_value; - -//! Opaque handle to a Perl array -typedef struct zeroperl_array_s { - AV *av; -} zeroperl_array; - -//! Opaque handle to a Perl hash -typedef struct zeroperl_hash_s { - HV *hv; -} zeroperl_hash; - -//! Opaque handle to a Perl code reference -typedef struct zeroperl_code_s { - CV *cv; -} zeroperl_code; - -//! Result structure for operations that return multiple values -typedef struct zeroperl_result_s { - int count; - zeroperl_value **values; -} zeroperl_result; - -//! Iterator for hash traversal -typedef struct zeroperl_hash_iter_s { - HV *hv; - HE *entry; -} zeroperl_hash_iter; - -//! Context type for calling Perl code -typedef enum { - ZEROPERL_VOID, - ZEROPERL_SCALAR, - ZEROPERL_LIST -} zeroperl_context_type; - -//! Value type enumeration -typedef enum { - ZEROPERL_TYPE_UNDEF, - ZEROPERL_TYPE_TRUE, - ZEROPERL_TYPE_FALSE, - ZEROPERL_TYPE_INT, - ZEROPERL_TYPE_DOUBLE, - ZEROPERL_TYPE_STRING, - ZEROPERL_TYPE_ARRAY, - ZEROPERL_TYPE_HASH, - ZEROPERL_TYPE_CODE, - ZEROPERL_TYPE_REF -} zeroperl_type; - -//! Operation type for unified context -typedef enum { - ZEROPERL_OP_INIT, - ZEROPERL_OP_EVAL, - ZEROPERL_OP_RUN_FILE, - ZEROPERL_OP_RESET, - ZEROPERL_OP_CALL -} zeroperl_op_type; - -//! Unified context structure for all Perl operations -typedef struct { - zeroperl_op_type op_type; - int result; - union { - struct { - int argc; - char **argv; - } init; - struct { - const char *code; - int argc; - char **argv; - zeroperl_context_type context; - } eval; - struct { - const char *filepath; - int argc; - char **argv; - } run_file; - struct { - const char *name; - int argc; - zeroperl_value **argv; - zeroperl_context_type context; - } call; - } data; -} zeroperl_context; - //! Host-implemented function for calling back into the host environment ZEROPERL_IMPORT("call_host_function") zeroperl_value *host_call_function(int32_t func_id, int32_t argc, @@ -2241,7 +2150,8 @@ EXTERN_C void boot_Cwd(pTHX_ CV *cv); EXTERN_C void boot_List__Util(pTHX_ CV *cv); EXTERN_C void boot_Fcntl(pTHX_ CV *cv); EXTERN_C void boot_Opcode(pTHX_ CV *cv); -EXTERN_C void boot_Time__HiRes(pTHX_ CV* cv); +EXTERN_C void boot_Time__HiRes(pTHX_ CV *cv); +EXTERN_C void boot_ZeroPerl__Web(pTHX_ CV *cv); static void xs_init(pTHX) { static const char file[] = __FILE__; @@ -2285,4 +2195,5 @@ static void xs_init(pTHX) { newXS("Fcntl::bootstrap", boot_Fcntl, file); newXS("Opcode::bootstrap", boot_Opcode, file); newXS("Time::HiRes::bootstrap", boot_Time__HiRes, file); + newXS("ZeroPerl::Web::bootstrap", boot_ZeroPerl__Web, file); } \ No newline at end of file diff --git a/stubs/zeroperl.h b/stubs/zeroperl.h new file mode 100644 index 0000000..abb910e --- /dev/null +++ b/stubs/zeroperl.h @@ -0,0 +1,118 @@ +#ifndef ZEROPERL_H +#define ZEROPERL_H + +#include "EXTERN.h" +#include "XSUB.h" +#include "perl.h" +#include + + +// Defined for SFS (Simple File System) prefix +#ifndef SFS_BUILTIN_PREFIX +#define SFS_BUILTIN_PREFIX "builtin:" +#endif + +// Opaque handle to a Perl scalar value +typedef struct zeroperl_value_s { + SV *sv; +} zeroperl_value; + +// Opaque handle to a Perl array +typedef struct zeroperl_array_s { + AV *av; +} zeroperl_array; + +// Opaque handle to a Perl hash +typedef struct zeroperl_hash_s { + HV *hv; +} zeroperl_hash; + +// Opaque handle to a Perl code reference +typedef struct zeroperl_code_s { + CV *cv; +} zeroperl_code; + +// Result structure for operations that return multiple values +typedef struct zeroperl_result_s { + int count; + zeroperl_value **values; +} zeroperl_result; + +// Iterator for hash traversal +typedef struct zeroperl_hash_iter_s { + HV *hv; + HE *entry; +} zeroperl_hash_iter; + +// Context type for calling Perl code +typedef enum { + ZEROPERL_VOID, + ZEROPERL_SCALAR, + ZEROPERL_LIST +} zeroperl_context_type; + +// Value type enumeration +typedef enum { + ZEROPERL_TYPE_UNDEF, + ZEROPERL_TYPE_TRUE, + ZEROPERL_TYPE_FALSE, + ZEROPERL_TYPE_INT, + ZEROPERL_TYPE_DOUBLE, + ZEROPERL_TYPE_STRING, + ZEROPERL_TYPE_ARRAY, + ZEROPERL_TYPE_HASH, + ZEROPERL_TYPE_CODE, + ZEROPERL_TYPE_REF +} zeroperl_type; + +// Operation type for unified context +typedef enum { + ZEROPERL_OP_INIT, + ZEROPERL_OP_EVAL, + ZEROPERL_OP_RUN_FILE, + ZEROPERL_OP_RESET, + ZEROPERL_OP_CALL +} zeroperl_op_type; + +// Unified context structure for all Perl operations +typedef struct { + zeroperl_op_type op_type; + int result; + union { + struct { + int argc; + char **argv; + } init; + struct { + const char *code; + int argc; + char **argv; + zeroperl_context_type context; + } eval; + struct { + const char *filepath; + int argc; + char **argv; + } run_file; + struct { + const char *name; + int argc; + zeroperl_value **argv; + zeroperl_context_type context; + } call; + } data; +} zeroperl_context; + +// Cleanup helper +void zeroperl_value_free(zeroperl_value *val); + +// Host call function +zeroperl_value *host_call_function(int32_t func_id, int32_t argc, + zeroperl_value **argv); + +// Error handling functions +void zeroperl_set_host_error(const char *error); +const char *zeroperl_get_host_error(void); +void zeroperl_clear_host_error(void); + +#endif // ZEROPERL_H diff --git a/stubs/zeroperl_web.c b/stubs/zeroperl_web.c new file mode 100644 index 0000000..e512999 --- /dev/null +++ b/stubs/zeroperl_web.c @@ -0,0 +1,115 @@ +#include "EXTERN.h" +#include "XSUB.h" +#include "perl.h" +#include "zeroperl.h" + + +// Define host function IDs for Web APIs +#define HOST_FUNC_FETCH 1001 +#define HOST_FUNC_SLEEP 1002 + +// External declaration for the host call function +extern zeroperl_value *host_call_function(int32_t func_id, int32_t argc, + zeroperl_value **argv); + +// Helper to create a zeroperl_value from SV +static zeroperl_value *create_zeroperl_value(pTHX_ SV *sv) { + zeroperl_value *val = (zeroperl_value *)malloc(sizeof(zeroperl_value)); + if (val) { + val->sv = sv; + SvREFCNT_inc(sv); + } + return val; +} + +// Helper to process result from host call +static void process_host_result(pTHX_ zeroperl_value *result) { + if (result && result->sv) { + ST(0) = sv_2mortal(SvREFCNT_inc(result->sv)); + zeroperl_value_free(result); + XSRETURN(1); + } else { + if (result) + zeroperl_value_free(result); + XSRETURN_UNDEF; + } +} + +XS(XS_ZeroPerl__Web_fetch) { + dXSARGS; + zeroperl_value **argv; + zeroperl_value *result; + int i; + + if (items < 1) { + croak("Usage: ZeroPerl::Web::fetch(url, [options])"); + } + + // Prepare arguments for host call + argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * items); + if (!argv) { + croak("Failed to allocate memory for arguments"); + } + + for (i = 0; i < items; i++) { + argv[i] = create_zeroperl_value(aTHX_ ST(i)); + if (!argv[i]) { + // Cleanup already allocated + while (--i >= 0) { + zeroperl_value_free(argv[i]); + } + free(argv); + croak("Failed to create zeroperl_value"); + } + } + + // Make the host call + result = host_call_function(HOST_FUNC_FETCH, items, argv); + + // Cleanup arguments + for (i = 0; i < items; i++) { + zeroperl_value_free(argv[i]); + } + free(argv); + + process_host_result(aTHX_ result); +} + +XS(XS_ZeroPerl__Web_sleep) { + dXSARGS; + zeroperl_value **argv; + zeroperl_value *result; + + if (items != 1) { + croak("Usage: ZeroPerl::Web::sleep(ms)"); + } + + argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * 1); + if (!argv) { + croak("Failed to allocate memory for arguments"); + } + + argv[0] = create_zeroperl_value(aTHX_ ST(0)); + if (!argv[0]) { + free(argv); + croak("Failed to create zeroperl_value"); + } + + result = host_call_function(HOST_FUNC_SLEEP, 1, argv); + + zeroperl_value_free(argv[0]); + free(argv); + + process_host_result(aTHX_ result); +} + +// Bootstrap function +XS(boot_ZeroPerl__Web) { + dXSARGS; + char *file = __FILE__; + + newXS("ZeroPerl::Web::fetch", XS_ZeroPerl__Web_fetch, file); + newXS("ZeroPerl::Web::sleep", XS_ZeroPerl__Web_sleep, file); + + XSRETURN_YES; +} diff --git a/zeroperl-ts-new b/zeroperl-ts-new new file mode 160000 index 0000000..e9943a6 --- /dev/null +++ b/zeroperl-ts-new @@ -0,0 +1 @@ +Subproject commit e9943a64b7a4f659ce9c973907acbddb200225ad