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