File Coverage

blib/lib/JavaScript/QuickJS.pm
Criterion Covered Total %
statement 19 19 100.0
branch 4 4 100.0
condition n/a
subroutine 7 7 100.0
pod 2 2 100.0
total 32 32 100.0


line stmt bran cond sub pod time code
1             package JavaScript::QuickJS;
2              
3 21     21   2635974 use strict;
  21         43  
  21         769  
4 21     21   128 use warnings;
  21         36  
  21         1332  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             JavaScript::QuickJS - Run JavaScript via L in Perl
11              
12             =head1 SYNOPSIS
13              
14             Quick and dirty …
15              
16             my $val = JavaScript::QuickJS->new()->eval( q<
17             let foo = "bar";
18             [ "The", "last", "value", "is", "returned." ];
19             > );
20              
21             … or load ES6 modules:
22              
23             my $js = JavaScript::QuickJS->new()->helpers();
24              
25             $js->eval_module( q/
26             import * as coolStuff from 'cool/stuff';
27              
28             for (const [key, value] of Object.entries(coolStuff)) {
29             console.log(key, value);
30             }
31             / );
32              
33             =head1 DESCRIPTION
34              
35             This library embeds Fabrice Bellard’s L
36             engine into a Perl XS module. You can thus run JavaScript
37             (L specification) directly in your
38             Perl programs.
39              
40             This distribution includes all needed C code; unlike with most XS modules
41             that interface with C libraries, you don’t need QuickJS pre-installed on
42             your system.
43              
44             =cut
45              
46             # ----------------------------------------------------------------------
47              
48 21     21   124 use XSLoader;
  21         43  
  21         7073  
49              
50             our $VERSION = '0.21';
51              
52             XSLoader::load( __PACKAGE__, $VERSION );
53              
54             # ----------------------------------------------------------------------
55              
56             =head1 METHODS
57              
58             =head2 $obj = I->new( %CONFIG_OPTS )
59              
60             Instantiates I. %CONFIG_OPTS have the same effect as in
61             C below.
62              
63             =cut
64              
65             sub new {
66 42     42 1 3912105 my ($class, %opts) = @_;
67              
68 42         27053 my $self = $class->_new();
69              
70 42 100       1553 return %opts ? $self->configure(%opts) : $self;
71             }
72              
73             =head2 $obj = I->configure( %OPTS )
74              
75             Tunes the QuickJS interpreter. Returns I.
76              
77             %OPTS are any of:
78              
79             =over
80              
81             =item * C
82              
83             =item * C
84              
85             =item * C
86              
87             =back
88              
89             For more information on these, see QuickJS itself.
90              
91             =cut
92              
93             sub configure {
94 10     10 1 40 my ($self, %opts) = @_;
95              
96 10         38 my ($stack, $memlimit, $gc_threshold) = delete @opts{'max_stack_size', 'memory_limit', 'gc_threshold'};
97              
98 10 100       49 if (my @extra = sort keys %opts) {
99 2         612 Carp::croak("Unknown: @extra");
100             }
101              
102 8         860 return $self->_configure($stack, $memlimit, $gc_threshold);
103             }
104              
105             #----------------------------------------------------------------------
106              
107             =head2 $obj = I->set_globals( NAME1 => VALUE1, .. )
108              
109             Sets 1 or more globals in I. See below for details on type conversions
110             from Perl to JavaScript.
111              
112             Returns I.
113              
114             =head2 $obj = I->helpers()
115              
116             Defines QuickJS’s “helpers”, e.g., C.
117              
118             Returns I.
119              
120             =head2 $obj = I->std()
121              
122             Enables QuickJS’s C module and creates a global of the same name
123             that’s usable from both script and module modes.
124              
125             This resembles C’s C<--std> flag except that it I enables
126             C, not C.
127              
128             Returns I.
129              
130             =head2 $obj = I->os()
131              
132             Like C but enables QuickJS’s C module instead of C.
133              
134             =head2 $VALUE = I->eval( $JS_CODE )
135              
136             Like running C. Returns $JS_CODE’s last value;
137             see below for details on type conversions from JavaScript to Perl.
138              
139             Untrapped exceptions in JavaScript will be rethrown as Perl exceptions.
140              
141             $JS_CODE is a I string.
142              
143             =head2 $promise = I->eval_module( $JS_CODE )
144              
145             Runs $JS_CODE as a module, which enables ES6 module syntax.
146             Note that no values can be returned directly in this mode of execution.
147              
148             Returns a promise that resolves once the module is loaded.
149              
150             =head2 $obj = I->await()
151              
152             Blocks until all of I’s pending work (if any) is complete.
153              
154             For example, if you C some code that creates a promise, call
155             this to wait for that promise to complete.
156              
157             Returns I.
158              
159             =head2 $obj = I->set_module_base( $PATH )
160              
161             Sets a base path (a byte string) for ES6 module imports.
162              
163             Returns I.
164              
165             =head2 $obj = I->unset_module_base()
166              
167             Restores QuickJS’s default directory for ES6 module imports
168             (as of this writing, it’s the process’s current directory).
169              
170             Returns I.
171              
172             =cut
173              
174             # ----------------------------------------------------------------------
175              
176             =head1 TYPE CONVERSION: JAVASCRIPT → PERL
177              
178             This module converts returned values from JavaScript thus:
179              
180             =over
181              
182             =item * JS string primitives become I strings in Perl.
183              
184             =item * JS number & boolean primitives become corresponding Perl values.
185              
186             =item * JS null & undefined become Perl undef.
187              
188             =item * JS objects …
189              
190             =over
191              
192             =item * Arrays become Perl array references.
193              
194             =item * “Plain” objects become Perl hash references.
195              
196             =item * Function, RegExp, and Date objects become Perl
197             L, L,
198             and L objects, respectively.
199              
200             =item * Behaviour is B for other object types.
201              
202             =back
203              
204             =back
205              
206             =head1 TYPE CONVERSION: PERL → JAVASCRIPT
207              
208             Generally speaking, it’s the inverse of JS → Perl:
209              
210             =over
211              
212             =item * Perl strings, numbers, & booleans become corresponding JavaScript
213             primitives.
214              
215             B Perl versions before 5.36 don’t reliably distinguish “numeric
216             strings” from “numbers”. If your perl predates 5.36, typecast accordingly
217             to prevent your Perl “number” from becoming a JavaScript string. (Even in
218             5.36 and later it’s still a good idea.)
219              
220             =item * Perl undef becomes JS null.
221              
222             =item * Unblessed array & hash references become JavaScript arrays and
223             “plain” objects.
224              
225             =item * L booleans become JavaScript booleans.
226              
227             =item * Perl code references become JavaScript functions.
228              
229             =item * Perl L, L,
230             and L objects become their original
231             JavaScript objects.
232              
233             =item * Anything else triggers an exception.
234              
235             =back
236              
237             =head1 MEMORY HANDLING NOTES
238              
239             If any instance of a class of this distribution is DESTROY()ed at Perl’s
240             global destruction, we assume that this is a memory leak, and a warning is
241             thrown. To prevent this, avoid circular references, and clean up all global
242             instances.
243              
244             Callbacks make that tricky. When you give a JavaScript function to Perl,
245             that Perl object holds a reference to the QuickJS context. Only once that
246             object is Ced do we release that QuickJS context reference.
247              
248             Consider the following:
249              
250             my $return;
251              
252             $js->set_globals( __return => sub { $return = shift; () } );
253              
254             $js->eval('__return( a => a )');
255              
256             This sets $return to be a L instance. That
257             object holds a reference to $js. $js also stores C<__return()>,
258             which is a Perl code reference that closes around $return. Thus, we have
259             a reference cycle: $return refers to $js, and $js refers to $return. Those
260             two values will thus leak, and you’ll see a warning about it at Perl’s
261             global destruction time.
262              
263             To break the reference cycle, just do:
264              
265             undef $return;
266              
267             … once you’re done with that variable.
268              
269             You I have thought you could instead do:
270              
271             $js->set_globals( __return => undef )
272              
273             … but that doesn’t work because $js holds a reference to all Perl code
274             references it B receives. This is because QuickJS, unlike Perl,
275             doesn’t expose object destructors (C in Perl), so there’s no
276             good way to release that reference to the code reference.
277              
278             =head1 CHARACTER ENCODING NOTES
279              
280             QuickJS (like all JS engines) assumes its strings are text. Since Perl
281             can’t distinguish text from bytes, though, it’s possible to convert
282             Perl byte strings to JavaScript strings. It often yields a reasonable
283             result, but not always.
284              
285             One place where this falls over, though, is ES6 modules. QuickJS, when
286             it loads an ES6 module, decodes that module’s string literals to characters.
287             Thus, if you pass in byte strings from Perl, QuickJS will treat your
288             Perl byte strings’ code points as character code points, and when you
289             combine those code points with those from your ES6 module you may
290             get mangled output.
291              
292             Another place that may create trouble is if your argument to C
293             or C (above) contains JSON. Perl’s popular JSON encoders
294             output byte strings by default, but as noted above, C and
295             C need I strings. So either configure your
296             JSON encoder to output characters, or decode JSON bytes to characters
297             before calling C/C.
298              
299             For best results, I interact with QuickJS via I
300             strings, and double-check that you’re doing it that way consistently.
301              
302             =head1 NUMERIC PRECISION
303              
304             Note the following if you expect to deal with “large” numbers:
305              
306             =over
307              
308             =item * JavaScript’s numeric-precision limits apply. (cf.
309             L.)
310              
311             =item * Perl’s stringification of numbers may be I precise than
312             JavaScript’s storage of those numbers, or even than Perl’s own storage.
313             For example, in Perl 5.34 C prints C<1e+15>.
314              
315             To counteract this loss of precision, add 0 to Perl’s numeric scalars
316             (e.g., C); this will encourage Perl to store
317             numbers as integers when possible, which fixes this precision problem.
318              
319             =item * Long-double and quad-math perls may lose precision when converting
320             numbers to/from JavaScript. To see if this affects your perl—which, if
321             you’re unsure, it probably doesn’t—run C, and see if that perl’s
322             compile-time options mention long doubles or quad math.
323              
324             =back
325              
326             =head1 OS SUPPORT
327              
328             QuickJS supports Linux, macOS, and Windows natively, so these work without
329             issue.
330              
331             FreeBSD, OpenBSD, & Cygwin work after a few patches that we apply when
332             building this library. (Hopefully these will eventually merge into QuickJS.)
333              
334             =head1 LIBATOMIC
335              
336             QuickJS uses C11 atomics. Most platforms implement that functionality in
337             hardware, but others (e.g., arm32) don’t. To fill that void, we need to link
338             to libatomic.
339              
340             This library’s build logic detects whether libatomic is necessary and will
341             only link to it if needed. If, for some reason, you need manual control over
342             that linking, set C in the environment to 1 or a
343             falsy value.
344              
345             If you don’t know what any of that means, you can probably ignore it.
346              
347             =head1 SEE ALSO
348              
349             Other JavaScript modules on CPAN include:
350              
351             =over
352              
353             =item * L and L make the
354             L library available to Perl. They’re similar to
355             this library, but Duktape itself (as of this writing) lacks support for
356             several JavaScript constructs that QuickJS supports. (It’s also slower.)
357              
358             =item * L and L expose Google’s
359             L library to Perl. Neither seems to support current
360             V8 versions.
361              
362             =item * L is a pure-Perl (!) JavaScript engine.
363              
364             =item * L and L expose Mozilla’s
365             L engine to Perl.
366              
367             =back
368              
369             =head1 LICENSE & COPYRIGHT
370              
371             This library is copyright 2023 Gasper Software Consulting.
372              
373             This library is licensed under the same terms as Perl itself.
374             See L.
375              
376             QuickJS is copyright Fabrice Bellard and Charlie Gordon. It is released
377             under the L.
378              
379             =cut
380              
381             #----------------------------------------------------------------------
382              
383             package JavaScript::QuickJS::JSObject;
384              
385             package JavaScript::QuickJS::RegExp;
386              
387             our @ISA;
388 21     21   1478 BEGIN { @ISA = 'JavaScript::QuickJS::JSObject' };
389              
390             package JavaScript::QuickJS::Function;
391              
392             our @ISA;
393 21     21   966 BEGIN { @ISA = 'JavaScript::QuickJS::JSObject' };
394              
395             1;