#!/usr/bin/perl # 99aeabc9ec7fe80b1b39f5e53dc7e49e <- self-modifying Perl magic # state: 28fef1b8aa03b40b2df41184052311dc # istate: 301b6ad00811db3b3363b2b5b0bbff81 # id: 97d938428ee6c4d2a505f6f35bad0906 # This is a self-modifying Perl file. I'm sorry you're viewing the source (it's # really gnarly). If you're curious what it's made of, I recommend reading # http://github.com/spencertipping/writing-self-modifying-perl. # # If you got one of these from someone and don't know what to do with it, send # it to spencer@spencertipping.com and I'll see if I can figure out what it # does. # For the benefit of HTML viewers (this is a hack): #
[ loading ]
$|++; my %data; my %transient; my %externalized_functions; my %datatypes; my %locations; # Maps eval-numbers to attribute names my $global_data = join '', ; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value, %options) = @_; chomp $value; $data{"${namespace}::$name"} = $value unless $options{no_binding}; &$delegate($name, $value) unless $options{no_delegate}}} sub meta::eval_in { my ($what, $where) = @_; # Obtain next eval-number and alias it to the designated location @locations{eval('__FILE__') =~ /\(eval (\d+)\)/} = ($where); my $result = eval $what; $@ =~ s/\(eval \d+\)/$where/ if $@; warn $@ if $@; $result} meta::define_form 'meta', sub { my ($name, $value) = @_; meta::eval_in($value, "meta::$name")}; meta::meta('configure', <<'__'); # A function to configure transients. Transients can be used to store any number of # different things, but one of the more common usages is type descriptors. sub meta::configure { my ($datatype, %options) = @_; $transient{$_}{$datatype} = $options{$_} for keys %options; } __ meta::meta('externalize', <<'__'); # Function externalization. Data types should call this method when defining a function # that has an external interface. sub meta::externalize { my ($name, $attribute, $implementation) = @_; my $escaped = $name; $escaped =~ s/[^A-Za-z0-9:]/_/go; $externalized_functions{$name} = $externalized_functions{$escaped} = $attribute; *{"::$name"} = *{"::$escaped"} = $implementation || $attribute; } __ meta::meta('functor::editable', <<'__'); # An editable type. This creates a type whose default action is to open an editor # on whichever value is mentioned. This can be changed using different flags. sub meta::functor::editable { my ($typename, %options) = @_; meta::configure $typename, %options; meta::define_form $typename, sub { my ($name, $value) = @_; $options{on_bind} && &{$options{on_bind}}($name, $value); meta::externalize $options{prefix} . $name, "${typename}::$name", sub { my $attribute = "${typename}::$name"; my ($command, @new_value) = @_; return &{$options{default}}(retrieve($attribute)) if ref $options{default} eq 'CODE' and not defined $command; return edit($attribute) if $command eq 'edit' or $options{default} eq 'edit' and not defined $command; return associate($attribute, @new_value ? join(' ', @new_value) : join('', )) if $command eq '=' or $command eq 'import' or $options{default} eq 'import' and not defined $command; return retrieve($attribute)}}} __ meta::meta('functor::html-templates', <<'__'); my @html_elements = qw/html head title meta script style link body div/; # Very incomplete list for my $e (@html_elements) { meta::externalize "template::$e", "template::$e", sub { my ($line, $block) = @_; "<$e $line>\n$block\n"; }; } __ meta::meta('type::alias', <<'__'); meta::configure 'alias', inherit => 0, trim => 1; meta::define_form 'alias', sub { my ($name, $value) = @_; meta::externalize $name, "alias::$name", sub { # Can't pre-tokenize because shell::tokenize doesn't exist until the library:: # namespace has been evaluated (which will be after alias::). shell::run(shell::tokenize($value), shell::tokenize(@_)); }; }; __ meta::meta('type::bootstrap', <<'__'); # Bootstrap attributes don't get executed. The reason for this is that because # they are serialized directly into the header of the file (and later duplicated # as regular data attributes), they will have already been executed when the # file is loaded. meta::configure 'bootstrap', extension => '.pl', inherit => 1, trim => 1; meta::define_form 'bootstrap', sub {}; __ meta::meta('type::cache', <<'__'); meta::configure 'cache', inherit => 0, trim => 1; meta::define_form 'cache', \&meta::bootstrap::implementation; __ meta::meta('type::cached_dependency', <<'__'); meta::configure 'cached_dependency', inherit => 0, extension => ''; meta::define_form 'cached_dependency', \&meta::bootstrap::implementation; __ meta::meta('type::configuration', <<'__'); meta::functor::editable 'configuration', inherit => 0, extension => '.conf', default => sub { # Any lines starting with #, with or without leading whitespace, are treated as comments. # Comments are not parsed in option text; that is, you could specify an option that contained # a # and the # and following text would be considered part of that option. my ($data) = @_; my @options = grep /:\h+/o && ! /^\h*#/o && ! /^\h*$/o, split(/\v+/o, $data); s/^\h+//o for @options; my @key_values = map split(/\h*:\h+/o, $_, 2), @options; $key_values[$_ << 1] and $key_values[$_ << 1] =~ s/\s/_/go for 0 .. @key_values >> 1; $key_values[$_ << 1] and $key_values[$_ << 1] = lc $key_values[$_ << 1] for 0 .. @key_values >> 1; @key_values; }; __ meta::meta('type::data', 'meta::functor::editable \'data\', extension => \'\', inherit => 0, default => \'cat\';'); meta::meta('type::function', <<'__'); meta::configure 'function', extension => '.pl', inherit => 1, trim => 1; meta::define_form 'function', sub { my ($name, $value) = @_; meta::externalize $name, "function::$name", meta::eval_in("sub {\n$value\n}", "function::$name"); }; __ meta::meta('type::hook', <<'__'); meta::configure 'hook', extension => '.pl', inherit => 0, trim => 1; meta::define_form 'hook', sub { my ($name, $value) = @_; *{"hook::$name"} = meta::eval_in("sub {\n$value\n}", "hook::$name"); }; __ meta::meta('type::inc', <<'__'); meta::configure 'inc', inherit => 1, extension => '.pl', trim => 1; meta::define_form 'inc', sub { use File::Path 'mkpath'; use File::Basename qw/basename dirname/; my ($name, $value) = @_; my $tmpdir = basename($0) . '-' . $$; my $filename = "/tmp/$tmpdir/$name"; push @INC, "/tmp/$tmpdir" unless grep /^\/tmp\/$tmpdir$/, @INC; mkpath(dirname($filename)); unless (-e $filename) { open my $fh, '>', $filename; print $fh $value; close $fh; } }; __ meta::meta('type::indicator', <<'__'); # Shell indicator function. The output of each of these is automatically # appended to the shell prompt. meta::configure 'indicator', inherit => 1, extension => '.pl', trim => 1; meta::define_form 'indicator', sub { my ($name, $value) = @_; *{"indicator::$name"} = meta::eval_in("sub {\n$value\n}", "indicator::$name"); }; __ meta::meta('type::internal_function', <<'__'); meta::configure 'internal_function', extension => '.pl', inherit => 1, trim => 1; meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = meta::eval_in("sub {\n$value\n}", "internal_function::$name"); }; __ meta::meta('type::js', 'meta::functor::editable \'js\', extension => \'.js\', inherit => 1;'); meta::meta('type::library', <<'__'); meta::configure 'library', extension => '.pl', inherit => 1, trim => 1; meta::define_form 'library', sub { my ($name, $value) = @_; meta::eval_in($value, "library::$name"); }; __ meta::meta('type::message_color', <<'__'); meta::configure 'message_color', extension => '', inherit => 1, trim => 1; meta::define_form 'message_color', sub { my ($name, $value) = @_; terminal::color($name, $value); }; __ meta::meta('type::meta', <<'__'); # This doesn't define a new type. It customizes the existing 'meta' type # defined in bootstrap::initialization. Note that horrible things will # happen if you redefine it using the editable functor. meta::configure 'meta', extension => '.pl', inherit => 1, trim => 1; __ meta::meta('type::note', 'meta::functor::editable \'note\', extension => \'.sdoc\', inherit => 0, default => \'edit\';'); meta::meta('type::parent', <<'__'); meta::define_form 'parent', \&meta::bootstrap::implementation; meta::configure 'parent', extension => '', inherit => 1, trim => 1; __ meta::meta('type::retriever', <<'__'); meta::configure 'retriever', extension => '.pl', inherit => 1, trim => 1; meta::define_form 'retriever', sub { my ($name, $value) = @_; $transient{retrievers}{$name} = meta::eval_in("sub {\n$value\n}", "retriever::$name"); }; __ meta::meta('type::sdoc', <<'__'); # A meta-type for other types. So retrieve('js::main') will work if you have # the attribute 'sdoc::js::main'. The filename will be main.js.sdoc. meta::functor::editable 'sdoc', inherit => 1, extension => sub { extension_for(attribute($_[0])) . '.sdoc'; }; __ meta::meta('type::slibrary', <<'__'); meta::configure 'slibrary', extension => '.pl.sdoc', inherit => 1; meta::define_form 'slibrary', sub { my ($name, $value) = @_; meta::eval_in(sdoc("slibrary::$name"), "slibrary::$name"); }; __ meta::meta('type::state', <<'__'); # Allows temporary or long-term storage of states. Nothing particularly insightful # is done about compression, so storing alternative states will cause a large # increase in size. Also, states don't contain other states -- otherwise the size # increase would be exponential. # States are created with the save-state function. meta::configure 'state', inherit => 0, extension => '.pl', trim => 1; meta::define_form 'state', \&meta::bootstrap::implementation; __ meta::meta('type::template', <<'__'); meta::configure 'template', extension => '.pl', inherit => 1; meta::define_form 'template', sub { my ($name, $value) = @_; meta::externalize "template::$name", "template::$name", meta::eval_in("sub {\n$value\n}", "template::$name"); }; __ meta::meta('type::todo', <<'__'); # Todo lists are special cases of SDoc syntax. meta::configure 'todo', inherit => 0, extension => '.sdoc'; meta::define_form 'todo', sub { my ($name, $value) = @_; meta::externalize $name, "todo::$name", sub { return edit("todo::$name") if ! @_ || $_[0] eq 'edit'; &{'todo-summary'}("todo::$name") if $_[0] eq 'show'}}; __ meta::meta('type::vim_highlighter', <<'__'); # Vim highlighters don't need to be inherited. The reason is that they are most # often configured from the parent object using the 'vim' function. They are # rarely sent to anyone else and configured after the fact. meta::configure 'vim_highlighter', extension => '.vim', inherit => 0; meta::define_form 'vim_highlighter', \&meta::bootstrap::implementation; __ meta::meta('type::watch', 'meta::functor::editable \'watch\', prefix => \'watch::\', inherit => 1, extension => \'.pl\', default => \'cat\';'); meta::meta('type::waul', 'meta::functor::editable \'waul\', inherit => 1, extension => \'.waul\', default => \'edit\';'); meta::alias('cloc', 'loc js::core/caterwaul\\.'); meta::alias('eR', 'edit sdoc::readme'); meta::alias('ec', 'edit sdoc::js::caterwaul'); meta::alias('eh', 'edit data::bootstrap.html.sdoc'); meta::alias('er', 'edit function::render'); meta::alias('es', 'edit sdoc::data::style.css'); meta::alias('ev', 'edit vim_highlighter::caterwaul'); meta::alias('ew', 'edit sdoc::waul::waul'); meta::alias('ewb', 'edit sdoc::js::waul-bootstrap'); meta::alias('lsc', 'ls -a sdoc::js::core/'); meta::alias('lse', 'ls -a sdoc::js::extensions/'); meta::alias('lst', 'ls -a sdoc::js::.*test/.*'); meta::alias('me', 'macroexpand'); meta::alias('meopt', 'macroexpander-optimization'); meta::alias('rloc', 'loc js::(?!.*test|.*format|unit|minify|macroexpand|precompile|modules|core/debug)'); meta::bootstrap('html', <<'__');
__ meta::bootstrap('initialization', <<'__'); #!/usr/bin/perl # 99aeabc9ec7fe80b1b39f5e53dc7e49e <- self-modifying Perl magic # state: __state # istate: __istate # id: __id # This is a self-modifying Perl file. I'm sorry you're viewing the source (it's # really gnarly). If you're curious what it's made of, I recommend reading # http://github.com/spencertipping/writing-self-modifying-perl. # # If you got one of these from someone and don't know what to do with it, send # it to spencer@spencertipping.com and I'll see if I can figure out what it # does. # For the benefit of HTML viewers (this is a hack): #
[ loading ]
$|++; my %data; my %transient; my %externalized_functions; my %datatypes; my %locations; # Maps eval-numbers to attribute names my $global_data = join '', ; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value, %options) = @_; chomp $value; $data{"${namespace}::$name"} = $value unless $options{no_binding}; &$delegate($name, $value) unless $options{no_delegate}}} sub meta::eval_in { my ($what, $where) = @_; # Obtain next eval-number and alias it to the designated location @locations{eval('__FILE__') =~ /\(eval (\d+)\)/} = ($where); my $result = eval $what; $@ =~ s/\(eval \d+\)/$where/ if $@; warn $@ if $@; $result} meta::define_form 'meta', sub { my ($name, $value) = @_; meta::eval_in($value, "meta::$name")}; __ meta::bootstrap('perldoc', <<'__'); =head1 Self-modifying Perl script =head2 Original implementation by Spencer Tipping L The prototype for this script is licensed under the terms of the MIT source code license. However, this script in particular may be under different licensing terms. To find out how this script is licensed, please contact whoever sent it to you. Alternatively, you may run it with the 'license' argument if they have specified a license that way. You should not edit this file directly. For information about how it was constructed, go to L. For quick usage guidelines, run this script with the 'usage' argument. =cut __ meta::cache('parent-identification', <<'__'); ../waul-object 4e04fdb8e560f4dd2ca4880b91a8e2ea ./sdoc /home/spencertipping/bin/configuration aa772900bb5b925cb84346bd72a4249d /home/spencertipping/bin/node-base da62d84a9e81832f089520c172982c1a /home/spencertipping/bin/object 99aeabc9ec7fe80b1b39f5e53dc7e49e /home/spencertipping/bin/repository 05bc3036c343fdb8aec5b0be12a9b19e /home/spencertipping/conjectures/perl-objects/js 246bc56c88e8e8daae3737dbb16a2a2c /home/spencertipping/conjectures/perl-objects/object 99aeabc9ec7fe80b1b39f5e53dc7e49e /home/spencertipping/conjectures/perl-objects/preprocessor 70dae4b46eb4e06798ec6f38d17d4c7b /home/spencertipping/conjectures/perl-objects/sdoc a1e8480e579614c01dabeecf0f963bcc git-integration 9fabb9b6f2f374c35ddcc26549ac2b65 html 1df113f7cef70e75214a2f28ca266c5e notes a9e5975593ed5d90d943ad98405c71e5 object 99aeabc9ec7fe80b1b39f5e53dc7e49e preprocessor 70dae4b46eb4e06798ec6f38d17d4c7b sdoc a1e8480e579614c01dabeecf0f963bcc todo 62bc8a83ef5d4941cd2c1cdfb57a0320 vim-highlighters 902333a0bd6ed90ff919fe8477cb4e69 waul-object 4e04fdb8e560f4dd2ca4880b91a8e2ea __ meta::cache('parent-state', <<'__'); 1df113f7cef70e75214a2f28ca266c5e a2f92446c1957947f847c939fdae3750 246bc56c88e8e8daae3737dbb16a2a2c 4b7d349f48353dcb3e5c5484bf18546c 4e04fdb8e560f4dd2ca4880b91a8e2ea caf90f5e1ff5a4b273faade4b138020c 62bc8a83ef5d4941cd2c1cdfb57a0320 b63584f1cde31191b9d7cc04c734d7ae 70dae4b46eb4e06798ec6f38d17d4c7b e25c4729aff094da49d7b9903452bcea 902333a0bd6ed90ff919fe8477cb4e69 8d4ebdc5d3e901edca2bec5d546d6f7f 99aeabc9ec7fe80b1b39f5e53dc7e49e 39f70215e806554b2130e45f1ad9c776 a1e8480e579614c01dabeecf0f963bcc 6a1b66304cbd4a94a05b0ef78f1e1bf4 __ meta::configuration('dependencies', <<'__'); # Named dependencies: #caterwaul.all.js: http://spencertipping.com/caterwaul/caterwaul.all.min.js #montenegro.server.js: http://spencertipping.com/montenegro/montenegro.server.js __ meta::data('author', 'Spencer Tipping'); meta::data('bootstrap.html.sdoc', <<'__'); - html << end - head << end - link rel='stylesheet' href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono&subset=latin' - link rel='stylesheet' href='http://fonts.googleapis.com/css?family=Neuton&subset=latin' - script-include deps/jquery-1.5.min.js - script << end $('#cover .status').text('loading precompiled caterwaul'); - end - script-include build/caterwaul.min.js - script-include build/caterwaul.std.min.js - script-include build/caterwaul.ui.min.js - script-include crunch::pp::js::web/main - style-include pp::code.css::sdoc::data::style.css - end - body << end - div class='page' id='sdoc-page' - div class='page' id='tutorial-page' - end - end __ meta::data('default-action', 'shell'); meta::data('libraries', <<'__'); # URLs of libraries to be downloaded into the lib/ directory. http://spencertipping.com/caterwaul/caterwaul.all.js http://spencertipping.com/montenegro/montenegro.server.js __ meta::data('license', <<'__'); MIT License Copyright (c) 2010 Spencer Tipping Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. __ meta::data('main', 'server.js'); meta::data('name', 'node-base'); meta::data('permanent-identity', '97d938428ee6c4d2a505f6f35bad0906'); meta::data('quiet', '1'); meta::data('watching', '1'); meta::function('ad', <<'__'); my ($options, @paths) = separate_options(@_); @{$transient{path}} = () if $$options{-c}; return @{$transient{path}} = () unless @paths; push @{$transient{path}}, @paths; __ meta::function('alias', <<'__'); my ($name, @stuff) = @_; @_ ? @stuff ? around_hook('alias', @_, sub {associate("alias::$name", join(' ', @stuff), execute => 1)}) : retrieve("alias::$name") // "Undefined alias $name" : table_display([select_keys('--namespace' => 'alias')], [map retrieve($_), select_keys('--namespace' => 'alias')]); __ meta::function('build-bootstrap-html', <<'__'); # This is called automatically prior to saving. associate('bootstrap::html', retrieve('pp::data::bootstrap.html.sdoc')); # Long story about why we're corrupting the HTML this way, but basically it has # to do with giving the user a nicer experience as the page is loading. my $initialization = retrieve('bootstrap::initialization'); my $new_div = <><div id='cover' style='position: absolute; z-index: 1; left: 0; top: 0; width: 10000px; height: 10000px; background: #111; font-family: sans-serif; color: #888; padding-left: 100px; padding-top: 100px'> [ loading ]
EOF $new_div =~ s/\n\s*//mg; $new_div =~ s/</>//g; $initialization =~ s/^# $/# $new_div/smg; associate('bootstrap::initialization', $initialization); __ meta::function('cat', 'join "\\n", retrieve(@_);'); meta::function('cc', <<'__'); # Stashes a quick one-line continuation. (Used to remind me what I was doing.) @_ ? associate('data::current-continuation', hook('set-cc', join(' ', @_))) : retrieve('data::current-continuation'); __ meta::function('ccc', 'rm(\'data::current-continuation\');'); meta::function('child', <<'__'); around_hook('child', @_, sub { my ($child_name) = @_; clone($child_name); enable(); qx($child_name update-from $0); terminal::info("$child_name\'s identity is " . join '', qx($child_name identity)); disable()}); __ meta::function('cloc', 'loc(\'modules/caterwaul\\.(?!format)[^/]+$\', \'caterwaul$\')'); meta::function('clone', <<'__'); my ($options, @files) = separate_options(@_); for my $file (@files) { around_hook('clone', $file, sub { hypothetically(sub { # Assign a new object identity. rm('data::permanent-identity'); identity(); file::write($file, serialize(), noclobber => 1); chmod(0700, $file)})})} __ meta::function('cp', <<'__'); my $from = shift @_; my $value = retrieve($from); associate($_, $value) for @_; __ meta::function('create', <<'__'); my ($name, $value) = @_; around_hook('create', $name, $value, sub { return edit($name) if exists $data{$name}; associate($name, defined $value ? $value : ''); edit($name) unless defined $value}); __ meta::function('crunch-whitespace', <<'__'); my ($attr) = @_; my $text = retrieve($attr) // $attr; $text =~ s/\h+/ /g; $text; __ meta::function('ct', 'create("sdoc::js::test/$_[0]");'); meta::function('current-state', 'serialize(\'-pS\');'); meta::function('cwd', <<'__'); use Cwd qw/getcwd/; getcwd(); __ meta::function('disable', 'hook(\'disable\', chmod_self(sub {$_[0] & 0666}));'); meta::function('dupdate', '# This function has been removed to eliminate dependency on LWP::Simple.'); meta::function('e', <<'__'); my @extensions = select_keys('--criteria' => "sdoc::js::extensions/(.*/)?$_[0]"); edit($extensions[0]); __ meta::function('edit', <<'__'); my ($options, @names) = separate_options(@_); @names = select_keys('--criteria' => "^$$options{'--prefix'}.*" . join('.*', @names), %$options) if $$options{'--prefix'}; my ($name, @others) = @names; die "cannot edit multiple attributes simultaneously (others are @others)" if @others; die "$name is virtual or does not exist" unless exists $data{$name}; die "$name is inherited; use 'edit $name -f' to edit anyway" unless is($name, '-u') || is($name, '-d') || exists $$options{'-f'}; my $extension = extension_for($name); around_hook('edit', @_, sub { associate($name, invoke_editor_on($data{$name} // '', %$options, attribute => $name, extension => $extension), execute => 1)}); save() unless $data{'data::edit::no-save'} or state() eq $transient{initial}; ''; __ meta::function('edit-self', <<'__'); $global_data = invoke_editor_on($global_data); save(); __ meta::function('enable', 'hook(\'enable\', chmod_self(sub {$_[0] | ($_[0] & 0444) >> 2}));'); meta::function('expanded-bootstrap', <<'__'); # Write headers into the bootstrap section. There is some subtle stuff going on # here with the istate header. The idea is to provide other objects a very quick # way to see whether our state has changed, but we don't want false positives. # We would get a false positive if, for instance, we included the contents of # parent:: attributes in the istate hash. The reason is that the parent:: # attribute contains a hash of every attribute provided by that parent, so any # change in the parent would impact the istate of the child. # # The best way to deal with this is to treat parent:: attributes as being # opaque; we record their existence or nonexistence, but we don't record their # contents. We also look only at inheritable and unique attributes and fail to # consider global state. my $bootstrap_text = retrieve('bootstrap::initialization'); my $state = state(); my $istate = state('-iGP'); my $object_id = identity(); $bootstrap_text =~ s/__state/$state/g; $bootstrap_text =~ s/__istate/$istate/g; $bootstrap_text =~ s/__id/$object_id/g; $bootstrap_text; __ meta::function('export', <<'__'); # Exports data into a text file. # export attr1 attr2 attr3 ... file.txt my $name = pop @_; @_ or die 'Expected filename'; file::write($name, join "\n", retrieve(@_)); __ meta::function('extern', '&{$_[0]}(retrieve(@_[1 .. $#_]));'); meta::function('gjs', <<'__'); # Runs GJS on a collection of source files and arguments. The format is: # gjs([@source_strings], @process_args); my ($sources, @args) = @_; with_exported(@$sources, sub { hook('before-gjs', $_[0], @args); sh('gjs', $_[0], @args); hook('after-gjs', $_[0], @args); }); __ meta::function('grep', <<'__'); # Looks through attributes for a pattern. Usage is grep pattern [options], where # [options] is the format as provided to select_keys. my ($pattern, @args) = @_; my ($options, @criteria) = separate_options(@args); my @attributes = select_keys(%$options, '--criteria' => join('|', @criteria)); $pattern = qr/$pattern/; my @m_attributes; my @m_line_numbers; my @m_lines; for my $k (@attributes) { next unless length $k; my @lines = split /\n/, retrieve($k); for (0 .. $#lines) { next unless $lines[$_] =~ $pattern; push @m_attributes, $k; push @m_line_numbers, $_ + 1; push @m_lines, '' . ($lines[$_] // '')}} unless ($$options{'-C'}) { s/($pattern)/\033[1;31m\1\033[0;0m/g for @m_lines; s/^/\033[1;34m/o for @m_attributes; s/^/\033[1;32m/o && s/$/\033[0;0m/o for @m_line_numbers} table_display([@m_attributes], [@m_line_numbers], [@m_lines]); __ meta::function('hash', 'fast_hash(@_);'); meta::function('hook', <<'__'); my ($hook, @args) = @_; $transient{active_hooks}{$hook} = 1; dangerous('', sub {&$_(@args)}) for grep /^hook::${hook}::/, sort keys %data; @args; __ meta::function('hooks', 'join "\\n", sort keys %{$transient{active_hooks}};'); meta::function('identity', <<'__'); retrieve('data::permanent-identity') or associate('data::permanent-identity', fast_hash(join '|', map rand(), 1 .. 32)); __ meta::function('import', <<'__'); my $name = pop @_; associate($name, @_ ? join('', map(file::read($_), @_)) : join('', )); __ meta::function('import-bundle', <<'__'); eval join '', ; die $@ if $@; __ meta::function('initial-state', '$transient{initial};'); meta::function('is', <<'__'); my ($attribute, @criteria) = @_; my ($options, @stuff) = separate_options(@criteria); exists $data{$attribute} and attribute_is($attribute, %$options); __ meta::function('line', <<'__'); # Prints a line with some context. This is useful when a test fails. my ($line, $part) = @_; my @lines = split /\n/, $part eq '-a' ? retrieve('pp::js::caterwaul.all') : retrieve('pp::js::caterwaul'); for ($line - 5 .. $line + 5) { print "\033[1;32m" if $_ == $line; printf "%04d: %s\n", $_, $lines[$_ - 1]; print "\033[0;0m" if $_ == $line; } __ meta::function('load-gjs', 'gjs([\'id::start = +new Date();\', \'pp::js::caterwaul.all\', \'id::print("caterwaul self-compiled in " + (+new Date() - start) + "ms");\']);'); meta::function('load-state', <<'__'); around_hook('load-state', @_, sub { my ($state_name) = @_; my $state = retrieve("state::$state_name"); terminal::state('saving current state into _...'); save_state('_'); delete $data{$_} for grep ! /^state::/, keys %data; %externalized_functions = (); terminal::state("restoring state $state_name..."); meta::eval_in($state, "state::$state_name"); terminal::error(hook('load-state-failed', $@)) if $@; reload(); verify()}); __ meta::function('load-time', <<'__'); sub load { my ($runtime, @files) = @_; with_exported(@files, sub { my ($file) = @_; terminal::info("$runtime: loading @files"); bench(sub {sh("$runtime $file > /dev/null 2>&1")}, 4)})} load 'gjs', qw(build/caterwaul.js); load 'gjs', qw(build/caterwaul.js build/caterwaul.std.js); load 'gjs', qw(build/caterwaul.js build/caterwaul.std.js build/caterwaul.ui.js); load 'node', qw(build/caterwaul.js); load 'node', qw(build/caterwaul.js build/caterwaul.std.js); load 'node', qw(build/caterwaul.js build/caterwaul.std.js build/caterwaul.ui.js); __ meta::function('lock', 'hook(\'lock\', chmod_self(sub {$_[0] & 0555}));'); meta::function('ls', <<'__'); my ($options, @criteria) = separate_options(@_); my ($external, $shadows, $sizes, $flags, $long, $hashes, $parent_hashes) = @$options{qw(-e -s -z -f -l -h -p)}; $sizes = $flags = $hashes = $parent_hashes = 1 if $long; return table_display([grep ! exists $data{$externalized_functions{$_}}, sort keys %externalized_functions]) if $shadows; my $criteria = join('|', @criteria); my @definitions = select_keys('--criteria' => $criteria, '--path' => $transient{path}, %$options); my %inverses = map {$externalized_functions{$_} => $_} keys %externalized_functions; my @externals = map $inverses{$_}, grep length, @definitions; my @internals = grep length $inverses{$_}, @definitions; my @sizes = map sprintf('%6d %6d', length(serialize_single($_)), length(retrieve($_))), @{$external ? \@internals : \@definitions} if $sizes; my @flags = map {my $k = $_; join '', map(is($k, "-$_") ? $_ : '-', qw(d i m u))} @definitions if $flags; my @hashes = map fast_hash(retrieve($_)), @definitions if $hashes; my %inherited = parent_attributes(grep /^parent::/o, keys %data) if $parent_hashes; my @parent_hashes = map $inherited{$_} || '-', @definitions if $parent_hashes; join "\n", map strip($_), split /\n/, table_display($external ? [grep length, @externals] : [@definitions], $sizes ? ([@sizes]) : (), $flags ? ([@flags]) : (), $hashes ? ([@hashes]) : (), $parent_hashes ? ([@parent_hashes]) : ()); __ meta::function('ls-a', 'ls(\'-ad\', @_);'); meta::function('metadata-from', <<'__'); my ($filename) = @_; my %metadata; # Not using file::read because we only need the first few lines. open my($fh), '<', $filename or return {}; while (<$fh>) { /^#\s*(\w+):\s*(.*)$/ and $metadata{$1} = $2; last unless /^#/; } close $fh; \%metadata; __ meta::function('min-gzipped', <<'__'); # Prints minified and gzipped size sub size_of {my @files = ('build/caterwaul.min.js', map "build/caterwaul.$_.min.js", @_); terminal::info("minified/gzipped(@_) is " . qx(cat @files | wc -c | tr -d '\n') . "/" . qx(cat @files | gzip --best -c | wc -c | tr -d '\n'))} size_of; size_of 'std'; size_of 'std', 'ui'; __ meta::function('minify', <<'__'); # Minify using YUI compressor my ($filename) = @_; my $minified = $filename; $minified =~ s/\.js$/.min.js/; terminal::info("minifying $filename"); file::write($minified, qx(yuicompressor "$filename")); __ meta::function('minify-uglify', <<'__'); # Minify using uglifyjs compressor my ($options, @filenames) = separate_options(@_); my $nomunge = $$options{-m} ? '' : '-nm'; my $lift = $$options{-L} ? '' : '--lift-vars'; for my $filename (@filenames) { my $minified = $filename; $minified =~ s/\.js$/.min.js/; terminal::info("minifying $filename"); file::write($minified, join '', qx(uglifyjs $nomunge $lift "$filename")); } __ meta::function('minify-yui', <<'__'); # Minify using YUI compressor my ($options, @filenames) = separate_options(@_); my $nomunge = $$options{-m} ? '' : '--nomunge'; my $linebreak = $$options{-B} ? '' : '--line-break 160'; for my $filename (@filenames) { my $minified = $filename; $minified =~ s/\.js$/.min.js/; terminal::info("minifying $filename"); file::write($minified, join '', qx(yuicompressor $nomunge $linebreak "$filename")); } __ meta::function('mv', <<'__'); my ($from, $to) = @_; die "'$from' does not exist" unless exists $data{$from}; associate($to, retrieve($from), execute => 1); rm($from); __ meta::function('name', <<'__'); my $name = $0; $name =~ s/^.*\///; $name; __ meta::function('note', <<'__'); # Creates a note with a given name, useful for jotting things down. create("note::$_[0]"); __ meta::function('notes', 'ls(\'-a\', \'^note::\');'); meta::function('parents', 'join "\\n", grep s/^parent:://o, sort keys %data;'); meta::function('parse-todo', <<'__'); # Takes a string of todo text and parses it. Multiple todo lists can be # combined, resulting in a longer list. my @todo_paragraphs = grep s/^\h*\|//, split /\n{2,}/, join "\n\n", retrieve(@_); my @todo_lines = grep /^\h*\[\W+\]\h*\w/, map split(/\n/), @todo_paragraphs; sub progress_of {my ($s) = @_; ($s =~ /^(\S*)/ and length $1) / length $s} map /^\s*\[(\W+)\]\h*(\H+)\h*(.*)$/ && {progress_meter => $1, progress => progress_of($1), category => $2, details => $3}, @todo_lines; __ meta::function('perl', <<'__'); my @result = eval(join ' ', @_); $@ ? terminal::error($@) : wantarray ? @result : $result[0]; __ meta::function('precompile', <<'__'); terminal::info("precompiling $_[0]"); node([qw|pp::js::tools/precompile|], @_); __ meta::function('preprocess', <<'__'); # Implements a simple preprocessing language. # Syntax follows two forms. One is the 'line form', which gives you a way to specify arguments inline # but not spanning multiple lines. The other is 'block form', which gives you access to both one-line # arguments and a block of lines. The line parameters are passed in verbatim, and the block is # indentation-adjusted and then passed in as a second parameter. (Indentation is adjusted to align # with the name of the command.) # # Here are the forms: # # - line arguments to function # # - block line arguments << eof # block contents # block contents # ... # - eof my ($string, %options) = @_; my $expansions = 0; my $old_string = ''; my $limit = $options{expansion_limit} || 100; my @pieces = (); sub adjust_spaces { my ($spaces, $string) = @_; $string =~ s/^$spaces //mg; chomp $string; $string; } while ($old_string ne $string and $expansions++ < $limit) { $old_string = $string; while ((my @pieces = split /(^(\h*)-\h \S+ \h* \V* <<\h*(\w+)$ \n .*? ^\2-\h\3$)/xms, $string) > 1 and $expansions++ < $limit) { $pieces[1 + ($_ << 2)] =~ /^ (\h*)-\h(\S+)\h*(\V*)<<\h*(\w+)$ \n(.*?) ^\1-\h\4 $/xms && $externalized_functions{"template::$2"} and $pieces[1 + ($_ << 2)] = &{"template::$2"}($3, adjust_spaces($1, $5)) for 0 .. $#pieces / 4; @pieces[2 + ($_ << 2), 3 + ($_ << 2)] = '' for 0 .. $#pieces / 4; $string = join '', @pieces; } if ((my @pieces = split /^(\h*-\h \S+ \h* .*)$/xom, $string) > 1) { $pieces[1 + ($_ << 1)] =~ /^ \h*-\h(\S+)\h*(.*)$/xom && $externalized_functions{"template::$1"} and $pieces[1 + ($_ << 1)] = &{"template::$1"}($2) for 0 .. $#pieces >> 1; $string = join '', @pieces; } } $string; __ meta::function('rd', <<'__'); if (@_) {my $pattern = join '|', @_; @{$transient{path}} = grep $_ !~ /^$pattern$/, @{$transient{path}}} else {pop @{$transient{path}}} __ meta::function('reload', 'around_hook(\'reload\', sub {execute($_) for grep ! /^bootstrap::/, keys %data});'); meta::function('render', <<'__'); my ($options, @stuff) = separate_options(@_); $ENV{PATH} = "$ENV{PATH}:$ENV{PWD}"; unless ($$options{-M}) { chdir 'src' or die 'no src/ directory exists; are you running this from the repository root?'; terminal::info('rendering caterwaul core (use -M to skip core/module rendering)'); sh('./caterwaul render -M'); terminal::info('rendering std module'); sh('./std render -M'); terminal::info('rendering ui module'); sh('./ui render -M'); chdir '..'; } else { terminal::info('skipping core file render because -M was specified'); } terminal::info('creating base files'); file::write('build/caterwaul.vim', retrieve('vim_highlighter::caterwaul'), mkpath => 1); file::write('build/caterwaul.node.js', retrieve('pp::js::caterwaul.node'), mkpath => 1); file::write('doc/caterwaul-tutorial.sdoc', retrieve('sdoc::web/tutorial'), mkpath => 1); file::write('README.md', retrieve('markdown::readme')); unless ($$options{-W}) { my $waul = $$options{'--waul'} || './waul'; terminal::info("regenerating waul-core using $waul as a bootstrap compiler"); file::write('src/waul/waul.waul', retrieve('waul::waul')); file::write('src/waul/waul.md', retrieve('markdown::waul::waul')); sh("$waul -e build/caterwaul.std.js src/waul/waul.waul"); file::write('waul-core', retrieve('pp::js::waul-bootstrap')); chmod 0755, 'waul-core'; file::write('waul-core-first', cat('waul-core')); terminal::info('regenerating waul-core using its replicator (identity transform)'); file::write('waul-core.temporary', join '', qx|./waul-core --replicate|); die 'failed to regenerate waul-core' if $?; rename 'waul-core.temporary', 'waul-core'; chmod 0755, 'waul-core'; terminal::info('regenerating waul from waul-core'); file::write('waul', join '', qx|./waul-core --replicate -e build/caterwaul.std.js -e build/caterwaul.ui.js|); die 'failed to construct waul from waul-core' if $?; chmod 0755, 'waul'; file::write('waul', join '', qx|./waul --replicate -e build/caterwaul.std.js -e build/caterwaul.ui.js|); die 'failed to reconstruct waul from itself' if $?; chmod 0755, 'waul'; } else { terminal::info('skipping waul render because -W was specified'); } save(); __ meta::function('repl', <<'__'); node(['id::start = +new Date();', 'pp::js::caterwaul.all', 'id::console.log("caterwaul self-compiled in " + (+new Date() - start) + "ms");', 'id::require("repl").start("caterwaul> ").context.caterwaul = caterwaul']); __ meta::function('replc', 'node([\'pp::js::caterwaul\', \'id::require("repl").start("caterwaul core> ").context.caterwaul = caterwaul\']);'); meta::function('repls', <<'__'); node(['id::start = +new Date();', 'pp::js::caterwaul', 'pp::js::extensions/std', 'id::console.log("caterwaul self-compiled in " + (+new Date() - start) + "ms");', 'id::require("repl").start("caterwaul> ").context.caterwaul = caterwaul']); __ meta::function('rm', <<'__'); around_hook('rm', @_, sub { exists $data{$_} or terminal::warning("$_ does not exist") for @_; delete @data{@_}}); __ meta::function('rmparent', <<'__'); # Removes one or more parents. my ($options, @parents) = separate_options(@_); my $clobber_divergent = $$options{'-D'} || $$options{'--clobber-divergent'}; my %parents = map {$_ => 1} @parents; my @other_parents = grep !$parents{$_}, grep s/^parent:://, select_keys('--namespace' => 'parent'); my %kept_by_another_parent; $kept_by_another_parent{$_} = 1 for grep s/^(\S+)\s.*$/\1/, split /\n/o, cat(@other_parents); for my $parent (@parents) { my $keep_parent_around = 0; for my $line (split /\n/, retrieve("parent::$parent")) { my ($name, $hash) = split /\s+/, $line; next unless exists $data{$name}; my $local_hash = fast_hash(retrieve($name)); if ($clobber_divergent or $hash eq $local_hash or ! defined $hash) {rm($name) unless $kept_by_another_parent{$name}} else {terminal::info("local attribute $name exists and is divergent; use rmparent -D $parent to delete it"); $keep_parent_around = 1}} $keep_parent_around ? terminal::info("not deleting parent::$parent so that you can run", "rmparent -D $parent if you want to nuke divergent attributes too") : rm("parent::$parent")} __ meta::function('save', 'around_hook(\'save\', sub {dangerous(\'\', sub {file::write($0, serialize(\'-V\')); $transient{initial} = state()}) if verify()});'); meta::function('save-state', <<'__'); # Creates a named copy of the current state and stores it. my ($state_name) = @_; around_hook('save-state', $state_name, sub { associate("state::$state_name", current_state(), execute => 1)}); __ meta::function('sdoc', <<'__'); # Applies SDoc processing to a file or attribute. Takes the file or attribute # name as the first argument and returns the processed text. my %comments_for_extension = qw|c /*,*/ cpp // cc // h // java // py # rb # pl # pm # ml (*,*) js // hs -- sh # lisp ;;; lsp ;;; s # scm ;;; sc ;;; as // html mli (*,*) cs // vim " elisp ; bas ' ada -- asm ; awk # bc # boo # tex % fss (*,*) erl % scala // hx // io // j NB. lua -- n // m % php // sql -- pov // pro % r # self "," tcl # texi @c tk # csh # vala // vbs ' v /*,*/ vhdl -- ss ;;; haml -# sass /*,*/ scss /*,*/ css /*,*/ fig / waul # canard nb[,]|; # No extension suggests a shebang line, which generally requires # to denote a comment. $comments_for_extension{''} = '#'; my $generated_string = 'Generated by SDoc'; sub is_code {map /^\s*[^A-Z\|\s]/o, @_} sub is_blank {map /^\n/o, @_} sub comment {my ($text, $s, $e) = @_; join "\n", map("$s $_" . (length $e ? " $e" : ''), split /\n/, $text)} sub paragraphs {map split(/((?:\n\h*){2,})/, $_), @_} my ($filename, $specified_extension) = @_; # Two possibilities here. One is that the filename is an attribute, in which case # we want to look up the extension in the transients table. The other is that # it's a real filename. my ($extension) = $specified_extension || extension_for($filename) || ($filename =~ /\.sdoc$/io ? $filename =~ /\.(\w+)\.sdoc$/igo : $filename =~ /\.(\w+)$/igo); $extension =~ s/^\.//o; my ($start, $end) = split /,/o, $comments_for_extension{lc $extension} // $comments_for_extension{''} // '#'; join '', map(is_code($_) || is_blank($_) ? ($_ =~ /^\s*c\n(.*)$/so ? $1 : $_) : comment($_, $start, $end), paragraphs retrieve($filename)), "\n" . comment($generated_string, $start, $end) . "\n"; __ meta::function('sdoc-html', <<'__'); # Converts SDoc to logically-structured HTML. Sections end up being nested, # and code sections and examples are marked as such. For instance, here is some # sample output: #
#

Foo

#

This is a paragraph...

#

This is another paragraph...

#
int main () {return 0;}
#
int main () {return 0} // Won't compile
#
#

Bar

# ... #
#
# It is generally good about escaping things that would interfere with HTML, # but within text paragraphs it lets you write literal HTML. The heuristic is # that known tags that are reasonably well-formed are allowed, but unknown ones # are escaped. my ($attribute) = @_; my @paragraphs = split /\n(?:\s*\n)+/, retrieve($attribute); my $known_tags = join '|', qw[html head body meta script style link title div a span input button textarea option select form label iframe blockquote code caption table tbody tr td th thead tfoot img h1 h2 h3 h4 h5 h6 li ol ul noscript p pre samp sub sup var canvas audio video strong em]; my $section_level = 0; my @markup; my $indent = sub {' ' x ($_[0] || $section_level)}; my $unindent = sub {my $spaces = ' ' x ($section_level - 1); s/^$spaces//gm}; my $escape_all = sub {s/&/&/g; s//>/g}; my $escape_some = sub {s/&/&/g; s/<(?!\/|($known_tags)[^>]*>.*<\/\1>)/</gs}; my $code = sub {&$escape_all(); &$unindent(); s/^c\n//; push @markup, &$indent() . "
$_
"}; my $quoted = sub {&$escape_all(); &$unindent(); s/^\|(\s?)/ \1/; s/^ //mg; push @markup, &$indent() . "
$_
"}; my $paragraph = sub {&$escape_some(); push @markup, &$indent() . "

$_

"}; my $section = sub {my $h = $_[0] > 6 ? 6 : $_[0]; push @markup, &$indent($_[0] - 1) . "
", &$indent($_[0]) . "$2"}; my $close_section = sub {push @markup, &$indent($_[0]) . "
"}; my $title = sub { my $indentation = (length($1) >> 1) + 1; &$close_section($section_level) while $section_level-- >= $indentation; &$section($indentation); $section_level = $indentation; }; for (@paragraphs) { &$code(), next unless /^\h*[A-Z|]/; &$quoted(), next if /^\h*\|/; &$title(), s/^.*\n// if /^(\s*)(\S.*)\.\n([^\n]+)/ and length("$1$2") < 60 and length("$1$2") - 10 < length($3); &$paragraph(); } &$close_section($section_level) while $section_level--; join "\n", @markup; __ meta::function('sdoc-markdown', <<'__'); # Renders a chunk of SDoc as Markdown. This involves converting quoted and # unquoted code and section headings, but not numbered lists. my ($attribute) = @_; my $extension = extension_for($attribute =~ /^sdoc::(.*)$/g) || ($attribute =~ /.*?\.([^\.]+)(?:\.sdoc)?$/gi)[0]; my @paragraphs = split /\n(?:\s*\n)+/, retrieve($attribute); my $section_level = 0; my @markup; my $indent = sub {' ' x ($_[0] || $section_level)}; my $unindent = sub {my $spaces = ' ' x ($section_level - 1); s/^$spaces//gm; $_}; my $code = sub {&$unindent(); s/^c\n//; push @markup, "```$extension\n$_\n```"}; my $quoted = sub {&$unindent(); s/^\|(\s?)/ \1/; s/^ //mg; push @markup, join("\n", map &$indent(2) . $_, split /\n/)}; my $heading = sub {'#' x $_[0]}; my $section = sub {&$unindent(); push @markup, &$heading($_[0]) . ' ' . $2}; my $title = sub { my $indentation = (length($1) >> 1) + 1; &$section($indentation); $section_level = $indentation; }; for (@paragraphs) { &$code(), next unless /^\h*[A-Z|]/; &$quoted(), next if /^\h*\|/; &$title(), s/^.*\n// if /^(\s*)(\S.*)\.\n([^\n]+)/ and length("$1$2") < 60 and length("$1$2") - 10 < length($3); push @markup, join "\n", map &$unindent(), split /\n/; } join "\n\n", @markup; __ meta::function('sdoc-packed', <<'__'); # An SDoc preprocessor that removes all comment paragraphs. Paragraph breaks # are preserved, and no "generated by SDoc" string is added. join "\n", grep /^\s*[^A-Z| ]/, split /\n(?:\s*\n)+/, retrieve(@_); __ meta::function('sdocp', <<'__'); # Renders an attribute as SDocP. This logic was taken directly from the sdoc script. my $attribute = retrieve($_[0]); sub escape {my @results = map {s/\\/\\\\/go; s/\n/\\n/go; s/'/\\'/go; $_} @_; wantarray ? @results : $results[0]} "sdocp('" . escape($_[0]) . "', '" . escape($attribute) . "');"; __ meta::function('serialize', <<'__'); my ($options, @criteria) = separate_options(@_); delete $$options{'-P'}; my $partial = delete $$options{'-p'}; my $criteria = join '|', @criteria; my @attributes = map serialize_single($_), select_keys(%$options, '-m' => 1, '--criteria' => $criteria), select_keys(%$options, '-M' => 1, '--criteria' => $criteria); my @final_array = @{$partial ? \@attributes : [expanded_bootstrap(), @attributes, 'internal::main();', '', '__DATA__', $global_data]}; join "\n", @final_array; __ meta::function('serialize-single', <<'__'); # Serializes a single attribute and optimizes for content. my $name = $_[0] || $_; my $contents = retrieve_trimmed($name); my $meta_function = 'meta::' . namespace($name); my $invocation = attribute($name); if ($contents !~ /\v/) { $contents =~ s/\\/\\\\/go; $contents =~ s/'/\\'/go; return "$meta_function('$invocation', '$contents');"} my $delimiter = '__' . fast_hash($contents); my $chars = 2; ++$chars until $chars >= length($delimiter) || index("\n$contents", "\n" . substr($delimiter, 0, $chars)) == -1; $delimiter = substr($delimiter, 0, $chars); "$meta_function('$invocation', <<'$delimiter');\n$contents\n$delimiter"; __ meta::function('sh', <<'__'); around_hook('sh', @_, sub { system(@_)}); __ meta::function('shb', <<'__'); # Backgrounded shell. with_fork(@_, \&::sh); __ meta::function('shell', <<'__'); my ($options, @arguments) = separate_options(@_); $transient{repl_prefix} = $$options{'--repl-prefix'}; terminal::cc(retrieve('data::current-continuation')) if length $data{'data::current-continuation'}; around_hook('shell', sub {shell::repl(%$options)}); __ meta::function('size', <<'__'); my $size = 0; $size += length $data{$_} for keys %data; sprintf " full logical unique self\n% 7d % 7d % 7d % 7d", length(serialize()), $size, length(serialize('-up')), length $global_data; __ meta::function('snapshot', <<'__'); my ($name) = @_; file::write(my $finalname = temporary_name($name), serialize(), noclobber => 1); chmod 0700, $finalname; hook('snapshot', $finalname); __ meta::function('snapshot-if-necessary', 'snapshot() if state() ne $transient{initial};'); meta::function('state', <<'__'); my ($options, @attributes) = separate_options(@_); @attributes = grep !is($_, '-v'), sort keys %data unless @attributes; @attributes = grep is($_, '-iu'), @attributes if $$options{'-i'}; @attributes = grep is($_, '-P'), @attributes if $$options{'-P'}; my $hash = fast_hash(fast_hash(scalar @attributes) . join '|', @attributes); $hash = fast_hash(retrieve_trimmed($_) . "|$hash") for @attributes; $hash = fast_hash(join '|', $hash, grep s/^parent:://, sort keys %data) if $$options{'-P'}; $$options{'-G'} ? $hash : fast_hash("$global_data|$hash"); __ meta::function('t', <<'__'); my @tests = select_keys('--criteria' => "sdoc::js::test/(.*\/)?$_[0]"); edit($tests[0]); __ meta::function('todo-summary', <<'__'); my @todo_items = &{'parse-todo'}(@_); my %categories; my %category_completion; push @{$categories{$_->{category}} ||= []}, $_ for @todo_items; $category_completion{$_->{category}} += $_->{progress} for @todo_items; # Adjust to represent averages rather than totals $category_completion{$_} /= @{$categories{$_}} for keys %categories; join "\n\n", map sprintf("%s: %4.2f%%\n%s", $_, $category_completion{$_} * 100, join "\n", map sprintf("%-8s %s", "[$_->{progress_meter}]", $_->{details}), @{$categories{$_}}), sort keys %categories; __ meta::function('touch', 'associate($_, \'\') for @_;'); meta::function('unlock', 'hook(\'unlock\', chmod_self(sub {$_[0] | 0200}));'); meta::function('update', 'update_from(@_, grep s/^parent:://o, sort keys %data);'); meta::function('update-from', <<'__'); # Upgrade all attributes that aren't customized. Customization is defined when the data type is created, # and we determine it here by checking for $transient{inherit}{$type}. # Note that this assumes you trust the remote script. If you don't, then you shouldn't update from it. around_hook('update-from-invocation', separate_options(@_), sub { my ($options, @targets) = @_; my %parent_id_cache = cache('parent-identification'); my %parent_state_cache = cache('parent-state'); my %already_seen; @targets or return; my @known_targets = grep s/^parent:://, parent_ordering(map "parent::$_", grep exists $data{"parent::$_"}, @targets); my @unknown_targets = grep ! exists $data{"parent::$_"}, @targets; @targets = (@known_targets, @unknown_targets); my $save_state = $$options{'-s'} || $$options{'--save'}; my $no_state = $$options{'-S'} || $$options{'--no-state'}; my $no_verify = $$options{'-V'} || $$options{'--no-verify'}; my $no_parents = $$options{'-P'} || $$options{'--no-parent'} || $$options{'--no-parents'}; my $force = $$options{'-f'} || $$options{'--force'}; my $clobber_divergent = $$options{'-D'} || $$options{'--clobber-divergent'}; my $can_skip_already_seen = !($$options{'-K'} || $$options{'--no-skip'}) && !$force && !$clobber_divergent; save_state('before-update') unless $no_state; for my $target (@targets) { dangerous("updating from $target", sub { around_hook('update-from', $target, sub { my $target_filename = strip(qx(which $target)) || $target; my %parent_metadata = %{metadata_from($target_filename)}; terminal::warning("$target_filename has no externally visible metadata (makes updating slower)") unless $parent_metadata{id}; my $identity = $parent_id_cache{$target} ||= $parent_metadata{id} || join '', qx($target identity); next if $can_skip_already_seen and exists $data{"parent::$target"} and $already_seen{$identity} || $parent_state_cache{$identity} eq $parent_metadata{istate}; my $attributes = join '', qx($target ls -ahiu); my %divergent; die "skipping unreachable $target" unless $attributes; # These need to come after the reachability check so that we retry against # other copies in case something fails. ++$already_seen{$identity}; $parent_state_cache{$identity} = $parent_metadata{istate} || join '', qx($target state -iPG); for my $to_rm (split /\n/, retrieve("parent::$target")) { my ($name, $hash) = split(/\s+/, $to_rm); next unless exists $data{$name}; my $local_hash = fast_hash(retrieve($name)); if ($clobber_divergent or $hash eq $local_hash or ! defined $hash) {rm($name)} else {terminal::info("preserving local version of divergent attribute $name (use update -D to clobber it)"); $divergent{$name} = retrieve($name)}} associate("parent::$target", $attributes) unless $no_parents; dangerous('', sub {eval qx($target serialize -ipmu)}); dangerous('', sub {eval qx($target serialize -ipMu)}); map associate($_, $divergent{$_}), keys %divergent unless $clobber_divergent; reload()})})} cache('parent-identification', %parent_id_cache); cache('parent-state', %parent_state_cache); if ($no_verify) {hook('update-from-presumably-succeeded', $options, @targets); rm('state::before-update') unless $no_state || $save_state} elsif (verify()) {hook('update-from-succeeded', $options, @targets); terminal::info("Successfully updated. Run 'load-state before-update' to undo this change.") if $save_state; rm('state::before-update') unless $no_state || $save_state} elsif ($force || $no_state) {hook('update-from-failed', $options, @targets); terminal::warning('Failed to verify: at this point your object will not save properly, though backup copies will be created.', $no_state ? 'You should attempt to repair this object since no prior state was saved.' : 'Run "load-state before-update" to undo the update and return to a working state.')} else {hook('update-from-failed', $options, @targets); terminal::error('Verification failed after the upgrade was complete.'); terminal::info("$0 has been reverted to its pre-upgrade state.", "If you want to upgrade and keep the failure state, then run 'update-from $target --force'."); load_state('before-update'); rm('state::before-update')}}); __ meta::function('usage', '"Usage: $0 action [arguments]\\nUnique actions (run \'$0 ls\' to see all actions):" . ls(\'-u\');'); meta::function('verify', <<'__'); file::write(my $other = $transient{temporary_filename} = temporary_name(), my $serialized_data = serialize()); chomp(my $observed = join '', qx|perl '$other' state|); unlink $other if my $result = $observed eq (my $state = state()); terminal::error("Verification failed; expected $state but got $observed from $other") unless $result; hook('after-verify', $result, observed => $observed, expected => $state); $result; __ meta::function('vim', <<'__'); # Installs VIM highlighters. file::write("$ENV{'HOME'}/.vim/syntax/$_.vim", retrieve("vim_highlighter::$_")) for grep s/^vim_highlighter:://o, keys %data; __ meta::function('w', <<'__'); my @web = select_keys('--criteria' => "::web/(.*/)?$_[0]"); edit($web[0]); __ meta::function('waul', <<'__'); my ($name, %options) = @_; my $output = $options{output} || "$name.js"; my $extensions = $options{extensions} ? join(' ', map "--extension '$_'", split /\s+/, $options{extensions}) : ''; my $waul = retrieve($name) =~ m-^#!/usr/bin/env (\S+)- ? $1 : 'waul'; terminal::info("compiling $name using $waul ($extensions)"); with_exported($name, sub { my ($exported) = @_; sh("$waul --output '$output' $extensions $exported")}); __ meta::hook('before-save::bootstrap.html', 'build_bootstrap_html();'); meta::hook('before-save::remove-lwp-dependency', 'associate($_, \'# This function has been removed to eliminate dependency on LWP::Simple.\') for qw/function::dupdate retriever::http/;'); meta::hook('before-shell::ad', 'ad(\'sdoc::\');'); meta::indicator('cc', 'length ::retrieve(\'data::current-continuation\') ? "\\033[1;36mcc\\033[0;0m" : \'\';'); meta::indicator('locked', 'is_locked() ? "\\033[1;31mlocked\\033[0;0m" : \'\';'); meta::indicator('path', <<'__'); my @highlighted = map join("\033[1;30m|\033[0;0m", split /\|/, $_), @{$transient{path}}; join "\033[1;30m/\033[0;0m", @highlighted; __ meta::internal_function('around_hook', <<'__'); # around_hook('hookname', @args, sub { # stuff; # }); # Invokes 'before-hookname' on @args before the sub runs, invokes the # sub on @args, then invokes 'after-hookname' on @args afterwards. # The after-hook is not invoked if the sub calls 'die' or otherwise # unwinds the stack. my $hook = shift @_; my $f = pop @_; hook("before-$hook", @_); my @result = &$f(@_); hook("after-$hook", @_, @result); wantarray ? @result : $result[0]; __ meta::internal_function('associate', <<'__'); my ($name, $value, %options) = @_; die "Namespace does not exist" unless exists $datatypes{namespace($name)}; $data{$name} = $value; execute($name) if $options{execute}; $value; __ meta::internal_function('attribute', <<'__'); my ($name) = @_; $name =~ s/^[^:]*:://; $name; __ meta::internal_function('attribute_is', <<'__'); my ($a, %options) = @_; my %inherited = parent_attributes(grep /^parent::/o, sort keys %data) if grep exists $options{$_}, qw/-u -U -d -D/; my $criteria = $options{'--criteria'} || $options{'--namespace'} && "^$options{'--namespace'}::" || '.'; my %tests = ('-u' => sub {! $inherited{$a}}, '-d' => sub {$inherited{$a} && fast_hash(retrieve($a)) ne $inherited{$a}}, '-i' => sub {$transient{inherit}{namespace($a)}}, '-v' => sub {$transient{virtual}{namespace($a)}}, '-p' => sub {$a =~ /^parent::/o}, '-s' => sub {$a =~ /^state::/o}, '-m' => sub {$a =~ /^meta::/o}); return 0 unless scalar keys %tests == scalar grep ! exists $options{$_} || &{$tests{$_}}(), keys %tests; return 0 unless scalar keys %tests == scalar grep ! exists $options{uc $_} || ! &{$tests{$_}}(), keys %tests; $a =~ /$_/ || return 0 for @{$options{'--path'}}; $a =~ /$criteria/; __ meta::internal_function('bench', <<'__'); use Time::HiRes qw/gettimeofday tv_interval/; my ($f, $times) = @_; $times ||= 1; my $start_time = [gettimeofday]; &$f() for 1 .. $times; my $duration = tv_interval($start_time) / $times; terminal::info("$duration seconds elapsed"); __ meta::internal_function('cache', <<'__'); my ($name, %pairs) = @_; if (%pairs) {associate("cache::$name", join "\n", map {$pairs{$_} =~ s/\n//g; "$_ $pairs{$_}"} sort keys %pairs)} else {map split(/\s/, $_, 2), split /\n/, retrieve("cache::$name")} __ meta::internal_function('chmod_self', <<'__'); my ($mode_function) = @_; my (undef, undef, $mode) = stat $0; chmod &$mode_function($mode), $0; __ meta::internal_function('dangerous', <<'__'); # Wraps a computation that may produce an error. my ($message, $computation) = @_; terminal::info($message) if $message; my @result = eval {&$computation()}; terminal::warning(translate_backtrace($@)), return undef if $@; wantarray ? @result : $result[0]; __ meta::internal_function('debug_trace', <<'__'); terminal::debug(join ', ', @_); wantarray ? @_ : $_[0]; __ meta::internal_function('execute', <<'__'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{$datatypes{$namespace}}(attribute($name), retrieve($name))}; warn $@ if $@ && $options{'carp'}; __ meta::internal_function('exported', <<'__'); # Allocates a temporary file containing the concatenation of attributes you specify, # and returns the filename. The filename will be safe for deletion anytime. my $filename = temporary_name(); file::write($filename, cat(@_)); $filename; __ meta::internal_function('extension_for', <<'__'); my $extension = $transient{extension}{namespace($_[0])}; $extension = &$extension($_[0]) if ref $extension eq 'CODE'; $extension || ''; __ meta::internal_function('fast_hash', <<'__'); my ($data) = @_; my $piece_size = length($data) >> 3; my @pieces = (substr($data, $piece_size * 8) . length($data), map(substr($data, $piece_size * $_, $piece_size), 0 .. 7)); my @hashes = (fnv_hash($pieces[0])); push @hashes, fnv_hash($pieces[$_ + 1] . $hashes[$_]) for 0 .. 7; $hashes[$_] ^= $hashes[$_ + 4] >> 16 | ($hashes[$_ + 4] & 0xffff) << 16 for 0 .. 3; $hashes[0] ^= $hashes[8]; sprintf '%08x' x 4, @hashes[0 .. 3]; __ meta::internal_function('file::read', <<'__'); my $name = shift; open my($handle), "<", $name; my $result = join "", <$handle>; close $handle; $result; __ meta::internal_function('file::write', <<'__'); use File::Path 'mkpath'; use File::Basename 'dirname'; my ($name, $contents, %options) = @_; die "Choosing not to overwrite file $name" if $options{noclobber} and -f $name; mkpath(dirname($name)) if $options{mkpath}; my $open_name = $name =~ /^[>|]/ ? $name : $options{append} ? ">> $name" : "> $name"; open my($handle), $open_name or die "Can't open $name for writing"; print $handle $contents; close $handle; __ meta::internal_function('fnv_hash', <<'__'); # A rough approximation to the Fowler-No Voll hash. It's been 32-bit vectorized # for efficiency, which may compromise its effectiveness for short strings. my ($data) = @_; my ($fnv_prime, $fnv_offset) = (16777619, 2166136261); my $hash = $fnv_offset; my $modulus = 2 ** 32; $hash = ($hash ^ ($_ & 0xffff) ^ ($_ >> 16)) * $fnv_prime % $modulus for unpack 'L*', $data . substr($data, -4) x 8; $hash; __ meta::internal_function('hypothetically', <<'__'); # Allows you to make changes to the data without committing them. # Usage: # # hypothetically(sub { # ... # }); # # Changes to %data made inside the sub {} are discarded. my %data_backup = %data; my ($side_effect) = @_; my @result = eval {&$side_effect()}; %data = %data_backup; die $@ if $@; wantarray ? @result : $result[0]; __ meta::internal_function('internal::main', <<'__'); disable(); $SIG{'INT'} = sub {snapshot_if_necessary(); exit 1}; $transient{initial} = state(); chomp(my $default_action = retrieve('data::default-action')); my $function_name = shift(@ARGV) || $default_action; my @effective_argv = @ARGV; unshift @effective_argv, $function_name and $function_name = 'method_missing' unless exists $externalized_functions{$function_name}; around_hook('main-function', $function_name, @effective_argv, sub { dangerous('', sub { chomp(my @result = &$function_name(@effective_argv)); print join("\n", @result), "\n" if @result})}); save() unless state() eq $transient{initial}; END {enable()} __ meta::internal_function('invoke_editor_on', <<'__'); my ($data, %options) = @_; my $editor = $options{editor} || $ENV{VISUAL} || $ENV{EDITOR} || die 'Either the $VISUAL or $EDITOR environment variable should be set to a valid editor'; my $options = $options{options} || $ENV{VISUAL_OPTS} || $ENV{EDITOR_OPTS} || ''; my $attribute = $options{attribute}; $attribute =~ s/\//-/g; my $filename = temporary_name() . "-$attribute$options{extension}"; file::write($filename, $data); system("$editor $options '$filename'"); my $result = file::read($filename); unlink $filename; $result; __ meta::internal_function('is_locked', '!((stat($0))[2] & 0222);'); meta::internal_function('namespace', <<'__'); my ($name) = @_; $name =~ s/::.*$//; $name; __ meta::internal_function('parent_attributes', <<'__'); my $attributes = sub {my ($name, $value) = split /\s+/o, $_; $name => ($value || 1)}; map &$attributes(), split /\n/o, join("\n", retrieve(@_)); __ meta::internal_function('parent_ordering', <<'__'); # Topsorts the parents by dependency chain. The simplest way to do this is to # transitively compute the number of parents referred to by each parent. my @parents = @_; my %all_parents = map {$_ => 1} @parents; my %parents_of = map { my $t = $_; my %attributes = parent_attributes($_); $t => [grep /^parent::/, keys %attributes]} @parents; my %parent_count; my $parent_count; $parent_count = sub { my ($key) = @_; return $parent_count{$key} if exists $parent_count{$key}; my $count = 0; $count += $parent_count->($_) + exists $data{$_} for @{$parents_of{$key}}; $parent_count{$key} = $count}; my %inverses; push @{$inverses{$parent_count->($_)} ||= []}, $_ for @parents; grep exists $all_parents{$_}, map @{$inverses{$_}}, sort keys %inverses; __ meta::internal_function('retrieve', <<'__'); my @results = map defined $data{$_} ? $data{$_} : retrieve_with_hooks($_), @_; wantarray ? @results : $results[0]; __ meta::internal_function('retrieve_trimmed', <<'__'); return strip(retrieve($_[0])) if $transient{trim}{namespace($_[0])}; retrieve($_[0]); __ meta::internal_function('retrieve_with_hooks', <<'__'); # Uses the hooks defined in $transient{retrievers}, and returns undef if none work. my ($attribute) = @_; my $result = undef; defined($result = &$_($attribute)) and return $result for map $transient{retrievers}{$_}, sort keys %{$transient{retrievers}}; return undef; __ meta::internal_function('select_keys', <<'__'); my %options = @_; grep attribute_is($_, %options), sort keys %data; __ meta::internal_function('separate_options', <<'__'); # Things with one dash are short-form options, two dashes are long-form. # Characters after short-form are combined; so -auv4 becomes -a -u -v -4. # Also finds equivalences; so --foo=bar separates into $$options{'--foo'} eq 'bar'. # Stops processing at the -- option, and removes it. Everything after that # is considered to be an 'other' argument. # The only form not supported by this function is the short-form with argument. # To pass keyed arguments, you need to use long-form options. my @parseable; push @parseable, shift @_ until ! @_ or $_[0] eq '--'; my @singles = grep /^-[^-]/, @parseable; my @longs = grep /^--/, @parseable; my @others = grep ! /^-/, @parseable; my @singles = map /-(.{2,})/ ? map("-$_", split(//, $1)) : $_, @singles; my %options; /^([^=]+)=(.*)$/ and $options{$1} = $2 for @longs; ++$options{$_} for grep ! /=/, @singles, @longs; ({%options}, grep length, @others, @_); __ meta::internal_function('strip', 'wantarray ? map {s/^\\s*|\\s*$//sgo; $_} @_ : $_[0] =~ /^\\s*(.*?)\\s*$/so && $1;'); meta::internal_function('table_display', <<'__'); # Displays an array of arrays as a table; that is, with alignment. Arrays are # expected to be in column-major order. sub maximum_length_in { my $maximum = 0; length > $maximum and $maximum = length for @_; $maximum; } my @arrays = @_; my @lengths = map maximum_length_in(@$_), @arrays; my @row_major = map {my $i = $_; [map $$_[$i], @arrays]} 0 .. $#{$arrays[0]}; my $format = join ' ', map "%-${_}s", @lengths; join "\n", map strip(sprintf($format, @$_)), @row_major; __ meta::internal_function('temporary_name', <<'__'); use File::Temp 'tempfile'; my (undef, $temporary_filename) = tempfile("$0." . 'X' x 4, OPEN => 0); $temporary_filename; __ meta::internal_function('translate_backtrace', <<'__'); my ($trace) = @_; $trace =~ s/\(eval (\d+)\)/$locations{$1 - 1}/g; $trace; __ meta::internal_function('with_cwd', <<'__'); my ($dir, $f) = @_; my $cwd = cwd(); my @result = eval {chdir $dir && &$f()}; chdir $cwd; die $@ if $@; wantarray ? @result : $result[0]; __ meta::internal_function('with_exported', <<'__'); # Like exported(), but removes the file after running some function. # Usage is with_exported(@files, sub {...}); my $f = pop @_; my $name = exported(@_); my $result = eval {&$f($name)}; terminal::warning("$@ when running with_exported()") if $@; unlink $name; $result; __ meta::internal_function('with_fork', <<'__'); my (@args) = @_; my $f = pop @args; return process->new($child_pid) if my $child_pid = fork; # This is the child process. Disable saving to prevent contention, and then # exit with the given status code. *::save = sub {}; exit &$f(@args); __ meta::library('process', <<'__'); package process; sub new {my ($class, $pid) = @_; bless \$pid, $class} sub kill {my ($self, $signal) = @_; ::kill $signal // 'KILL', $$self; $self->wait()} sub term {my ($self) = @_; $self->kill('TERM')} sub int {my ($self) = @_; $self->kill('INT')} sub stop {my ($self) = @_; $self->kill('STOP')} sub cont {my ($self) = @_; $self->kill('CONT')} sub wait {my ($self) = @_; ::wait($$self)} __ meta::library('shell', <<'__'); # Functions for shell parsing and execution. package shell; use Term::ReadLine; sub tokenize {grep length, split /\s+|("[^"\\]*(?:\\.)?")/o, join ' ', @_}; sub parse { my ($fn, @args) = @_; s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args; {function => $fn, args => [@args]}} sub execute { my %command = %{$_[0]}; die "undefined command: $command{function}" unless exists $externalized_functions{$command{function}}; &{"::$command{function}"}(@{$command{args}})} sub run {execute(parse(tokenize(@_)))} sub prompt { my %options = @_; my $name = $options{name} // ::name(); my $indicators = join '', map &{"::$_"}(), ::select_keys('--namespace' => 'indicator'); my $prefix = $transient{repl_prefix} // ''; "$prefix\033[1;32m$name\033[0;0m$indicators "} sub repl { my %options = @_; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $attribs = $term->Attribs; $attribs->{completion_entry_function} = $attribs->{list_completion_function}; my $autocomplete = $options{autocomplete} || sub {[sort(keys %data), grep !/-/, sort keys %externalized_functions]}; my $prompt = $options{prompt} || \&prompt; my $parse = $options{parse} || sub {parse(tokenize(@_))}; my $output = $options{output} || sub {print join("\n", @_), "\n"}; my $command = $options{command} || sub {my ($command) = @_; ::around_hook('shell-command', $command, sub {&$output(::dangerous('', sub {execute($command)}))})}; length $_ && &$command(&$parse($_)) while ($attribs->{completion_word} = &$autocomplete(), defined($_ = $term->readline(&$prompt())))} __ meta::library('terminal', <<'__'); # Functions for nice-looking terminal output. package terminal; my $process = ::name(); sub message {print STDERR "[$_[0]] $_[1]\n"} sub color { my ($name, $color) = @_; *{"terminal::$name"} = sub {chomp($_), print STDERR "\033[1;30m$process(\033[1;${color}m$name\033[1;30m)\033[0;0m $_\n" for map join('', $_), @_}} my %preloaded = (info => 32, progress => 32, state => 34, debug => 34, warning => 33, error => 31); color $_, $preloaded{$_} for keys %preloaded; __ meta::message_color('cc', '36'); meta::message_color('state', 'purple'); meta::message_color('states', 'yellow'); meta::message_color('watch', 'blue'); meta::parent('../waul-object', <<'__'); function::minify-yui 68564f46e60e2c77881e62cbb3a150fa function::rmparent 49051d669554867f87c08656380a8aba function::waul e1162e71693317dd52b5a30cac0a881a meta::type::waul 869b5820cd79178b94c3ccdd47dff9df parent::/home/spencertipping/conjectures/perl-objects/sdoc 8088a29685f29420fd00607006958087 parent::preprocessor 7172206e753310217b5f67ac8366c8bf __ meta::parent('/home/spencertipping/bin/configuration', <<'__'); meta::type::configuration 7f5ba514d47ac29a3c226d0e331d9da4 parent::/home/spencertipping/bin/object 208e4b12afe64a14bfd8cacf04181e14 __ meta::parent('/home/spencertipping/bin/object', <<'__'); bootstrap::html f44dd03cb0c904b3a5f69fbda5f018d0 bootstrap::initialization d22fafa2938ecb0d4728e2958b54ed3d bootstrap::perldoc 5793df44bdd2526bb461272924abfd4b function::ad 9220b9dc131f8f79878a6209adfe8ef2 function::alias 8eeeeb4e064ef3aba7edf8f254427bc2 function::cat f684de6c8776617a437b76009114f52e function::cc 12ea9176e388400704d823433c209b7a function::ccc d151a9793edd83f80fb880b7f0ab9b34 function::child f5764adf0b4e892f147a9b6b68d4816f function::clone bb42e04e10a8e54e88786b6fbc4fb213 function::cp 3fe69d1b58d90045ad520048977538c4 function::create d65deb895a848b32d3d7ed92e81e8cb0 function::current-state 6f03f86f1901e9ef07fdb5d4079a914c function::cwd fd9b58e76c474a8fe93dc3abdccb8857 function::disable 53b449708cc2ffdefa352e53bb7d847d function::edit 735dbdece3998a4478bec3b7bdc04f03 function::edit-self 71790df00f941ed9b56e17f789b93871 function::enable 7de1cedc36841f5de8f9fdfbc3b65097 function::export 2374cd1dbf7616cb38cafba4e171075d function::extern 1290a5223e2824763eecfb3a54961eff function::grep 55c3cea8ff4ec2403be2a9d948e59f14 function::hash 6ee131d093e95b80039b4df9c7c84a02 function::hook 675cdb98b5dd8567bdd5a02ead6184b5 function::hooks 3d989899c616f7440429a2d9bf1cc44b function::identity 6523885762fcc2f354fc25cf6ed126ce function::import 5d0f0634cbd01274f2237717507198a2 function::initial-state 03d8ed608855a723124e79ca184d8e73 function::is 41564c8f21b12ab80824ac825266d805 function::load-state b6cf278a1f351f316fa6e070359b6081 function::lock 5d8db258704e6a8623fac796f62fac02 function::ls 01a23d51d5b529e03943bd57e33f92df function::mv ccd000960db4cf627d9246c43d87ba4c function::name 955ba2d1fe1d67cd78651a4042283b00 function::parents 3da9e63b5aae9e2f5dcc946a86d166aa function::perl 9f9fd744f0ed225ad8fb3b79fa53dd9a function::rd 2adb16d7e819d2e87a27201744a581e7 function::reload 1589f4cf8374e0011991cb8907afca3e function::rm 6f6fd7a6c25558eb469d78ea888f8551 function::rmparent fc2884910a6939a47898a778f277332c function::save 3cbe5c3735ee4ff99cb60ccf68bc8b91 function::save-state 5af59ebc4ad8965767e4dc106d3b557e function::serialize a97c7391c635a93b0a710d10eb8cb3cd function::serialize-single 8bac97e94a1162947d274421053387b0 function::sh 1b2f542ca9dd63ad437058b7f6f61aac function::shb 21139548efb79500d9c999dba024ab32 function::shell a87f389b94713e5855e62241d649d01d function::size 69f6ab4a100c6ef05d4d41510004d645 function::snapshot 56939a47f2758421669641e15ebd66eb function::state 88bc24e732c55aac68a103eab96b9e48 function::touch 3991b1b7c7187566f50e5e58ce01fa06 function::unlock b4aac02f7f3fb700acf4acfd9b180ceb function::update ac391dc90e507e7586c81850e7c2ecdd function::update-from 06fef658374d482adb2e62fbeed9efb4 function::usage 5bdd370f5a56cfbf199e08d398091444 function::verify 0c0cc1dfeab7d705919df122f7850a4f indicator::cc 3db7509c521ee6abfedd33d5f0148ed3 indicator::locked fc2b4f4ca0d6a334b9ac423d06c8f18c indicator::path b5e2cb524caa0283f713a0ddf9f4c162 internal_function::around_hook 7cc876e7c5f78c34654337fc95255587 internal_function::associate 55f202ffdbc6b9005e53d3e82f5f9bfe internal_function::attribute dd6f010f9688977464783f60f5b6d3dd internal_function::attribute_is d28ac825b3937029386372c560a65775 internal_function::cache eb9da45580a9ac0882baf98acd2ecd60 internal_function::chmod_self 2035e861eedab55ba0a9f6f5a068ca70 internal_function::dangerous 46c4baaa214ab3d05af43e28083d5141 internal_function::debug_trace 0faf9d9f4159d72dfe4481f6f3607ce1 internal_function::execute f0924e087d978ff2ab1e117124db3042 internal_function::exported ae35afef7d4762f2818aee5872c75be0 internal_function::extension_for 9de8261d69cc93e9b92072b89c89befd internal_function::fast_hash ee5eba48f837fda0fe472645fdd8899a internal_function::file::read e647752332c8e05e81646a3ff98f9a8e internal_function::file::write 460e2343283eb9fd0e1815389f4e07e6 internal_function::fnv_hash c36d56f1e13a60ae427afc43ba025afc internal_function::hypothetically b83e3f894a6df8623ccd370515dfd976 internal_function::internal::main 12c18f86d766ffb67b8dc0572985bf98 internal_function::invoke_editor_on 5eb976796f0ec172d6ec036116a2f41e internal_function::is_locked da12ced6aa38295251f7e748ffd22925 internal_function::namespace 784d2e96003550681a4ae02b8d6d0a27 internal_function::parent_attributes f6ccfaa982ab1a4d066043981aaca277 internal_function::parent_ordering 57b6da88f76b59f3fed9abfa61280e5e internal_function::retrieve 721a6800f328da05047fd7392758f55d internal_function::retrieve_with_hooks 0f1b0220ccd973d57a2e96ff00458cf2 internal_function::select_keys a5e3532ec6d58151d0ee24416ea1e2b5 internal_function::separate_options 34ec41a6edaa15adde607a0db3ccfa36 internal_function::strip 14f490b10ebd519e829d8ae20ea4d536 internal_function::table_display d575f4dc873b2e0be5bd7352047fd904 internal_function::temporary_name 6f548d101fc68356515ffd0fc9ae0c93 internal_function::translate_backtrace d77a56d608473b3cd8a3c6cb84185e10 internal_function::with_cwd 928bae9caa3cb212f0c5c977ba55166c internal_function::with_exported df345d5095d5ed13328ddd07ea922b36 internal_function::with_fork b252464d1efd12dc4a5ee6890076b6a9 library::process 9fe4e2320eae9f6661a6eb6e777fb6ca library::shell f561500cf223df1bf6daf43af93577a5 library::terminal 7e2d045782405934a9614fe04bcfe559 message_color::cc 2218ef0f7425de5c717762ffb100eb43 message_color::state 03621cd6ac0b1a40d703f41e26c5807f message_color::states ac66eeeff487b5f43f88a78ea18b3d56 meta::configure 69c2e727c124521d074fde21f8bbc4db meta::externalize aa44e27e0bbee6f0ca4de25d603a1fc7 meta::functor::editable 48246c608f363de66511400e00b26164 meta::type::alias 889d26d2df385e9ff8e2da7de4e48374 meta::type::bootstrap 51108ab2ddb8d966e927c8f62d9ef3e5 meta::type::cache 9267171f2eace476f64a1a670eaaf2c7 meta::type::data 120e1649a468d3b3fd3fb783b4168499 meta::type::function 8ea626198861dc59dd7f303eecb5ff88 meta::type::hook ff92aef328b6bdc6f87ddd0821f3e42f meta::type::inc 78e0375b6725487cb1f0deca41e96bbe meta::type::indicator feb54a2624e6983617685047c717427f meta::type::internal_function eff3cf31e2635f51c83836f116c99d2f meta::type::library 7622e8d65e03066668bade74715d65ad meta::type::message_color 557a1b44979cbf77a7251fbdc4c5b82c meta::type::meta c6250056816b58a9608dd1b2614246f8 meta::type::parent 09d1d03379e4e0b262e06939f4e00464 meta::type::retriever 71a29050bf9f20f6c71afddff83addc9 meta::type::state 84da7d5220471307f1f990c5057d3319 retriever::file 3bbc9d8a887a536044bafff1d54def7e retriever::global 4fe8df0cca548075169968772843a156 retriever::id 4da6080168d32445150cc4200af7af6e retriever::object c7633990b4e01bdc783da7e545799f4f retriever::perl f41938e6dbad317f62abffc1e4d28cca __ meta::parent('/home/spencertipping/conjectures/perl-objects/js', <<'__'); meta::type::js e292996c992f4bc110e9577266c94784 parent::object 24eb27a5802fad8187b7a07b00297e21 __ meta::parent('/home/spencertipping/conjectures/perl-objects/object', <<'__'); bootstrap::html 66b779578ac8b63db9b910931f5435f6 bootstrap::initialization 03d3fde57935dfda65c369bf02e750e9 bootstrap::perldoc b27489e0e570796019f591b08c98c43a function::ad bcb317074915a656129f5355604bc152 function::alias 719b3df46720217d69911974c6bdc50f function::cat f684de6c8776617a437b76009114f52e function::cc 12ea9176e388400704d823433c209b7a function::ccc d151a9793edd83f80fb880b7f0ab9b34 function::child e5acd8aeb16262c3f2a52f6f64344d30 function::clone e30a067adfa6fee67abe8d7afd8b1527 function::cp 3fe69d1b58d90045ad520048977538c4 function::create 3010d55f4dfa59a998742e07823ed54d function::current-state 6f03f86f1901e9ef07fdb5d4079a914c function::cwd 12833315b8bea35754ae9dabe45dfde8 function::disable 53b449708cc2ffdefa352e53bb7d847d function::edit 20bb4117e5662906ad661a5f19e007d0 function::edit-self 08e8695218a2d01fc608e3ad066ba15a function::enable 2c08b091a96f80277dd2619c27221f48 function::expanded-bootstrap 46d8e0b43646d3c32de0057077da607c function::export 2374cd1dbf7616cb38cafba4e171075d function::extern 1290a5223e2824763eecfb3a54961eff function::grep 55c3cea8ff4ec2403be2a9d948e59f14 function::hash 6ee131d093e95b80039b4df9c7c84a02 function::hook 675cdb98b5dd8567bdd5a02ead6184b5 function::hooks 3d989899c616f7440429a2d9bf1cc44b function::identity beb93f8ea57fcc8454661f9dd057645f function::import 114e09cf19f50ad92e268bb4227e145f function::initial-state 03d8ed608855a723124e79ca184d8e73 function::is e9103d343e6ef7142e3c537478158dc1 function::load-state 970fba79b585e5638914a423bbeecf12 function::lock 5d8db258704e6a8623fac796f62fac02 function::ls b2d40715eddbc2c0da299b54a0b53523 function::metadata-from b4f02937a7c80157e468bd9dbfcaf975 function::mv 8b9d7817efbe38dff95aa99f3b51f32e function::name 955ba2d1fe1d67cd78651a4042283b00 function::parents 3da9e63b5aae9e2f5dcc946a86d166aa function::perl 6d3994ec69c238535f4daed6658537fd function::rd c41059d11d888922cfc5cbae92495632 function::reload 1589f4cf8374e0011991cb8907afca3e function::rm 6f6fd7a6c25558eb469d78ea888f8551 function::rmparent e33a3a1e4f8f4156850d695fdfa7cf0d function::save 31f22a1bb7250567f236b6eab8e661b5 function::save-state fe99cbdb228608102ab6d55373146118 function::serialize 133b52d4a95bee8ec397cffa25adfb30 function::serialize-single 0e1be51cdc0c8bcd94105a65c002e83f function::sh b3bcea16a756d15b5de588a90198a0c2 function::shb 9ee9d251ac3667f69e43d71d1073de01 function::shell e56eec74bb95a5d3cea974fd66acb433 function::size 593810a7ce46e5a42584991b9c30d94c function::snapshot 56939a47f2758421669641e15ebd66eb function::snapshot-if-necessary 68123e0e46509d9e56073683e1e023eb function::state 49b9310d43ef765345f080a23b929f15 function::touch 3991b1b7c7187566f50e5e58ce01fa06 function::unlock b4aac02f7f3fb700acf4acfd9b180ceb function::update f98ac65a795279cdbe67d6f3a5a05e42 function::update-from 5d73d698fc55121c98bb6d7d3623ca53 function::usage 5bdd370f5a56cfbf199e08d398091444 function::verify 0c0cc1dfeab7d705919df122f7850a4f indicator::cc 3db7509c521ee6abfedd33d5f0148ed3 indicator::locked fc2b4f4ca0d6a334b9ac423d06c8f18c indicator::path f7fc4b6dbc761995d991697a39e3e5ea internal_function::around_hook 091ece60ac5ec692e49d95b447d070c1 internal_function::associate 0ac272611ee95f9cef89fc8676886abc internal_function::attribute dd6f010f9688977464783f60f5b6d3dd internal_function::attribute_is e93028037a8d673aa4a1abfe1b70cccb internal_function::cache eb9da45580a9ac0882baf98acd2ecd60 internal_function::chmod_self 2035e861eedab55ba0a9f6f5a068ca70 internal_function::dangerous 46c4baaa214ab3d05af43e28083d5141 internal_function::debug_trace 0faf9d9f4159d72dfe4481f6f3607ce1 internal_function::execute 560da5928d1d8858f3a64011ab3a8603 internal_function::exported 3ec48f01deefa840b52111f2e3f34749 internal_function::extension_for 9de8261d69cc93e9b92072b89c89befd internal_function::fast_hash ee5eba48f837fda0fe472645fdd8899a internal_function::file::read e647752332c8e05e81646a3ff98f9a8e internal_function::file::write 0722e9f7e852269465e8e92d58ea8ffb internal_function::fnv_hash c36d56f1e13a60ae427afc43ba025afc internal_function::hypothetically 417ae217d0caa9c09f470b7c27703788 internal_function::internal::main c2410390ae10df76d89355c2a915aba5 internal_function::invoke_editor_on 5eb976796f0ec172d6ec036116a2f41e internal_function::is_locked da12ced6aa38295251f7e748ffd22925 internal_function::namespace 784d2e96003550681a4ae02b8d6d0a27 internal_function::parent_attributes f6ccfaa982ab1a4d066043981aaca277 internal_function::parent_ordering 57b6da88f76b59f3fed9abfa61280e5e internal_function::retrieve 8a34d1fe047fe1b40c3d2957c4a789eb internal_function::retrieve_trimmed ec9b2139757974d1e3149dd0f5acc393 internal_function::retrieve_with_hooks 0f1b0220ccd973d57a2e96ff00458cf2 internal_function::select_keys a5e3532ec6d58151d0ee24416ea1e2b5 internal_function::separate_options ae9043c4790e79013f1481b05f76d5df internal_function::strip 7169402db5f2f19e6b2411c30f2006e1 internal_function::table_display d575f4dc873b2e0be5bd7352047fd904 internal_function::temporary_name 6f548d101fc68356515ffd0fc9ae0c93 internal_function::translate_backtrace d77a56d608473b3cd8a3c6cb84185e10 internal_function::with_cwd d65eefc43140868a62d189f290da4b14 internal_function::with_exported df345d5095d5ed13328ddd07ea922b36 internal_function::with_fork d0963ac0472c5859c31e3177b37a90e8 library::process 9cb64aa7459a61081116ffe86da21e44 library::shell 6659bbab1051efd46b4a1a713edf6b28 library::terminal 7e2d045782405934a9614fe04bcfe559 message_color::cc 2218ef0f7425de5c717762ffb100eb43 message_color::state 03621cd6ac0b1a40d703f41e26c5807f message_color::states ac66eeeff487b5f43f88a78ea18b3d56 meta::configure 69c2e727c124521d074fde21f8bbc4db meta::externalize 1a1f89e195408cd3911a898bf15b9f0b meta::functor::editable 48246c608f363de66511400e00b26164 meta::type::alias 763587003448d1b7ddb2e287a52907d5 meta::type::bootstrap 3baa75727784ea678d9dc1a6e1fb25f7 meta::type::cache 2f093a1c4b0babc3a6678717f200d234 meta::type::data 120e1649a468d3b3fd3fb783b4168499 meta::type::function a7eacad143e6353d4d3550d977330ffc meta::type::hook 49c580ff8ab284e2b7e288f350a50ad9 meta::type::inc d151f7a09366d906cf06de3dc109a2d2 meta::type::indicator ad120d47ee3650141c9c8da6c349ba8c meta::type::internal_function 610adc3ec948ab7cc38662891a078d88 meta::type::library 7a26afd979d35ceac7b775764f569780 meta::type::message_color 4a162012064f1dc472df1f9d8ff632f5 meta::type::meta c4a2b36b99de12d647828c12bdbea408 meta::type::parent 28f7345e1438d6b8ef0e0dc7e6640910 meta::type::retriever a130f44d2fdac5cfc62baa07578c39ab meta::type::state f4e27d3dc999ae7f7f530677c823554b retriever::file 3bbc9d8a887a536044bafff1d54def7e retriever::global 73c667c53b5f4422d215392129f3ed18 retriever::id 4da6080168d32445150cc4200af7af6e retriever::object d70d593ada2f108b2909a9fc24c4f63e retriever::perl 58a2d30d05c22bf6b4461d93ae46f361 __ meta::parent('/home/spencertipping/conjectures/perl-objects/preprocessor', <<'__'); function::preprocess ab5526a02ff417d4c162357dc327e7c4 meta::type::template bc4b0c80b5efc716b19e99b832c22bf3 parent::object 24eb27a5802fad8187b7a07b00297e21 retriever::pp 3b5f5c5d30c5a04f72056dedaacfe7b7 template::comment dfe273d2dad3d8159b847545e4e5c309 template::def 0575aedbacf922d77f3419b817b7d579 template::eval 1a0e2124a05056be4abc11803883c294 template::failing_conditional e3a4523110dd859e828f342185de7c62 template::include 47b5552d609d97fe7f2522d5c1027014 template::pinclude c07ff79bf8d642cceaa9ef844bfcb189 __ meta::parent('/home/spencertipping/conjectures/perl-objects/sdoc', <<'__'); function::sdoc 4aac070122ecdc43b5a9d09baab005e6 function::sdoc-html 1bacf037ce7cad1b6339120e2eeab87f function::sdoc-markdown 885b3a873eff28ebe2d9f41c88c62fdd function::sdoc-packed 77ed0b1b297675176ba12fe8820284ee function::sdocp c3d738d982ba87418a298ff58478a85b meta::type::sdoc 22cd7315641d38c9d536344e83c36bed meta::type::slibrary 11af20c9992afa7ecaa370b2694823de parent::object 24eb27a5802fad8187b7a07b00297e21 retriever::code-sdoc 0096df7bf3836f5834205298823ef0f3 retriever::html-sdoc 05a3a1c747d4690741e663d34417bd32 retriever::markdown-sdoc f5aa8c4df39f34a58d75439c926e48c1 retriever::sdoc aea0d1024f5bb8c9a0cb10fb4fdf279a retriever::sdoc-packed 99b9ce123e95f677800585d5deae545d retriever::sdocp bb733b4bf092d4405d24b509d52eb193 __ meta::parent('html', <<'__'); meta::functor::html-templates 3c262e08cada91dcd486e0ad4b00614e parent::preprocessor 08006c9536bfe82d4b9b8bd57832eafa template::script-include 76be051ad116449ddebd10e7c3729afd template::style-include 8e5a06b70e1b00379765f319bf6c8066 __ meta::parent('object', <<'__'); bootstrap::html 66b779578ac8b63db9b910931f5435f6 bootstrap::initialization 03d3fde57935dfda65c369bf02e750e9 bootstrap::perldoc b27489e0e570796019f591b08c98c43a function::ad bcb317074915a656129f5355604bc152 function::alias 719b3df46720217d69911974c6bdc50f function::cat f684de6c8776617a437b76009114f52e function::cc 12ea9176e388400704d823433c209b7a function::ccc d151a9793edd83f80fb880b7f0ab9b34 function::child e5acd8aeb16262c3f2a52f6f64344d30 function::clone e30a067adfa6fee67abe8d7afd8b1527 function::cp 3fe69d1b58d90045ad520048977538c4 function::create 3010d55f4dfa59a998742e07823ed54d function::current-state 6f03f86f1901e9ef07fdb5d4079a914c function::cwd 12833315b8bea35754ae9dabe45dfde8 function::disable 53b449708cc2ffdefa352e53bb7d847d function::edit 20bb4117e5662906ad661a5f19e007d0 function::edit-self 08e8695218a2d01fc608e3ad066ba15a function::enable 2c08b091a96f80277dd2619c27221f48 function::expanded-bootstrap 46d8e0b43646d3c32de0057077da607c function::export 2374cd1dbf7616cb38cafba4e171075d function::extern 1290a5223e2824763eecfb3a54961eff function::grep 55c3cea8ff4ec2403be2a9d948e59f14 function::hash 6ee131d093e95b80039b4df9c7c84a02 function::hook 675cdb98b5dd8567bdd5a02ead6184b5 function::hooks 3d989899c616f7440429a2d9bf1cc44b function::identity beb93f8ea57fcc8454661f9dd057645f function::import 114e09cf19f50ad92e268bb4227e145f function::initial-state 03d8ed608855a723124e79ca184d8e73 function::is e9103d343e6ef7142e3c537478158dc1 function::load-state 970fba79b585e5638914a423bbeecf12 function::lock 5d8db258704e6a8623fac796f62fac02 function::ls b2d40715eddbc2c0da299b54a0b53523 function::metadata-from b4f02937a7c80157e468bd9dbfcaf975 function::mv 8b9d7817efbe38dff95aa99f3b51f32e function::name 955ba2d1fe1d67cd78651a4042283b00 function::parents 3da9e63b5aae9e2f5dcc946a86d166aa function::perl 6d3994ec69c238535f4daed6658537fd function::rd c41059d11d888922cfc5cbae92495632 function::reload 1589f4cf8374e0011991cb8907afca3e function::rm 6f6fd7a6c25558eb469d78ea888f8551 function::rmparent e33a3a1e4f8f4156850d695fdfa7cf0d function::save 31f22a1bb7250567f236b6eab8e661b5 function::save-state fe99cbdb228608102ab6d55373146118 function::serialize 133b52d4a95bee8ec397cffa25adfb30 function::serialize-single 0e1be51cdc0c8bcd94105a65c002e83f function::sh b3bcea16a756d15b5de588a90198a0c2 function::shb 9ee9d251ac3667f69e43d71d1073de01 function::shell e56eec74bb95a5d3cea974fd66acb433 function::size 593810a7ce46e5a42584991b9c30d94c function::snapshot 56939a47f2758421669641e15ebd66eb function::snapshot-if-necessary 68123e0e46509d9e56073683e1e023eb function::state 49b9310d43ef765345f080a23b929f15 function::touch 3991b1b7c7187566f50e5e58ce01fa06 function::unlock b4aac02f7f3fb700acf4acfd9b180ceb function::update f98ac65a795279cdbe67d6f3a5a05e42 function::update-from 5d73d698fc55121c98bb6d7d3623ca53 function::usage 5bdd370f5a56cfbf199e08d398091444 function::verify 0c0cc1dfeab7d705919df122f7850a4f indicator::cc 3db7509c521ee6abfedd33d5f0148ed3 indicator::locked fc2b4f4ca0d6a334b9ac423d06c8f18c indicator::path f7fc4b6dbc761995d991697a39e3e5ea internal_function::around_hook 091ece60ac5ec692e49d95b447d070c1 internal_function::associate 0ac272611ee95f9cef89fc8676886abc internal_function::attribute dd6f010f9688977464783f60f5b6d3dd internal_function::attribute_is e93028037a8d673aa4a1abfe1b70cccb internal_function::cache eb9da45580a9ac0882baf98acd2ecd60 internal_function::chmod_self 2035e861eedab55ba0a9f6f5a068ca70 internal_function::dangerous 46c4baaa214ab3d05af43e28083d5141 internal_function::debug_trace 0faf9d9f4159d72dfe4481f6f3607ce1 internal_function::execute 560da5928d1d8858f3a64011ab3a8603 internal_function::exported 3ec48f01deefa840b52111f2e3f34749 internal_function::extension_for 9de8261d69cc93e9b92072b89c89befd internal_function::fast_hash ee5eba48f837fda0fe472645fdd8899a internal_function::file::read e647752332c8e05e81646a3ff98f9a8e internal_function::file::write 0722e9f7e852269465e8e92d58ea8ffb internal_function::fnv_hash c36d56f1e13a60ae427afc43ba025afc internal_function::hypothetically 417ae217d0caa9c09f470b7c27703788 internal_function::internal::main c2410390ae10df76d89355c2a915aba5 internal_function::invoke_editor_on 5eb976796f0ec172d6ec036116a2f41e internal_function::is_locked da12ced6aa38295251f7e748ffd22925 internal_function::namespace 784d2e96003550681a4ae02b8d6d0a27 internal_function::parent_attributes f6ccfaa982ab1a4d066043981aaca277 internal_function::parent_ordering 57b6da88f76b59f3fed9abfa61280e5e internal_function::retrieve 8a34d1fe047fe1b40c3d2957c4a789eb internal_function::retrieve_trimmed ec9b2139757974d1e3149dd0f5acc393 internal_function::retrieve_with_hooks 0f1b0220ccd973d57a2e96ff00458cf2 internal_function::select_keys a5e3532ec6d58151d0ee24416ea1e2b5 internal_function::separate_options ae9043c4790e79013f1481b05f76d5df internal_function::strip 7169402db5f2f19e6b2411c30f2006e1 internal_function::table_display d575f4dc873b2e0be5bd7352047fd904 internal_function::temporary_name 6f548d101fc68356515ffd0fc9ae0c93 internal_function::translate_backtrace d77a56d608473b3cd8a3c6cb84185e10 internal_function::with_cwd d65eefc43140868a62d189f290da4b14 internal_function::with_exported df345d5095d5ed13328ddd07ea922b36 internal_function::with_fork d0963ac0472c5859c31e3177b37a90e8 library::process 9cb64aa7459a61081116ffe86da21e44 library::shell 6659bbab1051efd46b4a1a713edf6b28 library::terminal 7e2d045782405934a9614fe04bcfe559 message_color::cc 2218ef0f7425de5c717762ffb100eb43 message_color::state 03621cd6ac0b1a40d703f41e26c5807f message_color::states ac66eeeff487b5f43f88a78ea18b3d56 meta::configure 69c2e727c124521d074fde21f8bbc4db meta::externalize 1a1f89e195408cd3911a898bf15b9f0b meta::functor::editable 48246c608f363de66511400e00b26164 meta::type::alias 763587003448d1b7ddb2e287a52907d5 meta::type::bootstrap 3baa75727784ea678d9dc1a6e1fb25f7 meta::type::cache 2f093a1c4b0babc3a6678717f200d234 meta::type::data 120e1649a468d3b3fd3fb783b4168499 meta::type::function a7eacad143e6353d4d3550d977330ffc meta::type::hook 49c580ff8ab284e2b7e288f350a50ad9 meta::type::inc d151f7a09366d906cf06de3dc109a2d2 meta::type::indicator ad120d47ee3650141c9c8da6c349ba8c meta::type::internal_function 610adc3ec948ab7cc38662891a078d88 meta::type::library 7a26afd979d35ceac7b775764f569780 meta::type::message_color 4a162012064f1dc472df1f9d8ff632f5 meta::type::meta c4a2b36b99de12d647828c12bdbea408 meta::type::parent 28f7345e1438d6b8ef0e0dc7e6640910 meta::type::retriever a130f44d2fdac5cfc62baa07578c39ab meta::type::state f4e27d3dc999ae7f7f530677c823554b retriever::file 3bbc9d8a887a536044bafff1d54def7e retriever::global 73c667c53b5f4422d215392129f3ed18 retriever::id 4da6080168d32445150cc4200af7af6e retriever::object d70d593ada2f108b2909a9fc24c4f63e retriever::perl 58a2d30d05c22bf6b4461d93ae46f361 __ meta::parent('preprocessor', <<'__'); function::preprocess ab5526a02ff417d4c162357dc327e7c4 meta::type::template bc4b0c80b5efc716b19e99b832c22bf3 parent::object 4426dd6f4aebae284447047265132c82 retriever::pp 3b5f5c5d30c5a04f72056dedaacfe7b7 template::comment dfe273d2dad3d8159b847545e4e5c309 template::def 0575aedbacf922d77f3419b817b7d579 template::eval 1a0e2124a05056be4abc11803883c294 template::failing_conditional e3a4523110dd859e828f342185de7c62 template::include 47b5552d609d97fe7f2522d5c1027014 template::pinclude c07ff79bf8d642cceaa9ef844bfcb189 __ meta::parent('sdoc', <<'__'); function::sdoc 4aac070122ecdc43b5a9d09baab005e6 function::sdoc-html 1bacf037ce7cad1b6339120e2eeab87f function::sdoc-markdown 885b3a873eff28ebe2d9f41c88c62fdd function::sdoc-packed 77ed0b1b297675176ba12fe8820284ee function::sdocp c3d738d982ba87418a298ff58478a85b meta::type::sdoc 22cd7315641d38c9d536344e83c36bed meta::type::slibrary 11af20c9992afa7ecaa370b2694823de parent::object 24eb27a5802fad8187b7a07b00297e21 retriever::code-sdoc 0096df7bf3836f5834205298823ef0f3 retriever::html-sdoc 05a3a1c747d4690741e663d34417bd32 retriever::markdown-sdoc f5aa8c4df39f34a58d75439c926e48c1 retriever::sdoc aea0d1024f5bb8c9a0cb10fb4fdf279a retriever::sdoc-packed 99b9ce123e95f677800585d5deae545d retriever::sdocp bb733b4bf092d4405d24b509d52eb193 __ meta::parent('todo', <<'__'); function::parse-todo 8d86781e0f68dc8ca0a27fdc8cb9770b function::todo-summary 2bfbb14edc7e1200cb7592a80972d5f6 meta::type::todo 82cdf99cb03fb5ada05df8a27c472052 parent::sdoc 31aca176a7a8a7383fd172fc1c16944d parent::vim-highlighters ad6aeea7f2688cfd3f2cb956b76e7ec1 __ meta::parent('vim-highlighters', <<'__'); function::vim cf9e37026f6cd1499a6dd258fbbcd060 meta::type::vim_highlighter 38829105cbe400cb36d45357882c77fd parent::object 24eb27a5802fad8187b7a07b00297e21 __ meta::parent('waul-object', <<'__'); function::minify-uglify 37abe59669ec018b763b5820897c22a6 function::minify-yui f363deacbef70c5286ddac70b21f5f54 function::waul 518de6bb5fe2a0657416a445f57238b2 meta::type::waul 41f32bed465c74ebad13bc13905d1fb1 parent::/home/spencertipping/conjectures/perl-objects/js 01e2fe8433f9840c3aa3cdfc8bda5ca8 parent::/home/spencertipping/conjectures/perl-objects/sdoc 31aca176a7a8a7383fd172fc1c16944d parent::preprocessor 08006c9536bfe82d4b9b8bd57832eafa __ meta::retriever('code-sdoc', <<'__'); # Lets you specify the SDoc extension manually. For instance: # code.js::sdoc::foo causes sdoc::foo to be SDoc-rendered using Javascript comments. my ($name) = @_; return undef unless $name =~ s/^code\.(\w+)::// and defined retrieve($name); sdoc($name, $1); __ meta::retriever('crunch', <<'__'); # Crunches the whitespace out of stuff. return undef unless namespace($_[0]) eq 'crunch'; my $attr = retrieve(attribute($_[0])); defined $attr ? &{'crunch-whitespace'}($attr) : undef; __ meta::retriever('file', '-f $_[0] ? file::read($_[0]) : undef;'); meta::retriever('global', <<'__'); # Returns the global data stashed at the end of this perl object $_[0] eq 'self' ? $global_data : undef; __ meta::retriever('html-sdoc', <<'__'); my ($attribute) = @_; return undef unless $attribute =~ s/^html::/sdoc::/ and defined retrieve($attribute) || $attribute =~ s/^sdoc::// && defined retrieve($attribute); sdoc_html($attribute); __ meta::retriever('http', '# This function has been removed to eliminate dependency on LWP::Simple.'); meta::retriever('id', '$_[0] =~ /^id::/ ? substr($_[0], 4) : undef;'); meta::retriever('markdown-sdoc', <<'__'); my ($attribute) = @_; return undef unless $attribute =~ s/^markdown::/sdoc::/ and defined retrieve($attribute) || $attribute =~ s/^sdoc::// && defined retrieve($attribute); sdoc_markdown($attribute); __ meta::retriever('object', <<'__'); # Fetch a property from another Perl object. This uses the 'cat' function. return undef unless $_[0] =~ /^object::(.*?)::(.*)$/ && -x $1 && qx|$1 is '$2'|; join '', qx|$1 cat '$2'|; __ meta::retriever('perl', <<'__'); # Lets you use the result of evaluating some Perl expression return undef unless $_[0] =~ /^perl::(.*)$/; eval $1; __ meta::retriever('pp', <<'__'); return undef unless namespace($_[0]) eq 'pp'; my $attr = retrieve(attribute($_[0])); defined $attr ? preprocess($attr) : undef; __ meta::retriever('sdoc', 'exists $data{"sdoc::$_[0]"} ? sdoc("sdoc::$_[0]", extension_for($_[0])) : undef;'); meta::retriever('sdoc-packed', <<'__'); return undef unless $_[0] =~ /^sdoc-packed::(.*)$/; exists $data{"sdoc::$1"} ? sdoc_packed("sdoc::$1") : undef; __ meta::retriever('sdocp', <<'__'); return undef unless $_[0] =~ /^sdocp::(.*)$/; exists $data{"sdoc::$1"} ? sdocp("sdoc::$1") : undef; __ meta::sdoc('data::style.css', <<'__'); Caterwaul JS page style | Spencer Tipping Licensed under the terms of the MIT source code license body {font-family: 'Neuton', 'Garamond', serif; background: #111; margin: 0; color: #eee} a {text-decoration: none} .header * {background: rgba(17, 17, 17, 0.5)} .header .title {font-size: 20pt; text-transform: uppercase} .header .title .caterwaul {color: #fff; border-right: solid 4px #444; padding: 120px 10px 0 50px; z-index: 10} .header .title .js {color: #aaa; padding: 120px 0 0 10px; text-transform: lowercase; font-size: 16pt; z-index: 5} .header {padding-bottom: 40px; margin-top: 100px} .accent {color: #fa4; z-index: 20} .identify {background: #752 !important} Word wrapping inside
 elements.
A great article about this (and its cross-browser ramifications) at http://www.longren.org/2006/09/27/wrapping-text-inside-pre-tags/.

#tutorial-page pre {white-space: pre-wrap; word-wrap: break-word}

SDoc page styling.
This is optimized for long lines and lots of text.

#sdoc-page {padding-bottom: 100px; color: white; position: absolute; display: none}

#sdoc-page a.back {font-size: 16pt; color: #999; display: block; text-transform: lowercase; text-decoration: none}
#sdoc-page a.back:before {content: '<< '; color: #444}

#sdoc-page a.back:hover {color: #ccc}
#sdoc-page a.back:hover:before {content: '<< '; color: #fa4}

#sdoc-page .file > h1                  {color: #999; cursor: pointer; font-weight: normal; font-size: 16pt; white-space: nowrap; word-wrap: none}
#sdoc-page .file > h1 .path            {color: #444}
#sdoc-page .file > h1 .extension       {display: none; color: #444}

#sdoc-page .file > h1:hover .path      {color: #ccc}
#sdoc-page .file > h1:hover .extension {display: none; color: #ccc}

#sdoc-page .file > h1:hover            {color: #ccc}
#sdoc-page .file > h1:after            {content: ' >>'; color: #444}
#sdoc-page .file > h1:hover:after      {content: ' >>'; color: #fa4}

#sdoc-page .section {margin-top: 50px}

#sdoc-page .section h1:before, #sdoc-page .section h2:before, #sdoc-page .section h3:before {content: '< '; color: #fa4}
#sdoc-page .section h1:after,  #sdoc-page .section h2:after,  #sdoc-page .section h3:after  {content: ' >'; color: #fa4}

#sdoc-page .section h4:before {content: '> '; color: #fa4}

#sdoc-page .section h1 {font-size: 16pt}
#sdoc-page .section h2 {font-size: 13pt}
#sdoc-page .section h3 {font-size: 11pt}
#sdoc-page .section h4 {font-size: 10pt}

#sdoc-page .section h1 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222}
#sdoc-page .section h2 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222}
#sdoc-page .section h3 {text-transform: lowercase; color: #999; font-weight: normal}
#sdoc-page .section h4 {text-transform: lowercase; color: #999; font-weight: normal}

#sdoc-page p {color: #eee; font-family: sans-serif; font-size: 10pt; line-height: 1.8em; max-width: 500px; text-align: justify}

#sdoc-page pre.code {border: solid 1px #333; color: white; font-size: 10pt; font-family: 'Droid Sans Mono', monospace; padding: 4px; background: black; white-space: pre; word-wrap: none}
#sdoc-page pre.code {line-height: 1.8em}

Tutorial page styling.
These styles, while they should be more or less consistent across pages, are designed specifically for the tutorial.

#tutorial-page {width: 500px; padding-bottom: 100px; padding-right: 200px; padding-left: 50px; position: relative}

#tutorial-page p {font-family: sans-serif; font-size: 11pt}
#tutorial-page blockquote .signature {float: right; color: #aaa; font-variant: italic}
#tutorial-page blockquote {clear: right; margin-top: 10px}

#tutorial-page p a              {color: #888; text-decoration: none}
#tutorial-page p a:before       {content: '['; color: #444}
#tutorial-page p a:after        {content: ']'; color: #444}
#tutorial-page p a:hover        {color: #eee}
#tutorial-page p a:hover:before {content: '['; color: #fa4}
#tutorial-page p a:hover:after  {content: ']'; color: #fa4}

#tutorial-page .toc h1, #tutorial-page .toc h2, #tutorial-page .toc h3, #tutorial-page .toc h4
{color: #999; cursor: pointer; font-weight: normal; white-space: nowrap; word-wrap: none; text-transform: lowercase}

#tutorial-page .popdown       {position: fixed; top: 0; left: 50px; border-bottom: solid 1px #444; padding-bottom: 2px}
#tutorial-page .popdown:hover {border-bottom: solid 1px #fa4; padding-bottom: 10px}

#tutorial-page .popdown.open  {background: rgba(0, 0, 0, 0.9); left: 40px; border-bottom: solid 4px #fa4; padding-bottom: 10px; padding-left: 10px; padding-right: 10px}

#tutorial-page .popdown       .label        {font-size: 14pt; color: #888; text-transform: lowercase; cursor: pointer}
#tutorial-page .popdown       .label:hover  {color: #eee}
#tutorial-page .popdown       .label:before {content: '< '; color: #444}
#tutorial-page .popdown       .label:after  {content: ' >'; color: #444}
#tutorial-page .popdown:hover .label:before {content: '< '; color: #fa4}
#tutorial-page .popdown:hover .label:after  {content: ' >'; color: #fa4}

#tutorial-page .popdown      .contents {display: none}
#tutorial-page .popdown.open .contents {display: block; overflow-y: auto; overflow-x: hidden; max-height: 400px}

#tutorial-page .toc h1:hover,       #tutorial-page .toc h2:hover,       #tutorial-page .toc h3:hover,       #tutorial-page .toc h4:hover       {color: #eee}
#tutorial-page .toc h1:after,       #tutorial-page .toc h2:after,       #tutorial-page .toc h3:after,       #tutorial-page .toc h4:after       {content: ' >>'; color: #888}
#tutorial-page .toc h1:hover:after, #tutorial-page .toc h2:hover:after, #tutorial-page .toc h3:hover:after, #tutorial-page .toc h4:hover:after {content: ' >>'; color: #fa4}

#tutorial-page .toc h1 {font-size: 16pt}
#tutorial-page .toc h2 {font-size: 13pt; padding-left: 20px}
#tutorial-page .toc h3 {font-size: 11pt; padding-left: 40px}
#tutorial-page .toc h4 {font-size: 10pt; padding-left: 60px}

#tutorial-page .section h1:before, #tutorial-page .section h2:before, #tutorial-page .section h3:before {content: '< '; color: #fa4}
#tutorial-page .section h1:after,  #tutorial-page .section h2:after,  #tutorial-page .section h3:after  {content: ' >'; color: #fa4}

#tutorial-page .shell {position: fixed; border-radius: 0px; right: 50px; top: 0; bottom: 0; left: 600px; border: solid 2px #222; border-width: 0 1px; overflow-y: auto; overflow-x: hidden}
#tutorial-page .shell {font-family: 'Droid Sans Mono', monospace; font-size: 10pt; color: white; background: rgba(0, 0, 0, 0.9)}

#tutorial-page .shell .prompt > span {margin: 4px}

#tutorial-page .shell .syntax {border-bottom: dotted 1px #468; cursor: default}

#tutorial-page .shell .structure              {color: #797; display: block; margin: 4px; padding: 4px 0}
#tutorial-page .shell .structure:hover:before {color: #888; content: 'parse tree: '; padding-right: 4px}

#tutorial-page .shell .shadow              {color: #888; display: block; margin: 4px; padding: 4px 0}
#tutorial-page .shell .shadow:before       {color: #752; content: '>'; padding-right: 4px}
#tutorial-page .shell .shadow:hover:before {color: #888; content: 'compiled: '; padding-right: 4px}

#tutorial-page .shell .input {font-family: 'Droid Sans Mono', monospace; padding: 0; margin: 0; border: none !important; outline: none !important}
#tutorial-page .shell .input {font-size: 10pt; background: transparent; color: white}

#tutorial-page .shell .input:focus {border: none !important; outline: none !important}

#tutorial-page .shell .history {position: relative}
#tutorial-page .shell .history pre {font-family: 'Droid Sans Mono', monospace; font-size: 10pt}
#tutorial-page .shell .history .entry, .shell .history .result, .shell .history .error, .shell .history .log {margin: 4px}
#tutorial-page .shell .history .entry  {color: white}
#tutorial-page .shell .history .entry .command {margin-left: 4px}
#tutorial-page .shell .history .result {color: #7bf}
#tutorial-page .shell .history .log    {color: #7fb}
#tutorial-page .shell .history .error  {color: #f87}

#tutorial-page .shell .history .log:hover:before {color: #888; content: 'log: '}

#tutorial-page .shell .sandbox {font-family: 'Neuton', 'Garamond', serif; background: #222; color: #eee; padding: 10px; margin-top: 4px}

#tutorial-page #seq-decipher                      {padding: 4px 0; margin: 20px 0}
#tutorial-page #seq-decipher input                {border: solid 1px #444; background: black; padding: 4px; font-family: 'Droid Sans Mono', monospace; color: white; outline: none !important}
#tutorial-page #seq-decipher table.result         {font-size: 9pt}
#tutorial-page #seq-decipher td.name              {text-transform: lowercase; color: #aaa}
#tutorial-page #seq-decipher td.fragment          {font-family: 'Droid Sans Mono', monospace; color: #fa4}
#tutorial-page #seq-decipher tr.variables td.desc {font-family: 'Droid Sans Mono', monospace}

#tutorial-page .section h1, #tutorial-page .section h2, #tutorial-page .section h3, #tutorial-page .section h4 {padding-top: 50px}

#tutorial-page .section h1 {font-size: 16pt}
#tutorial-page .section h2 {font-size: 13pt}
#tutorial-page .section h3 {font-size: 11pt}

#tutorial-page .section h1 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222}
#tutorial-page .section h2 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222}
#tutorial-page .section h3 {text-transform: lowercase; color: #999; font-weight: normal}
#tutorial-page .section h4 {text-transform: lowercase; color: #999; font-weight: normal}

#tutorial-page p {color: #eee; font-size: 10pt; line-height: 1.44em; text-align: justify}
#tutorial-page code {color: #fff; font-size: 10pt; font-family: 'Droid Sans Mono', monospace; background: black; padding: 4px; border: solid 1px #333}
#tutorial-page pre  {color: #fff; font-size: 10pt; font-family: 'Droid Sans Mono', monospace; background: black; padding: 4px; border: solid 1px #333}

#tutorial-page pre.code {cursor: pointer; padding: 10px 4px}
#tutorial-page pre.code:before {content: '> '; color: #fa4}
#tutorial-page pre.code:hover {background: #222; color: #fa4}

__
meta::sdoc('js::caterwaul.all', <<'__');
This file isn't rendered -- it's just used internally for node testing.

- pinclude build/caterwaul.js
- pinclude build/caterwaul.std.js
- pinclude build/caterwaul.ui.js

__
meta::sdoc('js::caterwaul.node', <<'__');
CommonJS-compatible Caterwaul build | Spencer Tipping
Licensed under the terms of the MIT source code license

- pinclude build/caterwaul.js

exports.caterwaul = caterwaul;

__
meta::sdoc('js::waul-bootstrap', <<'__');
#!/usr/bin/env node

Waul: the offline precompiler for Caterwaul | Spencer Tipping
Licensed under the terms of the MIT source code license

- include build/caterwaul.min.js
- include src/waul/waul.js

__
meta::sdoc('js::web/code-snippets', <<'__');
Code snippet initialization.
This runs after the page is fully loaded. The idea is to setup clickability for each code snippet.

  setTimeout(linkify_code_snippets, 0),

  where [linkify_snippet(s)      = s.click(send_code_to_prompt),
         send_code_to_prompt()   = $('.shell .prompt .input').val($(this).text()) -se- $('.shell').click(),
         linkify_code_snippets() = $('#tutorial-page pre.code') *![linkify_snippet($(x))] /seq];

__
meta::sdoc('js::web/header', <<'__');
Page header.
This is basically just a navigation container.

  var page_header = jquery in div.header(div.title(span.caterwaul('caterwaul'), span.js('the ', span.accent('edge'), ' of javascript')));

__
meta::sdoc('js::web/main', <<'__');
Caterwaul JS web interface | Spencer Tipping
Licensed under the terms of the MIT source code license

$('#cover .status').text('loading tutorial content');

$(caterwaul('js_all jquery')(function () {
  var original_body_html = $('body').html(),
      original_pages     = $('#tutorial-page, #sdoc-page'),
      original_styles    = $('style, link[rel="stylesheet"]');

  $('#cover .status').text('parsing state');

  var construct_page = function (original_html) {
    original_html = original_html.replace ? original_html : original_body_html;

    - pinclude pp::js::web/header
    - pinclude pp::js::web/shell
    - pinclude pp::js::web/code-snippets

    - pinclude pp::js::web/state
    - pinclude pp::js::web/sdoc
    - pinclude pp::js::web/source

    - pinclude pp::js::web/render-tutorial

    $('head').append(jquery in title('caterwaul js'));
    $('body').empty().append(page_header, original_pages);

    original_styles.appendTo('head')};

  $.get(document.location.href, construct_page).error("construct_page(original_body_html)".qf)}));

__
meta::sdoc('js::web/render-tutorial', <<'__');
Building the tutorial.
All we have to do here is create a div to contain the tutorial and populate it with the SDoc obtained by parsing the object state. (Since this HTML file is actually a self-modifying Perl
object -- long story.) We also build a table of contents.

  $('#tutorial-page').append(top_wrapper_for(toc, 'Contents'), tutorial, shell())

  -where [tutorial_attribute       = attributes |[x.namespace === 'sdoc' && x.attribute === 'web/tutorial' && x] |seq,
          tutorial                 = jquery [div.tutorial[sdoc_to_dom(tutorial_attribute.value)]]
                                     -se- it.find('pre.code') *![$(x).text($(x).text().replace(/^\s*/, ''))] /seq,

          top_wrapper_for(e, name) = jquery [div.popdown(div.label /text(name), div.contents[e])]
                                     -se- setTimeout("it.click(given.e in it.toggleClass('open'))".qf, 10),

          toc                      = jquery [div.toc] -se- toc_links *~![x] *![it.append(x)] /seq
                                     -where [section_id_count    = 0,
                                             assign_section_id() = $(this).attr('id', 'section-#{++section_id_count}'),

                                             title_of(section)   = where [level = Number(/level(\d+)/.exec($(section).attr('class'))[1])] in
                                                                   $('').text($(section).children('h#{level}').text()),

                                             sections            = tutorial.find('.section').each(assign_section_id),

                                             toc_links           = [jquery in a.toc_link(h1('source code')) *href('#annotated')] +
                                                                   sections *[jquery in a.toc_link[title_of(x)] *href('##{$(x).attr("id")}')] -seq]];

__
meta::sdoc('js::web/sdoc', <<'__');
SDoc-to-HTML converter.
SDoc is a fairly simple format to parse. We just emit stuff based on indentation deltas (basically like the algorithm in the Perl). I'm doing this here rather than up-front to reduce the page
size. If we converted every SDoc attribute into HTML up front it would easily double the download size.

By the way, I'm using the same heuristic HTML-escaping algorithm that the Perl script uses. This basically tries to do the right thing with <> symbols in SDoc paragraphs by escaping anything
that doesn't look like a well-formed tag. Of course, this precludes your saying things like < and expecting that to render verbatim; instead it will be converted to an actual less-than sign
in the markup.

  var sdoc_to_dom = given.text in paragraphs *~!convert -seq
                                  -where [known_tags               = ('html head body meta script style link title div a span input button textarea option select form label iframe ' +
                                                                      'blockquote code caption table tbody tr td th thead tfoot img h1 h2 h3 h4 h5 h6 li ol ul noscript p pre samp sub sup ' +
                                                                      'var canvas audio video').replace(/\s+/g, '|'),

                                          paragraphs               = text.split(/\n\n+/),
                                          indentation_of(p)        = (/^(\s*(\|\s)?)/.exec(p)[1].length >> 1) + 1,

                                          convert(p)               = /^\s*[A-Z]/.test(p) ? documentation(p) :
                                                                        /^\s*\|/.test(p) ? quoted(unindent(p)) :
                                                                                           code(p),

                                          not_a_valid_tag          = new RegExp('<(?!\/|(#{known_tags})[^>]*>(?:.|\n)*)', 'g'),
                                          escape_html_in(s)        = s.replace(/&(?!gt;|lt;|amp;)/g, '&').replace(not_a_valid_tag, '<'),
                                          escape_all_in(s)         = s.replace(/&/g, '&').replace(//g, '>'),

                                          quoted(p)                = jquery in pre.quoted[escape_all_in(p)],
                                          code(p)                  = jquery in pre.code[p.replace(/^\s*c\n/, '')],

                                          starts_section(p)        = /^\s*(.*\.)\n\s*(.*)/.exec(p) -re [it && it[1].length + 10 <= it[2].length],
                                          unindent(p)              = p.replace(indentation, '') -where [spaces      = n[indentation_of(p) - 1] *['( |\\|){2}'] -seq -re- it.join(''),
                                                                                                        indentation = new RegExp('^#{spaces}', 'gm')],

                                          documentation(p)         = starts_section(p) ? documentation_section(p) : jquery in p[escape_html_in(p)],
                                          documentation_section(p) = jquery [div.section[header, paragraph]] -se- it.addClass('level#{indentation_of(p)}')
                                                                     -where [parts     = /^\s*(.*)\.\n((?:.|\n)*)/.exec(p),
                                                                             header    = $('').text(parts[1]),
                                                                             paragraph = jquery in p[escape_html_in(parts[2])]]];

__
meta::sdoc('js::web/shell', <<'__');
var shell = given.nothing in
            shell.append(history_container, shell_prompt)
            -se- setTimeout(given.nothing in shell.click(setTimeout(given.nothing in shell.find('.prompt .input').focus(), 10) -given.e), 0)

            -where [shell                 = jquery in div.shell,

                    history_container     = jquery in div.history,

                    history_entry_for(s)  = jquery in pre.entry(span.accent('>'), span.command /text(s)),
                    history_result_for(o) = jquery in pre.result[ui_for(o)],
                    history_log_for(o)    = jquery in pre.log   /text('' + o),
                    history_error_for(e)  = jquery in pre.error /text('' + e),

                    ui_for(x)             = ! x                      ? jquery in span /text('' + x) :
                                            x.is_caterwaul_syntax    ? jquery [span.syntax %syntax_for(x)] -where [syntax_for(node)(e) = e.text(x.toString()).hover("e.text(x.structure())".qf,
                                                                                                                                                                    "e.text(x.toString())".qf)] :

                                            x.constructor === jQuery ? x.parent().length ? jquery in div.sandbox('(A DOM node that is already in document)')
                                                                                                     /hover(given.e in x.addClass('identify'), given.e in x.removeClass('identify')) :
                                                                                           jquery in span /text(jquery in span[x] /html()) + div.sandbox[x.clone(true)] :
                                                                       jquery in span /text('' + x),

                    realign()             = setTimeout(input.css({width: input.parent().width() - (input.prev().width() + 10)})
                                                       -where [input = shell.find('.prompt .input')]
                                                       -given.nothing, 10),

                    log(xs = arguments)   = xs *![shell.children('.history').append(x) -se- realign()] -seq -re- xs[0],

                    history_n             = 0,
                    context               = capture [expand()            = shell.animate({left: 0,   right: 0},  realign),
                                                     collapse()          = shell.animate({left: 600, right: 50}, realign),
                                                     clear()             = shell.children('.history').empty() -re- realign() -re- '',
                                                     caterwaul           = caterwaul.clone(),
                                                     history             = [],
                                                     console             = capture [log() = context.log.apply(this, arguments) -then- undefined],

                                                     help()              = 'available variables:\n' +
                                                                           (pairs %[x[1] && x[1].description] *['#{x[0]}: #{x[1].description}'] /seq
                                                                            -where [keys  = context /keys -seq -re- it.sort(),
                                                                                    pairs = keys *[[x, context[x]]] -seq]).join("\n"),

                                                     log(xs = arguments) = xs *![log(history_log_for(x))] -seq -re- xs[0],
                                                     it                  = null]

                                            -se [it.context = it]
                                            -se [it.compiler = it.caterwaul.jquery(it.caterwaul.js_all())]
                                            -se [it.context.description   = 'variables available to the shell',
                                                 it.console.description   = 'a logging interface; use console.log()',
                                                 it.expand.description    = 'expands the shell',
                                                 it.collapse.description  = 'collapses the shell',
                                                 it.clear.description     = 'clears old output',
                                                 it.caterwaul.description = 'a copy of the caterwaul global',
                                                 it.compiler.description  = 'the compiler for this shell',
                                                 it.history.description   = 'shell input history',
                                                 it.log.description       = 'logs a value to the shell'],

                    run_command(c)        = log(history_entry_for(c))
                                            -re- log(history_result_for(context.it = context.compiler(c, context)))
                                                 /rescue [context.compiler('(function(){return #{c}})()', context)
                                                 /rescue [context.compiler('(function(){#{c}})()', context)
                                                 /rescue [log(history_error_for(context.it = e))]]],

                    shell_prompt          = jquery [div.prompt[prompt, input, structure, shadow]]
                                            -se- setTimeout(realign, 10)
                                            -se- setInterval(update_shadow, 10)

                                            -se- it.find('span.prompt').click($(this).siblings('.input').focus() -given.e)

                                            -se- setTimeout(given.nothing in
                                                   it.find('.input').keydown(realign() -re [history_prev() /se [e.preventDefault()] /when [e.which === 38] ||
                                                                                            history_next() /se [e.preventDefault()] /when [e.which === 40] ||
                                                                                            run_it()       /se [e.preventDefault()] /when [e.which === 13] || true] -given.e), 0)

                                            -where [input                = jquery in input.input,
                                                    prompt               = jquery in span.accent('>'),
                                                    structure            = jquery in div.structure,
                                                    shadow               = jquery in div.shadow,

                                                    last_input_text      = null,
                                                    rename_gensyms_in(t) = t.replace(context.caterwaul.gensym_rename_table(t)),
                                                    update_shadow()      = structure.text(context.caterwaul.parse(input.val()).structure() -rescue- 'invalid parse')
                                                                           -se- shadow.text(context.compiler(context.caterwaul.parse(last_input_text = input.val()))
                                                                                            -re- rename_gensyms_in(it).toString()
                                                                                            -rescue- e.toString())

                                                                           -unless [input.val() === last_input_text],

                                                    h_index              = 0,
                                                    history_prev()       = (h[h_index] = input.val()) -when [h_index < history_n] -re- input.val(h[--h_index]) -when [h_index > 0]
                                                                           -where [h = context.history],

                                                    history_next()       = (h[h_index] = input.val()) -when [h_index < history_n] -re- input.val(h[++h_index]) -when [h_index < history_n]
                                                                           -where [h = context.history],

                                                    history_add(s)       = history_n = h_index = context.history.push(s),

                                                    scroll_to_end()      = setTimeout(shell.scrollTop(shell.children(':last') -re [shell.scrollTop() + it.position().top + it.height()])
                                                                                      -given.nothing, 0),

                                                    run_it()             = history_add(t) -re- run_command(t) -re- input.val('') -re- scroll_to_end() -when.t -where [t = input.val()]]];

__
meta::sdoc('js::web/source', <<'__');
Building the documentation pages.
This is just a matter of finding the right SDoc sources and tying them together.

  var attributes = perl_attributes(original_html.replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&'));

  $('#sdoc-page').append(sections) -where[sdocs      = attributes %[x.namespace === 'sdoc' && /^js::/.test(x.attribute)] -seq,
                                          core       = sdocs %[/^js::caterwaul/.test(x.attribute)] -seq,
                                          extension  = sdocs %[/extensions\//.test(x.attribute)] -seq,
                                          web        = sdocs %[/web\//.test(x.attribute)] -seq,

                                          back_link  = jquery in a.back('Back to tutorial') *href('#tutorial'),

                                          title(a)   = /\//.test(a) ? jquery [span.path[a.replace(/^js::(.*\/).*/, '$1')],
                                                                              span.name[a.replace(/.*\//, '')],
                                                                              span.extension('.js')] :

                                                                      jquery [span.name[a.replace(/^js::/, '')],
                                                                              span.extension('.js')],

                                          section(x) = jquery [div.file(h1[title(x.attribute)], div.contents)]
                                                       -se- setTimeout(given.nothing in it.find('h1').click(given.e in $(this).next('.contents').toggle()), 0)
                                                       -se- it.find('.contents').hide().append(sdoc_to_dom(x.value)),

                                          sections   = jquery [div *id('annotated') >= back_link >= core_sections >= extension_sections >= web_sections]
                                                       -where [core_sections      = $([]) -se- core      *~!section *![it.push(x)] /seq,
                                                               extension_sections = $([]) -se- extension *~!section *![it.push(x)] /seq,
                                                               web_sections       = $([]) -se- web       *~!section *![it.push(x)] /seq]];

  setInterval(check_for_destination, 50)
  -where [viewing_annotated_source = false,
          moving                   = false,

          check_for_destination()  = show_annotated_source() -when [! moving && ! viewing_annotated_source &&   /^#annotated/.test(document.location.hash)] 
                                     hide_annotated_source() -when [! moving &&   viewing_annotated_source && ! /^#annotated/.test(document.location.hash)],

          show_annotated_source()  = moving = $('#sdoc-page').css({display: 'block', top: $(window).scrollTop(), left: $(window).width()}).
                                                          animate({left: 50}, 'slow')

                                             -se- $('#tutorial-page').animate({left: '-=#{distance}'}, 'slow', given.nothing [viewing_annotated_source = true, moving = false])
                                             -se- $('.shell').animate({left: '-=#{distance}', right: '+=#{distance}', opacity: 0}, 'slow')
                                             -se- $('.header, .popdown').hide('slow')

                                             -where [distance = $(window).width()],

          hide_annotated_source()  = moving = $('#sdoc-page').animate({left: $(window).width()}, 'slow',
                                                                      given.nothing [viewing_annotated_source = moving = false, $(this).css({display: 'none'})])

                                             -se- $('.shell').animate({left: '+=#{distance}', right: '-=#{distance}', opacity: 1}, 'slow')
                                             -se- $('#tutorial-page').animate({left: '+=#{distance}'}, 'slow')
                                             -se- $('.header, .popdown').show('slow')

                                             -where [distance = -$('#tutorial-page').position().left]];

__
meta::sdoc('js::web/state', <<'__');
Self-modifying Perl state parser.
This is actually really easy. All of the attributes in self-modifying Perl come in one of two forms. One is the short form, written as meta::\w+('stuff', 'stuff');\n. The other is the long
form, written meta::\w+('stuff', <<'eof');\n...\neof\n. We just need to find all occurrences of either one of these things.

  var perl_attributes = given.text in text.match(long_form) *parse_long + text.match(short_form) *parse_short -seq
                                      -where [long_form          = /^meta::(\w+)\('([^']+)', (?:<|<){2}'([^']+)'\);[\r\n]{1,2}([\s\S]*?)[\r\n]{1,2}\3$/mg,
                                              short_form         = /^meta::(\w+)\('([^']+)', '([^']+)'\);$/mg,

                                              long_form_parser   = new RegExp(long_form .toString().replace(/^\/(.*)\/[mg][mg]$/, '$1')),
                                              short_form_parser  = new RegExp(short_form.toString().replace(/^\/(.*)\/[mg][mg]$/, '$1')),

                                              parse_long(match)  = long_form_parser.exec(match)  -re- {namespace: it[1], attribute: it[2], value: it[4]},
                                              parse_short(match) = short_form_parser.exec(match) -re- {namespace: it[1], attribute: it[2], value: it[3]}];

__
meta::sdoc('readme', <<'__');
Caterwaul.
Caterwaul is two different things. First, and most importantly, it is a powerful low-level Javascript code manipulation library with a Javascript parser, AST, in-process compiler, and
replication. Second, it is a programming language implementation that uses this library to transform your code in various arcane ways that I happen to find useful.

The whole project is MIT-licensed, and in the unlikely event that you want to use it you are free to [email me](mailto:spencer@spencertipping.com) with any questions/issues.

What follows is a ten-minute introduction to caterwaul's core concepts. It covers about 5% of what caterwaul does.

  Heads up: before you use this.
  It has a few defects. One relates to stuff like `if (x) switch (y) {} else z();`, which gets rewritten to broken code. This is a parse-level problem and I haven't had time to fix it yet.

  Second, various ES5/6 features aren't entirely supported; for example functions-in-objects (`let x = {f() {}};`). There's probably more; the ES5 support was hacked on last-minute.

  Third, Caterwaul parses non-JS. `let @x = 100` is fine, `let x\y\z = @foo[\]` works, etc. Not a defect exactly, but something to be aware of.

  Caterwaul as a library.
  Caterwaul is implemented in pure Javascript, so you can use it to live-compile your code in a browser, or you can use the `waul` precompiler to compile your code up-front. You can see the
  live compiler in action by going to [the caterwaul website](http://caterwauljs.org). This site embeds a caterwaul compiler configured to use the standard macro library; this causes it to
  compile what I refer to as the caterwaul programming language. The website documents this language in some detail, as does [Caterwaul by
  Example](http://caterwauljs.org/doc/caterwaul-by-example.pdf) - though some of the examples will fail since Caterwaul 1.2.3, which introduces [a breaking
  change](https://github.com/spencertipping/caterwaul/commit/05a5e317336e751cbf90a7f574070d3eca4f69a4) to the `seq` library.

  If you're interested in using caterwaul as a compiler library, I recommend reading the [caterwaul reference manual](http://caterwauljs.org/doc/caterwaul-reference-manual.pdf), which covers
  its core API in significantly more detail than this readme. You can also read through the caterwaul source code, which contains copious documentation, some of which is up to date.

    Parsing things.
    Caterwaul's Javascript parser takes anything with a valid `toString()` method as input, including Javascript functions.

    | var tree = caterwaul.parse('3 + 4');
      tree.toString()           // '3 + 4'
      tree.data                 // '+'
      tree.length               // 2
      tree[0].data              // '3'
      tree[1].data              // '4'

    Detecting patterns.
    If you're serious about this stuff, I recommend writing this code in the caterwaul programming language. It supports a lot of advanced features that make syntax trees much easier to work
    with; in particular, preparsed quoted constructs (similar to the ' operator in Lisp). These will give you a significant performance advantage and better notation.

    | var pattern = caterwaul.parse('_x + _y');
      var match   = pattern.match(tree);
      match._x.data             // '3'
      match._y.data             // '4'
      var template = caterwaul.parse('f(_x, _y)');
      var new_tree = template.replace(match);
      new_tree.toString()       // 'f(3, 4)'

    Compiling things.
    Caterwaul's compiler does a lot, but the basic case works like `eval`:

    | var f = function (x, y) {return x * y};
      caterwaul.compile(new_tree)       // 12

    You can also bind variables from the compiling environment:

    | var new_f = function (x, y) {return x + y};
      caterwaul.compile(new_tree, {f: new_f})   // 7

    You can only compile things that return values (technically, things which are expressions; the litmus test is whether you could legally wrap it in parentheses), so stuff like `var x = 10`
    won't work. This is different from Javascript's `eval` function. If you want to execute imperative code, you should wrap it in a function:

    | var function_wrapper = caterwaul.parse('(function() {_body})');
      var code = caterwaul.parse('if (x) {console.log(x)}');
      var new_function = caterwaul.compile(function_wrapper.replace({_body: code}));
      new_function();

  Caterwaul as a programming language.
  I wrote a set of macros that use the above API to modify Javascript code; this macro set has been refined over the past year to become a programming language that I find useful. You can
  learn this language on the caterwaul website, which goes through it by example and provides an interactive shell so you can see what the compilation process looks like.

    Using caterwaul this way.
    There are two ways to use caterwaul as a programming language. You can compile code from inside another Javascript process, which works because all caterwaul code is syntactically valid
    Javascript code as well (note that the following example will work only if you've loaded `caterwaul.std.js` from the `build/` directory):

    | var compiler = caterwaul(':all');         // :all means 'every macro you know about'
      var compiled = compiler(function () {
        console.log(x) -where [x = 10];
      });
      compiled();               // logs 10

    The other way to use the programming language is by using the `waul` precompiler. This compiles your code to straight Javascript, eliminating the runtime overhead imposed by caterwaul's
    parser, macroexpander, and compiler. Waul files typically end in `.waul` or `.waul.sdoc` (if you're using [SDoc](http://github.com/spencertipping/sdoc), which waul will transparently
    parse) and contain code like this:

    | caterwaul(':all')(function () {
        n[10] *console.log -seq;
        console.log('done #{message}')
          -where [message = 'iterating through numbers'];
      })();

    You can compile this by running `waul file.waul`, which will generate `file.js`. `file.js` may contain references to the `caterwaul` global if you use certain macros, but there will be no
    compilation overhead.

  Caterwaul as a self-replicating monstrosity.
  This is the coolest part of caterwaul in my opinion. Both `caterwaul` as a Javascript object and `waul` can give you string expressions that reproduce them. This is very useful for library
  bundling; for example:

  | var r = caterwaul.replicator();
    var code = r.toString();

  If you do this, someone else can `eval` 'code' and they will end up with a global called `caterwaul` that is configured exactly as your `caterwaul` is configured. The only requirement is
  that configurations be declared as modules, which is done like this:

  | // caterwaul.module(name, [compiler_configuration], function)
    caterwaul.module('my-configuration', ':all', function ($) {
      // $ is the global caterwaul object
      $.foo = 'bar';
    });

  The output of `replicator` is a function that recreates all modules by re-running their initializers. Note that `replicator` rewrites the module functions into their post-compilation
  equivalents; in other words, the body of each function has already been compiled into normal Javascript. This reduces total compilation overhead.

    Waul replication.
    Suppose you write a custom caterwaul module and want a new version of `waul` that contains it. You can ask `waul` to replicate itself with an extension like this:

    | $ ./waul -r -e extension.waul > new-waul
      $ chmod u+x new-waul
      $ ./new-waul my-file.waul

    This is especially useful for setting up shebang lines for scripts that require custom waul extensions:

    | #!./new-waul
      caterwaul(':all')(function () {
        // custom code
      })();

    The `waul` in caterwaul's root directory is preloaded with `build/caterwaul.std.js` and `build/caterwaul.ui.js`. As you might guess, `waul` wasn't written specially to contain these;
    rather, it was generated by `waul-core` (which doesn't have any libraries built-in) by this process:

    | $ ./waul-core --replicate -e build/caterwaul.std.min.js -e build/caterwaul.ui.min.js > waul
      $ chmod 0700 waul

__
meta::sdoc('waul::waul', <<'__');
Waul precompiler implementation | Spencer Tipping
Licensed under the terms of the MIT source code license

Introduction.
Caterwaul is useful as a library and an online compiler, but when performance is important you probably want to deliver precompiled code. Waul is a program that does exactly that. You write a
regular file that invokes Caterwaul on a function, and Waul emits a Javascript file that contains the Caterwaul-compiled results. For example:

| $ cat > test.waul < limit] -then- x,
                                                bindings() = n[limit] *[['_#{x + 1}', items[x]]] -object -seq],

         waul_node_repl()           = introduce() -then- require('repl').start('waul> ', undefined, evaluator)
                              -where [evaluator(s, _1, _2, cc) = cc(null, history /~push/ instance(s, {$: $, caterwaul: $, require: require} /-$.merge/ history.bindings())) -rescue- cc(e, undefined),
                                      instance                 = $(options.configuration),
                                      history                  = repl_history()],

         waul_simple_repl()         = introduce() -then- process.stdin.on('data', require('util') /~inspect/ history.push(instance(s, {$: $, caterwaul: $, require: require}
                                                                                                                                      /-$.merge/ history.bindings()))
                                                                                  -re-     process.stderr.write('#{it}\n', 'utf8')
                                                                                  -rescue- process.stderr.write('Error: #{e}') -given.s)
                                                  -then- process.stdin /~setEncoding/ 'utf8'
                                                  -then- process.stdin.resume()
                              -where [instance = $(options.configuration),
                                      history  = repl_history()],

         waul_repl()                = options.simple_repl ? waul_simple_repl() : waul_node_repl(),

         waul_identity(file, m)     = file /-waul_output/ m._,

         waul_transform(file, m, t) = file /-waul_output/ t.replace({_name: m._name, _compiled: transformed_function}).guarded()
                              -where [transformed_function = $(m._transform.as_escaped_string())(m._function) / null /-$.late_bound_tree/ options
                                                        -re- it.replace(it /!$.gensym_rename_table)],

         waul_run(m)                = $(m._transform.as_escaped_string())(m._function) /-$.compile/ {require: require, caterwaul: $} -re- it()]});

__
meta::sdoc('web/tutorial', <<'__');
Introduction.
Caterwaul is a Javascript recompiler that lets you change the semantics of functions. To do this it implements a modular decompiler, macroexpander, and compiler that allow you to manipulate
code in a first-class way. It also comes with several macro (Lisp-style, not C-style) libraries to make Javascript more fun (though you can easily disable them and/or write your own).

If you see merit in the linguistic features of Haskell, APL, Lisp, and Perl, then you will probably also see merit in the things that Caterwaul tries to achieve and you might even like it. If
you're a normal person who wants to use modern tools to write normal, maintainable apps, and you're not a programming language enthusiast who seeks out the pathological and the bizarre, then I
recommend using CoffeeScript instead.

This page is written in Caterwaul using the libraries covered below. If you're feeling adventurous, you might be interested to see the annotated source (the standard library has similar documentation and is in the same directory).

A shell is available to interactively use Caterwaul while reading the tutorial below.

I've also started writing documentation in a more traditional form, available from the documentation directory. This is a great place to start if you're
interested in using Caterwaul for production projects (which you shouldn't be).

  Praise for Caterwaul.
  Lots of people have looked at Caterwaul and great things to say about it. Here are some of them:
  
LaunchCtl? You may as well learn Caterwaul! Jeff Simpson, Senior Software Engineer @ LivingSocial
Embrace the dark side of Javascript. Use Caterwaul. Dan Hopkins, Senior Software Engineer @ LivingSocial
Nobody in their right mind would use this language. Anonymous
Using this to build applications is like using quantum mechanics to build a car. Hank Racette, Founder of Arden Lake Technologies
Caterwaul is a terrible language for the vast majority of developers. But I like it well enough. Spencer Tipping, author of Caterwaul
Genius bilalhusain@ycombinator
Caterwaul is f**king terrifying @wookiehangover
Caterwaul is for advanced JavaScript developers only, and is in no way recommended to people that don't know what they're doing. Softpedia
Using Caterwaul. I recommend that you don't use Caterwaul because it's a monstrosity. However, if you're adventurous and self-destructive, here's how to go about it. Caterwaul is pure Javascript, so you can integrate it into any web page without much effort. For example: | <script src='http://caterwauljs.org/build/caterwaul.min.js'></script> <script src='http://caterwauljs.org/build/caterwaul.std.min.js'></script> <script src='http://caterwauljs.org/build/caterwaul.ui.min.js'></script> The next step is to configure a compiler instance. Caterwaul provides a framework that you can use to build custom compilers, so this step tells Caterwaul what kind of macros you want to expand. (This could include your own custom macros or third-party extensions.) For example, here's how to get a compiler that compiles all of the macros discussed on this page: | var compiler = caterwaul('js_all jquery'); What we're saying here is "build a compiler with all Javascript core macros, then add jQuery support." A compiler is a function that recompiles any function you give it: | var compiled = compiler(function () {alert(x) -where [x = 10]}); Generally you create the compiler inline and use it to transform your app's initialization function: | // For jQuery apps: $(caterwaul('js_all jquery')(function () { $('body').append(jquery in div('hi there!')); })); | // For non-jQuery apps, or libraries: caterwaul('js_all')(function () { // app code })(); Check out Caterwaul by example for a more detailed discussion about writing apps with Caterwaul. Libraries. I maintain a motley set of libraries that are in various ways related to Caterwaul. Right now they are: I should mention that they're in various states of disrepair and sometimes break. However, if you'd like to use one in production (which you shouldn't), feel free to e-mail me and I'll set up a stable versioning scheme for it. They're all MIT-licensed, as is Caterwaul. Downloading Caterwaul. Here are some relevant links if you want to hack on or use Caterwaul: Caterwaul in node.js. It's really easy to use Caterwaul with node.js. You just need to download caterwaul.node.js and whatever extensions you want to use (I'll assume caterwaul.std.js for the purposes of this example), and concatenate them into one big file. On Mac or Linux the process looks like this: | $ curl http://caterwauljs.org/build/caterwaul.{node,std}.js > caterwaul-custom.js $ node > var caterwaul = require('./caterwaul-custom.js').caterwaul; > caterwaul.js_all()('[1, 2, 3] *[x + 1] -seq'); [ 2, 3, 4 ] > Because Caterwaul recompiles functions, you'll have to explicitly bind require if you want your app to have access to it. This can be done by specifying a hash of variable bindings after the function you're compiling. For example: | var main_function = function () {...}; caterwaul.js_all()(main_function, {require: require}); Doing this puts require into the local scope of the compiled function. (Unfortunately, Caterwaul has no way of doing this automatically, since all functions that it constructs are created in the global scope and lexical scopes are not first-class in Javascript.) Javascript extensions. Caterwaul's core macro set starts by extending Javascript syntax in some helpful ways. In particular, it enables quick function assignment, infix and postfix function application (experimental), and Ruby-style string interpolation (which works with both single and double quotes): add(x, y) = x + y c String.prototype.say_hi() = 'hi from #{this}!' Caterwaul translates these expressions into this: | add = function (x, y) { return x + y; }; String.prototype.say_hi = function () { return 'hi ' + (this) + '!'; }; Now we can use the new functions in the shell: add(3, 4) 'javascript'.say_hi() Caterwaul also supports some experimental features when defining functions this way. String interpolation, function assignment, and infix and postfix function application are the only irregular syntactic forms provided by Caterwaul. Everything else is implemented as a regular form called a modifier. General modifiers. A modifier is a word that is used with an operator to modify a bit of syntax. For example, Caterwaul provides a modifier called when to execute things conditionally: log('hi') -when ['foo'.length === 3] There are two parts to a modifier. The first is the operator you use with it (in this case minus), and the second is the modifier and any arguments it takes. The operator is very important; it determines how much stuff you're modifying. For example: log('hi'), log('again') -when [1 === 2] Here the when [1 === 2] only modifies log('again') because minus has much higher precedence than the comma operator. However, Caterwaul lets you use several other operators to change this: log('hi'), log('again'), when [1 === 2] In this case the when [1 === 2] modifies both log statements. The reason for this is kind of subtle: comma left-associates, so the first comma was collapsed into a single syntax node that then became the left-hand side of the second comma. Because Caterwaul operates on the structure of your code, it groups both log statements into the conditional. You can inspect Caterwaul's parse tree by using the qs modifier (for "quote syntax"). For example: qs [log('hi'), log('again'), when [1 === 2]] qs [log('hi'), log('again'), when [1 === 2]].data qs [log('hi'), log('again'), when [1 === 2]].length qs [log('hi'), log('again'), when [1 === 2]][0] qs [log('hi'), log('again'), when [1 === 2]][1] qs [log('hi'), log('again'), when [1 === 2]].structure() The structure method gives you a string containing the syntax tree in S-expression form. I talk more about this in the section about quotation. Modifier operators. There are about six different operators you can use in conjunction with a modifier. From highest to lowest precedence they are:
  1. The slash. For example, log('hi') /when [true]. I use this when I need something tighter than a minus.
  2. The minus. For example, log('hi') -when [true]. It also comes in another form: log('hi') -when- true. I use this most of the time because it seems easier to read.
  3. The in operator. For example, given [x] in x + 1. in has the same precedence as < and >, which is lower than the arithmetic operators. As a result, it's useful when you're binding variables or creating functions around simple expressions.
  4. The <> operators. These are used around a modifier: log('hi') no_logging. This has the same precedence as in and other relational operators.
  5. The | operator. This is the lowest-precedence regular operator; the only things lower are &&, ||, ?:, assignment, and the comma.
  6. The , operator. This is the lowest-precedence operator in Javascript. It can be dangerous to use because it left-associates; for example, f(x, y, z, where [z = 10]) will invoke f on just one parameter, since the where gobbles everything to its left. (Using a | here would fix the problem.)
  7. The [] operator. This starts the precedence hierarchy over by using explicit grouping. For example, where [x = 10] [log(x)].
Conditional modifiers. when and unless are used to make conditionals more flexible. The semantics and return values are: | x -when- y -> y && x x -unless- y -> !y && x Similar are and and or, which behave identically to && and || but can have different precedence. For example: | x -or- y + 1 -> (x || y) + 1 x /and.y * 3 -> (x && y) * 3 Binding modifiers. These let you define locally-scoped variables. There are two of these modifiers, where and using. where is used to bind local variables at runtime, much like it is in Haskell: x -where [x = 10] f(10) -where [f(x) = x + 1] Sometimes, though, you want to have access to all of the properties of an object without qualifying them. Javascript provides the with keyword for this purpose, but because it is evaluated at runtime it has significant performance implications. A much faster alternative is to use Caterwaul's using modifier, which evaluates its object at compile-time and produces a list of local variable definitions that refer to the object's properties. (Naturally, this means that the object you're using needs to be computable at compile-time.) Also, any variables defined with using will shadow surrounding variables with the same name. For example, this refers to caterwaul.compile: compile -using.caterwaul The opposite of using is capture, which makes an object out of a series of assignments. The assignment structure is just like it is for where: result = capture [f(x) = x + 1, y = 10] result.f(10) result.y Capturing assignments this way has one problem: since it just builds an object, your expressions can't refer to each other. For example, this won't do what you want: capture [f(x) = x + 1, g = f] The way to work around this is to use wcapture, which, as its name suggests, combines where and capture. Like capture, it returns an object built out of assignments, but like where, it introduces a local scope so that all of the assignments can see each other: wcapture [f(x) = x + 1, g = f].g(10) Function modifiers. There are two words that create functions. One is given, which creates a regular function. The other is bgiven, which binds the function to the this where it was defined. For example: given [x] in x + 1 x + 1 -given [x] f.call(10) -where [f = this -given- x] f.call(10) -where [f = this -bgiven- x] There's a shorthand you can use if you just have a single operand for a modifier: x + 1 -given.x given.x in x + 1 given.x [x + 1] Side-effecting modifiers. These make it easy to manipulate values and return things without using an explicit variable. We do this in English using pronouns, and Caterwaul binds the variable it to refer to "the thing that we're working with." There are two ways to create a side-effect. One is to return the side-effecting expression and the other is to return the original value. For example, suppose you want to write a function hash(k, v) that returns a hash h such that h[k] === v. In plain Javascript you'd write this: | var hash = function (k, v) { var result = {}; result[k] = v; return result; }; However, the amount of typing required is much larger than the complexity of the problem. We want to return an object after applying a side-effect to it; to do this with Caterwaul we would use the se modifier, which stands for "side-effect": hash(k, v) = {} -se [it[k] = v] This style of side-effects returns the original expression. Sometimes, though, you want to return the result of the side-effect rather than the original. For example, here's a zero-division check in plain Javascript: | var x_over_yp1 = function (x, y) { var y_plus_1 = y + 1; return y_plus_1 === 0 ? 0 : x / y_plus_1; }; Here's the same function using a returning side-effect ("re" stands for "returning effect"): x_over_yp1(x, y) = y + 1 -re [it === 0 ? 0 : x / it] Side-effecting won't impact the evaluation order of your code. That is, x -se- y and x -re- y will always evaluate x before y. Another modifier is -then-, which is synonymous to -re- but doesn't allocate a closure or bind the it variable. It is semantically equivalent to Javascript's native comma operator, but can take on different precedence levels. Quotation. Most people won't use this, but it's handy if you're doing heavy-duty syntax analysis or writing complex macros. The standard library includes an obscure modifier called qs that you can use to quote a piece of code. Quotation is basically grabbing the literal syntax rather than evaluating it normally. For example: qs [foo + bar] qs [foo + bar].data qs [foo + bar].length qs [foo + bar][0] qs [foo + bar].structure() Quotation is an idea that comes from Lisp and is handled similarly by Caterwaul. (The only difference is that Caterwaul returns its own n-ary syntax tree format instead of cons trees.) A variant, qse, macroexpands the quoted code before returning it as a syntax tree. For example: qse [log(foo) -unless [true]] log(foo) -unless [true], qse You can use this in the shell to see how Caterwaul will macroexpand something. Note that the shell's caterwaul function is configured with all extensions enabled. Evaluation modifiers. You can inform Caterwaul that you want to evaluate an expression at compile-time rather than at runtime by using the eval modifier. For example: given.x in x + Math.sqrt(2) given.x in x + Math.sqrt(2) /eval In the second example, Math.sqrt(2) is evaluated when the code is macroexpanded and a variable called eval is inserted into the code in its place. eval is bound to the result of the compile-time evaluation. Generally you wouldn't use this modifier, but I've included it for completeness. Macroexpansion modifiers. The reexpand and noexpand modifiers give Caterwaul instructions about how to handle an expression. For instance, suppose you have a variable called given, and you want to use it without worrying that Caterwaul will interpret it as a modifier. You can wrap expressions that contain this variable with the noexpand modifier to prevent any macroexpansion from happening: qse in noexpand [x -given.x] Similar to noexpand is reexpand, which tells Caterwaul to re-expand the output of the first macroexpansion. Normally you don't need to use this because all of the standard macros re-expand their output automatically and therefore require only the initial expansion. Other modifiers. There are a few more modifiers that I threw in to the standard library to make some edge cases easier: new Error('uh-oh') -raise null.foo -rescue- log('caught #{e}') The exception is always called e when using the rescue modifier. Sequence library. This is probably the gnarliest part of Caterwaul, but in my opinion it's also the most useful. The sequence library provides a modifier called seq that reinterprets some syntax within an APL-like domain-specific language. It generates very efficient code and lets you express maps, folds, unfolds, filters, etc, with very little effort. For instance, suppose we want an array of the first 10 squares. Using the sequence library looks like this: ni[1, 10] *[x * x] /seq Mapping and iterating. The * operator is responsible for mapping, iterating, and flat-mapping. It's fairly easy to use; you just "multiply" a sequence by a bracketed expression. * will create a variable called x and evaluate your expression for each element in the sequence. It then collects these results and returns a new array. For example: seq in [1, 2, 3] *['x = #{x}'] You don't have to use just arrays. You can use anything with a .length and [0] ... [n - 1] attributes. One of the most common non-array collections I use is a jQuery selector (just be sure to wrap x again so that you're not dealing with a plain DOM node): seq in +$('div') *[$(x).attr('class')] In this case, the + converts the jQuery collection into an array by invoking Array.prototype.slice on it. We can also map into another jQuery collection if we want to; for instance: seq in $('div') *[x.parentNode] Alternative forms. Most operators have an alternative form that does something similar to the original. You specify this form by using a ! after the operator. The alternative form of * is used to iterate without collecting the results; doing this returns the original array. For example: seq in [1, 2, 3] *![log(x)] The third use of * is flat-mapping, which is denoted by writing *~!. For example: seq in [1, 2, 3] *~![[x, x + 1]] Like the original form, these alternative forms can be combined with any of the operator features below. Prefixes. Caterwaul 1.0.3 supports prefixes for mapping and iterating over non-array structures. Right now there are two prefixes, %k and %v. These can be used with * and *!, but not *~!. These prefixes stand for 'keys' and 'values', respectively, and they're used to limit the scope of a map or iteration to the keys or values of an object. For example: (seq in {foo: 'bar'} %k*[x + 'hi']).foohi (seq in {foo: 'bar'} %v*[x + 'hi']).foo seq in {foo: 'bar'} %v*![log(x)] The reason you can't use %k and %v with *~! is that *~! isn't a componentwise operator. Because it could return zero, one, or many values for each one that gets visited, it isn't clear what to do with the result. (One example of this is seq in value %v*~![[x, x + 'foo']] -- the intent is unclear.) Initialized forms. You can provide an initialization block to *~! to iterate without building up an array. For example, to find the 1024th root of a number: 5 *~![xi < 10][Math.sqrt(x)] -seq This is equivalent to using an unfold (see below) and selecting only the last element of the resulting array. Because no array is built, *~![] ends up being significantly faster. Hacking iteration. It's not hard to achieve a lot of flexibility with the seq macro. For example, you can manually increment xi to skip elements: [1, 2, 3, 4, 5] *[++xi /unless [x & 1], xs[xi]] -seq I don't exactly recommend that you do this, but under the hood the seq macro writes more or less normal-looking for loops and so is amenable to this kind of manipulation. Operator features. The sequence library uses operators to describe operations on arrays. Most of them are regular binary infix operators like + and *, though a few of them have names (such as ni[] above). Despite the wide array of operators supported, there is a high degree of regularity among them. Each operator that takes a block (like * does) has several options that can be set to change the way it interprets the block. Variable renaming. The sequence language lets you rename any variable by prefixing the [] with a new variable name: seq in [1, 2, 3] *y[y + 1] Function promotion. Caterwaul 1.1 adds the ability to implicitly promote functions by using them instead of a [] block. For example: seq in [1, 2, 3] *![log(x)] seq in [1, 2, 3] *!log Note that the function name is evaluated within the context of the loop, so it is re-evaluated each iteration. This could, in pathological cases, be beneficial; but mostly it's something to watch out for. Generally you should only use local variables, or simple permutations of them, as functions. Function promotion applies to all operators that take blocks. Filtering. The filtering family of operators is denoted by %. For instance, here's a way to get multiples of three: seq in [1, 2, 3] %[x % 3 === 0] Alternative forms. Negation is so high precedence that it's often difficult to work it into a form without adding parentheses. The alternative form of % negates the predicate: seq in [1, 2, 3] %![x % 3] The other alternative form of % is a simultaneous map/filter. The idea is to return the expression value when it's truthy and drop the element otherwise. For example, we can get the squares of all negative elements this way: seq in [1, -2, -3, 4] %~![x < 0 && x * x] Prefixes. Filter operations can all take the %k and %v prefixes. These remove and/or transform key-value mapping in objects. For example: seq in {foo: 'bar', bif: 'baz'} %k%[/foo/.test(x)] /pairs seq in {foo: 'bar', bif: 'baz'} %v%[/z/.test(x)] /pairs seq in {foo: 'bar', bif: 'baz'} %k%![/o/.test(x)] /pairs seq in {foo: 'bar', bif: 'baz'} %k%~![/o/.test(x) && x.replace(/o/g, 'a')] /pairs Using /pairs with prefixes isn't necessary in most cases. I've included it in these examples to better illustrate what's happening. Initialized forms. You can use an init-block with %~! to separate the selector and the map. For example: [1, 2, 3] %~![x & 1][x + 1] -seq This is faster than its equivalent, %[x & 1] *[x + 1]. Folding. You can fold stuff through a binary expression by using the / family of operators. / has two forms: left fold (the default), and right fold (written as /!). For example, here is how you might sum a bunch of numbers: seq in [1, 2, 3] /[x + x0] Since + is associative it doesn't matter which direction the fold goes. It becomes obvious, however, if we interpolate the values into a string: seq in [1, 2, 3] /['[#{x0}, #{x}]'] seq in [1, 2, 3] /!['[#{x}, #{x0}]'] Notice that for folding we have a new variable x0. x0 is always used as the accumulator; that is, the inductive step is x0 = f(x0, x). There are actually a few variables you have access to depending on what you're doing. Inside any block you'll have x, xi (the current index), xs (the current source collection), xr (the result collection), and xl (the length of the original sequence). Each of these changes uniformly if you rename the variable; so for instance: seq in [1, 2, 3] /bar[bar + bar0 + bari + barl + bars + barr] Prefixes. Sometimes you want to fold into an existing element. For example, suppose you want the sum of the squares of numbers from 1 to 10. This code won't work: seq in n[1, 11] /[x*x + x0*x0] The reason is that you can't square the accumulator. If you can't specify the starting element of the fold, the best you can do is to pre-map the elements and then sum them normally: seq in n[1, 11] *[x * x] /[x + x0] However, the other option is to specify the initial value for x0 by using the fold prefix: seq in n[1, 11] /[0][x0 + x*x] seq in n[1, 11] /![0][x0 + x*x] The fold prefix value is never interpreted in sequence context, even if you modify the body of the fold to do so. Unfolding. Sometimes it's useful to have anamorphic value generators. These are the opposite of folds: unfolds produce multiple values from one. For instance, summing the array [1, 2, 3, 4, 5] can be done using a fold over the + operator: [1, 2, 3, 4, 5] /[x + x0] -seq Similarly, generating the array [1, 2, 3, 4, 5] can be done using an unfold over the increment operator: 1 /~![x < 5 ? x + 1 : null] -seq If we treat the body of the unfold as a function f(x) = x < 5 ? x + 1 : null, then an unfold could be seen as [1, f(1), f(f(1)), f(f(f(1))), f(f(f(f(1))))]. The last element returns null, which tells the sequence library to stop unfolding. I recently received some great feedback from @Phlogistique regarding unfolding. He suggested that I could use an extra body to determine the condition rather than using null as a sentinel. This is a very elegant approach, and it's now implemented in version 1.1.5: 1 /~![x < 5][x + 1] -seq If you use two blocks, null can be generated from your unfolds and elements will be generated as long as the first block returns truthy values. Also, x0 is set to the value of the first block. So, for example: 1 /~![xi < 5 && 'woot'][x0] -seq Quantification. The sequence library provides existential quantification on arrays. This uses a block that acts as a predicate. So, for instance, to determine whether any element in an array is positive: [-4, -5, 10, 2] |[x > 0] |seq The | operator returns the first truthy value generated by the expression (not just true or false), so you can use it to detect things too. This block causes the sequence comprehension to return not only whether an element is positive, but if so the first such element will be returned: [-4, -5, 10, 2] |[x > 0 && x] |seq [-4, -5, 10, 2] |[x -when [x > 0]] |seq We can also use this construct to return the index of the first matching element. Because an index of 0 is falsy, we'll have to add one (so 0 is the not-found value rather than -1): [-4, -5, 10, 2] |[xi + 1 -when [x > 0]] |seq You can also ask whether something doesn't exist. This is done with the |! sequence operator. For example, to ask whether an array contains no negative numbers: [1, 2, 3, 4] |![x < 0] |seq Caterwaul 1.2 adds the |~! operator, which is identical to | but matches from the right instead of from the left. For example: [1, 2, 3] |[x & 1 && x] |seq [1, 2, 3] |~![x & 1 && x] |seq Combination. You can combine sequences via concatenation, written +: seq in [1, 2, 3] + [4, 5, 6] Objects. A really useful and important feature of the sequence library is that it works with objects very easily. It has four operators, /keys, /values, /pairs, and |object, that can convert between objects and arrays. You can pull an array of the keys or values of an object (not in any particular order of course) by using /keys and /values. For example: window /keys -seq jQuery /values -seq More interesting is the /pairs operator. This pulls out key-value pairs as two-element arrays: {foo: 'bar', bif: 'baz'} /pairs -seq Its inverse is the |object operator (also can be written as -object or /object, depending on what kind of precedence you want), which turns an array of those pairs back into an object: [['foo', 'bar'], ['bif', 'baz']] |object |seq [['foo', 'bar'], ['bif', 'baz']] -object -seq [['foo', 'bar'], ['bif', 'baz']] /object /seq Note the differing precedences of /keys etc. and |object. This is intentional. The rationale is that you rarely manipulate objects as objects in sequence comprehensions, since the sequence library has no useful operators for objects other than unpacking. Therefore, objects come from various other values and enter a sequence comprehension, which may at the very end zip an intermediate result into a final object return value. The alternative higher-precedence forms of object didn't exist before, but after some real-world use I've found it useful to be able to maintain a certain precedence level. Also note that when possible you should use %k and %v instead of packing and unpacking objects. These prefixes are faster and, in my experience, make the code easier to read. Caterwaul 1.2 introduces a new operator called /mobject (which can also be written as -mobject and |mobject). This is identical to /object, but groups values by key rather than assigning a single value per key. The result is that each key is mapped to an array of values. For example: m = [[1, 2], [1, 3], [2, 3]] /mobject -seq m[1] This behavior differs from /object, which just takes the last value: m = [[1, 2], [1, 3], [2, 3]] /object -seq m[1] Numerical iteration. Within a sequence comprehension you have access to the n[] operator, which generates arrays of evenly-spaced numbers. It has three uses. When invoked on one argument it returns integers between 0, inclusive, and the number, exclusive. When invoked with two arguments the first becomes the inclusive lower bound and the second is the exclusive upper bound. Adding a third argument changes the increment from its default value of 1. For example: n[10] -seq n[5, 8] -seq n[0, 1, 0.25] -seq n[0, -1, 0.25] -seq n[0, -1, -0.25] -seq Another similar operator is ni[], which behaves exactly like n[] except that it includes its upper bound. For instance: n[10] -seq ni[10] -seq n[1, 4] -seq ni[1, 4] -seq n[0, 1, 0.25] -seq ni[0, 1, 0.25] -seq Note that the usual floating-point caveats apply; for example: n[0, 1, 0.1] -seq ni[0, 1, 0.1] -seq These results are the same because of the inductive loops used in n and ni. If you need endpoint accuracy in floating-point situations, your best bet is to generate an integer sequence and map across a scaling factor: n[10] *[x * 0.1] -seq ni[10] *[x * 0.1] -seq DOM/jQuery driver. One of the benefits of promoting syntax into a first-class construct is that you can specialize certain syntactic constructs for library interoperation. Caterwaul provides a module that integrates jQuery-based DOM node construction right into the syntax of your program. (You can also write modules to do similar things for other client-side libraries.) For example: jquery in div.foo('hi there') In this example, jquery is a modifier that interprets its code as HAML-like DOM construction. The code above is translated into this: | jQuery('
').addClass('foo').append('' + ('hi there') + '') Nodes and classes. The example above illustrates the node and class syntax. The way Caterwaul sees this is that div is a node, and each dot-expression after it denotes a class. For example, div.foo.bar.bif creates a div with three classes. You can also create just plain elements; div creates an empty div element with no CSS classes. This DOM driver uses context to determine when a word should be interpreted as an element name. Importantly, it doesn't have a list of known elements that it knows to promote. So, for example, this is also perfectly valid code: jquery in foo.bar(bif) If you run this you'll get a <foo> node that contains an empty <bif> node. Appending children. If you invoke one node on another, you're telling the driver to add the "parameters" of the invocation as children. This is translated into an append call to jQuery. So, for example, div.foo(span.bar('hi there')) creates an anonymous span containing hi there, adds that to a span with the "bar" class, and adds that to a div with the "foo" class. The div is returned. For reasons that will shortly become apparent there is a lower-precedence way to represent appending. You can use the > operator to do the same thing as invocation. For example: jquery [div > p] Perhaps counterintuitively, chaining the > operator does not result in further nesting. This is because > left-associates, so div > p > pre would be interpreted as (div > p) > pre. This actually ends up being really convenient -- more so than if it did what it does in CSS, in my opinion. Appending other stuff. Because you can easily use functional abstraction over DOM nodes you'll probably end up factoring the creation of elements into a bunch of different functions. As a result, you'll end up calling those functions and wrapping some of the children in new nodes. The way to do this is to append stuff in a non-DOM context using [] instead of (): foo = jquery in div.foo jquery in div.container[foo] The low-precedence counterpart is >=, and like > it left-associates. You can also mix the two because its precedence is identical to >. In the "hi there" example at the top of this section I appended the string "hi there" (which was interpreted as a Javascript value, not as a node constructor) using parentheses rather than square brackets. The DOM driver has an exception for string values, since often you'll want to insert plain text between other nodes: jquery in div('foo', button, 'bar') There's also a much more sinister aspect to it, though. Firefox (and SpiderMonkey-based Javascript engines in general) rewrites your code at compile-time, before Caterwaul can see it. One of the optimizations it performs is constant-folding, which involves rewriting things of the form x['y'] to x.y whenever y is a valid identifier. As a result, if you write something like this: jquery in button['hi'] You will get the undesirable outcome <button class='hi'> in the generated code on Firefox. As a result you are always better off using () when there is text involved (as long as the text is a literal string, that is). Attributes and jQuery data. These can be setup by using the * operator. For example: jquery in a('google') *href('http://google.com') This invokes jQuery's attr() method on 'href', 'http://google.com'. A similar shorthand is provided for jQuery's data(): jquery in a('got data') *!foo('bar') This results in data('foo', 'bar'). The expression inside parentheses is evaluated in normal Javascript context. Arbitrary methods and event bindings. These are available by using the / operator. For instance: jquery in button /text('hi') The slash simply turns into a regular method call: $('<button>').text('hi'). Similar is the /! operator, which turns into a bind() call: jquery in button /!click(given.e in alert('hi')) Calling functions. One of the downsides of having a DSL for DOM node construction is that it's hard to call a function on a small piece of the structure. The DOM library addresses this by using the % operator to represent function invocation. For instance: says_hi(e) = e.text('hi there') jquery in button %says_hi This expands into say_hi($('<button>')). Sometimes you want to pass parameters into the function you're using. This is achieved by currying: says(thing)(e) = e.text(thing) jquery in button %says('click me') Side-effecting. Caterwaul 1.2.1r2 introduces the ability to use - as a modifier prefix inside a jQuery expression. For example: jquery in button -se [it.text('hi')] jquery in button[contents] -where [contents = 'woot'] Experimental extensions. These are things that I'm experimenting with before committing to the design. As such, they may change or be removed in the next few versions. (So use at your own peril, both due to volatility and because they might be useless.) Side-effects in function definitions. I was writing some parser combinators using curried functions, and realized that it would be useful to be able to do things in between function definitions. For example, consider this definition: f(x)(y) = x + y It would be nice to be able to ensure that x was a number as soon as we passed it to f, rather than waiting for it to also receive y. To do this, you can use a side-effect, introduced in Caterwaul 1.1.3: must_be_numeric(n) = n.constructor === Number || raise [new Error('#{n} is not a number')] f(x, must_be_numeric(x))(y) = x + y You can also side-effect on the result of the function. This can be useful if you want to attach metadata to intermediate invocations: f(x, result.toString() = 'f(#{x})')(y) = x + y f(5).toString() Infix function application. Haskell gives you a nice way to use a function as an infix operator: you use backticks (so x `f` y becomes f x y). This reduces an API designer's pressure to use operator overloading, since now there's another way to get the normal subject-verb-object ordering in expressions. Caterwaul has a couple of similar constructs, though they don't look very nice compared to Haskell. The first is a simple binary application, which looks like this: 1 /-log/ 2 1 |-log| 2 You can stack these up, as they associate leftward: 1 /-log/ 2 /-log/ 3 You can extend the invocation to take arbitrarily many arguments by adding them to the left and prefixing them with /: 1 / 2 / 3 /-log/ 4 1 | 2 | 3 |-log| 4 Naturally, this syntax is somewhat dangerous since it might collide with certain arithmetic expressions. Hopefully nobody will be dividing twice in a row, but it could happen. That's one of the reasons I consider it to be experimental. Infix method application. You can invoke a method in much the same way that you can invoke a function. To do this, use the ~ prefix: caterwaul /~parse/ log Like infix function application, this form can become variadic: foo = 'hi' bar = 'there' console /foo /~log/ bar Postfix function application. This is an alternative way to invoke functions. Rather than putting the function in the second-to-last position, you can put it after all of its arguments. This enables you to invoke functions with only one argument. Expressions of the form x /!f are converted to f(x): 100 /!log Prefix slashes are recognized as before: x /y /... /!f is converted to f(x, y, ...). Literal modifiers. There are two kinds of modifiers. Literal modifiers are used to modify literal values (such as strings and regular expressions). For example, Caterwaul provides a literal modifier called .x that removes spaces from regular expressions: /foo bar bif/.x All literal modifiers look like this; that is, they appear to be properties and aren't invoked. Caterwaul will only process literal modifiers that it knows about and that match literals of the right type. All of these expansions happen at compile-time, so there is no significant runtime impact of using them. Regular expression modifiers. The simplest regular expression modifier is .x. This lets you insert whitespace into your regular expressions without actually matching against whitespace; it's very similar to the 'x' flag in Perl or Ruby. /^\w+ @ \w+ \. com$/.x Another is .qf, short for "quote function". This causes the regular expression to be turned into a function that matches against strings (very similar to Perl's behavior): /foo/.qf String modifiers. Strings can be modified in several different ways: 'foo bar'.qw Here, .qw causes the string to be split into an array of words. You can put arbitrarily much whitespace between words, though the usual Javascript syntax rules apply. 'foo bar bif baz'.qh Similar to .qw, but a hash (object) is constructed instead of an array. Every other word is used as a key, and each following word is a value. '^http://'.qr Converts a string into a regular expression, but properly escapes the forward slashes. This is primarily for notational convenience, and has the caveat that some things that you'd do within regular expressions aren't allowed in strings. (For example, 'foo\[bar\]'.qr will fail in Javascript's initial parse because \[ and \] are invalid escape sequences.) 'x + 1'.qs This creates a reference to the syntax tree generated by parsing x + 1. It's rare that you'd use this unless you're writing macros. You can also use .qse as of Caterwaul 1.2.2. '_ + 1'.qf Constructs a function whose sole argument is _ and whose body is _ + 1. Code within the function is macroexpanded automatically, so you can do things like this: '_ + 1 -when._'.qf __ meta::template('comment', '\'\'; # A mechanism for line or block comments.'); meta::template('def', <<'__'); # Define a new template. Analogous to the C preprocessor's #define directive. # For example: # # - def foo x, y, z << end # hello $x, $y, and $z! # This is an ${x}message. # - end # # You can then use that template immediately: # # - foo 3, 4, 5 # # Interpolation is achieved by evaling a Perl heredoc; the usual caveats apply. # A variable called $body is automatically bound to the body contents if there # are any; for example: # # - def named x << end # name $x { # $body # } # - end # # - named 'foo' << end # woohoo # - end # # You can define a "plural" form like this: # # - def -p say_hi_to << end # Hi $_! # - end # # - say_hi_to Foo, Bar, Baz # # Plural forms still have the $body variable (which doesn't change across # arguments), but they can't take formal parameters. my ($options, $name, @args) = separate_options(split /\s+/, $_[0]); my @formals = map split(/,\s*/), @args; my $body = $_[1]; my $formal_list = join(', ', map "\$$_", @formals); my $parameter_bindings = "my ($formal_list) = split /,\\s*/, \$_[0]"; my $body_binding = "my \$body = \$_[1]"; my $heredoc_delimiter = state(); # This will work great unless you put large hexadecimal barewords on otherwise # blank lines in your code. At that point there's a 2^-128 chance that it will # bomb out horribly. my $plural = $$options{'--plural'} || $$options{'-p'}; die "- def $name: cannot use formal parameters with the -p option" if $plural && @formals; $plural ? meta::externalize "template::$name", "- def -p $name", eval "sub {\n$body_binding;\n" . "join \"\\n\", map <<$heredoc_delimiter, " . "split /,\\s*/, \$_[0];\n" . "$body\n" . "$heredoc_delimiter\n}" : meta::externalize "template::$name", "- def $name @formals", eval "sub {\n$parameter_bindings;\n$body_binding;\n" . "<<$heredoc_delimiter\n$body\n$heredoc_delimiter\n}"; die $@ if $@; # No output from this template. ''; __ meta::template('eval', <<'__'); my $result = eval $_[0]; terminal::warning("Error during template evaluation: $@") if $@; $result; __ meta::template('failing_conditional', <<'__'); my ($commands) = @_; my $should_return = $commands =~ / if (.*)$/ && ! eval $1; terminal::warning("eval of template condition failed: $@") if $@; $should_return; __ meta::template('include', <<'__'); my ($commands) = @_; return '' if template::failing_conditional($commands); join "\n", map retrieve($_), split /\s+/, $commands; __ meta::template('pinclude', <<'__'); # Just like the regular include, but makes sure to insert paragraph boundaries # (this is required for SDoc to function properly). my ($commands) = @_; return '' if template::failing_conditional($commands); my $text = join "\n\n", map retrieve($_), split /\s+/, $commands; "\n\n$text\n\n"; __ meta::template('script-include', <<'__'); my ($name) = @_; my $s = 'script'; my $script = retrieve($name); "<$s>\n$script\n"; __ meta::template('style-include', <<'__'); my ($name) = @_; my $s = 'style'; my $style = retrieve($name); "<$s>\n$style\n"; __ meta::vim_highlighter('caterwaul', <<'__'); " Caterwaul VIM highlighter | Spencer Tipping " Licensed under the terms of the MIT source code license " Language: Javascript with Caterwaul extensions " Maintainer: Spencer Tipping " URL: http://caterwauljs.org/build/caterwaul.vim if !exists("main_syntax") if version < 600 syntax clear elseif exists("b:current_syntax") finish endif let main_syntax = 'caterwaul' endif syn case match setlocal iskeyword=48-57,95,36,A-Z,a-z,@ syn region jsWaulComment start=/#/ end=/$/ syn region jsNodeShebang start=/\%^#!/ end=/$/ syn region jsParenGroup matchgroup=jsParen start=/(/ end=/)/ contains=js.* syn region jsBracketGroup matchgroup=jsBracket start=/\[/ end=/\]/ contains=js.* syn region jsBraceGroup matchgroup=jsBrace start=/{/ end=/}/ contains=js.* syn region jsTernary matchgroup=jsTernaryOperator start=/?/ end=/:/ contains=js\(ColonLHS\)\@!.* syn match jsOperator /[-+*^%&\|!~;=><,]\{1,4\}/ syn match jsDot /\./ syn keyword jsReservedToplevel if else switch while for do break continue return with case default try catch finally throw delete void syn keyword jsOperator in instanceof typeof new syn keyword jsBuiltinType Array Boolean Date Function Number Object String RegExp syn keyword jsBuiltinLiteral true false null undefined syn keyword jsBuiltinValue this arguments syn keyword jsPrototype prototype constructor syn match jsAssignment /\k\+\s*[-+*/^&|%<>]*=[^=]\@=/ contains=jsOperator syn match jsWordPrefix /[-\/|,<]\k\@=/ syn match jsIdentifier /[A-Za-z$_][A-Za-z0-9$@_]*/ contains=jcMetadata syn match jcMetadata /@[A-Za-z0-9$@_]*/ contained syn match jsWildcard /\<_[A-Za-z0-9$_@]\+\>/ syn match jsNumber /-\?0x[0-9A-Fa-f]\+\|-\?\(\d*\.\d\+\|\d\+\.\d*\|\d\+\)\([eE][+-]\?\d\{1,3\}\)\?\|-\?0[0-7]\+/ syn region jsStringD matchgroup=jsQuote start=/"/ skip=/\\\\\|\\"/ end=/"/ oneline keepend contains=jcStringEscape,jcCaterwaulEscape syn region jsStringS matchgroup=jsQuote start=/'/ skip=/\\\\\|\\'/ end=/'/ oneline keepend contains=jcStringEscape,jcCaterwaulEscape syn region jsRegexp matchgroup=jsQuote start=+/[^/ ]+rs=e-1 skip=+\\\\\|\\/+ end=+/[gims]*[^-~\+!\/A-Za-z0-9 #(\[{]\@=+ oneline contains=jcRegexpSpecial syn region jsCodeString matchgroup=jsCodeQuote start=/\z(['"]\)/ end=/\z1\.qf\>/ skip=/\\./ oneline keepend contains=js.* syn match jcCodeStringVariable /\<_\>/ containedin=jsCodeString contained syn match jcRegexpSpecial /\\[sSbBwWdDnr\\\[\]]\|[+*|?]\|\[\([^]\\\/]\|\\.\)\+\]/ contained syn match jcStringEscape /\\\d\{3\}\|\\u[0-9A-Za-z]\{4\}\|\\[a-z"'\\]/ contained syn region jcCaterwaulEscape start=/#{/ end=/}/ contained contains=js\(WaulComment\)\@!.* keepend syn match jsCaterwaulNumericHex /\/ syn match jsCaterwaulNumericBinary /\/ syn match jsColonLHS /\k\+\s*:/ syn region jsVarBinding matchgroup=jsVarBindingConstruct start=/\\|\/ end=/;/ contains=js.* syn match jsVarInBinding /var\s\+\k\+\s\+in/ contains=jcVarBindingKeyword,jsOperator syn region jsParamBinding matchgroup=jsBindingConstruct start=/\(function\|catch\)\s*(/ end=/)/ contains=jsOperator syn keyword jcVarBindingKeyword const var contained syn keyword jcBindingKeyword function catch contained syn match jcBindingAssignment /\k\+\s*=\([^=]\|$\)\@=/ contains=jsOperator contained containedin=jsVarBinding syn match jcExtraBindingAssignment /\k\+\s*\(=\([^=]\|$\)\@=\|(.*=\([^=]\|$\)\)\@=/ contained containedin=jcBindingGroup syn keyword jsBindingMacro where capture wcapture nextgroup=jcBindingGroup syn keyword jsFunctionMacro given bgiven nextgroup=jcFunctionGroup syn keyword jsQuotationMacro qs qse qc qce nextgroup=jcQuotationGroup syn keyword jsOtherMacro raise seq noexpand reexpand eval ahead bitwise syn keyword jsParameterizedMacro se re then and or when unless using rescue eq aeq oeq deq neq acq ocq dcq ncq nextgroup=jsModifierSuffix syn match jcModifierSuffix /[->]/ contained syn cluster jsMacro add=jsBindingMacro,jsFunctionMacro,jsQuotationMacro,jsOtherMacro syn match jsLiteralModifier /\.\(q[frwhs]\|qse\|x\)\>/ syn match jsSeqFilter /\/\(pairs\|keys\|values\)\>/ syn match jsSeqFilter /%[kv][\*%\/~!]/ syn match jsSeqFilter /[-\/|]m\?object\>/ syn region jcBindingGroup matchgroup=jsCaterwaulMacro start='\s*\[' end=']' contained contains=js.* syn region jcFunctionGroup matchgroup=jsCaterwaulMacro start='\s*\[' end=']' contained syn region jcQuotationGroup matchgroup=jsCaterwaulMacro start='\s*\[' end=']' contained contains=js.* syn match jcBindingGroup /\.\k\+/ contained syn match jcFunctionGroup /\.\k\+/ contained syn match jcParens /[()]/ contained syn match jsClosers /[\]})]/ syn match jsCaterwaulInfixFunction /\([|\/]\)[-~][^ \t\/|]\+\1/ syn match jsCaterwaulUnaryFunction +/![^ ,\]\)\}]\++ syn cluster jsCaterwaulHtmlOps contains=jcCaterwaulHtmlClass,jcCaterwaulHtmlSlash,jcCaterwaulHtmlMap,jcCaterwaulHtmlAttr,jcCaterwaulHtmlElement,jcCaterwaulHtmlParens syn cluster jsCaterwaulHtmlOps add=jcCaterwaulHtmlArray,jcCaterwaulHtmlSlashB,jcCaterwaulHtmlAttrB,jcCaterwaulHtmlPlus,jcCaterwaulHtmlContains syn region jsCaterwaulHtmlPrefix1 matchgroup=jsCaterwaulMacro start=/\\s*/ contained nextgroup=@jsCaterwaulHtmlOps syn region jcCaterwaulHtmlParens matchgroup=jcParens start=/(/ end=/)/ contained nextgroup=@jsCaterwaulHtmlOps containedin=@jsCaterwaulHtmlGroups contains=jsCaterwaulHtmlElement,jsStringS,jsStringD syn region jcCaterwaulHtmlArray matchgroup=jcParens start=/\[/ end=/]/ contained nextgroup=@jsCaterwaulHtmlOps containedin=@jsCaterwaulHtmlGroups contains=js.* syn keyword jcCaterwaulHtmlElement html head body meta script style link title div a span input button textarea option contained containedin=@jsCaterwaulHtmlGroups nextgroup=@jsCaterwaulHtmlOps syn keyword jcCaterwaulHtmlElement table tbody tr td th thead tfoot img h1 h2 h3 h4 h5 h6 li ol ul noscript p pre samp contained containedin=@jsCaterwaulHtmlGroups nextgroup=@jsCaterwaulHtmlOps syn keyword jcCaterwaulHtmlElement blockquote select form label iframe sub sup var code caption canvas audio video contained containedin=@jsCaterwaulHtmlGroups nextgroup=@jsCaterwaulHtmlOps syn region jsBlockComment start=+/\*+ end=+\*/+ contains=@Spell,jsCommentTags syn region jsLineComment start=+//+ end=+$+ contains=@Spell,jsCommentTags syn keyword jcCommentTags TODO FIXME XXX TBD contained syn sync fromstart if main_syntax == "caterwaul" syn sync ccomment javaScriptComment endif hi def link jsNodeShebang Special hi def link jsClosers Error hi def link jsCaterwaulNumericHex Number hi def link jsCaterwaulNumericBinary Number hi def link jcCaterwaulHtmlElement Keyword hi def link jcCaterwaulHtmlClass Special hi def link jcCaterwaulHtmlClassName Type hi def link jcCaterwaulHtmlSlash Special hi def link jcCaterwaulHtmlSlashB Special hi def link jcCaterwaulHtmlMap Special hi def link jcCaterwaulHtmlAttr Special hi def link jcCaterwaulHtmlAttrB Special hi def link jcCaterwaulHtmlPlus Special hi def link jcCaterwaulHtmlContains Special hi def link jsCaterwaulHtmlPrefix2 Special hi def link jsCaterwaulSeqVariable Identifier hi def link jsCaterwaulUnaryLeftOp Special hi def link jsCaterwaulComplexOp Special hi def link jsCaterwaulOperatorFn Special hi def link jsCaterwaulMacro Special hi def link jsCaterwaulInfixFunction Type hi def link jsCaterwaulUnaryFunction Type hi def link jsLiteralModifier Special hi def link jsSeqFilter Special hi def link jsWordPrefix Special hi def link jsParameterizedMacro Special hi def link jsModifierSuffix Special hi def link jsBindingMacro Special hi def link jsFunctionMacro Special hi def link jsOtherMacro Special hi def link jsQuotationMacro Special hi def link jcFunctionGroup Identifier hi def link jcQuotationGroup String hi def link jsWaulComment Comment hi def link jsLineComment Comment hi def link jsBlockComment Comment hi def link jsCommentTags Todo hi def link jsCodeQuote Special hi def link jcCodeStringVariable Identifier hi def link jsQuote Special hi def link jsNumber Number hi def link jsStringS String hi def link jsStringD String hi def link jsRegexp String hi def link jsRegexpEscape Special hi def link jcRegexpSpecial Special hi def link jcStringEscape Special hi def link jcCaterwaulEscape Special hi def link jsColonLHS Type hi def link jsAssignment Type hi def link jsParen Special hi def link jcParens Special hi def link jsBracket Special hi def link jsBrace Special hi def link jsParenCloseError Error hi def link jsBracketCloseError Error hi def link jsBraceCloseError Error hi def link jsTernaryOperator Special hi def link jsVarInBinding Type hi def link jcVarBindingKeyword Keyword hi def link jsVarBindingConstruct Keyword hi def link jsBindingConstruct Special hi def link jcBindingKeyword Keyword hi def link jcBindingAssignment Type hi def link jcExtraBindingAssignment Identifier hi def link jsParamBinding Identifier hi def link jsReservedToplevel Keyword hi def link jsOperator Keyword hi def link jsDot Special hi def link jsBuiltinType Type hi def link jsBuiltinLiteral Special hi def link jsBuiltinValue Special hi def link jsPrototype Special hi def link jsWildcard Identifier hi def link jcMetadata Type let b:current_syntax = "caterwaul" if main_syntax == 'caterwaul' unlet main_syntax endif __ internal::main(); __DATA__