Compare commits

..

No commits in common. 'master' and 'gh-pages' have entirely different histories.

4
.gitattributes vendored

@ -1,4 +0,0 @@
# See http://bitbucket.org/haukex/htools/src/HEAD/htmlrescache
# Set up via: htmlrescache -cweb/_cache init
/web/*.html filter=htmlrescache
/web/**/*.html filter=htmlrescache

@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the issue, including any relevant code in the form of a representative [Short, Self Contained, Correct (Compilable), Example](http://sscce.org/), and sample input.
**Expected behavior vs. actual behavior**
A clear and concise description of what you expected to happen, and what actually happened instead. Include expected output for the sample input given above, and the actual output you're getting including exact copies of any error messages.
**Versions**
- Device: [desktop or mobile; specify model]
- OS: [e.g. Ubuntu Linux 16.04, Windows 10, etc.]
- Browser and version: [e.g. Firefox 61, Chrome 68, etc.]
- WebPerl: [e.g. v0.01-beta]
If building:
- Perl: [e.g. v5.26.2]
- Emscripten: [e.g. 1.38.10]
- Any other versions that may be relevant, such as compiler, libraries, etc.
**Additional context**
Add any other context about the problem here. If applicable, add screenshots to help explain your problem.

12
.gitignore vendored

@ -1,13 +1,3 @@
/*.zip
/web/
/work/
/emperl5/
/web/emperl.js
/web/emperl.wasm
/web/emperl.data
/web/_cache/
/pages/
/wiki/
# For experimental P6 support:
/web/perl6.js
/web/6demo.html
/web/test6.html

@ -0,0 +1 @@
webperl.zero-g.net

@ -1,59 +0,0 @@
WebPerl Changelog
=================
2019-08-03: v0.11-beta
----------------------
- Updated for Emscripten 1.38.31 / latest Fastcomp (1.38.40) and Perl v5.30.0
2019-03-03: v0.09-beta
----------------------
- Updated for Emscripten 1.38.28 and Perl v5.28.1
- Added experimental Perl 6 support
- Added modules Future, Digest::MD5, and Digest::SHA
- Added Perl.exitStatus
- Updated regex_tester.html
- Added "Code Demo Editor" in web/democode/
- Added "cpanfile"s for dependencies
- Minor fixes and updates to build.pl
2018-09-04: v0.07-beta
----------------------
- Updated regex_tester.html (improvements and bugfixes)
- Added WebPerl::JSObject::jscode()
2018-09-02: v0.05-beta
----------------------
- Added Perl.addStateChangeListener and deprecated Perl.stateChanged
- Added WebPerl::js_new()
- Added regex_tester.html
- Added Perl.noMountIdbfs
- A few other minor fixes and updates
2018-08-14: v0.03-beta
----------------------
- Fixed an issue with WebPerl::JSObject::toperl()
where JS objects were not being converted properly.
- Added AJAX demo
- Added WebPerl autoloading for script tags
- Various small changes, bugfixes and enhancements
(mostly not user-visible)
- Added `runtests.html` and `experiments` dir
2018-08-12: v0.01-beta
----------------------
- First public release

Binary file not shown.

Binary file not shown.

@ -1,158 +0,0 @@
NAME
perlartistic - the Perl Artistic License
SYNOPSIS
You can refer to this document in Pod via "L<perlartistic>"
Or you can see this document by entering "perldoc perlartistic"
DESCRIPTION
Perl is free software; you can redistribute it and/or modify it under
the terms of either:
a) the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or
b) the "Artistic License" which comes with this Kit.
This is "The Artistic License". It's here so that modules, programs,
etc., that want to declare this as their distribution license can link
to it.
For the GNU General Public License, see perlgpl.
The "Artistic License"
Preamble
The intent of this document is to state the conditions under which a
Package may be copied, such that the Copyright Holder maintains some
semblance of artistic control over the development of the package, while
giving the users of the package the right to use and distribute the
Package in a more-or-less customary fashion, plus the right to make
reasonable modifications.
Definitions
"Package"
refers to the collection of files distributed by the Copyright
Holder, and derivatives of that collection of files created through
textual modification.
"Standard Version"
refers to such a Package if it has not been modified, or has been
modified in accordance with the wishes of the Copyright Holder as
specified below.
"Copyright Holder"
is whoever is named in the copyright or copyrights for the package.
"You"
is you, if you're thinking about copying or distributing this
Package.
"Reasonable copying fee"
is whatever you can justify on the basis of media cost, duplication
charges, time of people involved, and so on. (You will not be
required to justify it to the Copyright Holder, but only to the
computing community at large as a market that must bear the fee.)
"Freely Available"
means that no fee is charged for the item itself, though there may
be fees involved in handling the item. It also means that recipients
of the item may redistribute it under the same conditions they
received it.
Conditions
1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that
you duplicate all of the original copyright notices and associated
disclaimers.
2. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A
Package modified in such a way shall still be considered the
Standard Version.
3. You may otherwise modify your copy of this Package in any way,
provided that you insert a prominent notice in each changed file
stating how and when you changed that file, and provided that you do
at least ONE of the following:
a) place your modifications in the Public Domain or otherwise make
them Freely Available, such as by posting said modifications to
Usenet or an equivalent medium, or placing the modifications on
a major archive site such as uunet.uu.net, or by allowing the
Copyright Holder to include your modifications in the Standard
Version of the Package.
b) use the modified Package only within your corporation or
organization.
c) rename any non-standard executables so the names do not conflict
with standard executables, which must also be provided, and
provide a separate manual page for each non-standard executable
that clearly documents how it differs from the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
4. You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following:
a) distribute a Standard Version of the executables and library
files, together with instructions (in the manual page or
equivalent) on where to get the Standard Version.
b) accompany the distribution with the machine-readable source of
the Package with your modifications.
c) give non-standard executables non-standard names, and clearly
document the differences in manual pages (or equivalent),
together with instructions on where to get the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
5. You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this
Package. You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial)
software distribution provided that you do not advertise this
Package as a product of your own. You may embed this Package's
interpreter within an executable of yours (by linking); this shall
be construed as a mere form of aggregation, provided that the
complete Standard Version of the interpreter is so embedded.
6. The scripts and library files supplied as input to or produced as
output from the programs of this Package do not automatically fall
under the copyright of this Package, but belong to whoever generated
them, and may be sold commercially, and may be aggregated with this
Package. If such scripts or library files are aggregated with this
Package via the so-called "undump" or "unexec" methods of producing
a binary executable image, then distribution of such an image shall
neither be construed as a distribution of this Package nor shall it
fall under the restrictions of Paragraphs 3 and 4, provided that you
do not represent such an executable image as a Standard Version of
this Package.
7. C subroutines (or comparably compiled subroutines in other
languages) supplied by you and linked into this Package in order to
emulate subroutines and variables of the language defined by this
Package shall not be considered part of this Package, but are the
equivalent of input as in Paragraph 6, provided these subroutines do
not change the language in any way that would cause it to fail the
regression tests for the language.
8. Aggregation of this Package with a commercial distribution is always
permitted provided that the use of this Package is embedded; that
is, when no overt attempt is made to make this Package's interfaces
visible to the end user of the commercial distribution. Such use
shall not be construed as a distribution of this Package.
9. The name of the Copyright Holder may not be used to endorse or
promote products derived from this software without specific prior
written permission.
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End

@ -1,281 +0,0 @@
NAME
perlgpl - the GNU General Public License, version 1
SYNOPSIS
You can refer to this document in Pod via "L<perlgpl>"
Or you can see this document by entering "perldoc perlgpl"
DESCRIPTION
Perl is free software; you can redistribute it and/or modify it under
the terms of either:
a) the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or
b) the "Artistic License" which comes with this Kit.
This is the "GNU General Public License, version 1". It's here so that
modules, programs, etc., that want to declare this as their distribution
license can link to it.
For the Perl Artistic License, see perlartistic.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 1, February 1989
Copyright (C) 1989 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The license agreements of most software companies try to keep users
at the mercy of those companies. By contrast, our General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. The
General Public License applies to the Free Software Foundation's
software and to any other program whose authors commit to using it.
You can use it for your programs, too.
When we speak of free software, we are referring to freedom, not
price. Specifically, the General Public License is designed to make
sure that you have the freedom to give away or sell copies of free
software, that you receive source code or can get it if you want it,
that you can change the software or use pieces of it in new free
programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of a such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must tell them their rights.
We protect your rights with two steps: (1) copyright the software,
and (2) offer you this license which gives you legal permission to
copy, distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on,
we want its recipients to know that what they have is not the original,
so that any problems introduced by others will not reflect on the
original authors' reputations.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any program or other work which
contains a notice placed by the copyright holder saying it may be
distributed under the terms of this General Public License. The
"Program", below, refers to any such program or work, and a "work based
on the Program" means either the Program or any work containing the
Program or a portion of it, either verbatim or with modifications.
Each licensee is addressed as "you".
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this General Public License and to the absence of
any warranty; and give any other recipients of the Program a copy of
this General Public License along with the Program. You may charge a
fee for the physical act of transferring a copy.
2. You may modify your copy or copies of the Program or any portion
of it, and copy and distribute such modifications under the terms of
Paragraph 1 above, provided that you also do the following:
a) cause the modified files to carry prominent notices stating that
you changed the files and the date of any change; and
b) cause the whole of any work that you distribute or publish, that
in whole or in part contains the Program or any part thereof,
either with or without modifications, to be licensed at no charge
to all third parties under the terms of this General Public License
(except that you may choose to grant warranty protection to some or
all third parties, at your option).
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the simplest and most usual way, to print or
display an announcement including an appropriate copyright notice
and a notice that there is no warranty (or else, saying that you
provide a warranty) and that users may redistribute the program
under these conditions, and telling the user how to view a copy of
this General Public License.
d) You may charge a fee for the physical act of transferring a
copy, and you may at your option offer warranty protection in
exchange for a fee.
Mere aggregation of another independent work with the Program (or its
derivative) on a volume of a storage or distribution medium does not
bring the other work under the scope of these terms.
3. You may copy and distribute the Program (or a portion or
derivative of it, under Paragraph 2) in object code or executable form
under the terms of Paragraphs 1 and 2 above provided that you also do
one of the following:
a) accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of
Paragraphs 1 and 2 above; or,
b) accompany it with a written offer, valid for at least three
years, to give any third party free (except for a nominal charge
for the cost of distribution) a complete machine-readable copy of
the corresponding source code, to be distributed under the terms of
Paragraphs 1 and 2 above; or,
c) accompany it with the information you received as to where the
corresponding source code may be obtained. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form alone.)
Source code for a work means the preferred form of the work for making
modifications to it. For an executable file, complete source code
means all the source code for all modules it contains; but, as a
special exception, it need not include source code for modules which
are standard libraries that accompany the operating system on which the
executable file runs, or for standard header files or definitions files
that accompany that operating system.
4. You may not copy, modify, sublicense, distribute or transfer the
Program except as expressly provided under this General Public License.
Any attempt otherwise to copy, modify, sublicense, distribute or
transfer the Program is void, and will automatically terminate your
rights to use the Program under this License. However, parties who
have received copies, or rights to use copies, from you under this
General Public License will not have their licenses terminated so long
as such parties remain in full compliance.
5. By copying, distributing or modifying the Program (or any work
based on the Program) you indicate your acceptance of this license to
do so, and all its terms and conditions.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
7. The Free Software Foundation may publish revised and/or new
versions of the General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of the license which applies to it and "any
later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Program does not specify a
version number of the license, you may choose any version ever
published by the Free Software Foundation.
8. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the
author to ask for permission. For software which is copyrighted by the
Free Software Foundation, write to the Free Software Foundation; we
sometimes make exceptions for this. Our decision will be guided by the
two goals of preserving the free status of all derivatives of our free
software and of promoting the sharing and reuse of software generally.
NO WARRANTY
9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS
WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
Appendix: How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to humanity, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it
does.>
Copyright (C) 19yy <name of author>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 1, or (at
your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA
02110-1301 USA
Also add information on how to contact you by electronic and paper
mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19xx name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type
'show w'. This is free software, and you are welcome to
redistribute it under certain conditions; type 'show c' for
details.
The hypothetical commands 'show w' and 'show c' should show the
appropriate parts of the General Public License. Of course, the
commands you use may be called something other than 'show w' and 'show
c'; they could even be mouse-clicks or menu items--whatever suits your
program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
program 'Gnomovision' (a program to direct compilers to make passes
at assemblers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
That's all there is to it!

@ -0,0 +1,7 @@
theme: jekyll-theme-minimal
title: WebPerl
description: Run Perl in the browser with WebPerl!
logo: webperl.png
show_downloads: false
exclude:
- CNAME

@ -0,0 +1,52 @@
#!/usr/bin/env perl
use warnings;
use 5.026;
use FindBin ();
use Path::Class qw/dir file/;
# A quick and dirty script for importing stuff from webperl/master to gh-pages
sub edit (&$$) {
my ($code,$source,$dest) = @_;
state $basedir = dir($FindBin::Bin)->parent->parent;
local $_ = file($source)->absolute($basedir)->slurp(iomode=>'<:raw:encoding(UTF-8)');
$code->();
file($dest)->absolute($basedir)->spew(iomode=>'>:raw:encoding(UTF-8)', $_);
}
edit {
s{ iframe.perleditor\s*\{ [^\}]* border: \s* \K \N* (?=\n) }{1px dotted lightgrey;}xmsg==1 or die;
s{ <!--(?<x>script\s+src="http.+?iframeResizer.min.js"[^>]+crossorigin[^>]+></script)--> }{<$+{x}>}xmsg==1 or die;
s{ ^ \s* \K /[/*] (?= \s* iFrameResize ) }{}xmsg==2 or die;
} 'web/democode/demo.html', 'pages/democode/index.html';
edit {
s{ <!-- [^>]* \K demo.html (?= [^>]* --> ) }{index.html}xmsg==1 or die;
s{ <!--(?<x>script\s+src="http.+?iframeResizer.contentWindow.min.js"[^>]+crossorigin[^>]+></script)--> }{<$+{x}>}xmsg==1 or die;
} 'web/democode/perleditor.html', 'pages/democode/perleditor.html';
edit {
s{ <!-- [^>]* \K demo.html (?= [^>]* --> ) }{index.html}xmsg==1 or die;
s{ <(?<x>script\s+src="[^"]*webperl\.js"\s*></script)> }{<!--$+{x}-->}xmsg==1 or die;
s{ <!--(?<x>script\s+src="http.+?webperl\.js"[^>]+crossorigin[^>]+></script)--> }{<$+{x}>}xmsg==1 or die;
} 'web/democode/perlrunner.html', 'pages/democode/perlrunner.html';
edit {
} 'web/democode/perleditor.css', 'pages/democode/perleditor.css';
edit {
my $msg = <<'ENDMSG';
This is essentially a copy of
https://github.com/haukex/webperl/blob/master/web/regex_tester.html
with the following differences:
- webperl.js from CDN
- $RUN_CODE_IN_IFRAME enabled
- URL updated to https://github.com/haukex/webperl/blob/gh-pages/regex.html
(see import_regex_tester.pl)
ENDMSG
s{ <(?<x>script\s+src="(?:webperl\.js|__WEBPERLURL__)"\s*></scr_*ipt)> }{<!--$+{x}-->}xmsg==2 or die;
s{ <!--(?<x>script\s+src="http.+?webperl\.js"[^>]+crossorigin[^>]+></scr_*ipt)--> }{<$+{x}>}xmsg==2 or die;
s{ ^ \s* our \s+ \$RUN_CODE_IN_IFRAME\s*=\s*\K[01](?=\s*;\s*) }{1}xmsg==1 or die;
s{ https?://github.com/haukex/webperl/blob/\Kmaster/web/regex_tester.html }{gh-pages/regex.html}xmsg==1 or die;
s{ \#\#\#\#\#\s*-->\n\K }{\n<!-- $msg-->\n}xmsg==1 or die;
} 'web/regex_tester.html', 'pages/regex.html';

@ -0,0 +1,36 @@
#!/usr/bin/env perl
use warnings;
use strict;
use open qw/:std :utf8/;
use FindBin ();
# Generate a preview of the site using `markdown`
# (I use this mostly just to check for any Markdown syntax mistakes)
#TODO Later: Use a markdown processor that handles GitHub's markdown enhancements?
my $dir = $FindBin::Bin.'/..';
opendir my $dh, $dir or die $!;
my @files = grep { ! -d } map { "$dir/$_" } sort grep {/\.md\z/i} readdir $dh;
close $dh;
print <<'ENDHTML';
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Site Preview</title>
</head>
<body>
ENDHTML
print "<hr/>\n";
for my $f (@files) {
system('markdown',$f)==0
or die "markdown failed, \$?=$?";
print "<hr/>\n";
}
print <<'ENDHTML';
</body>
</html>
ENDHTML

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,497 +0,0 @@
#!/usr/bin/env perl
use warnings;
use 5.026;
=head1 SYNOPSIS
Build script for WebPerl; see L<http://webperl.zero-g.net>.
build.pl [OPTIONS]
OPTIONS:
--showconf - Show configuration
--reconfig - Force regeneration config.sh
--forceext - Force fetching of extensions
--applyconfig - Apply any changes to config.sh (sh Configure -S)
--remakeout - Force rebuild of the output directory
--forceemperl - Force rebuild of emperl.js
--dist=FN - Create a distro file "FN.zip"
--verbose - Be more verbose
=head1 Author, Copyright, and License
B<< WebPerl - L<http://webperl.zero-g.net> >>
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, L<http://www.igb-berlin.de>
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
B<WITHOUT ANY WARRANTY>; without even the implied warranty of
B<MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE>.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see L<http://perldoc.perl.org/index-licence.html>.
=cut
use Getopt::Long qw/ HelpMessage :config posix_default gnu_compat bundling auto_version auto_help /;
use Hash::Util qw/lock_hash/;
use Data::Dump;
use Path::Class qw/file dir/;
use IPC::Run3::Shell {show_cmd=>1}, qw/ :FATAL :run git make emmake /;
use ExtUtils::MakeMaker qw/prompt/;
use FindBin ();
use Carp;
use Cwd qw/getcwd abs_path/;
use URI ();
use IO::Socket::SSL 1.56 (); # for HTTP::Tiny
use Net::SSLeay 1.49 (); # for HTTP::Tiny
use HTTP::Tiny ();
use Cpanel::JSON::XS qw/decode_json/;
use File::Temp qw/tempdir/;
use Archive::Tar ();
use File::Copy::Recursive qw/dirmove/;
use File::Replace qw/replace3/;
use Pod::Strip ();
use Archive::Zip qw/AZ_OK/;
GetOptions(\my %opts,'showconf','reconfig','forceext','applyconfig',
'forceemperl','remakeout','dist=s','verbose')
or HelpMessage(-exitval=>255);
# check that emperl_config.sh has been run
die "Please run '. emperl_config.sh' to set up the environment variables.\n"
unless $ENV{EMPERL_PERLVER};
die "Please edit 'emperl_config.sh' to point it to the correct location of 'emsdk_env.sh'\n"
unless $ENV{EMSCRIPTEN} && -d $ENV{EMSCRIPTEN} && $ENV{EMSDK} && -d $ENV{EMSDK};
# copy over config variables from environment
my %C = map {$_=>$ENV{'EMPERL_'.$_}} qw/ EXTENSIONS
HOSTPERLDIR OUTPUTDIR DOWNLOADDIR PERLSRCDIR
PREFIX PERLVER
PERL_REPO PERL_BRANCH CLOBBER_BRANCH /;
$C{$_} = dir($C{$_}) for qw/ HOSTPERLDIR OUTPUTDIR DOWNLOADDIR PERLSRCDIR /;
$C{EXTENSIONS} = [ split ' ', $C{EXTENSIONS} ];
lock_hash %C; # typo prevention
dd \%C if $opts{showconf};
my $VERBOSE = $opts{verbose}?1:0;
my $needs_reconfig = !!$opts{reconfig};
# ##### ##### ##### Step: Patch Emscripten ##### ##### #####
{
my $d = pushd( dir($ENV{EMSCRIPTEN}, 'src') );
# Emscripten's fork() (and system()) stubs return EAGAIN, meaning "Resource temporarily unavailable".
# So perl will wait 5 seconds and try again, which is not helpful to us, since Emscripten doesn't support those functions at all.
# This patch fixes that on the Emscripten side, so the stubs return ENOTSUP.
# first, we need to take a guess which version of the patch to apply.
my $libraryjs = file($ENV{EMSCRIPTEN}, 'src', 'library.js')->slurp;
my $patchf;
if ( $libraryjs=~/\b\Q___setErrNo(ERRNO_CODES.\E(EAGAIN|ENOTSUP)\b/ )
{ $patchf = 'emscripten_1.38.10_eagain.patch' }
elsif ( $libraryjs=~/no shell available\s+setErrNo\Q({{{ cDefine('EAGAIN') }}})\E/ )
{ $patchf = 'emscripten_1.39.16_eagain.patch' }
elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ ) # note that this appears in 1.38.1* versions too
{ $patchf = 'emscripten_1.38.28_eagain.patch' }
else { die "Could not figure out which library.js patch to use" }
#TODO Later: we should probably verify the Emscripten version too, and in the future we may need different patches for different versions
if ( try_patch_file( file($FindBin::Bin,$patchf) ) ) {
say STDERR "# Emscripten was newly patched, forcing a rebuild";
# not sure if the following is needed, but playing it safe:
run 'emcc', '--clear-cache'; # force Emscripten to rebuild libs (takes a bit of time)
$needs_reconfig=1;
}
}
# ##### ##### ##### Step: Check out Perl sources ##### ##### #####
if (!-e $C{PERLSRCDIR}) {
say STDERR "# $C{PERLSRCDIR} doesn't exist, checking out";
my $d = pushd($C{PERLSRCDIR}->parent);
git 'clone', '--branch', $C{PERL_BRANCH}, $C{PERL_REPO}, $C{PERLSRCDIR}->basename;
die "something went wrong with git clone" unless -d $C{PERLSRCDIR};
$needs_reconfig=1;
}
GITSTUFF: {
my $d = pushd($C{PERLSRCDIR});
my $remhead;
eval {
git 'fetch';
$remhead = git 'log', '-1', '--format=%h', 'origin/'.$C{PERL_BRANCH}, {chomp=>1,show_cmd=>$VERBOSE};
1 } or do {
warn $@;
# Maybe we don't have network connectivity
if (prompt("Whoops, 'git' failed. Continue anyway? [Yn]","y")=~/^\s*y/i)
{ last GITSTUFF }
else { die "git fetch failed, aborting" }
};
my $myhead = git 'log', '-1', '--format=%h', $C{PERL_BRANCH}, {chomp=>1,show_cmd=>$VERBOSE};
say STDERR "# Local branch is at $myhead, remote is $remhead";
if ($myhead ne $remhead) {
git 'merge-base', '--is-ancestor', $remhead, $myhead, {allow_exit=>[0,1]};
if ($?==0) {
say STDERR "# However, it looks like $myhead is newer than $remhead, won't ask for update";
last GITSTUFF }
if (prompt("Would you like to update? WARNING: Unsaved local changes may be lost! [Yn]","y")=~/^\s*y/i) {
eval {
if ($C{CLOBBER_BRANCH}) {
say "WARNING: I am about to clobber the branch $C{PERL_BRANCH} in $C{PERLSRCDIR}!";
verify_perlsrc_modify(1);
git 'checkout', '-q', $C{PERLVER};
git 'branch', '-D', $C{PERL_BRANCH};
git 'branch', $C{PERL_BRANCH}, 'origin/'.$C{PERL_BRANCH};
git 'checkout', $C{PERL_BRANCH};
}
else {
git 'checkout', $C{PERL_BRANCH};
git 'pull';
}
1 } or die "$@\nA git step failed - perhaps you have uncommited changes in $C{PERLSRCDIR}?\n";
$needs_reconfig=1;
}
}
my $tags = git 'tag', '--list', {show_cmd=>$VERBOSE};
die "could not find tag '$C{PERLVER}', is this the right repository?"
unless $tags=~/^\Q$C{PERLVER}\E$/m;
my $branches = git 'branch', '--list', {show_cmd=>$VERBOSE};
die "could not find branch '$C{PERL_BRANCH}', is this the right repository?"
. " (or the WebPerl author forgot to push tags to the emperl5 repo)"
unless $branches=~/^\*?\s*\b\Q$C{PERL_BRANCH}\E$/m;
say STDERR "# Found tag '$C{PERLVER}' and branch '$C{PERL_BRANCH}' in $C{PERLSRCDIR}";
}
sub verify_perlsrc_modify {
my $force = shift;
state $already_prompted = 0;
$already_prompted=0 if $force;
return if $already_prompted;
if (prompt("WARNING: You will lose any changes to the working copy and index in $C{PERLSRCDIR}!\n"
." Continue? [yN]","n")!~/^\s*y/i) {
say STDERR "Aborting.";
exit 1;
} else { $already_prompted = 1 }
}
# ##### ##### ##### Step: Check/build hostperl ##### ##### #####
sub verify_hostperl {
my $miniperl = $C{HOSTPERLDIR}->file('miniperl');
return 0 unless -e $miniperl;
my $miniperlver = run $miniperl, '-e', 'print $^V', {show_cmd=>$VERBOSE};
say STDERR "# Detected hostperl / miniperl '$miniperlver' (need '$C{PERLVER}')";
my $perl = $C{HOSTPERLDIR}->file('perl');
if (-e $perl) { # currently just an optional check
my $perlver = run $perl, '-e', 'print $^V', {show_cmd=>$VERBOSE};
say STDERR "# Detected hostperl / perl '$perlver'";
die "miniperl ('$miniperlver') / perl ('$perlver') version mismatch"
unless $miniperlver eq $perlver;
}
return $miniperlver eq $C{PERLVER};
}
if (!verify_hostperl()) {
say STDERR "# A rebuild of hostperl is required";
$C{HOSTPERLDIR}->rmtree(1);
$C{HOSTPERLDIR}->mkpath(1);
verify_perlsrc_modify();
{
my $d = pushd($C{PERLSRCDIR});
git 'checkout', '-qf', $C{PERLVER};
git 'clean', '-dxf';
}
{
my $d = pushd($C{HOSTPERLDIR});
run {stdin=>\undef}, 'sh', file($C{PERLSRCDIR},'Configure'),
'-des', '-Dusedevel', '-Dmksymlinks';
make 'miniperl';
make 'minitest';
make 'generate_uudmap';
#TODO Later: do we really need the following full perl build as well? (good for testing?)
# if we do, make the test for "perl" in verify_hostperl required, not optional
make 'perl';
make 'test';
}
$needs_reconfig=1;
die "something went wrong with hostperl" unless verify_hostperl();
}
# ##### ##### ##### Step: Prep "emperl" sources (for next steps) ##### ##### #####
my $config_sh = $C{PERLSRCDIR}->file('config.sh');
if (!-e $config_sh) {
say STDERR "# config.sh NOT found, forcing a reconfig";
$needs_reconfig=1 }
else { say STDERR "# config.sh found" }
if (-e $config_sh) {
my $our_mtime = file($FindBin::Bin, 'emperl_config.sh')->stat->mtime;
my $perl_mtime = $config_sh->stat->mtime;
if ($perl_mtime>$our_mtime)
{ say STDERR "# config.sh is newer than emperl_config.sh" }
else {
say STDERR "# config.sh is OLDER than emperl_config.sh";
exit 1 if prompt("Did you remember to run '. emperl_config.sh'? [yN]","n")!~/^\s*y/i;
say STDERR "# ok, forcing a reconfig";
$needs_reconfig=1;
}
}
if ($needs_reconfig) {
exit 1 if prompt("Looks like we need a full reconfig. Continue? [Yn]","y")!~/^\s*y/i;
verify_perlsrc_modify();
my $d = pushd($C{PERLSRCDIR});
# Note: could get the current branch with: git 'rev-parse', '--abbrev-ref', 'HEAD', {chomp=>1};
# but since we're clobbering anyway...
git 'checkout', '-qf', $C{PERL_BRANCH};
git 'clean', '-dxf';
}
# ##### ##### ##### Step: Add custom extensions ##### ##### #####
if ($needs_reconfig || $opts{forceext}) {
my $http = HTTP::Tiny->new;
$C{DOWNLOADDIR}->mkpath(1);
for my $modname ($C{EXTENSIONS}->@*) {
my $apiuri = URI->new('https://fastapi.metacpan.org/v1/download_url');
$apiuri->path_segments( $apiuri->path_segments, $modname );
say STDERR "# Fetching $apiuri...";
my $resp1 = $http->get($apiuri);
die "$apiuri: $resp1->{status} $resp1->{reason}\n" unless $resp1->{success};
my $apiresp = decode_json($resp1->{content});
my $version = $apiresp->{version};
my $dluri = URI->new($apiresp->{download_url});
my $file = $C{DOWNLOADDIR}->file( ($dluri->path_segments)[-1] );
die "I don't know what to do with this file type (yet): $file"
unless $file->basename=~/(?:\.tar\.gz|\.tgz)$/i;
say STDERR "# Fetching $dluri into $file...";
my $resp2 = $http->mirror($dluri, $file);
die "$dluri: $resp2->{status} $resp2->{reason}\n" unless $resp2->{success};
say STDERR "# $dluri: $resp2->{status} $resp2->{reason}";
my $tempd = dir( tempdir(DIR=>$C{DOWNLOADDIR}, CLEANUP => 1) );
{
my $d = pushd($tempd);
my @files = Archive::Tar->new->extract_archive($file, Archive::Tar::COMPRESS_GZIP);
say STDERR "# Extracted ",0+@files," files into $tempd";
}
my @dirs = $tempd->children;
die "Can't handle the directory structure of this file (yet): $file"
unless @dirs==1 && $dirs[0]->is_dir;
my ($dirname) = $dirs[0]->basename =~ /^(.+)-\Q$version\E$/g
or die "Failed to parse ".$dirs[0]->basename;
my $targdir = $C{PERLSRCDIR}->subdir( 'ext', $dirname );
my $domove = 1;
if (-e $targdir) {
if ( prompt("WARNING: $targdir exists, Keep or Delete? [Kd]","k")=~/^\s*d/i )
{ $targdir->rmtree(1) }
else { $domove=0 }
}
if ($domove) {
say STDERR "# Moving $dirs[0] to $targdir";
dirmove($dirs[0], $targdir)
or die "move failed: $!";
}
}
say STDERR "# Done setting up modules";
}
else { say STDERR "# Since we don't need a reconfig, not looking at extensions" }
# ##### ##### ##### Step: Run configure ##### ##### #####
if ($needs_reconfig) { # this means that we cleaned the source tree above
say STDERR "# Running Configure...";
my $d = pushd($C{PERLSRCDIR});
# note that we don't use -Dmksymlinks here because something in the
# Emscripten build process seems to have issues with the symlinks (?)
run {stdin=>\undef}, 'emconfigure', 'sh', 'Configure', '-des',
'-Dhintfile=emscripten';
}
elsif ($opts{applyconfig}) {
say STDERR "# Running Configure -S...";
my $d = pushd($C{PERLSRCDIR});
run {stdin=>\undef}, 'emconfigure', 'sh', 'Configure', '-S';
}
# ##### ##### ##### Step: Build perl into outputdir ##### ##### #####
my $destdir = dir($C{OUTPUTDIR},$C{PREFIX});
if ($needs_reconfig || !-e $destdir || $opts{remakeout}) {
say STDERR "# Rebuilding $destdir...";
$destdir->rmtree(1);
# make the target dir here so that nodeperl_dev_prerun.js can mount it during build
$destdir->mkpath(1);
my $d = pushd($C{PERLSRCDIR});
emmake 'make', 'perl';
# a really basic test to see if the build succeeded
my $perltest = run file($C{PERLSRCDIR},'perl'), '-e', q{print "$^O $^V"},
{chomp=>1,show_cmd=>$VERBOSE,fail_on_stderr=>1};
die "something went wrong building perl (got: '$perltest')"
unless $perltest eq 'emscripten '.$C{PERLVER};
# note that installperl requires ./perl to be executable (our Makefile patch currently takes care of that)
run $C{HOSTPERLDIR}.'/miniperl', 'installperl', '-p', '--destdir='.$C{OUTPUTDIR};
# clean out the stuff we really don't need
$destdir->subdir('bin')->rmtree(1);
$destdir->recurse( callback => sub {
my $f = shift;
return if $f->is_dir;
if ( ( $f->basename=~/\.(?:h|a|pod)$/i ) || ( $f->basename eq 'extralibs.ld' && (-s $f)==1 )
|| ( $f->basename eq '.packlist' ) ) {
print STDERR "removing $f\n";
$f->remove or die "failed to remove $f";
}
elsif ( $f->basename=~/\.(?:pm|pl)$/i && $f->basename ne 'WebPerl.pm' ) {
print STDERR "stripping POD from $f\n";
my $strip = Pod::Strip->new;
my ($infh,$outfh,$repl) = replace3($f);
$strip->output_fh($outfh);
$strip->parse_file($infh);
$repl->finish;
}
});
CLEAN_EMPTY: {
my @todel;
$destdir->recurse( callback => sub { push @todel, $_[0] if $_[0]->is_dir && !$_[0]->children } );
for my $f (@todel) {
print STDERR "removing $f\n";
$f->remove or die "failed to remove $f";
}
redo CLEAN_EMPTY if @todel;
}
# Development aides:
$destdir->subdir('dev')->mkpath(1);
# we make them hard links so that edits to WebPerl.pm don't require a full
# rebuild of the output directory (a rebuild of emperl.js is enough)
safelink( $C{PERLSRCDIR}->file('ext','WebPerl','t','WebPerl.t'),
$destdir->file('dev','WebPerl.t') );
safelink( $C{PERLSRCDIR}->file('ext','WebPerl','lib','WebPerl.pm'),
$destdir->file('lib',$C{PERLVER}=~s/^v(?=5)//r,'wasm','WebPerl.pm') );
#TODO Later: Provide an easy way for users to add files to the virtual file system
say STDERR "# Done rebuilding $destdir";
}
# ##### ##### ##### Step: Build emperl.js ##### ##### #####
{
say STDERR "# Making emperl.js...";
my $targ = $C{PERLSRCDIR}->file('emperl.js');
if ( ($opts{forceemperl} || $opts{remakeout}) && -e $targ )
{ $targ->remove or die "failed to delete $targ: $!" }
my $d = pushd($C{PERLSRCDIR});
emmake 'make', 'emperl.js';
die "Target file not generated?" unless -e $targ;
say STDERR "# Done making emperl.js";
}
for my $f (qw/ emperl.js emperl.wasm emperl.data /) {
$C{PERLSRCDIR}->file($f)
->copy_to( dir($FindBin::Bin)->parent->subdir('web') )
or die "failed to copy $f: $!";
}
say STDERR "# Copied emperl.* files to web dir";
# ##### ##### ##### Step: Build distro ##### ##### #####
if (my $dist = $opts{dist}) {
my $basedir = dir($FindBin::Bin)->parent;
my $zipfn = $basedir->file("$dist.zip");
my $zip = Archive::Zip->new();
$zip->addTree($basedir->subdir('web').'', dir($dist).'');
$zip->addFile($basedir->file($_).'', dir($dist)->file($_).'') for
qw/ README.md LICENSE_artistic.txt LICENSE_gpl.txt cpanfile /;
$zip->writeToFileNamed("$zipfn") == AZ_OK or die "$zipfn write error";
say STDERR "# Wrote to $zipfn:";
my $unzip = Archive::Zip->new("$zipfn");
say "\t$_" for $unzip->memberNames;
}
# ##### ##### ##### subs ##### ##### #####
sub safelink { # like link(OLDFILE,NEWFILE) but with extra checks
my ($oldfile,$newfile) = @_;
die "not a file: $oldfile" unless -f $oldfile;
if (-e $newfile) {
die "files don't match: $oldfile vs. $newfile"
unless do { open my $fh, '<:raw', $oldfile or die "$oldfile: $!"; local $/; <$fh> }
eq do { open my $fh, '<:raw', $newfile or die "$newfile: $!"; local $/; <$fh> };
file($newfile)->remove or die "failed to remove $newfile: $!";
}
link($oldfile,$newfile)
or die "link('$oldfile','$newfile'): $!";
}
# First argument: the filename of the .patch file
# Any following arguments are additionally passed to "patch" (e.g. "-p1")
# Attempts to run "patch", will fail gracefully if the patch has already been applied.
# Dies if anything goes wrong (patch not applied cleanly, etc.).
# Returns false (0) if the patch was already applied previously, true (1) if the patch was newly applied.
sub try_patch_file {
my ($patchf,@args) = @_;
say STDERR "# Attempting to apply patch $patchf...";
run 'patch', @args, '-r-', '-sNi', $patchf, {allow_exit=>[0,1],show_cmd=>$VERBOSE};
if ($?==1<<8) {
# Slightly hackish way to test if the patch did not apply cleanly, or it's just already been applied:
# Apply the patch in reverse once, and then apply it again, if both go through without errors all is ok.
# There is probably a better way to do this, I'm just feeling a little lazy at the moment.
run 'patch', @args, '-sRi', $patchf, {show_cmd=>$VERBOSE};
run 'patch', @args, '-si', $patchf, {show_cmd=>$VERBOSE};
say STDERR "# Verified that $patchf was previously applied";
return 0;
}
elsif ($?) { die "patch $patchf \$?=$?" }
else { say STDERR "# Successfully applied patch $patchf"; return 1 }
}
# A simplified version of File::pushd that outputs debug info. TODO Later: should probably propose a patch for a debug option.
sub pushd {
if (not defined wantarray) { carp "pushd in void context"; return }
croak "bad arguments to pushd" unless @_==1 && defined $_[0];
my $targ = abs_path(shift);
croak "not a directory: $targ" unless -d $targ;
my $orig = getcwd;
if ($targ ne $orig) {
say STDERR "\$ cd $targ";
chdir $targ or croak "chdir to $targ failed: $!";
}
return bless { orig=>$orig }, 'PushedDir';
}
sub PushedDir::DESTROY {
my $self = shift;
if (getcwd ne $self->{orig}) {
say STDERR "\$ cd ".$self->{orig};
chdir $self->{orig} or croak "chdir to ".$self->{orig}." failed: $!";
}
}
__END__
#TODO Later: Fix the following (note setting d_getgrgid_r and d_getgrnam_r in the hints file didn't seem to help)
warning: unresolved symbol: getgrgid
warning: unresolved symbol: getgrnam
warning: unresolved symbol: llvm_fma_f64
warning: unresolved symbol: sigsuspend
#TODO Later: Fix the following "miniperl make_ext.pl" errors (warnings?)
./miniperl -Ilib make_ext.pl lib/auto/Encode/Byte/Byte.a MAKE="make" LIBPERL_A=libperl.a LINKTYPE=static CCCDLFLAGS=
Can't find extension Encode/Byte in any of cpan dist ext at make_ext.pl line 251.
./miniperl -Ilib make_ext.pl lib/auto/Encode/Symbol/Symbol.a MAKE="make" LIBPERL_A=libperl.a LINKTYPE=static CCCDLFLAGS=
Can't find extension Encode/Symbol in any of cpan dist ext at make_ext.pl line 251.
./miniperl -Ilib make_ext.pl lib/auto/Encode/Unicode/Unicode.a MAKE="make" LIBPERL_A=libperl.a LINKTYPE=static CCCDLFLAGS=
Can't find extension Encode/Unicode in any of cpan dist ext at make_ext.pl line 251.

@ -1,15 +0,0 @@
# Install the dependencies for "build" via:
# $ cpanm --installdeps .
requires 'Data::Dump';
requires 'Path::Class';
requires 'IPC::Run3::Shell', '0.56';
requires 'URI';
requires 'Net::SSLeay', 1.49;
requires 'IO::Socket::SSL', '1.56';
requires 'Cpanel::JSON::XS';
requires 'File::Copy::Recursive';
requires 'File::Replace', '0.08';
requires 'Pod::Strip';
requires 'Archive::Zip';

@ -1,51 +0,0 @@
#!/bin/bash
# This is the configuration file for building WebPerl.
# You should edit it according to the comments below.
# Remember to reload this file after making changes! (". emperl_config.sh")
# You must edit this to point to your Emscripten SDK's emsdk_env.sh.
. $HOME/emsdk/emsdk_env.sh
# A whitespace-separated list of modules to download and add to the build.
# Note: Cpanel::JSON::XS is required for WebPerl!
export EMPERL_EXTENSIONS="Cpanel::JSON::XS Devel::StackTrace Future"
# Modules from the above list that have XS code need to be linked statically.
# Add them here, separated by whitespace (see also the "static_ext" variable
# in https://perl5.git.perl.org/perl.git/blob/HEAD:/Porting/Glossary ).
export EMPERL_STATIC_EXT="Cpanel/JSON/XS"
# Do not edit (this gets this script's parent directory)
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. >/dev/null && pwd )"
# Various working directories, you normally don't need to edit these
export EMPERL_PERLSRCDIR="$BASEDIR/emperl5"
export EMPERL_HOSTPERLDIR="$BASEDIR/work/hostperl"
export EMPERL_DOWNLOADDIR="$BASEDIR/work/download"
export EMPERL_OUTPUTDIR="$BASEDIR/work/outputperl"
# Don't edit the following options unless you know what you're doing!
# Note to self: In build.pl, we take advantage of the fact that on Perls >=v5.10.0, "$^V" is the same as the tag name.
export EMPERL_PERLVER="v5.30.0"
export EMPERL_PREFIX="/opt/perl"
# Note: strace shows this is how file_packager.py is called: ["/usr/bin/python", "/home/haukex/emsdk/emscripten/1.38.28/tools/file_packager.py", "emperl.data", "--from-emcc", "--export-name=Module", "--preload", "/home/haukex/code/webperl/work/outputperl/opt/perl@/opt/perl", "--no-heap-copy"]
export EMPERL_PRELOAD_FILE="$EMPERL_OUTPUTDIR$EMPERL_PREFIX@$EMPERL_PREFIX"
export EMPERL_OPTIMIZ="-O2"
# Note: We explicitly disable ERROR_ON_UNDEFINED_SYMBOLS because it was enabled by default in Emscripten 1.38.13.
#TODO Later: Why does --no-heap-copy not get rid of the "in memory growth we are forced to copy it again" assertion warning? (https://github.com/emscripten-core/emscripten/commit/ec764ace634f13bab5ae932912da53fe93ee1b69)
export EMPERL_LINK_FLAGS="--pre-js common_preamble.js --no-heap-copy -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s EXPORTED_FUNCTIONS=['_main','_emperl_end_perl','_Perl_call_sv','_Perl_call_pv','_Perl_call_method','_Perl_call_argv','_Perl_eval_pv','_Perl_eval_sv','_webperl_eval_perl'] -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap']"
export EMPERL_DEBUG_FLAGS=""
#export EMPERL_DEBUG_FLAGS="-s ASSERTIONS=2 -s STACK_OVERFLOW_CHECK=2"
# Note: not including "-s SAFE_HEAP=1" in the debug flags because we're building to WebAssembly, which doesn't require alignment
#TODO Later: Can some of the SAFE_HEAP functionality (null pointer access I think?) be replaced by the WASM error traps?
# http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html#binaryen-codegen-options
# Location and branch of the perl git repository that contains the emperl branch
export EMPERL_PERL_REPO="https://github.com/haukex/emperl5.git"
export EMPERL_PERL_BRANCH="emperl_$EMPERL_PERLVER"
# Enabling this setting causes the local emperl branch to be deleted and re-fetched from the origin.
# This is useful during development, when rewrites of the (unpublished!) git history of the branch might happen.
export EMPERL_CLOBBER_BRANCH=0

@ -1,20 +0,0 @@
--- emscripten/src/library.js 2018-08-01 18:58:49.428051969 +0200
+++ emsdk/emscripten/1.38.10/src/library.js 2018-08-01 18:58:03.288051969 +0200
@@ -250,7 +250,7 @@
// pid_t fork(void);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
// We don't support multiple processes.
- ___setErrNo(ERRNO_CODES.EAGAIN);
+ ___setErrNo(ERRNO_CODES.ENOTSUP);
return -1;
},
vfork: 'fork',
@@ -545,7 +545,7 @@
// int system(const char *command);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
// Can't call external programs.
- ___setErrNo(ERRNO_CODES.EAGAIN);
+ ___setErrNo(ERRNO_CODES.ENOTSUP);
return -1;
},

@ -1,20 +0,0 @@
--- library.js.orig 2019-03-02 16:08:24.404047130 +0100
+++ library.js 2019-03-02 16:19:30.588047130 +0100
@@ -291,7 +291,7 @@
// pid_t fork(void);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
// We don't support multiple processes.
- ___setErrNo({{{ cDefine('EAGAIN') }}});
+ ___setErrNo({{{ cDefine('ENOTSUP') }}});
return -1;
},
vfork: 'fork',
@@ -817,7 +817,7 @@
// int system(const char *command);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
// Can't call external programs.
- ___setErrNo({{{ cDefine('EAGAIN') }}});
+ ___setErrNo({{{ cDefine('ENOTSUP') }}});
return -1;
},

@ -1,19 +0,0 @@
--- library.js.orig 2020-05-18 17:14:18.682328912 +0200
+++ library.js 2020-05-18 17:14:48.366639562 +0200
@@ -271,7 +271,7 @@
// pid_t fork(void);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
// We don't support multiple processes.
- setErrNo({{{ cDefine('EAGAIN') }}});
+ setErrNo({{{ cDefine('ENOTSUP') }}});
return -1;
},
vfork: 'fork',
@@ -696,7 +696,7 @@
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
// Can't call external programs.
if (!command) return 0; // no shell available
- setErrNo({{{ cDefine('EAGAIN') }}});
+ setErrNo({{{ cDefine('ENOTSUP') }}});
return -1;
},

@ -1,24 +0,0 @@
#!/bin/bash
set -e
# Finds and displays TODOs for the WebPerl project.
# the output can be piped into e.g. "less -R"
if [ -z ${EMPERL_PERLVER+x} ]; then
echo "Please source emperl_config.sh first"
exit 1
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. >/dev/null && pwd )"
TEMPFILE="`mktemp`"
trap 'rm -f "$TEMPFILE"' EXIT
(
cd "$DIR/emperl5"
# only look at files that have been added
git diff --numstat --diff-filter=A $EMPERL_PERLVER $EMPERL_PERL_BRANCH \
| BASEDIR=$DIR perl -wMstrict -MFile::Spec::Functions=abs2rel,rel2abs -nl0 \
-e '/^\d+\s+\d+\s+(.+)$/ or die $_; -e $1 and print abs2rel(rel2abs($1),$ENV{BASEDIR})'
) >>"$TEMPFILE"
cd $DIR
find . -mindepth 1 \( -path ./.git -o -path ./work -o -path ./emperl5 \) -prune \
-o ! -name 'emperl.*' ! -type d -print0 \
| perl -wMstrict -MFile::Spec::Functions=canonpath -n0le 'print canonpath($_)' >>"$TEMPFILE"
xargs -0 -a "$TEMPFILE" \
grep --color=always -C1 -niE '\bto.?do\b'

@ -0,0 +1,195 @@
\[ [Using](using.html) -
Building -
[🦋](perl6.html) -
[Notes](notes.html) -
[Legal](legal.html) -
[Wiki](https://github.com/haukex/webperl/wiki/Building-WebPerl) \]
Building WebPerl
================
**Notice: WebPerl is very much in beta.**
Some things may not work yet, and parts of the API may still change.
Your feedback is always appreciated!
This page documents the Perl 5 support, for the *experimental*
Perl 6 support, see [here](perl6.html).
Prerequisites
-------------
- Linux, a fairly modern release is strongly recommended, and `bash`
(tested on a minimum of Ubuntu 16.04)
- `git` (e.g. Ubuntu/Debian: `sudo apt-get install git-core`)
- Build tools for `perl`; for example on Ubuntu:
`sudo apt-get install build-essential` and `sudo apt-get build-dep perl`
- Perl, at least v5.26 (for example via [Perlbrew](http://perlbrew.pl/))
- [Emscripten](http://emscripten.org) SDK, 1.38.10 and up,
please see the prerequisites and installation instructions at
<http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html#installation-instructions>
- The build script has several CPAN dependencies. You can install these
via [`cpanm`](https://metacpan.org/pod/App::cpanminus) and the provided
`cpanfile`: `cd` to the `build` directory, then run `cpanm --installdeps .`
(Otherwise, see the `cpanfile` or the source of `build.pl` and install these
modules with the installer of your choice. Another possibility is to use
[`lazy`](https://metacpan.org/pod/lazy), e.g. `perl -Mlazy build.pl --help`)
- A working Internet connection is needed for installation and the first build.
Source Code
-----------
The source code is in two repositories:
- <https://github.com/haukex/webperl> - the main WebPerl repository
- <https://github.com/haukex/emperl5> - a fork of the Perl 5 source
repository where the WebPerl-specific patches are applied
You only need to check out the first of the two, the `emperl5` repository
is checked out by the build script.
Some of the central source files of WebPerl are:
- [`webperl/build/build.pl`](https://github.com/haukex/webperl/blob/master/build/build.pl)
- [`emperl5/hints/emscripten.sh`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/hints/emscripten.sh)
- [`emperl5/perlmain_noexit_patch`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/perlmain_noexit_patch)
- [`emperl5/ext/WebPerl/WebPerl.xs`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/ext/WebPerl/WebPerl.xs)
- [`emperl5/ext/WebPerl/lib/WebPerl.pm`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/ext/WebPerl/lib/WebPerl.pm)
- [`webperl/web/webperl.js`](https://github.com/haukex/webperl/blob/master/web/webperl.js)
Running the Build
-----------------
1. Fetch the source code.
$ git clone https://github.com/haukex/webperl.git
$ cd webperl
2. Install the [prerequisites](#prerequisites).
3. Edit the configuration file, `./build/emperl_config.sh`, to fit
your system. For a first build, just make sure the path to
`emsdk_env.sh` is correct.
4. Source the configuration file to set the environment variables.
Remember to do this anytime you change variables. You may also
add the sourcing of the configuration file to your `~/.bashrc`.
$ . ./build/emperl_config.sh
5. Run the build script:
$ build/build.pl
6. If the build succeeds, the output files `emperl.*` will be
copied to the `web` directory of the repository. You can
then use the files in the `web` directory as described in
[Using WebPerl](using.html).
Build Process Overview
----------------------
The build script `build.pl` tries to take care of as much of the build process as
possible. Most of the work happens in a subdirectory `work` of the repository.
Similar to `make`, it tries to not run build steps that don't need to be rerun.
> A brief note on naming:
>
> - *`emperl`* is generally used for the build products of Emscripten
> - *`emperl5`* is the Perl 5 source tree modified for WebPerl
> - *WebPerl* is the finished product, including `emperl`
> and the WebPerl APIs (`WebPerl.pm` and `webperl.js`)
The steps in the build process are roughly as follows.
Since WebPerl is still in beta, they are subject to change.
See
[the source of the `build.pl` script](https://github.com/haukex/webperl/blob/master/build/build.pl)
for the current details.
1. Patch Emscripten
(currently just a minor patch, but important for Perl)
2. Fetch/update the `emperl5` Perl source tree
3. If necessary, build "host Perl" - in Perl's cross-compilation system,
this is the Perl that is built for the host system architecture,
i.e. in the case of Linux, a normal build of Perl for Linux. The
`miniperl` from the host Perl will be used for some of the build
steps for the target architecture.
(Note: This step can take quite a while, but it usually only needs
to be run once.)
4. Download and extract any CPAN modules, such as the required `Cpanel::JSON::XS`,
into the Perl source tree so that they will be built as part of the normal
build process and any XS extensions linked statically into the `perl` binary.
(See ["Adding CPAN Modules"](#adding-cpan-modules))
5. Run Perl's `Configure` script using the custom "hints" file for the Emscripten
architecture.
6. Run `make` to compile `perl`. This produces a file `perl.bc` with LLVM IR
bitcode, which the Emscripten compiler will then compile to JavaScript/WebAssembly.
Because some steps in the build process require a working `perl` binary,
Emscripten's compiler is used together with a supporting JavaScript file to
generate JavaScript/WebAssembly code that can be run with `node.js` (called `nodeperl_dev.js`).
8. Run the equivalent of `make install`, which copies all the Perl modules
etc. into the target directory that will become part of the Emscripten
virtual file system. Then, we clean this directory up by deleting anything
that we don't need for WebPerl: additional binaries (it's a single-process
environment), `*.pod` files, as well as stripping the POD out of `*.pm`
files, etc. to reduce the download size.
9. The Emscripten compiler is used to take the previously compiled `perl.bc`
and build the final output, `emperl.js` along with the corresponding
`.wasm` and `.data` file. This step also includes the packaging of the
virtual filesystem.
`build.pl` provides various command-line options that allow you to control
parts of the build process. See `build.pl --help` for details.
Adding CPAN Modules
-------------------
In the configuration file `emperl_config.sh`, the variable `EMPERL_EXTENSIONS`
is a whitespace-separated list of module names. `build.pl` will fetch these
from CPAN and extract them into the `ext` directory of the Perl source tree
so that they are compiled along with Perl. Any XS modules that need to be
linked into `perl` need to be added to the variable `EMPERL_STATIC_EXT` in
the format expected by Perl's `static_ext` configuration variable,
so for example `Cpanel/JSON/XS` instead of `Cpanel::JSON::XS`
(see <http://perl5.git.perl.org/perl.git/blob/HEAD:/Porting/Glossary>).
Note that the build script does **not** automatically fetch modules'
dependencies, for now you will need to resolve them and add them to
`EMPERL_EXTENSIONS` yourself. (This may be improved upon in the future.)
You can try out the script
[`experiments/depend.pl`](https://github.com/haukex/webperl/blob/master/experiments/depend.pl),
which uses the MetaCPAN API to resolve dependencies.
***
Additional notes on building WebPerl may be found in the
[GitHub Wiki](https://github.com/haukex/webperl/wiki/Building-WebPerl).
***
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
Please see the ["Legal" page](legal.html) for details.
***
You can find the source for this page at
<https://github.com/haukex/webperl/blob/gh-pages/building.md>

@ -1,8 +0,0 @@
# Install the dependencies for "web" via:
# $ cpanm --installdeps .
requires 'Cpanel::JSON::XS';
requires 'Plack';
requires 'Plack::Middleware::CrossOrigin';
requires 'Plack::Middleware::Auth::Digest';

@ -34,14 +34,14 @@ pre,textarea,code {
}
iframe.perleditor {
display: block;
border: 1px solid black;
border: 1px dotted lightgrey;
width: 100%;
max-width: 50em;
margin: 0.2em 0;
}
</style>
<!-- Optional "IFrame Resizer": -->
<!--cacheable--><!--script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/3.6.2/iframeResizer.min.js" integrity="sha256-aYf0FZGWqOuKNPJ4HkmnMZeODgj3DVslnYf+8dCN9/k=" crossorigin="anonymous"></script-->
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/3.6.2/iframeResizer.min.js" integrity="sha256-aYf0FZGWqOuKNPJ4HkmnMZeODgj3DVslnYf+8dCN9/k=" crossorigin="anonymous"></script>
</head>
<body>
@ -113,7 +113,7 @@ document.getElementById('perl1').src =
cmdline: "perl -CSD -pe 's/[aeiou]/_/g' in.txt",
} ));
// Example of how to use the Optional "IFrame Resizer":
//iFrameResize({checkOrigin:false}, document.getElementById('perl1'));
iFrameResize({checkOrigin:false}, document.getElementById('perl1'));
</script>
<p>This example includes several files:</p>
@ -201,7 +201,7 @@ document.getElementById('perl2').src =
outputs: [ "vowels.txt" ],
autorun: true,
} ));
//iFrameResize({checkOrigin:false}, document.getElementById('perl2'));
iFrameResize({checkOrigin:false}, document.getElementById('perl2'));
</script>
</body>

@ -25,14 +25,14 @@ You should have received a copy of the licenses along with this program.
If not, see http://perldoc.perl.org/index-licence.html
##### -->
<!-- Please see the documentation on how to use this in demo.html. -->
<!-- Please see the documentation on how to use this in index.html. -->
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" integrity="sha256-oSrCnRYXvHG31SBifqP2PM1uje7SJUyX0nTwO2RJV54=" crossorigin="anonymous" />
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.css" integrity="sha256-I8NyGs4wjbMuBSUE40o55W6k6P7tu/7G28/JGUUYCIs=" crossorigin="anonymous" />
<link rel="stylesheet" href="perleditor.css" />
<!-- Optional "IFrame Resizer": -->
<!--cacheable--><!--script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/3.6.2/iframeResizer.contentWindow.min.js" integrity="sha256-dEPtZVO6cj6PAmBeDzFskohUob+woyzF6TaNcYpAk84=" crossorigin="anonymous"></script-->
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/3.6.2/iframeResizer.contentWindow.min.js" integrity="sha256-dEPtZVO6cj6PAmBeDzFskohUob+woyzF6TaNcYpAk84=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.js" integrity="sha256-uRIJ6Wfou5cTtmcCvQNA9lvsYl8sUbZXxnfG+P79ssY=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/mode/perl/perl.min.js" integrity="sha256-Uu9QBfi8gU6J/MzQunal8ewmY+i/BbCkBrcAXA5bcCM=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>

@ -25,7 +25,7 @@ You should have received a copy of the licenses along with this program.
If not, see http://perldoc.perl.org/index-licence.html
##### -->
<!-- Please see the documentation on how to use this in demo.html. -->
<!-- Please see the documentation on how to use this in index.html. -->
<!-- Possible To-Do for Later: This whole thing could probably also be
accomplished with a Web Worker, but that would probably require a
@ -38,9 +38,9 @@ If not, see http://perldoc.perl.org/index-licence.html
is to re-start an Emscripten program...
-->
<script src="../webperl.js"></script>
<!--script src="https://webperlcdn.zero-g.net/v0.09-beta/webperl.js"
integrity="sha256-1RaYAh/WfDl3WZl+BDpSuSycg4x88pfkAFSxhWjBONk=" crossorigin="anonymous"></script-->
<!--script src="../webperl.js"></script-->
<script src="https://webperlcdn.zero-g.net/v0.09-beta/webperl.js"
integrity="sha256-1RaYAh/WfDl3WZl+BDpSuSycg4x88pfkAFSxhWjBONk=" crossorigin="anonymous"></script>
<script>
"use strict";

@ -1,20 +0,0 @@
use warnings;
use 5.026;
use Time::HiRes qw/gettimeofday tv_interval/;
my $t0 = [gettimeofday];
my @primes = join ',', grep {prime($_)} 1..1000000;
my $elapsed = tv_interval($t0);
printf "%.3f\n", $elapsed;
# http://www.rosettacode.org/wiki/Primality_by_trial_division#Perl
sub prime {
my $n = shift;
$n % $_ or return for 2 .. sqrt $n;
$n > 1
}
# A quick test: This program, when run
# from WebPerl (Firefox): ~7.4s
# natively (same machine): ~2.3s
# => roughly 3.2 times slower

@ -1,8 +0,0 @@
# Install the dependencies for "experiments" via:
# $ cpanm --installdeps .
requires 'Data::Dump';
requires 'Graph';
requires 'MetaCPAN::Client';
requires 'Path::Class';

@ -1,111 +0,0 @@
#!/usr/bin/env perl
use warnings;
use 5.026;
use Getopt::Long qw/ HelpMessage :config posix_default gnu_compat
bundling auto_version auto_help /;
use Graph ();
use Memoize 'memoize';
use Memoize::Storable ();
=head1 SYNOPSIS
depend.pl MODULE(s)
OPTIONS:
-v | --verbose - more output
-t | --want-test - include modules needed for test phase
-p | --perl-ver VER - Perl version for corelist (default: 5.026)
-c | --cache-file FILE - cache file for MetaCPAN API requests
(default: /tmp/.metacpan_deps_cache)
-C | --clear-cache - clear cache before running
=head1 DESCRIPTION
A test of resolving module dependences, currently via the MetaCPAN API.
(The list of dependencies that MetaCPAN knows about may not always be complete.)
Outputs a possible install order that should satisfy dependencies.
Note this order can change across runs, but theoretically it should
always be a valid install order.
Notes for WebPerl:
Could be used in F<build.pl>.
I don't really need C<is_installed>.
Perhaps instead of C<is_core> I should check if the module exists
in the Perl source tree and is enabled in F<config.sh>...
=cut
our $VERSION = '0.01-beta';
GetOptions(
'v|verbose' => \(my $VERBOSE),
't|want-test' => \(my $WANT_TEST),
'p|perl-ver=s' => \(my $PERL_VER='5.026'),
'c|cache-file=s' => \(my $CACHE_FILE='/tmp/.metacpan_deps_cache'),
'C|clear-cache' => \(my $NO_CACHE),
) or HelpMessage(-exitval=>255);
HelpMessage(-msg=>'Not enough arguments',-exitval=>255) unless @ARGV;
if ($NO_CACHE && -e $CACHE_FILE)
{ unlink($CACHE_FILE)==1 or die "Failed to unlink $CACHE_FILE: $!" }
tie my %get_deps_cache, 'Memoize::Storable', $CACHE_FILE;
memoize 'get_deps', SCALAR_CACHE=>[HASH=>\%get_deps_cache], LIST_CACHE=>'FAULT';
memoize 'is_core';
memoize 'is_installed';
my $dep_graph = Graph->new(directed => 1);
resolve_deps($_, $dep_graph) for @ARGV;
my @topo = $dep_graph->topological_sort;
say for reverse @topo;
warn "No (non-core) dependencies\n" unless @topo;
use MetaCPAN::Client ();
sub get_deps { # will be memoized (and persisted)
my ($module) = @_;
state $mcpan = MetaCPAN::Client->new();
$VERBOSE and say STDERR "Fetching dependencies of $module from MetaCPAN API";
return $mcpan->release($mcpan->module($module)->distribution)->dependency;
}
use Module::CoreList ();
sub is_core { # will be memoized
my ($module,$version) = @_;
return Module::CoreList::is_core($module,$version,$PERL_VER);
}
use Module::Load::Conditional ();
sub is_installed { # will be memoized
my ($module,$version) = @_;
return Module::Load::Conditional::check_install(module=>$module,version=>$version);
}
sub resolve_deps {
my $module = shift;
my $graph = @_ ? shift : Graph->new(directed => 1);
for my $dep ( get_deps($module)->@* ) {
next if is_core( $dep->{module}, $dep->{version} ); # ignore core modules
next if $dep->{module} eq 'perl'; # ignore perl dist itself
next unless $dep->{relationship} eq 'requires'; # ignore 'recommends' and 'suggests'
die "Unknown relationship '$dep->{relationship}'"
unless $dep->{relationship}=~/\A(?:requires|recommends|suggests)\z/;
next if $dep->{phase} eq 'develop'; # ignore phase 'develop'
next if !$WANT_TEST && $dep->{phase} eq 'test'; # ignore phase 'test' unless user wants it
next if $dep->{phase}=~/\Ax_/; # ignore e.g. "x_Dist_Zilla"
die "Unknown phase '$dep->{phase}'"
unless $dep->{phase}=~/\A(?:configure|build|runtime|test)\z/;
my $installed = is_installed( $dep->{module}, $dep->{version} ); # just for info
$VERBOSE and say STDERR "$module requires $dep->{module}",
$dep->{version} ? " (version $dep->{version})" : " (any version)",
" for $dep->{phase}",
$installed ? " (installed)" : " (not installed)";
$graph->add_edge($module, $dep->{module});
die "Fatal: Circular dependency detected (just added $module->$dep->{module})"
if $graph->has_a_cycle;
resolve_deps($dep->{module}, $graph)
}
return $graph;
}

@ -1,5 +0,0 @@
/database.db
/web/webperl.js
/web/emperl.*
/gui_basic
/gui_basic.exe

@ -1,50 +0,0 @@
WebPerl Basic GUI Example
=========================
This is a demo of a very basic GUI using WebPerl. It consists of a
local web server, which includes code to access an SQLite database,
and this web server also serves up WebPerl code to a browser, where
the GUI is implemented as HTML with Perl.
To get this to work, you will need to copy the `webperl.js` and the
three `emperl.*` files from the main `web` directory to the `web`
subdirectory in this project.
Note that this should not be considered production-ready, as there
are several key features missing, such as HTTPS or access control.
Also, a limitation is that the server does not know when the browser
window is closed, so it must be stopped manually.
You can pack this application into a single executable using:
DOING_PAR_PACKER=1 pp -o gui_basic -z 9 -x -a gui_basic_app.psgi -a web gui_basic.pl
Note: I'm not yet sure why, but sometimes this fails with errors such
as *"error extracting info from -c/-x file"*, in that case just try
the above command again.
Author, Copyright, and License
==============================
**WebPerl - <http://webperl.zero-g.net>**
Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
**WITHOUT ANY WARRANTY**; without even the implied warranty of
**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see <http://perldoc.perl.org/index-licence.html>.

@ -1,50 +0,0 @@
#!/usr/bin/env perl
use warnings;
use 5.018;
use FindBin;
use File::Spec::Functions qw/catdir/;
use Plack::Runner ();
use Starman ();
use Browser::Open qw/open_browser/;
# This just serves up gui_basic_app.psgi in the Starman web server.
# You can also say "plackup gui_basic_app.psgi" instead.
BEGIN {
my $dir = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
chdir $dir or die "chdir $dir: $!";
}
my $SERV_PORT = 5000;
my $THE_APP = 'gui_basic_app.psgi';
# AFAICT, both Plack::Runner->new(@args) and ->parse_options(@argv) set
# options, and these options are shared between "Starman::Server"
# (documented in "starman") and "Plack::Runner" (documented in "plackup").
my @args = (
server => 'Starman', loader => 'Delayed', env => 'development',
version_cb => sub { print "Starman $Starman::VERSION\n" } );
my @argv = ( '--listen', "localhost:$SERV_PORT", $THE_APP );
my $runner = Plack::Runner->new(@args);
$runner->parse_options(@argv);
$runner->set_options(argv => \@argv);
die "loader shouldn't be Restarter" if $runner->{loader} eq 'Restarter';
if ($ENV{DOING_PAR_PACKER}) {
require Plack::Util;
Plack::Util::load_psgi($THE_APP); # for dependency resolution
# arrange to have the server shut down in a few moments
my $procpid = $$;
my $pid = fork();
if (!defined $pid) { die "fork failed" }
elsif ($pid==0) { sleep 5; kill 'INT', $procpid; exit; } # child
print "====> Please wait a few seconds...\n";
}
else {
# There's a small chance here that the browser could open before the server
# starts up. In that case, a reload of the browser window is needed.
print "Attempting to open in browser: http://localhost:$SERV_PORT/\n";
open_browser("http://localhost:$SERV_PORT/");
}
$runner->run;

@ -1,67 +0,0 @@
#!/usr/bin/env perl
use warnings;
use 5.018;
use Plack::MIME;
use Plack::Builder qw/builder enable mount/;
use Plack::Request ();
use Plack::Response (); # declare compile-time dependency
use Cpanel::JSON::XS qw/decode_json encode_json/;
use DBI ();
use DBD::SQLite (); # declare compile-time dependency
use HTML::Tiny ();
# This is the server-side code.
# note we rely on gui_basic.pl to set the working directory correctly
my $SERV_ROOT = 'web';
my $DB_FILE = 'database.db';
my $dbh = DBI->connect("DBI:SQLite:dbname=$DB_FILE",
undef, undef, { RaiseError=>1, AutoCommit=>1 });
$dbh->do(q{ CREATE TABLE IF NOT EXISTS FooBar (
foo VARCHAR(255), bar VARCHAR(255) ) });
# This sends HTML to the browser, but we could also send JSON
# and build the HTML table dynamically in the browser.
my $app_select = sub {
state $html = HTML::Tiny->new;
state $sth_select = $dbh->prepare(q{ SELECT rowid,foo,bar FROM FooBar });
$sth_select->execute;
my $data = $sth_select->fetchall_arrayref;
my $out = $html->table(
[ \'tr',
[ \'th', 'rowid', 'foo', 'bar' ],
map { [ \'td', @$_ ] } @$data
] );
return [ 200, [ "Content-Type"=>"text/html" ], [ $out ] ];
};
# This is an example of one way to communicate with JSON.
my $app_insert = sub {
my $req = Plack::Request->new(shift);
state $sth_insert = $dbh->prepare(q{ INSERT INTO FooBar (foo,bar) VALUES (?,?) });
my $rv = eval { # catch errors and return as 500 Server Error
my $content = decode_json( $req->content );
$sth_insert->execute($content->{foo}, $content->{bar});
{ ok=>1 }; # return value from eval, sent to client as JSON
}; my $e = $@||'unknown error';
my $res = $req->new_response($rv ? 200 : 500);
$res->content_type($rv ? 'application/json' : 'text/plain');
$res->body($rv ? encode_json($rv) : 'Server Error: '.$e);
return $res->finalize;
};
Plack::MIME->add_type(".js" => "application/javascript");
Plack::MIME->add_type(".data" => "application/octet-stream");
Plack::MIME->add_type(".mem" => "application/octet-stream");
Plack::MIME->add_type(".wasm" => "application/wasm");
builder {
enable 'SimpleLogger';
enable 'Static',
path => sub { s#\A/\z#/index.html#; /\.(?:html?|js|css|data|mem|wasm|pl)\z/i },
root => $SERV_ROOT;
mount '/select' => $app_select;
mount '/insert' => $app_insert;
}

@ -1,32 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl GUI Demo</title>
<script src="webperl.js"></script>
<script type="text/perl" src="web.pl"></script>
</head>
<body style="font-family:sans-serif;">
<h1>WebPerl GUI Demo</h1>
<div id="datatable"><i>No data loaded yet...</i></div>
<div><button id="reload_data">Reload Data</button></div>
<div style="margin-top:1em">
<div>
<label for="input_foo">foo</label>
<input type="text" id="input_foo">
</div>
<div>
<label for="input_bar">bar</label>
<input type="text" id="input_bar">
</div>
<div>
<button id="do_insert">Insert Data</button>
</div>
</div>
<p>Powered by <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a> (beta)</p>
</body>
</html>

@ -1,69 +0,0 @@
#!perl
use warnings;
use 5.028;
use WebPerl qw/js js_new sub1 encode_json/;
# This is the code that WebPerl runs in the browser. It is loaded by index.html.
sub do_xhr {
my %args = @_;
die "must specify a url" unless $args{url};
$args{fail} ||= sub { js('window')->alert(shift) };
my $xhr = js_new('XMLHttpRequest');
$xhr->addEventListener("error", sub1 {
$args{fail}->("XHR Error on $args{url}: ".(shift->{textContent}||"unknown"));
return;
});
$xhr->addEventListener("load", sub1 {
if ($xhr->{status}==200) {
$args{done}->($xhr->{response}) if $args{done};
}
else {
$args{fail}->("XHR Error on $args{url}: ".$xhr->{status}." ".$xhr->{statusText});
}
return;
});
$xhr->addEventListener("loadend", sub1 {
$args{always}->() if $args{always};
return;
});
# when given data, default to POST (JSON), otherwise GET
if ($args{data}) {
$xhr->open($args{method}||'POST', $args{url});
$xhr->setRequestHeader('Content-Type', 'application/json');
$xhr->send(encode_json($args{data}));
}
else {
$xhr->open($args{method}||'GET', $args{url});
$xhr->send();
}
return;
}
my $document = js('document');
my $btn_reload = $document->getElementById('reload_data');
sub do_reload {
state $dtbl = $document->getElementById('datatable');
$btn_reload->{disabled} = 1;
do_xhr(url => 'select',
done => sub { $dtbl->{innerHTML} = shift; },
always => sub { $btn_reload->{disabled} = 0; } );
return;
}
$btn_reload->addEventListener("click", \&do_reload);
my $btn_insert = $document->getElementById('do_insert');
sub do_insert {
state $txt_foo = $document->getElementById('input_foo');
state $txt_bar = $document->getElementById('input_bar');
$btn_insert->{disabled} = 1;
do_xhr(url => 'insert',
data => { foo=>$txt_foo->{value}, bar=>$txt_bar->{value} },
always => sub { $btn_insert->{disabled} = 0; do_reload; } );
return;
}
$btn_insert->addEventListener("click", \&do_insert);
do_reload; # initial load

@ -1,4 +0,0 @@
/public/webperl.js
/public/emperl.*
/gui_sweet
/gui_sweet.exe

@ -1,44 +0,0 @@
WebPerl Advanced GUI Example
============================
Similar to the "WebPerl Basic GUI Example", this is a demo of a GUI
using WebPerl, but using [Bootstrap](https://getbootstrap.com/)
and [jQuery](https://jquery.com/) instead of plain JavaScript,
and [Mojolicious](https://mojolicious.org/) instead of plain Plack.
To get this to work, you will need to copy the `webperl.js` and the
three `emperl.*` files from the main `web` directory to the `public`
subdirectory in this project.
Also, a limitation is that the server does not know when the browser
window is closed, so it must be stopped manually.
You can pack this application into a single executable using `do_pp.pl`.
Note: I'm not yet sure why, but sometimes this fails with errors such
as *"error extracting info from -c/-x file"*, in that case just try
the command again.
Author, Copyright, and License
==============================
**WebPerl - <http://webperl.zero-g.net>**
Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
**WITHOUT ANY WARRANTY**; without even the implied warranty of
**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see <http://perldoc.perl.org/index-licence.html>.

@ -1,23 +0,0 @@
#!/usr/bin/env perl
use warnings;
use strict;
use File::Basename qw/fileparse/;
use File::Spec::Functions qw/catfile/;
use File::Temp qw/tempfile/;
# this attempts to locate Mojo's default server.crt/server.key files
chomp( my $dir = `perldoc -l Mojo::IOLoop::Server` );
die "perldoc -l failed, \$?=$?" if $? || !-e $dir;
(undef, $dir) = fileparse($dir);
# set up a file for pp's -A switch
my ($tfh, $tfn) = tempfile(UNLINK=>1);
print {$tfh} catfile($dir,'resources','server.crt'),";server.crt\n";
print {$tfh} catfile($dir,'resources','server.key'),";server.key\n";
close $tfh;
my @args = (qw/ -a public -a templates -A /, $tfn);
local $ENV{DOING_PAR_PACKER}=1;
system(qw/ pp -o gui_sweet -z 9 -x /,@args,'gui_sweet.pl')==0
or die "pp failed, \$?=$?";

@ -1,77 +0,0 @@
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mojo::Util qw/md5_sum/;
use FindBin;
use File::Spec::Functions qw/catdir/;
use Browser::Open qw/open_browser/;
# This is the server-side code.
my $SERV_PORT = 3000;
my ($SSLCERTS,$HOMEDIR);
BEGIN {
$HOMEDIR = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
chdir $HOMEDIR or die "chdir $HOMEDIR: $!";
# do_pp.pl pulls the default Mojo SSL certs into the archive for us
$SSLCERTS = $ENV{PAR_TEMP} ? '?cert=./server.crt&key=./server.key' : '';
}
app->static->paths([catdir($HOMEDIR,'public')]);
app->renderer->paths([catdir($HOMEDIR,'templates')]);
app->secrets(['Hello, Perl World!']);
app->types->type(js => "application/javascript");
app->types->type(data => "application/octet-stream");
app->types->type(mem => "application/octet-stream");
app->types->type(wasm => "application/wasm");
# Authentication and browser-launching stuff (optional)
my $TOKEN = md5_sum(rand(1e15).time);
hook before_server_start => sub {
my ($server, $app) = @_;
my @urls = map {Mojo::URL->new($_)->query(token=>$TOKEN)} @{$server->listen};
my $url = shift @urls or die "No urls?";
if ($ENV{DOING_PAR_PACKER}) {
# arrange to have the server shut down in a few moments
my $procpid = $$;
my $pid = fork();
if (!defined $pid) { die "fork failed" }
elsif ($pid==0) { sleep 5; kill 'USR1', $procpid; exit; } # child
print "====> Please wait a few seconds...\n";
$SIG{USR1} = sub { $server->stop; exit };
}
else {
print "Attempting to open in browser: $url\n";
open_browser($url);
}
};
under sub {
my $c = shift;
return 1 if ($c->param('token')//'') eq $TOKEN;
$c->render(text => 'Bad token!', status => 403);
return undef;
};
get '/' => sub { shift->render } => 'index';
post '/example' => sub {
my $c = shift;
my $data = $c->req->json;
# can do anything here, this is just an example
$data->{string} = reverse $data->{string};
$c->render(json => $data);
};
app->start('daemon', '-l', "https://localhost:$SERV_PORT$SSLCERTS");
__DATA__
@@ index.html.ep
% layout 'main', title => 'WebPerl GUI Demo';
<main role="main" class="container">
<div>
<h1>WebPerl Advanced GUI Demo</h1>
<p class="lead">Hello, Perl World!</p>
<div id="buttons"></div>
</div>
</main>

@ -1,44 +0,0 @@
#!perl
use warnings;
use 5.028;
use WebPerl qw/js sub1 encode_json/;
# This is the code that WebPerl runs in the browser. It is loaded by index.html.
my $window = js('window');
my $document = js('document');
my $jq = js('jQuery');
sub do_ajax {
my %args = @_;
die "must specify a url" unless $args{url};
$args{fail} ||= sub { $window->alert(shift) };
$jq->ajax( $args{url}, {
$args{data} # when given data, default to POST (JSON), otherwise GET
? ( method=>$args{method}||'POST',
data=>encode_json($args{data}) )
: ( method=>$args{method}||'GET' ),
} )->done( sub1 {
$args{done}->(shift) if $args{done};
} )->fail( sub1 {
my ($jqXHR, $textStatus, $errorThrown) = @_;
$args{fail}->("AJAX Failed! ($errorThrown)");
} )->always( sub1 {
$args{always}->() if $args{always};
} );
return;
}
# slightly hacky way to get the access token, but it works fine
my ($token) = $window->{location}{search}=~/\btoken=([a-fA-F0-9]+)\b/;
my $btn = $jq->('<button>', { text=>"Click me!" } );
$btn->click(sub {
$btn->prop('disabled',1);
do_ajax( url=>"/example?token=$token",
data => { string=>"rekcaH lreP rehtonA tsuJ" },
done => sub { $window->alert("The server says: ".shift->{string}) },
always => sub { $btn->prop('disabled',0); } );
} );
$btn->appendTo( $jq->('#buttons') );

@ -1,50 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title><%= title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style>
body { padding-top: 5rem; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#"><%= title %></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
</ul>
</div>
</nav>
<%= content %>
<!-- Bootstrap wants its script tags at the end of the body element, so we'll put everything here: -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="webperl.js"></script>
<script type="text/perl" src="web.pl"></script>
</body>
</html>

@ -1,50 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Perl 6 Demos (Experimental)</title>
<script src="webperl.js"></script>
<!-- Please see the documentation at http://webperl.zero-g.net/perl6.html -->
<!-- Example 1: A really basic script -->
<script type="text/perl6">
print "Hello, Perl 6 World!\n";
</script>
<!-- Example 2: Accessing JavaScript -->
<script type="text/perl6">
my $document = EVAL(:lang<JavaScript>, 'return document');
$document.getElementById('my_button')
.addEventListener("click", -> $event {
print "You clicked 'Testing!'\n";
} );
</script>
<!-- Optional STDOUT/STDERR text area (if you don't use this, output goes to Javascript console) -->
<script>
window.addEventListener("load", function () {
document.getElementById('output')
.appendChild( Raku.makeOutputTextarea() );
});
</script>
</head>
<body>
<p>This is a demo of the
<a href="http://webperl.zero-g.net/perl6.html" target="_blank">experimental
Perl 6 support</a> in
<a href="http://webperl.zero-g.net" target="_blank">WebPerl</a>!</p>
<p><em>Currently only works in Chrome (needs BigInt support) and
may take a few seconds to load.</em></p>
<div id="output"></div>
<div id="buttons">
<button id="my_button">Testing!</button>
</div>
</body>
</html>

@ -1,71 +0,0 @@
#!/usr/bin/env perl
use warnings;
use strict;
use FindBin;
use Path::Class qw/dir/;
use HTTP::Tiny;
use File::Copy qw/copy/;
$|++;
# Quick & dirty script to patch P6 into the "web" dir
# Note: To restore webperl.js to the original version:
# $ git checkout web/webperl.js
my $p6url = 'https://perl6.github.io/6pad/gen/eval_code.js';
my $mydir = dir($FindBin::Bin);
my $webdir = $mydir->parent->parent->subdir('web');
print "Patching experimental Perl 6 support into ",$webdir->relative,"...\n";
my $wpfile = $webdir->file('webperl.js');
die "File structure not as I expected" unless -e $wpfile;
my $http = HTTP::Tiny->new();
my $jsfile = $webdir->file('perl6.js');
print "$p6url: ";
my $resp = $http->mirror($p6url, "$jsfile");
print "$resp->{status} $resp->{reason}\n";
die unless $resp->{success};
print "-> mirrored to ",$jsfile->relative,"\n";
my $wp = $wpfile->slurp(iomode=>'<:raw:encoding(UTF-8)');
$wp =~ s{
^ \N* \bbegin_webperl6_patch\b \N* $
.*
^ \N* \bend_webperl6_patch\b \N* $
}{}msxi;
die "I thought I clobbered the webperl6.js patch, why is there still a reference to Raku?"
if $wp=~/\bRaku\./;
my $wp6file = $mydir->file('webperl6.js');
my $wp6 = $wp6file->slurp(iomode=>'<:raw:encoding(UTF-8)');
1 while chomp($wp6);
$wpfile->spew(iomode=>'>:raw:encoding(UTF-8)', $wp.$wp6);
print "Patched ",$wp6file->relative," into ",$wpfile->relative,"\n";
for my $f ($mydir->children) {
next unless $f->basename=~/(?:html?|css)\z/i;
link_or_copy($f, $webdir);
}
sub link_or_copy {
my ($src,$dest) = @_;
die "Not a dir: $dest" unless -d $dest;
$dest = $dest->file( $src->basename );
if ( eval { symlink("",""); 1 } ) { # we have symlink support
if (!-l $dest) {
$dest->remove or die "$dest: $!" if -e $dest;
my $targ = $src->relative( $dest->dir );
symlink($targ,$dest) or die "symlink: $!";
print "Linked ",$dest->relative," to $targ\n";
}
else { print "Link ",$dest->relative," exists\n"; }
}
else {
$dest->remove or die "$dest: $!" if -e $dest;
copy($src,$dest) or die "copy: $!";
print "Copied ",$src->relative," to ",$dest->relative,"\n";
}
}

@ -1,72 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Perl 6 Experiments</title>
<script src="webperl.js"></script>
<!--
The following is a demo of Perl 5 and Perl 6 calling each other via JavaScript.
-->
<script>
window.Foo = {
set: function (x,y) { window.Foo[x]=y }, // workaround, see P6 below
};
</script>
<script type="text/perl">
use warnings;
use 5.028;
sub hello {
my $x = shift;
say "Hello from Perl 5! You said '$x'";
}
my $Foo = js('window.Foo');
$Foo->{p5} = \&hello;
js('document')->getElementById('btn_p5')
->addEventListener("click", sub {
say "This is Perl 5, attempting to call Perl 6...";
$Foo->p6("I am Perl 5!");
} );
say "Perl 5 is ready.";
</script>
<script type="text/raku">
sub hello ($x) {
say "Hello from Perl 6! You said '$x'"
}
my $Foo = EVAL(:lang<JavaScript>, 'return window.Foo');
# I'm not yet sure why the following doesn't work, Foo.set is a workaround
#$Foo<p6> = &hello;
$Foo.set("p6", &hello);
my $document = EVAL(:lang<JavaScript>, 'return document');
$document.getElementById('btn_p6')
.addEventListener("click", -> $event {
say "This is Perl 6, attempting to call Perl 5...";
$Foo.p5("I am Perl 6!");
} );
say "Perl 6 is ready.";
</script>
</head>
<body>
<p>See the JS console! Don't click the buttons until both languages are ready.</p>
<div id="buttons">
<button id="btn_p5">Perl 5</button>
<button id="btn_p6">Perl 6</button>
</div>
</body>
</html>

@ -1,148 +0,0 @@
"use strict"; /* DO NOT EDIT THIS LINE! begin_webperl6_patch */
/***** NOTICE: This is part of the experimental WebPerl Perl 6 support.
* This file (webperl6.js) is currently patched into webperl.js by 6init.pl.
* There is currently a fair amount of duplication between the following code
* and webperl.js that should probably be reduced.
* This file should eventually be merged permanently into webperl.js.
*/
/** ***** WebPerl - http://webperl.zero-g.net *****
*
* Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
* at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
* Berlin, Germany, http://www.igb-berlin.de
*
* This program is free software; you can redistribute it and/or modify
* it under the same terms as Perl 5 itself: either the GNU General Public
* License as published by the Free Software Foundation (either version 1,
* or, at your option, any later version), or the "Artistic License" which
* comes with Perl 5.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the licenses for details.
*
* You should have received a copy of the licenses along with this program.
* If not, see http://perldoc.perl.org/index-licence.html
**/
// I'm using "Raku" because the Hamming distance from Perl <-> Perl6 is too small for me,
// it's too much of a risk for typos since webperl.js also provides the "Perl" object.
// But the following functions are currently available on both the Raku.* and Perl6.* objects:
// .init(), .eval(), .addStateChangeListener(), .makeOutputTextarea()
// but everything else, such as Raku.state or Raku.output, needs to go via the Raku object.
var Raku = {
state: "Uninitialized", // user may read (only!) this
// internal variables:
stdout_buf: "", stderr_buf: "", // for our default Raku.output implementation
};
var Perl6 = {};
Raku.changeState = function (newState) {
if (Raku.state==newState) return;
var oldState = Raku.state;
Raku.state = newState;
for( var i=0 ; i<Raku.stateChangeListeners.length ; i++ )
Raku.stateChangeListeners[i](oldState,newState);
};
Raku.stateChangeListeners = [ function (from,to) {
console.debug("Raku: state changed from "+from+" to "+to);
} ];
Raku.addStateChangeListener = Perl6.addStateChangeListener = function (handler) {
Raku.stateChangeListeners.push(handler);
};
// chan: 1=STDOUT, 2=STDERR
// implementations are free to ignore the "chan" argument if they want to merge the two streams
Raku.output = function (str,chan) { // can be overridden by the user
var buf = chan==2 ? 'stderr_buf' : 'stdout_buf';
Raku[buf] += str;
var pos = Raku[buf].indexOf("\n");
while (pos>-1) {
console.log( chan==2?"STDERR":"STDOUT", Raku[buf].slice(0,pos) );
Raku[buf] = Raku[buf].slice(pos+1);
pos = Raku[buf].indexOf("\n");
}
};
Raku.makeOutputTextarea = Perl6.makeOutputTextarea = function (id) {
var ta = document.createElement('textarea');
if (id) ta.id = id;
ta.rows = 24; ta.cols = 80;
ta.setAttribute("readonly",true);
Raku.output = function (str) {
ta.value = ta.value + str;
ta.scrollTop = ta.scrollHeight;
};
return ta;
};
Raku.init = Perl6.init = function (readyCallback) {
if (Raku.state != "Uninitialized")
throw "Raku: can't call init in state "+Raku.state;
Raku.changeState("Initializing");
var baseurl = Perl.Util.baseurl(getScriptURL()); // from webperl.js
// NOTE that NQP_STDOUT currently gets handed HTML,
// so we jump through some hoops to decode it here:
var decode_div = document.createElement('div');
window.NQP_STDOUT = function (str) {
str = str.replace(/[\<\>]/g,''); // declaw unexpected tags
decode_div.innerHTML = str;
str = decode_div.textContent;
decode_div.textContent = '';
Raku.output(str,1);
};
console.debug("Raku: Fetching Perl6...");
var script = document.createElement('script');
script.async = true; script.defer = true;
// Order is important here: 1. Add to DOM, 2. set onload, 3. set src
document.getElementsByTagName('head')[0].appendChild(script);
script.onload = function () {
Raku.eval = Perl6.eval = window.evalP6;
Raku.changeState("Ready");
if (readyCallback) readyCallback();
};
script.src = baseurl+"/perl6.js";
}
window.addEventListener("load", function () {
var scripts = [];
var script_src;
document.querySelectorAll("script[type='text/perl6'],script[type='text/raku']")
.forEach(function (el) {
if (el.src) {
if (script_src || scripts.length)
console.error('Only a single Perl6 script may be loaded via "script src=", ignoring others');
else
script_src = el.src;
}
else {
if (script_src)
console.error('Only a single Perl6 script may be loaded via "script src=", ignoring others');
else
scripts.push(el.innerHTML);
}
});
if (script_src) {
console.debug("Raku: Found a script with src, fetching and running...", script_src);
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
var code = this.responseText;
Raku.init(function () { Raku.eval(code); });
});
xhr.open("GET", script_src);
xhr.send();
}
else if (scripts.length) {
console.debug("Raku: Found",scripts.length,"embedded script(s), autorunning...");
var code = scripts.join(";\n");
Raku.init(function () { Raku.eval(code); });
}
else console.debug("Raku: No embedded scripts");
});
/* DO NOT EDIT THIS LINE! end_webperl6_patch */

@ -1,24 +0,0 @@
#!/usr/bin/env perl
use warnings;
use strict;
use Data::Dump;
use IO::Socket;
# $ git clone https://github.com/novnc/websockify
# $ cd websockify
# $ ./run 2345 localhost:2346
my $serv = IO::Socket::INET->new(
LocalAddr => 'localhost',
LocalPort => 2346,
Proto => 'tcp',
Listen => 5,
Reuse => 1 ) or die $@;
# really dumb server
print "Listening...\n";
while (my $client = $serv->accept()) {
print "Got a client...\n";
print $client "Hello, Perl!\n";
}

@ -1,24 +0,0 @@
use warnings;
use 5.028;
use Socket;
use Fcntl qw/F_GETFL F_SETFL O_NONBLOCK/;
use IO::Select;
use Data::Dumper;
$Data::Dumper::Useqq=1;
my $port = 2345;
my $iaddr = inet_aton("localhost") || die "host not found";
my $paddr = sockaddr_in($port, $iaddr);
# Note: Emscripten apparently doesn't like NONBLOCK being passed to socket(),
# and I couldn't get setsockopt to work yet - but the following works.
# https://github.com/kripken/emscripten/blob/d08bf13/tests/sockets/test_sockets_echo_client.c#L166
# everything is async - need "our $sock" here so it doesn't go out of scope at end of file
socket(our $sock, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die "socket: $!";
my $flags = fcntl($sock, F_GETFL, 0) or die "get flags: $!";
fcntl($sock, F_SETFL, $flags | O_NONBLOCK) or die "set flags: $!";
connect $sock, $paddr or !$!{EINPROGRESS} && die "connect: $!";
# so far so good... but probably should just use something like IO::Async instead

@ -1,59 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Sync HTTP Demo</title>
<!--
This is a demo of dynamically loading modules via synchronous
XMLHttpRequests.
WARNING: Please note that https://xhr.spec.whatwg.org/ says:
"Synchronous XMLHttpRequest outside of workers is in the process of
being removed from the web platform as it has detrimental effects to
the end users experience. (This is a long process that takes many
years.)"
The method was first described by LanX at
https://www.perlmonks.org/?node_id=1225490
Thank you! :-)
-->
<script src="webperl.js"></script>
<script type="text/perl">
use warnings;
use 5.028;
use WebPerl qw/js js_new/;
BEGIN {
push @INC, sub {
my (undef,$file) = @_;
# sadly, MetaCPAN doesn't send CORS headers (yet)
#my $url = 'https://fastapi.metacpan.org/v1/source/'
# . ( $file =~ s/\//::/r =~ s/\.pm$//ir );
# this requires one to copy Dump.pm into web/Data/:
my $url = $file;
my $xhr = js_new('XMLHttpRequest');
$xhr->open('GET', $url, 0);
$xhr->send();
if ($xhr->{status}==200)
{ return \$xhr->{responseText} }
else { return }
};
}
use Data::Dump 'pp';
js('window')->alert(pp({Hello=>"World!"}));
</script>
</head>
<body>
<p>Output: see JS console</p>
</body>
</html>

@ -1,42 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl XTerm.js Test</title>
<!--cacheable--><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@3.7.0/dist/xterm.css" integrity="sha256-OSfRj4jMeYVFSwgcvVvKj4V0+mwqSP9YJjyEJe7dmK0=" crossorigin="anonymous" />
<!--cacheable--><script src="https://cdn.jsdelivr.net/npm/xterm@3.7.0/dist/xterm.js" integrity="sha256-gIILiZzLBFrmY1dzcKJC2Nmw4o9ISITTNsro2rf8svM=" crossorigin="anonymous"></script>
<script src="webperl.js"></script>
<script>
"use strict";
window.addEventListener('load', function () {
var term = new Terminal();
term.open(document.getElementById('terminal'));
Perl.output = function (str) { term.write(str) };
Module.preRun.push(function () { ENV.TERM = "xterm" });
});
</script>
<script type="text/perl">
use warnings;
use strict;
use Term::ANSIColor qw/colored/;
print colored("Hello, Color World!\n", 'black on_yellow');
# Possible To-Do for Later: can we accept input from XTerm?
# might not be so easy: https://github.com/xtermjs/xterm.js/issues/1546#issuecomment-402547923
# (keypresses are events, but reading from STDIN is normally blocking...)
</script>
</head>
<body>
<div id="terminal"></div>
<p><a href="http://xtermjs.org/" target="_blank">xterm.js</a></p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,90 @@
\[ [Using](using.html) -
[Building](building.html) -
[🦋](perl6.html) -
[Notes](notes.html) -
[Legal](legal.html) -
[Wiki](https://github.com/haukex/webperl/wiki) \]
Welcome to WebPerl!
===================
WebPerl uses the power of [WebAssembly](https://webassembly.org/) and
[Emscripten](http://emscripten.org/) to let you run Perl in the browser!
WebPerl does not translate your Perl code to JavaScript, instead, it is
a port of the `perl` binary to WebAssembly, so that you have the full
power of Perl at your disposal!
**Notice: WebPerl is very much in beta.**
Some things may not work yet, and parts of the API may still change.
Your feedback is always appreciated!
[**New: Experimental Perl 6 Support**](perl6.html)
```html
<script src="webperl.js"></script>
<script type="text/perl">
print "Hello, Perl World!\n"; # goes to JavaScript console by default
js('document')->getElementById('my_button')
->addEventListener('click', sub {
js('window')->alert("You clicked the button!");
} );
</script>
```
- [**Download `webperl_prebuilt_v0.09-beta.zip`**](https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip)
- [**Get the sources on GitHub**](https://github.com/haukex/webperl)
For web applications written with WebPerl, see:
- [**WebPerl Code Demo Editor** (beta)](democode/index.html)
- [**WebPerl Regex Tester** (beta)](regex.html)
Quick Start
-----------
- Prerequisites: `perl` (a recent version is recommended, e.g. v5.26 and up),
and [`cpanm`](https://metacpan.org/pod/App::cpanminus) to easily install
dependencies (otherwise, see the file `cpanfile` for the dependencies and
use the module installer of your choce).
- In a shell:
$ wget https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip
$ unzip webperl_prebuilt_v0.09-beta.zip
$ cd webperl_prebuilt_v0.09-beta
$ cpanm --installdeps .
$ plackup webperl.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/
- Then point your browser at <http://localhost:5000/webperl_demo.html>
and have a look at its source. The ZIP archive also contains several
other examples, which you can access at <http://localhost:5000/>.
You may also host the contents of the above ZIP archive on a webserver of your
choice, or some browsers will support opening the files locally; both are
described in [Serving WebPerl](using.html#serving-webperl).
(Note: In `webperl_demo.html`, you'll likely see "AJAX Failed!", which is to be
expected since your webserver won't know how to handle the example AJAX request.)
Have fun!
***
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
Please see the ["Legal" page](legal.html) for details.
***
You can find the source for this page at
<https://github.com/haukex/webperl/blob/gh-pages/index.md>

@ -1,27 +1,14 @@
WebPerl
=======
\[ [Using](using.html) -
[Building](building.html) -
[🦋](perl6.html) -
[Notes](notes.html) -
Legal -
[Wiki](https://github.com/haukex/webperl/wiki) \]
Welcome to WebPerl!
Please see the documentation on the main site:
WebPerl Author, Copyright, and License
======================================
**<http://webperl.zero-g.net>**:
[Using](https://webperl.zero-g.net/using.html),
[Building](https://webperl.zero-g.net/building.html),
[Notes](https://webperl.zero-g.net/notes.html)
As well as the
[GitHub Wiki](https://github.com/haukex/webperl/wiki).
You can also check out all of the documentation into subdirectories
of this project, for example:
$ git clone --branch gh-pages https://github.com/haukex/webperl.git pages
$ git clone https://github.com/haukex/webperl.wiki.git wiki
Author, Copyright, and License
==============================
**WebPerl - <http://webperl.zero-g.net>**
@ -43,3 +30,9 @@ See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see <http://perldoc.perl.org/index-licence.html>.
***
You can find the source for this page at
<https://github.com/haukex/webperl/blob/gh-pages/legal.md>

@ -0,0 +1,203 @@
\[ [Using](using.html) -
[Building](building.html) -
[🦋](perl6.html) -
Notes -
[Legal](legal.html) -
[Wiki](https://github.com/haukex/webperl/wiki) \]
Misc. Notes on WebPerl
======================
Links
-----
Other places I've written about WebPerl:
- [Run Perl 5 in the Browser!](https://www.perlmonks.org/?node_id=1220426)
- [WebPerl Regex Tester (beta)](https://www.perlmonks.org/?node_id=1221705)
- [Embedding WebPerl Code Demos into Other Pages](https://www.perlmonks.org/?node_id=1223812)
- [WebPerl now has Experimental Perl 6 Support!](https://www.perlmonks.org/?node_id=1225647)
### Talks
- YAPC::EU 2019 (PerlCon) in Rīga - [talk](https://perlcon.eu/talk/40), [slides](https://goo.gl/QPvUb9) ([PDF](HD_WebPerl_Riga_2019_Slides.pdf)), [Video Link](https://youtu.be/bT17TCMbsdc)
- Bonus Lightning Talk - [Video Link](https://youtu.be/tenl6JEum5k?t=29642)
- German Perl Workshop 2019 in Munich - [talk](http://act.yapc.eu/gpw2019/talk/7616), [slides in English](https://goo.gl/yM6dff) ([PDF](HD_WebPerl_GPW_2019_Slides.pdf)), talk in German 🇩🇪, [Video Link](https://www.youtube.com/watch?v=EI8js5zf_Pw)
- YAPC::EU 2018 in Glasgow - [lightning talk](http://act.perlconference.org/tpc-2018-glasgow/talk/7475), [Video Link](https://youtu.be/KrGSg7uVZj0?t=29520)
[![DOI 10.5281/zenodo.1400490](https://zenodo.org/badge/DOI/10.5281/zenodo.1400490.svg)](https://doi.org/10.5281/zenodo.1400490)
### "In the Press"
- WebPerl gets a mention in Sawyer X's talk ["Guac!"](https://www.youtube.com/watch?v=sTEshbh2lYQ) about [`Guacamole`](https://metacpan.org/pod/Guacamole) at The Perl Conference in the Cloud 2020
- WebPerl gets another mention in Will Braswell's talk ["The Perl Family Tree: Discover Your Heritage"](https://www.youtube.com/watch?v=ZM-KHZJpy84) at The Perl Conference 2019 in Pittsburgh
- WebPerl gets a mention in Will Braswell's talk ["Perl 11 - The Future of Saint Larry's Language"](https://fosdem.org/2019/schedule/event/perl11/) at FOSDEM '19
- [Perl Advent Calendar 2018, Day 2](http://www.perladvent.org/2018/2018-12-02.html) (by Mark Fowler)
- [Reddit: Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should (2018-10-22)](https://www.reddit.com/r/programmingcirclejerk/comments/9qerw5/your_scientists_were_so_preoccupied_with_whether/) 😉
- [Hacker News (2018-10-21)](https://news.ycombinator.com/item?id=18269071)
- [Reddit /r/programming (2018-10-21)](https://www.reddit.com/r/programming/comments/9q65tf/run_perl_in_the_browser_with_webperl/)
- [Facebook Group "Perl Programmers" (2018-10-20)](https://www.facebook.com/groups/perlprogrammers/permalink/2141844605848316/)
- [Reddit /r/perl: WebPerl Regex Tester (2018-09-05)](https://www.reddit.com/r/perl/comments/9d5n77/webperl_regex_tester/)
### Prior Art
Several people have built microperl with Emscripten:
- Harsha <https://github.com/moodyharsh/plu>
- Shlomi Fish <https://github.com/shlomif/perl5-for-JavaScript--take2>
- FUJI Goro <https://github.com/gfx/perl.js>
TODOs
-----
1. Testing
- How to best disable individual tests that we know won't work? (qx etc.)
- How to handle the many tests that call an external Perl?
- patching t/test.pl's runperl() seems easiest at the moment, and we can use the iframe method from the IDE
- Continue work on `WebPerl.t`
- More tests for Unicode support (Perl/JS interface, Perl.eval(), plus Emscripten's virtual FS)
- Focus on getting the tests running in the browser instead of node.js
- How to best package tests?
- If possible, a separate bundle, so that it can be loaded optionally and we don't need to rebuild
- How does `make test` find and handle all the various modules' `t`s?
2. Misc
- Write up a full RPC example
- `Future`
- Would `Future::AsyncAwait` work in WebPerl? (Or maybe with a JS backend?)
- `Future::HTTP` (probably just as a frontend to XHR?)
- Improve Perl 6 integration
- Build a packaged Rakudo.js ourselves instead of borrowing from <https://perl6.github.io/6pad/>
- Perhaps get an API change or two into Rakudo.js: `STDOUT` currently gets output with HTML escapes
- See also notes in [Perl 6 Support 🦋](perl6.html)
- Investigate Emscripten's main loop concept for handling nonblocking sockets?
- Use Multiplicity for re-starting perl? (Thanks to Nick Clark for the idea)
- Add Regex::Debugger into regex tester? (Thanks to Renee for the idea)
- Turn some patches from emperl5 into patches for P5P
- Submit some patches to Emscripten
- <https://github.com/kripken/emscripten/pull/7005>
- <https://github.com/kripken/emscripten/issues/7029>
- Would we need to patch Perl's signal functions if Emscripten's stubs weren't noisy?
- Add `Perl.Util` functions for making file uploads and downloads easier
- Plus an example showing how to use it to run a "legacy" Perl script with inputs and output
- There is some potential for restructuring:
- `Perl.glue()` and `Perl.dispatch()` could go into `WebPerl.xs` (?)
- Parts of `webperl.js` could go into `common_preamble.js` or `WebPerl.xs`,
so that `emperl.js` is **runnable on its own in a Web Worker (?)**
(see notes in `perlrunner.html` / `e12f1aa25a000`)
→ this might be interesting for running tests?!
- `nodeperl_dev_prerun.js` could probably be merged into that as well
- Could put a `WebPerl` stub on CPAN
- In theory could write a `WebPerl::Local` that contains the `WebPerl` API, and
have various drivers such as `WebPerl::WebDriver` or `WebPerl::JSAny` that
runs the code given to WebPerl's `js()`...
- Regarding the funky syntax highlighting on GitHub: <https://github.com/atom/language-html/issues/88#issuecomment-431361414>
3. See Also
- <https://github.com/haukex/webperl/issues>
- <https://github.com/haukex/webperl/pulls>
- See also `TODO`s in the source tree by grepping for `TODO`
or using the included `findtodo.sh`.
SSL
---
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout selfsigned.key -out selfsigned.crt
...
Common Name (e.g. server FQDN or YOUR name) []: localhost
...
$ plackup --enable-ssl --ssl-key-file=selfsigned.key --ssl-cert-file=selfsigned.crt web/webperl.psgi
# then go to https://localhost:5000 and accept the certificate warning
Possible Improvements
---------------------
- More efficient JS/C/Perl glue
- Test/Support sockets/WebSockets
- for example, can we compile a DBD:: module to connect to a DB on the server?
- A RPC module for communicating between client and server Perls
- I think it's probably best to not have WebPerl prescribe a specific RPC mechanism,
since there's a big variety and many are pretty simple to implement using e.g. jQuery
- Support some of the Emscripten C API (like wget?)
- Try to shrink the download size more (exclude more modules, ...?)
Limitations
-----------
- Only works in browsers with WebAssembly support
(asm.js requires aligned memory access, and Perl apparently has quite a few places with unaligned access)
- 32-bit ints
- No `system`, `qx`, `fork`, `kill`, `wait`, `waitpid`, threads, etc.
- Theoretically, we could link in BusyBox to get a shell and utilities (??)
- (`system` and `qx` support could theoretically be added by patching `pp_system`/`pp_backtick` in `pp_sys.c`)
- No signals (except `SIGALRM`)
- In the current configuration, `exit` is not supported, and therefore `atexit` handlers aren't supported
(see discussion in [Using WebPerl](using.html), and `NO_EXIT_RUNTIME` in the Emscripten documentation -
currently it seems to make the most sense to build with `NO_EXIT_RUNTIME=1`)
- Static linking, requires rebuild to add modules
(Emscripten apparently only supports asm.js dynamic linking when dynamic memory growth is disabled, which is not very useful)
Release Checklist
-----------------
- Update `Changes.md` with all changes since last release
As an example, to list changes since a specific version, excluding the regex tester:
$ git log --stat v0.05-beta.. -- . ':!web/regex_tester.html' ':!.gitignore'
- Also make sure that the documentation in `using.md` etc. mentions when features were added/deprecated
- Update version numbers everywhere; use `grep` to find them, for example:
$ grep -Er --exclude-dir=hostperl --exclude-dir=.git --exclude-dir=emperl5 --exclude=emperl.* '0\.[01][0-9]' .
$ ( cd emperl5; grep -Er '0\.[01][0-9]' `git diff --numstat --diff-filter=ACMRT v5.28.1 HEAD | cut -f3` )
At a minimum there is:
- `web/webperl.js` - `Perl.WebPerlVersion`
- `emperl5/ext/WebPerl/lib/WebPerl.pm` - `$VERSION`
- `pages/index.md` and `pages/using.md` - download links
- Update [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) values as needed, e.g.:
$ perl -wMstrict -MDigest::SRI=sri -le 'print sri "SHA-256","web/webperl.js"'
- Build and create dist, e.g. `build/build.pl --reconfig --dist=webperl_prebuilt_v0.07-beta`
- Test all build results, both from `file://...` and `http://localhost`
- Add tags, the `webperl` repo gets an annotated tag such as `v0.07-beta`,
and the `emperl5` repo gets an unannotated tag such as `webperl_v0.07-beta`,
then `git push --tags`
- Create a release on GitHub and upload the `webperl_prebuilt_*.zip` as an asset
- Uploading to AWS S3:
1. `for X in emperl.* webperl.js; do gzip -v -9 $X && mv -v $X.gz $X ; done`
2. Upload them with the appropriate `Content-Type` (see e.g. `web/webperl.psgi`) and a `Content-Encoding` of `gzip`
- If there was a `pages_for_vX.XX` branch of `gh-pages`, don't forget to merge that
***
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
Please see the ["Legal" page](legal.html) for details.
***
You can find the source for this page at
<https://github.com/haukex/webperl/blob/gh-pages/notes.md>

@ -0,0 +1,139 @@
\[ [Using](using.html) -
[Building](building.html) -
🦋 -
[Notes](notes.html) -
[Legal](legal.html) -
[Wiki](https://github.com/haukex/webperl/wiki/Perl6) \]
WebPerl Experimental Perl 6 Support 🦋
=====================================
Thanks to [**Paweł Murias**](https://github.com/pmurias) and his
amazing work on **`Rakudo.js`** and
[**6Pad**](https://perl6.github.io/6pad/), I've been able to patch
support for Perl 6 into WebPerl!
Currently **requires Google Chrome** (due to BigInt support), see the
[Quick Start](#quick-start) below on how to get this up and running on
your local machine!
```html
<script src="webperl.js"></script>
<script type="text/perl6">
say "Hello, Perl 6 World!"; # goes to JavaScript console by default
my $document = EVAL(:lang<JavaScript>, 'return document');
my $window = EVAL(:lang<JavaScript>, 'return window');
$document.getElementById('my_button')
.addEventListener("click", -> $event {
$window.alert("You clicked the button!");
} );
</script>
```
Quick Start
-----------
- Prerequisites: `perl` (a recent version is recommended, e.g. v5.26 and up),
and [`cpanm`](https://metacpan.org/pod/App::cpanminus) to easily install
dependencies (otherwise, see the files `cpanfile` for the dependencies and
use the module installer of your choce).
- In a shell (the following assumes Linux):
$ git clone https://github.com/haukex/webperl.git
$ cd webperl
$ wget https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip
$ unzip -j webperl_prebuilt_v0.09-beta.zip '*/emperl.*' -d web
$ cpanm --installdeps .
$ cd experiments ; cpanm --installdeps . ; cd ..
$ experiments/p6/6init.pl # this patches Perl 6 support in
$ plackup web/webperl.psgi
- Then point your Chrome browser at <http://localhost:5000/6demo.html>,
and have a look at its source.
Have fun!
Experimental Status and Notes
-----------------------------
- I don't have enough experience with `Rakudo.js`
- <https://github.com/rakudo/rakudo/tree/master/src/vm/js>
- <https://perl6.github.io/6pad/>
- <http://blogs.perl.org/users/pawel_murias/>
- <https://github.com/perl6/perl6-parcel-example>
- <https://www.youtube.com/watch?v=LN0mKjmraVs>
- requires BigInt support, which is currently only available in Chrome
- <https://developers.google.com/web/updates/2018/05/bigint>
- <https://github.com/tc39/proposal-bigint>
- <https://v8.dev/blog/bigint>
- Large download (10MB compressed, 74MB uncompressed) - can we
repackage it to make it smaller, or is there a good way to
distribute this?
- STDERR only goes to console, STDOUT gets output with HTML escapes
Documentation
-------------
My code steal^H^H^H^H^Hborrows the prepackaged `Rakudo.js` build from
[6Pad](https://perl6.github.io/6pad/) and caches it locally. The script
`experiments/p6/6init.pl` also patches the experimental P6 support into
`webperl.js` (see the [Quick Start](#quick-start) above).
Note that both Perl 5 and Perl 6 are only loaded on demand by
`webperl.js`, so if you only use one or the other, you won't have the
overhead of loading both.
For now, I've basically just patched `Rakudo.js`'s `evalP6()` into
`Raku.eval()`, and `NQP_STDOUT` into `Raku.output`, to make things more
like the Perl 5 WebPerl, and provided some of the same API for Perl 6
as I provide for Perl 5.
The JS API provided by WebPerl for Perl 6 currently closely mirrors
[the Perl 5 API](using.html#webperljs): There is a JS object `Raku`
which provides the following functions / properties that do mostly the
same as for Perl 5:
- `Raku.addStateChangeListener( function (from,to) {} )`
- `Raku.state`
- `Raku.output = function (str,chan) {}`
- `Raku.makeOutputTextarea()`
- `Raku.init( function () {} )`
- `Raku.eval( code )`
You can add Perl 6 code to your HTML pages with `<script>` tags
with `type="text/perl6"` or `type="text/raku"`.
For everything else, I defer to `Rakudo.js` for now! I will update this
documentation as things evolve.
***
Additional notes on WebPerl's experimental Perl 6 support may be found
in the [GitHub Wiki](https://github.com/haukex/webperl/wiki/Perl6).
***
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
Please see the ["Legal" page](legal.html) for details.
***
You can find the source for this page at
<https://github.com/haukex/webperl/blob/gh-pages/perl6.md>

@ -25,6 +25,15 @@ You should have received a copy of the licenses along with this program.
If not, see http://perldoc.perl.org/index-licence.html
##### -->
<!-- This is essentially a copy of
https://github.com/haukex/webperl/blob/master/web/regex_tester.html
with the following differences:
- webperl.js from CDN
- $RUN_CODE_IN_IFRAME enabled
- URL updated to https://github.com/haukex/webperl/blob/gh-pages/regex.html
(see import_regex_tester.pl)
-->
<meta name="viewport" content="width=600" />
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" integrity="sha256-oSrCnRYXvHG31SBifqP2PM1uje7SJUyX0nTwO2RJV54=" crossorigin="anonymous" />
<style>
@ -99,9 +108,9 @@ textarea.samp_ta {
}
</style>
<script src="webperl.js"></script>
<!--script src="https://webperlcdn.zero-g.net/v0.09-beta/webperl.js"
integrity="sha256-1RaYAh/WfDl3WZl+BDpSuSycg4x88pfkAFSxhWjBONk=" crossorigin="anonymous"></script-->
<!--script src="webperl.js"></script-->
<script src="https://webperlcdn.zero-g.net/v0.09-beta/webperl.js"
integrity="sha256-1RaYAh/WfDl3WZl+BDpSuSycg4x88pfkAFSxhWjBONk=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script type="text/perl">
@ -114,7 +123,7 @@ my $jq = js('jQuery');
sub pp { Data::Dumper->new([@_])->Useqq(1)->Terse(1)->Pair('=>')
->Sortkeys(1)->Quotekeys(0)->Indent(0)->Purity(1)->Dump }
our $RUN_CODE_IN_IFRAME=0;
our $RUN_CODE_IN_IFRAME=1;
# Possible To-Do for Later: Subs in preamble code produce "subroutine redefined" warnings.
# I could think about whether there's a decent way around that.
@ -163,9 +172,9 @@ if ($RUN_CODE_IN_IFRAME) { # https://www.html5rocks.com/en/tutorials/security/sa
my $iframe_html = <<~'END_IFRAME_HTML';
<html>
<head>
<script src="__WEBPERLURL__"></scr__ipt>
<!--script src="https://webperlcdn.zero-g.net/v0.09-beta/webperl.js"
integrity="sha256-1RaYAh/WfDl3WZl+BDpSuSycg4x88pfkAFSxhWjBONk=" crossorigin="anonymous"></scr__ipt-->
<!--script src="__WEBPERLURL__"></scr__ipt-->
<script src="https://webperlcdn.zero-g.net/v0.09-beta/webperl.js"
integrity="sha256-1RaYAh/WfDl3WZl+BDpSuSycg4x88pfkAFSxhWjBONk=" crossorigin="anonymous"></scr__ipt>
<script>
Perl.noMountIdbfs=true; // we're sandboxed
</scr__ipt>
@ -676,7 +685,7 @@ Copyright &copy; 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <a href="http://www.igb-berlin.de" target="_blank">http://www.igb-berlin.de</a>.
For details, please see
<a href="https://github.com/haukex/webperl/blob/master/web/regex_tester.html" target="_blank">the source code of this file</a>.
<a href="https://github.com/haukex/webperl/blob/gh-pages/regex.html" target="_blank">the source code of this file</a>.
</div>
</body>

@ -0,0 +1,655 @@
\[ Using -
[Building](building.html) -
[🦋](perl6.html) -
[Notes](notes.html) -
[Legal](legal.html) -
[Wiki](https://github.com/haukex/webperl/wiki/Using-WebPerl) \]
Using WebPerl
=============
**Notice: WebPerl is very much in beta.**
Some things may not work yet, and parts of the API may still change.
Your feedback is always appreciated!
This page documents the Perl 5 support, for the *experimental*
Perl 6 support, see [here](perl6.html).
- [Basic Usage](#basic-usage)
- [Serving WebPerl](#serving-webperl)
- [Including Perl code in your HTML](#including-perl-code-in-your-html)
- [The Perl Interpreter and its Environment](#the-perl-interpreter-and-its-environment)
- [Memory Management and Anonymous `sub`s](#memory-management-and-anonymous-subs)
- [Virtual Filesystem](#virtual-filesystem)
- [webperl.js](#webperljs)
- The JS `Perl` object
- [WebPerl.pm](#webperlpm)
- Perl <-> JavaScript mappings
- [The Mini IDE](#the-mini-ide)
Basic Usage
-----------
### Getting WebPerl
If you plan on building WebPerl, for example if you'd like to add more CPAN
modules, then head on over to [Building WebPerl](building.html). Otherwise, if
you'd just like to get started quickly and work with the prebuilt WebPerl
(includes many of the Perl core modules plus a couple extras), then download
[`webperl_prebuilt_v0.09-beta.zip`](https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip)
and unpack it. This ZIP file includes the contents of the
[`web`](https://github.com/haukex/webperl/tree/master/web) directory of the
source code, as well as the build products `emperl.*` (currently three files).
If you'd like to work with the source code as checked out from GitHub, then you
can copy these `emperl.*` files into the `web` directory of the source tree.
### Serving WebPerl
You should serve WebPerl via a webserver of your choice, or you can
use the included simple `webperl.psgi` for testing. You can run it using
[`plackup` from Plack](https://metacpan.org/pod/distribution/Plack/script/plackup)
by simply saying `plackup webperl.psgi`.
The following four files make up WebPerl:
- `webperl.js` - Contains the WebPerl JavaScript API and supporting code.
- `emperl.js` - Emscripten-generated supporting JavaScript.
- `emperl.wasm` - The `perl` binary and libraries compiled to WebAssembly.
- `emperl.data` - The Emscripten virtual file system data (`.pm` files etc.).
I strongly recommend you add a MIME type of `application/wasm` for `.wasm` files,
otherwise you may see warnings like
"wasm streaming compile failed: TypeError: Response has unsupported MIME type" and
"falling back to ArrayBuffer instantiation".
For example, in an Apache `.htaccess` file, you can say: `AddType application/wasm .wasm`
Note that opening the files locally (via `file://`) may not work
due to browsers' Same-Origin Policy. However, there are some workarounds:
* On Linux, the "wasm streaming compile failed: TypeError: Response has unsupported MIME type /
falling back to ArrayBuffer instantiation" warnings can be worked around by
adding the line `application/wasm wasm` to `~/.mime.types` or `/etc/mime.types`
* In Firefox, if your files reside in different directories, the same-origin policy can be
made more lax for `file://` URIs by disabling the
[security.fileuri.strict_origin_policy](http://kb.mozillazine.org/Security.fileuri.strict_origin_policy)
option. **But be aware** of the security implications of disabling this option!
See also the Emscripten deployment notes at
<http://kripken.github.io/emscripten-site/docs/compiling/Deploying-Pages.html>,
in particular I'd recommended using gzip encoding to serve the WebPerl files.
### Including Perl code in your HTML
In your HTML file, add the following (usually inside the `<head>` tags):
<script src="webperl.js"></script>
Then, you can add one or more `<script type="text/perl">` tags containing embedded Perl code,
or a single `<script type="text/perl" src="foo.pl"></script>` tag which loads a
Perl script from the server - but not both! The code from multiple
`<script type="text/perl">` tags will be concatenated and run as a single script.
If you use embedded `<script type="text/perl">` tags, then the function `js` from
`WebPerl.pm` will be imported automatically. If you want to customize the import
list, then add `use WebPerl ...;` as one of the first five lines of your Perl code
(to be exact, WebPerl will look for `/^\s*use\s+WebPerl(\s|;)/m`).
If you don't have any such script tags in the document, Perl won't be run
automatically, and you can control Perl in detail via the JavaScript `Perl`
object provided by [webperl.js](#webperljs).
Note that unlike JavaScript, which is run immediately, WebPerl will always be loaded
and run asynchronously from the page load. If you use `<script type="text/perl">` tags,
these will always be run after the document is ready, and if you use the `Perl` object
as described below, you will have control over when Perl is initialized and run, but
it will still be asynchronous because files need to be fetched from the server.
The Perl Interpreter and its Environment
----------------------------------------
The `perl` compiled for WebPerl is mostly a standard build of Perl, except
for a few patches to make things compile properly, and the major differences
described here.
[Emscripten](http://emscripten.org/) provides emulation for a number of system
calls, most notably for WebPerl, it provides a
[**virtual filesystem** (details below)](#virtual-filesystem) from which Perl can
load its modules, since of course JavaScript in the browser is a sandboxed
environment (no access to hardware, the local filesystem, etc.).
However, because Perl is the *only* Emscripten process running in the browser,
there are **several things that won't work** either because Emscripten doesn't
support them (yet) or because they are simply not possible in this
single-process environment:
- Running other programs via e.g. `system`, backticks (`qx`), piped `open`, etc.
- No `fork` or multithreading, no `kill`, `wait`, `waitpid`, etc.
- There is experimental support for pthreads, but this is not tested with WebPerl yet.
See also <http://kripken.github.io/emscripten-site/docs/porting/pthreads.html>.
- No signals (except `SIGALRM`)
Like many UI frameworks, scripting in the browser is usually **asynchronous and event-driven**.
In addition, in Emscripten it is currently not easy to run a program multiple times.
In order to better support these circumstances, WebPerl's C `main()` function has been
patched to *not* end the runtime. This means that once the main Perl script is run,
the interpreter is *not* shut down, meaning `END` blocks and global destruction are not run,
and instead control is passed back to the browser.
This way, you can write Perl in an event-driven manner: in your main code, you can register
callbacks as event handlers for events such as button clicks, network communication, etc., and
then control is passed back to the browser. When the events occur, your Perl callbacks will be run.
In order to allow for this mode of execution, WebPerl is built with Emscripten's
`NO_EXIT_RUNTIME` option enabled. When this option is enabled, `atexit` handlers are
not supported, and calls to `exit` will result in a warning. For this reason, WebPerl
is patched to not call `exit` when the exit code is zero. As a result of all this,
in your scripts, I strongly recommend you **don't use Perl's `exit;`/`exit(0);`**,
as it will not likely do what you want.
Remember that in the browser, the user may leave a page at any time, and there is little
a script can do to prevent this. Although it's possible to ask Perl to end early as follows,
I would still recommend that you **don't rely on `END` blocks or global destruction**.
If your program is doing things like saving files (e.g. via an asynchronous network request),
then you should provide some kind of feedback to your user to know that a process is still
going on, and possibly install your own "beforeunload" handler.
WebPerl includes a C function `int emperl_end_perl()` which will perform the normal
Perl interpreter shutdown (but as mentioned above, not call `exit` if the exit code is zero).
This function is accessible in several ways:
- From JavaScript, set `Perl.endAfterMain` before calling `Perl.init()`
(this enables a "hack" that calls `emperl_end_perl()` after `main()` returns)
- From JavaScript, call `Perl.end()`
- From Perl, call `WebPerl::end_perl()`
These options might be useful if you're porting an existing script to run in WebPerl.
(In addition, WebPerl currently registers an "beforeunload" handler that attempts to call
the "end" function, but since this will be happening as the page is being unloaded,
do *not* rely on this being able to do very much, or even being called at all!)
### Memory Management and Anonymous `sub`s
**Anonymous `sub`s passed from Perl to JavaScript must be explicitly freed**
**when you are done using them, or else this is a memory leak.**
Please read this section!
When JavaScript arrays, objects, and functions are passed to Perl, they are not
copied, instead they are given an ID and placed in a table so that when Perl
wants to access them, it only needs to remember the ID, and pass the ID and the
corresponding operation to JavaScript. In JavaScript, these objects are kept alive
because of the entry in the table. Once the object goes out of scope in Perl,
its `DESTROY` method lets JavaScript know that it can free that entry from the
table, so JavaScript is free to garbage collect it if there are no other references.
When Perl values are passed to JavaScript, they are generally copied, except
for anonymous `sub`s, where a mechanism similar to the above is used, and a reference
to the `sub`s is kept alive using a table in Perl. *However,* JavaScript has
no equivalent of the `DESTROY` method, which means that even if you are done
using a `sub` in JavaScript, Perl will not know when it can free the table
entry, unless you explicitly tell it to!
WebPerl provides two mechanisms for freeing `sub`s:
- `WebPerl::unregister()` (can be exported), which takes a single argument that is
a reference to an anonymous sub previously passed to JavaScript. If you
`use 5.028;` or `use feature 'current_sub';`, anonymous `sub`s can refer to
themselves using the special `__SUB__` identifier, so for example, you can say:
use 5.028;
js( sub {
print "I was called, now I am going away\n";
WebPerl::unregister(__SUB__);
} )->();
- `WebPerl::sub_once` aka `WebPerl::sub1` are wrappers for `sub`s that essentially
call the `sub` once and then immediately `unregister` it. The above example can be
written as:
use WebPerl qw/js sub1/;
js( sub1 { print "Single-use sub called\n"; } )->();
`unregister` is still useful for anonymous `sub`s that need to be called multiple
times before falling out of use.
Of course, it is often the case that anonymous `sub`s need to persist for the
entire run of a program (like for example click handlers for buttons), or that
you may only have a handful of anonymous `sub`s in your program overall.
In such cases, you probably don't need to `unregister` them. However, there are
cases where this is very important to keep in mind - for example anonymous
`sub`s generated via stringy `eval`s.
If you want to check how many anonymous `sub`s are registered, you can say
`print scalar(keys %WebPerl::CodeTable);` (*do not* modify this hash).
Note that the above only applies to *anonymous* `sub`s. `sub`s that exist
in Perl's symbol table will persist in Perl's memory anyway, and no table entry
is generated for them, because it is assumed you won't delete them from the
symbol table - so please don't do that. Also, don't rename or redefine `sub`s
after having passed them to JavaScript, as that will probably cause mysterious behavior.
### Virtual Filesystem
Emscripten provides a virtual file system that also provides a few "fake" files such
as `/home/web_user`, `/dev`, and others, so that it resembles a normal *NIX file system.
This filesystem resides entirely in memory in the browser.
Perl's libraries (`*.pm`) are installed into this virtual file system at `/opt/perl`.
Note that because the `perl` binary is compiled to WebAssembly and XS libraries are
statically linked into it, you won't find any `perl` binary or shared library files in the
virtual file system, or for that matter any other binaries, since this is a
single-process environment.
It is important to keep apart the different ways to access files:
- There's your local file system and the web server's file system, to which WebPerl does *not* have direct access.
There are only a few ways to get files from a local file system into WebPerl:
- The user can upload files from their local system using a file upload control and JavaScript.
The "mini IDE" included with WebPerl includes a demo of this, see
`file_upload` and `upload_file` in
[`emscr_ide.js`](https://github.com/haukex/webperl/blob/master/web/mini_ide/emscr_ide.js).
(Also, `file_download` shows a way to send files to the user's browser.)
- An RPC service that you set up on your webserver would allow WebPerl to communicate
with the server, and the service can provide access to the files on the server.
For one example, see
[`webperl.psgi`](https://github.com/haukex/webperl/blob/master/web/webperl.psgi)
for the server side and
[`webperl_demo.html`](https://github.com/haukex/webperl/blob/master/web/webperl_demo.html)
for the client side.
- For more permanent additions to the virtual filesystem, you could compile files
into Emscripten's virtual file system (mentioned below).
- All that WebPerl can see directly is the virtual file system provided by Emscripten.
More details are below.
- When you use the JavaScript `Perl` object provided by `webperl.js` to control Perl,
the `argv` you supply to `Perl.start` references files in the virtual file system.
- A `<script type="text/perl" src="foo.pl"></script>` tag will cause `webperl.js`
to fetch `foo.pl` from the *web server*, not the virtual filesystem!
- *However*, that Perl script will *also* only see the virtual filesystem,
not the web server, so it won't even be able to see "itself" (`webperl.js`
may save files such as `/tmp/scripts.pl`, but that's *not* guaranteed).
You can still fetch things from the webserver using e.g. AJAX requests.
While a WebPerl instance is running, you can modify files in the virtual file system
as you might be used to from regular Perl. But the virtual filesystem is reloaded every
time WebPerl is reloaded, so any changes are lost! The exception is the "`IDBFS`"
provided by Emscripten, which stores files in an `IndexedDB`, so they typically persist
in the browser's storage across sessions. WebPerl mounts an instance of this filesystem
at `/mnt/idb`, which you are free to use. If you want your Perl script to write to files
there, you **must** also use Emscripten's `FS.syncfs()` interface after writing files,
for example:
js(q/ FS.syncfs(false, function (err) {
if(err) alert("FS sync failed: "+err);
else console.log("FS sync ok"); }); /);
Remember that users may clear this storage at any time, so it is not really a permanent storage
either. If you need to safely store files, it's best to store them on the user's machine
(or the web server, if they are different machines) using one of the methods described above.
In particular, even though you might make heavy use of `/mnt/idb` when testing with the "mini IDE",
remember that this storage is *not* a way to distribute files to your users, and in fact, some
users' browsers may automatically regularly clear the `IndexedDB`, or have it disabled altogether.
It also may not work at all in a "sandboxed" `iframe`.
For providing your application to your users, either use `<script type="text/perl">` tags,
compile the script into the virtual file system, or use the JavaScript `Perl` object.
You might also put files into the virtual filesystem more permanently by modifying the "make install"
step of [`build.pl`](https://github.com/haukex/webperl/blob/master/build/build.pl).
Keep in mind that like the other files in the virtual file system,
any modifications will be lost once WebPerl is reloaded, and the only way to modify them
is re-running `build.pl`.
Additional information on the virtual file system may be found at:
- <http://kripken.github.io/emscripten-site/docs/porting/files/file_systems_overview.html>
- <http://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html>
- <http://kripken.github.io/emscripten-site/docs/api_reference/advanced-apis.html#advanced-file-system-api>
Note that WebPerl's build process strips any POD from the Perl libraries, to reduce download size.
By the way, I don't recommend relying on the initial working directory when WebPerl
starts; either `chdir` to a known location, or always use absolute filenames.
webperl.js
----------
`webperl.js` provides a JavaScript object `Perl` that can be used to control
the Perl interpreter. Many properties of this object are intended for internal
use by WebPerl only, so please **only use the interface documented here**.
### Controlling Perl
As documented above, if your HTML file contains `<script type="text/perl">`
tags, these will be run automatically, so you should *not* use `Perl.init()`
and `Perl.start()` in this case.
#### `Perl.init(function)`
Initializes the Perl interpreter (asynchronously fetches the `emperl.*` files).
You should pass this function a callback function, which is to be called when
Perl is ready to be run - normally you would call `Perl.start()` from this callback.
#### `Perl.start(argv)`
Runs Perl with the given `argv` array. If `argv` is not provided, uses Emscripten's
`Module.arguments`, which currently defaults to `['--version']`.
#### `Perl.eval(code)`
Evaluates the given Perl code. Currently always returns a string.
The functionality of this function *may* be expanded upon in the future
to return more than just a string. See the discussion in
[Mappings from Perl to JavaScript](#mappings-from-perl-to-javascript).
#### `Perl.end()`
Ends the Perl interpreter. See the discussion under
["The Perl Interpreter and its Environment"](#the-perl-interpreter-and-its-environment)
for details.
### Options
#### `Perl.output`
Set this to a `function (str,chan) {...}` to handle Perl writing to `STDOUT` or `STDERR`.
`str` is the string to be written, which may consist of a single character, a whole
line, or multiple lines. `chan` will be either 1 for `STDOUT` or 2 for `STDERR`.
If you want to merge the two streams, you can simply ignore the `chan` argument.
Defaults to an implementation that line-buffers and logs via `console.log()`,
prefixing either `STDOUT` or `STDERR` depending on the channel.
See also `Perl.makeOutputTextarea`, which installs a different output handler.
#### `Perl.endAfterMain`
If set to `true` before calling `Perl.init()`, then WebPerl will automatically
end the Perl interpreter after it finishes running the main script. See the
discussion under
["The Perl Interpreter and its Environment"](#the-perl-interpreter-and-its-environment).
Defaults to `false`.
#### `Perl.noMountIdbfs`
If set to `true` before calling `Perl.start()`, then WebPerl will not automatically
mount the IDBFS filesystem (see ["Virtual File System"](#virtual-file-system).
Defaults to `false`.
This option was added in `v0.05-beta`.
#### `Perl.trace`
Enable this option at any time to get additional trace-level output
to `console.debug()`. Defaults to `false`.
#### `Perl.addStateChangeListener(function)`
Pass this function a `function (from,to) {...}` to register a new handler
for state changes of the Perl interpreter.
The states currently are:
- `"Uninitialized"` - `Perl.init` has not been called yet.
- `"Initializing"` - `Perl.init` is currently operating.
- `"Ready"` - `Perl.init` is finished and `Perl.start` can be called.
- `"Running"` - The Perl interpreter is running, `Perl.eval` and `Perl.end` may be called
- `"Ended"` - The Perl interpreter has ended.
~~You might receive several state change notifications for this state.~~
This is no longer the case as of WebPerl `v0.09-beta`:
you should only receive one event per state change.
This function was added in WebPerl `v0.05-beta`.
#### `Perl.exitStatus`
This property should be **read only**! After Perl's state has changed
to `Ended`, you can retrieve the exit code here.
This property was added in WebPerl `v0.09-beta`.
#### `Perl.stateChanged`
**Deprecated** in WebPerl `v0.05-beta`. Use `Perl.addStateChangeListener` instead.
Set this to a `function (from,to) {...}` to handle state changes of the Perl interpreter.
Defaults to a simple implementation that logs via `console.debug()`.
### Utility Functions
#### `Perl.makeOutputTextarea(id)`
This function will create a new DOM `<textarea>` element, set up a
`Perl.output` handler that redirects Perl's output (merged STDOUT and STDERR)
into the `<textarea>`, and return the DOM element. You may optionally pass this
function a string argument giving a DOM ID. You will need to add the
`<textarea>` to your DOM yourself (see `webperl_demo.html` for an example).
WebPerl.pm
----------
`WebPerl.pm` provides the Perl side of the WebPerl API.
Its central function is `js()`, documented below.
It also provides the functions `unregister`, `sub_once`, and `sub1`
(the latter two are aliases for each other), which are documented
in ["Memory Management and Anonymous `sub`s"](#memory-management-and-anonymous-subs).
For convenience, it can also re-export `encode_json`, so you can
request it directly from `WebPerl` instead of needing to `use` another module.
Additional functions, like `js_new()`, are documented below.
All functions are exported only on request.
Note that WebPerl will also enable autoflush for `STDOUT`.
### `js()`
This function takes a single string argument consisting of JavaScript code to
run, uses JavaScript's `eval` to run it, and returns the result, as follows.
You may also pass an arrayref, hashref, or coderef, and this data structure
will be passed to JavaScript, and a corresponding `WebPerl::JSObject` returned.
Other references, including objects, are currently not supported.
### Mappings from JavaScript to Perl
If the code given to `js()` throws a JavaScript error, `js()` will `die`.
Otherwise, the `js()` function will return:
- JS `undefined` becomes Perl `undef`
- JS booleans become Perl's "booleans" (`!0` and `!1`)
- JS numbers and strings become Perl numbers and strings (values are copied)
- JS "Symbol"s currently cause a warning and and `js()` returns `undef`
- JS functions, objects (hashes), and arrays are returned as
Perl objects of the class `WebPerl::JSObject`.
### `WebPerl::JSObject`
A `WebPerl::JSObject` is a thin wrapper around a JavaScript object.
The contents of the JavaScript object are not copied to Perl, they are kept in
JavaScript and accessed only when requested from Perl.
`JSObject`s support overload array, hash, and code dereferencing, plus
autoloaded method calls. This means that if you have a `WebPerl::JSObject`
stored in a Perl scalar `$foo` pointing to a JavaScript object `foo`:
- Perl `$foo->{bar}` is the equivalent of JavaScript `foo["bar"]`
- Perl `$foo->[42]` is the equivalent of JavaScript `foo[42]`
- Perl `$foo->("arg")` is the equivalent of JavaScript `foo("arg")`
- Perl `$foo->bar("arg")` is the equivalent of JavaScript `foo.bar("arg")`
`JSObject`s provide the following methods:
- `hashref` is the method behind hashref overloading. It returns a reference
to a tied hash which accesses the underlying JavaScript object. The tied
hash should behave like a normal Perl hash, except that all operations
on it are passed to JavaScript.
- `arrayref` is the method behind arrayref overloading. It returns a reference
to a tied array which accesses the underlying JavaScript array. The tied
array should behave like a normal Perl array, except that all operations
on it are passed to JavaScript.
- `coderef` is the method behind coderef overloading. It returns a reference
to a `sub` that, when called, calls the underlying JavaScript function.
- `methodcall` is the method behind method autoloading. Its first argument is
the name of the method, and the further arguments are arguments to the method.
- `toperl` is a method that translates the object from a `JSObject` into a
regular Perl data structure (deep copy). Note that JavaScript functions are
kept wrapped inside anonymous Perl `sub`s.
- `jscode` returns a string of JavaScript code that represents a reference
to the JavaScript object. **Warning:** Treat this value as read-only in
JavaScript! *Do not* assign to this value, call JavaScript's `delete` on
this value, etc. (calling methods that may mutate the object is ok, though).
You should treat the string as an opaque value, no guarantees are made about
its format and whether it may change in future releases.
This is an advanced function that should not normally be needed,
unless you are building strings of JavaScript to run. In that case, you
may need to wrap the value in parentheses for it to evaluate correctly in
JavaScript. Example: `js( "console.log(".$jsobject->jscode.")" )`
(`jscode` was added in WebPerl `v0.07-beta`.)
Method autoloading will of course not work for JavaScript methods that have
the same name as existing Perl methods - these are the above methods,
plus methods named `AUTOLOAD`, `DESTROY`, plus any methods inherited from Perl's
[`UNIVERSAL`](http://perldoc.perl.org/UNIVERSAL.html) class, such as `can` or `isa`.
If you need to call JavaScript methods with any of these names,
use `methodcall`. For example, `$jsobject->methodcall("can", "arg1")` will call
the JavaScript method `can` instead of the Perl method `can`.
Arguments from Perl to JavaScript function or method calls are mapped as follows.
### Mappings from Perl to JavaScript
Unlike the JavaScript to Perl mappings, values are (currently¹) generally *copied* from
Perl to JavaScript, instead of being *referenced*.
The exceptions are Perl `sub`s and `WebPerl::JSObject`s.
- Perl arrayrefs become JavaScript arrays (deep copy)
- Perl hashrefs become JavaScript objects (deep copy)
- Perl coderefs become JavaScript functions² -
**Warning:** please see the discussion in
["Memory Management and Anonymous `sub`s"](#memory-management-and-anonymous-subs)!
- Perl `WebPerl::JSObject`s become references to the wrapped JavaScript objects
(i.e. the underlying JS object is passed back to JS transparently)
- Perl numbers/strings are copied to JavaScript via `Cpanel::JSON::XS::encode_json`
(with its `allow_nonref` option enabled). This means that the choice
for whether to encode a Perl scalar as a JavaScript number or string is
left up to the module, and is subject to the usual ambiguities when
serializing Perl scalars. See
[the `Cpanel::JSON::XS` documentation](https://metacpan.org/pod/Cpanel::JSON::XS).
- Other references, including Perl objects, are currently not supported.
¹ So far, the focus of WebPerl has been to replace JavaScript with Perl, and
therefore on accessing JavaScript from Perl, and not as much the other
way around, that is, doing complex things with Perl from JavaScript code.
For example, currently, `Perl.eval()` always returns a string, but could in the
future be extended to return more than that, similar to `WebPerl::js()`,
and then the passing of Perl values to JavaScript could be accomplished
differently as well.
² **Remember** that Perl `sub`s without an explicit `return` statement will
implicitly return the value of the last statement (if it is an expression).
This value will in turn be passed to JavaScript, which may be inefficient
if this value is not needed, and it may cause errors if the return value cannot
be encoded to JavaScript. Therefore it is recommended to get into the
habit of adding an explicit `return;` at the end of `sub`s passed to JS.
### `js_new()`
This function is a convenience function for calling JavaScript's `new`.
The first argument is the name of the class, the following arguments are
passed to the constructor. It returns the same thing as the `js()` function,
in this case that would be the new object. For example:
js_new('Blob', ["<html></html>"], {type=>"text/html;charset=utf-8"})
is the same as calling this in JavaScript:
new Blob(["<html></html>"], {type:"text/html;charset=utf-8"})
This function was added in WebPerl `v0.05-beta`.
The Mini IDE
------------
**Warning:** The "mini IDE" included with WebPerl is currently *not* meant to
be a full-featured IDE in which you're supposed to develop your WebPerl scripts.
It started out as a way to simply inspect Emscripten's
[virtual filesystem](#virtual-filesystem), and quickly test some features.
Please consider it more of a *demo* of some of the things that are possible,
and don't be surprised that it doesn't have many of the features you might
expect from an IDE, and it has a few "quirks" (you are of course free to provide
patches, if you like `;-)` ).
Note the default encoding for files is assumed to be UTF-8.
### The Editor
- 📄 "New File" - Clears the editor and starts a new file.
- ⬆ "File from Disk" - Allows you to select a file from your local computer
for upload *into the editor*, i.e. it does not save anything into the
virtual file system; you need to use "Save File" to do that.
- ⬇ "Download editor contents" - Allows you to download the text as currently
shown in the editor as a file to your local disk.
- 📁 "Open File" - Lets you browse the virtual file system and select a file
to open in the editor. Click the "Open File" button again to cancel the open.
- 💾 "Save File" - Saves the text currently shown in the editor into the
file named in the "File Name" text box, so enter the file name there **first**.
Remember that to persist something in the [virtual filesystem](#virtual-filesystem)
longer than the current page, you need to save the file in a location like `/mnt/idb`.
### "Run & Persist"
This controls the Perl interpreter that is part of the current page.
[Remember](#the-perl-interpreter-and-its-environment) that there can be only
one Perl interpreter in a page at once, and it can only run once, which means
that if you edit the code in the editor, you'll need to save the file to
a location like `/mnt/idb` and re-load the entire page for the changes
to be able to take effect.
The advantage of this mode is that it most resembles the way WebPerl is
intended to be run as part of a web page - it starts once when the page
is loaded (or in this case, when you click "Run"), and is then suspended
after `main()` finishes, so that control passes back to the browser, and
JavaScript can then call any handlers you've installed for things like click events.
### "Run Multi (via IFrame)"
This provides a "hacked" way to run multiple Perl interpreters without reloading
the entire page, by loading a new page in an `<iframe>`. The script is run there,
and when `main()` exits, Perl is ended, and its output fetched into the "output"
textarea. The advantage of this mode is that it allows quicker edit-and-run cycles,
which is good for testing, the disadvantage is that you can't really interact with
Perl from the browser while it is running.
Remember to first save your script in a location like `/mnt/idb`, because
otherwise the Perl instance in the `iframe` won't be able to see it
(since it gets a fresh copy of the virtual file system).
***
Additional notes on using WebPerl may be found in the
[GitHub Wiki](https://github.com/haukex/webperl/wiki/Using-WebPerl).
***
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
Please see the ["Legal" page](legal.html) for details.
***
You can find the source for this page at
<https://github.com/haukex/webperl/blob/gh-pages/using.md>

@ -1,36 +0,0 @@
/** CSS file for emscr_ide.js **/
.emide {
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
}
.CodeMirror,.code {
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
}
.toolbar button {
margin: 0 0.2em;
}
.statusbar {
padding: 0.2em;
height: 1em;
font-size: 10pt;
}
.filebrowser {
border: 1px solid black;
padding: 0.2em 0.5em;
overflow: auto;
height: 20em;
}
.fb-link { cursor:pointer; }
.fb-link:hover { color:#000090; background:rgba(255,255,102,0.4); }
.fb-link:active { color:#FF0000; }
.cm-resize-frame {
overflow: hidden;
resize: vertical;
height: 24em;
border: 1px solid grey;
}

@ -1,259 +0,0 @@
"use strict";
/** ***** WebPerl - http://webperl.zero-g.net *****
*
* Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
* at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
* Berlin, Germany, http://www.igb-berlin.de
*
* This program is free software; you can redistribute it and/or modify
* it under the same terms as Perl 5 itself: either the GNU General Public
* License as published by the Free Software Foundation (either version 1,
* or, at your option, any later version), or the "Artistic License" which
* comes with Perl 5.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the licenses for details.
*
* You should have received a copy of the licenses along with this program.
* If not, see http://perldoc.perl.org/index-licence.html
**/
function make_emscr_ide (textarea, callbacks) {
var defaulttext = "use warnings;\nuse 5.028;\nuse WebPerl qw/js/;\n\n";
var div = $('<div/>').addClass("emide");
var ide = { elem: div, cleanval: '' };
var tb = $('<div/>').addClass("toolbar").appendTo(div);
var file_new = $('<button/>',{title:"New File",html:"&#x1F4C4;"}).appendTo(tb);
var file_upload = $('<button/>',{title:"File from Disk",html:"&#x2B06;"}).appendTo(tb);
var file_download = $('<button/>',{title:"Download editor contents",html:"&#x2B07;"}).appendTo(tb);
var file_open = $('<button/>',{title:"Open File",html:"&#x1F4C1;"}).appendTo(tb);
var file_save = $('<button/>',{title:"Save File",html:"&#x1F4BE;"}).appendTo(tb);
var file_name = $('<input/>',{title:"File Name",type:"text",size:60}).addClass("code").appendTo(tb);
//TODO Later: support for deleting files?
var upload_file = $('<input/>',{type:"file"});
var file_up_form = $('<div/>').append(
$('<form/>').append( upload_file ) ).appendTo(div);
file_up_form.hide();
var fbrowser = make_emscr_ide_filebrowser();
fbrowser.elem.hide();
fbrowser.elem.appendTo(div);
var resize_frame = $('<div/>').addClass("cm-resize-frame").appendTo(div);
textarea.replaceWith(div);
resize_frame.append(textarea);
var statusbar = $('<div/>').addClass("statusbar").appendTo(div);
var cm = CodeMirror.fromTextArea( textarea[0], {
lineNumbers: true, indentWithTabs: true,
tabSize: 4, indentUnit: 4 });
ide.cm = cm;
/* With thanks to https://codepen.io/sakifargo/pen/KodNyR */
var fr = resize_frame[0];
var cm_resize = function() { cm.setSize(fr.clientWidth + 2, fr.clientHeight - 10); };
cm_resize();
if (window.ResizeObserver)
new ResizeObserver(cm_resize).observe(fr);
else if (window.MutationObserver)
new MutationObserver(cm_resize).observe(fr, {attributes: true});
ide.isDirty = function () { return cm.getValue()!=ide.cleanval; };
// returns true if the user chose to abort
// returns false if the buffer is not dirty or the user chose to continue anyway
ide.dirtyCheck = function () {
if (ide.isDirty()) {
// confirm() returns true if the user clicked "OK", and false otherwise.
if (confirm("Unsaved changes in editor!\nContinue anyway?"))
return false;
else return true; // buffer is dirty and user chose to abort
}
else return false;
};
file_new.click(function () {
if (ide.dirtyCheck()) return;
console.debug("IDE: New File");
file_name.val("");
ide.cleanval = defaulttext;
cm.setValue(defaulttext);
sessionStorage.removeItem('file_mru');
statusbar.text("New File");
});
file_upload.click(function () {
if(!window.FileReader) {
alert("Sorry, your browser does not support file uploads.");
return; }
if (file_up_form.is(":visible")) file_up_form.hide(); else file_up_form.show();
});
upload_file.on('change', function (chgEvt) {
statusbar.text("");
file_up_form.hide();
if (ide.dirtyCheck()) return;
file_name.val("");
sessionStorage.removeItem('file_mru');
console.debug("IDE: Reading file from local disk...");
var reader = new FileReader();
reader.onload = function(loadEvt) {
if(loadEvt.target.readyState != 2) return;
if(loadEvt.target.error) {
alert('Error while reading file');
return; }
console.debug("IDE: File read!");
statusbar.text("File opened from local disk");
cm.setValue(loadEvt.target.result);
};
reader.readAsText(chgEvt.target.files[0]);
});
file_download.click(function () {
var blob = new Blob([cm.getValue()],
{type: "text/plain;charset=utf-8"});
var link = document.createElement("a");
link.download = 'script.pl';
link.href = URL.createObjectURL(blob);
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
file_open.click(function () {
statusbar.text("");
if (!fbrowser) return;
if (fbrowser.isVisible()) {
file_open.html("&#x1F4C1;");
fbrowser.cancel();
}
else {
file_open.html("&#x1F4C2;&#x20E0;");
fbrowser.show(function (file) {
file_open.html("&#x1F4C1;");
if (ide.dirtyCheck()) return;
console.debug("IDE: Opening "+file);
file_name.val(file);
var data = FS.readFile(file,{encoding:"utf8"});
ide.cleanval = data;
cm.setValue(data);
sessionStorage.setItem('file_mru',file);
statusbar.text("Opened "+file);
console.debug("IDE: Opened "+file);
if (callbacks && callbacks.open) callbacks.open(file);
});
}
});
file_save.click(function () {
statusbar.text("");
var file = file_name.val();
if (file.length<1) {
alert("Invalid File Name");
return; }
try {
console.debug("IDE: Trying to save "+file);
var data = cm.getValue();
FS.writeFile( file, data );
/* Note: The user may be saving to some location outside of the IDBFS, in which
* case this syncfs call isn't really needed. But it doesn't really hurt either,
* so we always do it. */
FS.syncfs(false, function (err) {
if(err) { console.error(err); alert("Saving IDBFS failed: "+err); return; }
statusbar.text("Saved to "+file);
console.debug("IDE: Saved "+file);
sessionStorage.setItem('file_mru',file);
ide.cleanval = data;
if (callbacks && callbacks.save) callbacks.save(file);
});
}
catch (err) { console.error(err); alert("Save Failed: "+err); }
});
var mru_file = sessionStorage.getItem('file_mru');
try {
if (!mru_file) throw "No MRU file";
var data = FS.readFile( mru_file, {encoding:"utf8"} );
ide.cleanval = data;
cm.setValue(data);
file_name.val(mru_file);
console.debug("IDE: Loaded "+mru_file);
statusbar.text("Opened "+mru_file);
if (callbacks && callbacks.open) callbacks.open(mru_file);
}
catch (e) {
console.debug("IDE: Loading MRU failed:",e,"- falling back...");
file_name.val('');
ide.cleanval = defaulttext;
cm.setValue(defaulttext);
}
window.addEventListener("beforeunload", function (evt) {
if (ide.isDirty()) {
var dialogText = "Unsaved changes in editor!";
evt.returnValue = dialogText;
return dialogText;
}
});
return ide;
}
function make_emscr_ide_filebrowser() {
var fb = {
elem: $('<div/>').addClass("filebrowser"),
curpath: (ENV && ENV.HOME) ? ENV.HOME : FS.cwd(),
};
fb.update = function () {
fb.elem.empty();
$('<div>&#x1F4C2; </div>').addClass("fb-curdir")
.append(fb.curpath).appendTo(fb.elem);
var files = FS.readdir(fb.curpath);
$.each( files.sort(), function (idx,file) {
if (file=='.') return;
if (file=='..' && fb.curpath=='/') return;
var fullfile = FS.joinPath([fb.curpath,file]);
var stat = FS.stat(fullfile);
var icon = "&#x1F4DC;";
var click;
if (FS.isFile(stat.mode)) {
icon = "&#x1F4C4;";
click = function (evt) {
fb.elem.hide();
if(fb.callback) fb.callback(fullfile);
fb.callback = null;
};
}
else if (FS.isDir(stat.mode)) {
icon = file=='..'?"&#x1F4C1;&#x20D6;":"&#x1F4C1;";
click = function (evt) {
fb.curpath = fullfile;
fb.update();
};
}
else if (FS.isLink(stat.mode)) {
icon = "&#x1F4C4;&#x20EA;";
}
var el = $('<div>'+icon+' </div>').addClass("fb-link")
.append(file).appendTo(fb.elem);
if (click) el.click(click);
});
};
fb.show = function (callback) {
fb.callback = callback;
fb.elem.show();
fb.curpath = FS.cwd();
fb.update();
};
fb.cancel = function () {
fb.elem.hide();
fb.callback = null;
}
fb.isVisible = function () { return fb.elem.is(":visible") }
return fb;
}

@ -1,246 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl (IDE)</title>
<!-- ##### WebPerl - http://webperl.zero-g.net #####
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, http://www.igb-berlin.de
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see http://perldoc.perl.org/index-licence.html
-->
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.css" integrity="sha256-I8NyGs4wjbMuBSUE40o55W6k6P7tu/7G28/JGUUYCIs=" crossorigin="anonymous" />
<link rel="stylesheet" type="text/css" href="emscr_ide.css" />
<style>
button,.text {
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
}
pre,.code,textarea {
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
}
.fakelink {
color: darkblue;
cursor: pointer;
}
.border {
border: 1px solid black;
margin: 0.5em;
padding: 0.2em;
}
#evalcode_container .CodeMirror {
height: 4em;
border: 1px solid grey;
margin: 0.2em 0;
}
.output {
margin-top: 0.5em;
}
.output textarea {
max-width: 100%;
}
</style>
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.js" integrity="sha256-uRIJ6Wfou5cTtmcCvQNA9lvsYl8sUbZXxnfG+P79ssY=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/mode/perl/perl.min.js" integrity="sha256-Uu9QBfi8gU6J/MzQunal8ewmY+i/BbCkBrcAXA5bcCM=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="emscr_ide.js"></script>
<script src="../webperl.js"></script>
<script>
"use strict";
console.debug("Running our Javascript...");
window.onerror = function(event) {
alert('Exception thrown, see JavaScript console'); };
// This is a workaround for Emscripten only being able to call main() once per page load.
// I wouldn't recommend this for "production" use.
var baseurl = Perl.Util.baseurl(window.location);
function run_perl_iframe (argv, state_callback, done_callback) {
var html = '<html><head><base href="'+baseurl+'"><script src="webperl.js"></scr'+'ipt></head><body></body></html>';
var blob = new Blob([html], {type: "text/html;charset=utf-8"});
var src = URL.createObjectURL(blob);
var iframe = $('<iframe/>',{id:'Perl_IFrame',src:src}).hide().appendTo('body');
iframe.on('load', function() {
var IFramePerl = iframe[0].contentWindow['Perl'];
var outbuf = '';
IFramePerl.output = function (str) { outbuf+=str }; //TODO Later: maybe dynamic output updating is possible?
IFramePerl.endAfterMain = true;
IFramePerl.addStateChangeListener( function (from,to) {
if (state_callback) state_callback(to);
if (from!='Ended' && to=='Ended') {
iframe.remove();
if (done_callback) done_callback(outbuf);
URL.revokeObjectURL(src);
}
} );
IFramePerl.init(function () {
window.setTimeout(function () { IFramePerl.start(argv); }, 1);
});
});
}
var eval_cm;
$( function() {
console.debug("Document is ready, setting up...");
$('#runperl').prop("disabled",true);
$('#evalperl').prop("disabled",true);
$('#endperl').prop("disabled",true);
var once_out_ta = Perl.makeOutputTextarea();
$("#once_output").append(once_out_ta);
$("#once_out_clear").click(function () { $(once_out_ta).val("") });
{
var multirun = $('#multi_run');
var multirun_toggle = $('<div/>',{text:"Hide Run Multi (via IFrame)"});
multirun_toggle.addClass('fakelink');
multirun_toggle.click(function () {
if (multirun.is(":visible")) {
multirun.hide();
multirun_toggle.text("Show Run Multi (via IFrame)");
}
else {
multirun.show();
multirun_toggle.text("Hide Run Multi (via IFrame)");
}
});
multirun.before( multirun_toggle );
}
// need to set up CodeMirror while #runonce is still visible (!)
eval_cm = CodeMirror.fromTextArea( $('#evalcode')[0], {
lineNumbers: true, indentWithTabs: true,
tabSize: 4, indentUnit: 4 });
{
var runonce = $('#runonce');
var runonce_toggle = $('<div/>',{text:"Show Run & Persist"});
runonce_toggle.addClass('fakelink');
runonce_toggle.click(function () {
if (runonce.is(":visible")) {
runonce.hide();
runonce_toggle.text("Show Run & Persist");
}
else {
runonce.show();
runonce_toggle.text("Hide Run & Persist");
}
});
runonce.before( runonce_toggle );
runonce.hide();
}
Perl.addStateChangeListener( function (from,to) {
$('#runstate').text("State: "+to);
$('#runperl' ).prop("disabled", to!="Ready");
$('#argv' ).prop("disabled", to!="Ready");
$('#evalperl').prop("disabled", to!="Running");
$('#endperl' ).prop("disabled", to!="Running");
} );
Perl.init(final_init);
});
function final_init () { // Called when Perl goes to Ready state
console.debug("Final initialization steps...");
// set up the IDE now that the Emscripten FS object is all ready to go
var ide = make_emscr_ide( $('textarea#ide'), {
open: function(file) { $('#argv,#multi_argv').val( JSON.stringify([file]) ) },
save: function(file) { $('#argv,#multi_argv').val( JSON.stringify([file]) ) },
} );
$('#multi_runperl').click(function () {
if (ide.dirtyCheck()) return;
$('#multi_runperl').prop("disabled",true); // we *could* run multiple Perls in parallel, I just don't recommend it (memory & performance issues)
var argv = JSON.parse( $('#multi_argv').val() );
$('#multi_out_ta').text("");
run_perl_iframe(argv,
//TODO Later: the user never gets to see the "Running" state
function (newstate) { $('#multi_state').text("State: "+newstate) },
function (output) {
var ta = $('#multi_out_ta');
ta.text(output);
ta[0].scrollTop = ta[0].scrollHeight;
$('#multi_runperl').prop("disabled",false);
});
});
$('#runperl').click(function () {
if (ide.dirtyCheck()) return;
var argv = JSON.parse( $('#argv').val() );
// run Perl async so that the window has a chance to refresh
window.setTimeout(function () { Perl.start(argv); }, 1);
});
$('#evalperl').click(function () {
var rv = Perl.eval( eval_cm.getValue() );
console.debug('eval returned',rv);
});
$('#endperl').click(function () {
if (confirm("Are you sure you want to end Perl?\nYou'll have to reload the page to restart Perl."))
Perl.end();
});
}
</script>
</head>
<body>
<div class="border text" style="text-align:center"><small>
Really Simple Mini IDE as a Demo for <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a>
- <a href="http://webperl.zero-g.net/using.html#the-mini-ide" target="_blank"><b>Documentation</b></a>
</small></div>
<div class="border">
<textarea id="ide" rows="24" cols="80"></textarea>
</div>
<div class="text border">
<div id="runonce">
<div>
<span class="code">ARGV:</span> <small>(JSON)</small>
<input type="text" id="argv" class="code" value='["--version"]' size="60" title="argv for perl (JSON)"/>
<button id="runperl" title="Run perl"><span class="code">perl</span> &#x25BA;</button>
<button id="endperl" title="End perl">end &#x25A0;</button>
</div>
<div class="statusbar" id="runstate">State: None</div>
<div>
<div id="evalcode_container"><textarea id="evalcode" rows="3" cols="80"></textarea></div>
<div><button id="evalperl" title="eval Perl code"><span class="code">eval</span> &#x25BA;</button></div>
</div>
<div class="output" id="once_output">
<span class="code">STDOUT and STDERR:</span> <button id="once_out_clear">Clear</button><br/>
<!-- output textarea will be inserted here -->
</div>
</div>
</div>
<div class="text border">
<div id="multi_run">
<div>
<span class="code">ARGV:</span> <small>(JSON)</small>
<input type="text" id="multi_argv" class="code" value='["--version"]' size="60" title="argv for perl (JSON)"/>
<button id="multi_runperl" title="Run perl"><span class="code">perl</span> &#x25BA;&#x25A0;</button><br/>
<small><i>(Remember to store code in a persistent storage like <span class="code">/mnt/idb</span>!)</i></small>
</div>
<div class="statusbar" id="multi_state">State: None</div>
<div class="output">
<span class="code">STDOUT and STDERR:</span><br/>
<textarea id="multi_out_ta" rows="24" cols="80" readonly></textarea>
</div>
</div>
</div>
</body>
</html>

@ -1,41 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Tests</title>
<script src="webperl.js"></script>
<script>
"use strict";
window.onerror = function(event) {
alert('Exception thrown, see JavaScript console'); };
window.addEventListener("load", function () {
document.getElementById('output')
.appendChild( Perl.makeOutputTextarea() );
var status = document.getElementById("status");
Perl.endAfterMain = true;
Perl.addStateChangeListener( function (from,to) {
if (from!="Ended" && to=="Ended")
status.textContent = "Tests finished, see output:";
} );
Perl.init(function () {
status.textContent = "Running tests...";
window.setTimeout(function () {
Perl.start(['/opt/perl/dev/WebPerl.t']);
}, 1);
});
});
</script>
</head>
<body>
<p id="status">Loading tests...</p>
<div id="output"></div>
</body>
</html>

@ -1,388 +0,0 @@
"use strict";
/** ***** WebPerl - http://webperl.zero-g.net *****
*
* Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
* at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
* Berlin, Germany, http://www.igb-berlin.de
*
* This program is free software; you can redistribute it and/or modify
* it under the same terms as Perl 5 itself: either the GNU General Public
* License as published by the Free Software Foundation (either version 1,
* or, at your option, any later version), or the "Artistic License" which
* comes with Perl 5.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the licenses for details.
*
* You should have received a copy of the licenses along with this program.
* If not, see http://perldoc.perl.org/index-licence.html
**/
/* -- Please see the documentation at http://webperl.zero-g.net/using.html -- */
var Module;
var Perl = {
trace: false, // user may enable this
endAfterMain: false, // user may enable this (before Perl.init)
noMountIdbfs: false, // user may enable this (before Perl.start)
WebPerlVersion: 'v0.11-beta', // user may read (only!) this
state: "Uninitialized", // user may read (only!) this
exitStatus: undefined, // user may read (only!) this
Util: {},
// internal variables:
initStepsLeft: 2, // Must match number of Perl.initStepFinished() calls!
readyCallback: null,
stdout_buf: "", stderr_buf: "", // for our default Perl.output implementation
dispatch: function (perl) {
Perl._call_code_args = Array.prototype.slice.call(arguments, 1);
Perl.eval(perl);
if (Perl._call_code_error) {
var err = Perl._call_code_error;
delete Perl._call_code_error;
throw err;
}
else {
var rv = Perl._call_code_rv;
delete Perl._call_code_rv;
return rv;
}
},
};
/* TODO: Embedded script should be able to influence the running of Perl,
* the cleanest would probably be to set properties on the Perl object,
* such as Perl.autorun = false or Perl.argv = [...]. It should be possible
* for the user to do this for embedded scripts also! Will probably need
* to change the initialization of Perl so that the user can set its properties
* *before* loading webperl.js. */
window.addEventListener("load", function () {
// Note: to get the content of script tags with jQuery: $('script[type="text/perl"]').html()
var scripts = [];
var script_src;
document.querySelectorAll("script[type='text/perl']")
.forEach(function (el) {
if (el.src) {
if (script_src || scripts.length)
console.error('Only a single Perl script may be loaded via "script src=", ignoring others');
else
script_src = el.src;
}
else {
if (script_src)
console.error('Only a single Perl script may be loaded via "script src=", ignoring others');
else
scripts.push(el.innerHTML);
}
});
if (script_src) {
console.debug("Perl: Found a script with src, fetching and running...", script_src);
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
//TODO Later: Might be nice to name the script in the virtual FS after the URL instead of a generic name
Perl._saveAndRun( this.responseText );
});
xhr.open("GET", script_src);
xhr.send();
}
else if (scripts.length) {
console.debug("Perl: Found",scripts.length,"embedded script(s), autorunning...");
var code = scripts.join(";\n");
// get the first five lines of code
var n = 5 + 1; // the contents of the <script> tag will usually begin with a newline
var i = -1;
while (n-- && i++ < code.length) {
i = code.indexOf("\n", i);
if (i < 0) break;
}
var head = i<0 ? code : code.substring(0,i);
// look for a "use WebPerl"
const regex = /^\s*use\s+WebPerl(\s|;)/m;
if (!regex.exec(head)) { // load WebPerl unless the user loaded it
console.debug("Perl: Autoloading WebPerl");
code = "use WebPerl 'js';\n" + code;
}
Perl._saveAndRun(code);
}
else console.debug("Perl: No embedded scripts");
});
Perl._saveAndRun = function (script) {
Perl.init(function () {
var file = "/tmp/scripts.pl";
try {
FS.writeFile( file, script );
console.debug("Perl: Saved script(s) to ",file,", now running");
window.addEventListener("beforeunload", function () {
// not really needed because we're unloading anyway, but for good measure, end Perl...
console.debug("Perl: beforeunload, ending...");
Perl.end();
});
// run Perl async so that the window has a chance to refresh
window.setTimeout(function () { Perl.start( [ file ] ); }, 1);
}
catch (err) { console.error("Perl:",err); alert("Save to "+file+" failed: "+err); }
});
};
Perl.changeState = function (newState) {
if (Perl.state==newState) return;
var oldState = Perl.state;
Perl.state = newState;
if (Perl.stateChanged) {
console.info("Perl.stateChanged is deprecated, please use Perl.addStateChangeListener instead");
Perl.stateChanged(oldState,newState);
}
for( var i=0 ; i<Perl.stateChangeListeners.length ; i++ )
Perl.stateChangeListeners[i](oldState,newState);
};
Perl.stateChangeListeners = [ function (from,to) {
console.debug("Perl: state changed from "+from+" to "+to);
} ];
Perl.addStateChangeListener = function (handler) {
Perl.stateChangeListeners.push(handler);
};
// chan: 1=STDOUT, 2=STDERR
// implementations are free to ignore the "chan" argument if they want to merge the two streams
Perl.output = function (str,chan) { // can be overridden by the user
var buf = chan==2 ? 'stderr_buf' : 'stdout_buf';
Perl[buf] += str;
var pos = Perl[buf].indexOf("\n");
while (pos>-1) {
console.log( chan==2?"STDERR":"STDOUT", Perl[buf].slice(0,pos) );
Perl[buf] = Perl[buf].slice(pos+1);
pos = Perl[buf].indexOf("\n");
}
};
Perl.outputLine = function (chan,text) { // internal function
if (arguments.length > 2) text = Array.prototype.slice.call(arguments,1).join(' ');
Perl.output(text,chan);
Perl.output("\n",chan);
};
Perl.outputChar = function (chan,c) { // internal function
Perl.output(String.fromCharCode(c),chan);
};
Perl.makeOutputTextarea = function (id) {
var ta = document.createElement('textarea');
if (id) ta.id = id;
ta.rows = 24; ta.cols = 80;
ta.setAttribute("readonly",true);
Perl.output = function (str) {
ta.value = ta.value + str;
ta.scrollTop = ta.scrollHeight;
};
return ta;
};
var getScriptURL = (function() { // with thanks to https://stackoverflow.com/a/2976714
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
return function() { return myScript.src; };
})();
Perl.Util.baseurl = function (urlstr) {
var url = new URL(urlstr);
if (url.protocol=='file:')
return url.href.substring(0, url.href.lastIndexOf('/'));
else
return url.origin + url.pathname.substring(0, url.pathname.lastIndexOf('/'));
};
Perl.init = function (readyCallback) {
if (Perl.state != "Uninitialized")
throw "Perl: can't call init in state "+Perl.state;
Perl.changeState("Initializing");
// Note that a lot of things still won't work for file:// URLs because of the Same-Origin Policy.
// see e.g. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp
var baseurl = Perl.Util.baseurl(getScriptURL());
Perl.readyCallback = readyCallback;
Module = {
noInitialRun: true,
noExitRuntime: true,
print: Perl.outputLine.bind(null,1), printErr: Perl.outputLine.bind(null,2),
stdout: Perl.outputChar.bind(null,1), stderr: Perl.outputChar.bind(null,2),
stdin: function () { return null },
arguments: ['--version'],
onAbort: function () {
console.error("Perl: Aborted (state",Perl.state,")");
alert("Perl aborted in state "+Perl.state);
Perl.exitStatus = -1;
Perl.changeState("Ended");
},
onExit: function (status) { // note this may be called multiple times
if (status==0)
console.debug( "Perl: Exit status",status,"(state",Perl.state,")");
else {
console.error("Perl: Exit status",status,"(state",Perl.state,")");
alert("Perl exited with exit status "+status+" in state "+Perl.state);
}
Perl.exitStatus = status;
Perl.changeState("Ended");
},
onRuntimeInitialized: function () {
console.debug("Perl: Module.onRuntimeInitialized");
Perl.initStepFinished();
},
preRun: [
function () {
if (Perl.noMountIdbfs) {
console.debug("Perl: doing preRun, skipping IndexDB filesystem");
Perl.initStepFinished();
return;
}
console.debug("Perl: doing preRun, fetching IndexDB filesystem...");
try { FS.mkdir('/mnt'); } catch(e) {}
try { FS.mkdir('/mnt/idb'); } catch(e) {}
FS.mount(IDBFS, {} ,'/mnt/idb');
FS.syncfs(true, function (err) {
if (err) { console.error("Perl:",err); alert("Perl: Loading IDBFS failed: "+err); return; }
console.debug("Perl: IndexDB filesystem ready");
Perl.initStepFinished();
});
}
],
locateFile: function (file) {
var wasmRe = /\.(wast|wasm|asm\.js|data)$/;
if (wasmRe.exec(file))
return baseurl+"/"+file;
return file;
},
};
if (Perl.endAfterMain) {
Module.preRun.push(function () {
// patch _main so that afterwards we call emperl_end_perl
var origMain = Module._main;
Module._main = function() {
origMain.apply(this, arguments);
console.debug("Perl: main() has ended, ending perl...");
return Perl.end();
};
});
}
console.debug("Perl: Fetching Emscripten/Perl...");
var script = document.createElement('script');
script.async = true; script.defer = true;
script.src = baseurl+"/emperl.js";
document.getElementsByTagName('head')[0].appendChild(script);
};
Perl.initStepFinished = function () {
if (Perl.state!="Initializing" || Perl.initStepsLeft<1)
throw "Perl: internal error: can't call initStepFinished in state "+Perl.state+" ("+Perl.initStepsLeft+")";
if (--Perl.initStepsLeft) {
console.debug("Perl: One init step done, but "+Perl.initStepsLeft+" steps left, waiting...");
return;
} else console.debug("Perl: All init steps done, doing final initialization...");
/* NOTE: Apparently, when NO_EXIT_RUNTIME is set, and exit() is called from the main program
* (from Module.callMain), Emscripten doesn't report this back to us via an ExitStatus exception
* like it does from ccall - it doesn't even call the addOnExit/ATEXIT or addOnPostRun/ATPOSTRUN
* handlers! So at the moment, the only reliable way I've found to catch the program exit()ing
* is to patch into Emscripten's (undocumented!) Module.quit... */
var origQuit = Module.quit;
Module.quit = function (status, exception) {
console.debug("Perl: quit with",exception);
Module.onExit(status);
origQuit(status,exception);
}
Perl.changeState("Ready");
if (Perl.readyCallback) Perl.readyCallback();
Perl.readyCallback = null;
};
Perl.start = function (argv) {
if (Perl.state!="Ready")
throw "Perl: can't call start in state "+Perl.state;
Perl.changeState("Running");
try {
// Note: currently callMain doesn't seem to throw ExitStatus exceptions, see discussion in Perl.initStepFinished
Module.callMain(argv ? argv : Module.arguments);
}
catch (e) {
if (e instanceof ExitStatus) {
console.debug("Perl: start:",e);
Module.onExit(e.status);
} else throw e;
}
};
Perl.eval = function (code) {
if (Perl.state!="Running")
throw "Perl: can't call eval in state "+Perl.state;
if (Perl.trace) console.debug('Perl: ccall webperl_eval_perl',code);
try {
return ccall("webperl_eval_perl","string",["string"],[code]);
}
catch (e) {
if (e instanceof ExitStatus) {
// the code caused perl to (try to) exit - now we need to call
// Perl's global destruction via our emperl_end_perl() function
Perl.end(); //TODO: Perl.end has already been called at this point (how?)
} else throw e;
}
};
/* Note that Emscripten apparently doesn't support re-running the program once it exits (?).
* So at the moment, once we end Perl, that's it. The only useful effect of ending Perl is
* that global destruction is executed and END blocks are called. But since a user may leave
* a webpage at any moment without warning, WebPerl scripts should not rely on normal termination! */
Perl.end = function () {
if (Perl.state!="Running") {
if (Perl.state=="Ended") {
console.debug("Perl: end called when already Ended");
return;
}
else throw "Perl: can't call end in state "+Perl.state;
}
var status;
try {
status = ccall("emperl_end_perl","number",[],[]);
// we know that emperl_end_perl only calls exit() on a nonzero exit code,
// which means no ExitStatus exception gets thrown on a zero exit code,
// so we *should* reach this point only with status==0
if (status!=0) console.warn("emperl_end_perl returned with status",status);
Module.onExit(status); // does Perl.changeState() for us
}
catch (e) {
if (e instanceof ExitStatus) {
console.debug("Perl: end: ",e);
status = e.status;
Module.onExit(e.status); // does Perl.changeState() for us
} else throw e;
}
return status;
};
Perl.next_glue_id = 0;
Perl.GlueTable = {};
Perl._glue_free_ids = {};
Perl.glue = function (ref) {
var id;
var free_ids = Object.keys(Perl._glue_free_ids);
if (free_ids.length>0) {
id = free_ids[0];
delete Perl._glue_free_ids[id];
if (Perl.trace) console.debug('Perl: Glue reused id',id,'to',ref);
}
else {
if (Perl.next_glue_id>=Number.MAX_SAFE_INTEGER)
throw "Perl.GlueTable is overflowing!"
id = ''+(Perl.next_glue_id++);
if (Perl.trace) console.debug('Perl: Glue id',id,'to',ref);
}
Perl.GlueTable[id] = ref;
return id;
}
Perl.unglue = function (id) {
if (Perl.trace) console.debug('Perl: Unglue id',id,'from',Perl.GlueTable[id]);
delete Perl.GlueTable[id];
Perl._glue_free_ids[id]=1;
}

@ -1,53 +0,0 @@
#!/usr/bin/env perl
use warnings;
use strict;
use FindBin;
use Plack::MIME;
use Plack::Builder qw/builder enable mount/;
use Plack::App::Directory ();
use Cpanel::JSON::XS qw/decode_json encode_json/;
require Plack::Middleware::CrossOrigin;
# Demo Plack server for WebPerl
# run me with "plackup webperl.psgi"
# in an Apache .htaccess file, one could say:
#AddType application/javascript .js
#AddType application/octet-stream .data .mem
#AddType application/wasm .wasm
Plack::MIME->add_type(".js" => "application/javascript");
Plack::MIME->add_type(".data" => "application/octet-stream");
Plack::MIME->add_type(".mem" => "application/octet-stream");
Plack::MIME->add_type(".wasm" => "application/wasm");
my $SERV_ROOT = $FindBin::Bin;
my $app_ajaxtest = sub {
my $req = Plack::Request->new(shift);
my $rv = eval {
my $content = decode_json( $req->content );
# We can do anything we like here, like e.g. call Perl subs,
# read/write files on the server, etc. - for this demo we're
# just going to munge some data from the request.
$content->{hello} .= "The server says hello!\n";
$content; # return value from eval (must be a true value)
}; my $e = $@||'unknown error';
my $res = $req->new_response($rv ? 200 : 500);
$res->content_type($rv ? 'application/json' : 'text/plain');
$res->body($rv ? encode_json($rv) : 'Server Error: '.$e);
return $res->finalize;
};
builder {
enable 'SimpleLogger';
enable 'CrossOrigin', origins => '*';
enable 'Static',
path => qr/\.(?:html?|js|css|data|mem|wasm|pl)\z/i,
root => $SERV_ROOT;
mount '/' => Plack::App::Directory->new({root=>$SERV_ROOT})->to_app;
mount '/ajaxtest' => $app_ajaxtest;
}

@ -1,79 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl &lt;script&gt; Demos</title>
<script src="webperl.js"></script>
<!-- Please see the documentation at http://webperl.zero-g.net/using.html -->
<!-- Example 1: A really basic script -->
<script type="text/perl">
print "Hello, Perl World!\n";
</script>
<!-- Example 2: Accessing JavaScript -->
<script type="text/perl">
use warnings;
use strict;
use WebPerl qw/js/;
js('document')->getElementById('my_button')
->addEventListener("click", sub {
print "You clicked 'Testing!'\n";
} );
</script>
<!-- Example 3: Using jQuery -->
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script type="text/perl">
use warnings;
use strict;
use WebPerl qw/js/;
my $jq = js('jQuery');
my $btn = $jq->('<button>', { text=>"Click me!" } );
$btn->click(sub {
print "You clicked the jQuery button!\n";
} );
$btn->appendTo( $jq->('#buttons') );
# And a demo of AJAX using jQuery:
use Cpanel::JSON::XS qw/encode_json/;
use Data::Dumper;
my $data_out = { hello => "Hello, World!\n" };
$jq->ajax( '/ajaxtest', {
method => 'POST', # we're sending JSON in the POST body
data => encode_json($data_out),
} )->done( sub {
my $data_in = shift;
print "AJAX Success! Data: ", Dumper($data_in->toperl);
} )->fail( sub {
my ($jqXHR, $textStatus, $errorThrown) = @_;
print "AJAX Failed! ($errorThrown)\n";
} );
</script>
<!-- Optional STDOUT/STDERR text area (if you don't use this, output goes to Javascript console) -->
<script>
window.addEventListener("load", function () {
document.getElementById('output')
.appendChild( Perl.makeOutputTextarea() );
});
</script>
</head>
<body>
<p>This is a demo of <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a>!</p>
<div id="output"></div>
<div id="buttons">
<button id="my_button">Testing!</button>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Loading…
Cancel
Save