#!/usr/bin/perl # Run perldoc on this file for documentation. $|++; my %data; my %transient; my %externalized_functions; my %datatypes; my %locations; # Maps eval-numbers to attribute names 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', <<'__25976e07665878d3fae18f050160343f'); # 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; } __25976e07665878d3fae18f050160343f meta::meta('externalize', <<'__9141b4e8752515391385516ae94b23b5'); # Function externalization. Data types should call this method when defining a function # that has an external interface. sub meta::externalize { my ($name, $attribute, $implementation) = @_; $externalized_functions{$name} = $attribute; *{"::$name"} = $implementation || $attribute; } __9141b4e8752515391385516ae94b23b5 meta::meta('functor::editable', <<'__e3d2ede6edf65ffe2123584b2bd5dab7'); # 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)}}} __e3d2ede6edf65ffe2123584b2bd5dab7 meta::meta('type::alias', <<'__28fe15dd61f4902ed5180d8604d15d97'); meta::configure 'alias', inherit => 0; 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(@_)); }; }; __28fe15dd61f4902ed5180d8604d15d97 meta::meta('type::bootstrap', <<'__297d03fb32df03b46ea418469fc4e49e'); # 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; meta::define_form 'bootstrap', sub {}; __297d03fb32df03b46ea418469fc4e49e meta::meta('type::cache', <<'__52eac0d7b550a358cc86803fe1a9d921'); meta::configure 'cache', inherit => 0; meta::define_form 'cache', \&meta::bootstrap::implementation; __52eac0d7b550a358cc86803fe1a9d921 meta::meta('type::cached_dependency', <<'__e9455b403cbff27bbcc41d917fef482f'); meta::configure 'cached_dependency', inherit => 0, extension => ''; meta::define_form 'cached_dependency', \&meta::bootstrap::implementation; __e9455b403cbff27bbcc41d917fef482f meta::meta('type::configuration', <<'__d67e10a128e6b1d958c5b9d3bbe25aa4'); 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 /:/o && ! /^\h*#/o && ! /^\h*$/o, split(/\v+/o, $data); s/^\h+//o for @options; my @key_values = map split(/\s*:\s*/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; }; __d67e10a128e6b1d958c5b9d3bbe25aa4 meta::meta('type::data', 'meta::functor::editable \'data\', extension => \'\', inherit => 0, default => \'cat\';'); meta::meta('type::function', <<'__d93b3cc15693707dac518e3d6b1f5648'); meta::configure 'function', extension => '.pl', inherit => 1; meta::define_form 'function', sub { my ($name, $value) = @_; meta::externalize $name, "function::$name", meta::eval_in("sub {\n$value\n}", "function::$name"); }; __d93b3cc15693707dac518e3d6b1f5648 meta::meta('type::hook', <<'__f55a3f728ddfb90204dff3fe5d86845c'); meta::configure 'hook', extension => '.pl', inherit => 0; meta::define_form 'hook', sub { my ($name, $value) = @_; *{"hook::$name"} = meta::eval_in("sub {\n$value\n}", "hook::$name"); }; __f55a3f728ddfb90204dff3fe5d86845c meta::meta('type::inc', <<'__c95915391b969734305f2f492d5ca8e3'); meta::configure 'inc', inherit => 1, extension => '.pl'; 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; } }; __c95915391b969734305f2f492d5ca8e3 meta::meta('type::internal_function', <<'__34abb44c67c7e282569e28ef6f4d62ab'); meta::configure 'internal_function', extension => '.pl', inherit => 1; meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = meta::eval_in("sub {\n$value\n}", "internal_function::$name"); }; __34abb44c67c7e282569e28ef6f4d62ab meta::meta('type::js', 'meta::functor::editable \'js\', extension => \'.js\', inherit => 1;'); meta::meta('type::library', <<'__b6dd78120e6d787acdb5c1629f7f1896'); meta::configure 'library', extension => '.pl', inherit => 1; meta::define_form 'library', sub { my ($name, $value) = @_; meta::eval_in($value, "library::$name"); }; __b6dd78120e6d787acdb5c1629f7f1896 meta::meta('type::message_color', <<'__794bf137c425293738f07636bcfb5c55'); meta::configure 'message_color', extension => '', inherit => 1; meta::define_form 'message_color', sub { my ($name, $value) = @_; terminal::color($name, $value); }; __794bf137c425293738f07636bcfb5c55 meta::meta('type::meta', <<'__640f25635ce2365b0648962918cf9932'); # 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; __640f25635ce2365b0648962918cf9932 meta::meta('type::note', 'meta::functor::editable \'note\', extension => \'.sdoc\', inherit => 0, default => \'edit\';'); meta::meta('type::parent', <<'__607e9931309b1b595424bedcee5dfa45'); meta::define_form 'parent', \&meta::bootstrap::implementation; meta::configure 'parent', extension => '', inherit => 1; __607e9931309b1b595424bedcee5dfa45 meta::meta('type::retriever', <<'__6e847a9d205e4a5589765a3366cdd115'); meta::configure 'retriever', extension => '.pl', inherit => 1; meta::define_form 'retriever', sub { my ($name, $value) = @_; $transient{retrievers}{$name} = meta::eval_in("sub {\n$value\n}", "retriever::$name"); }; __6e847a9d205e4a5589765a3366cdd115 meta::meta('type::sdoc', <<'__392c65eddae300e2aa67014b85884979'); # 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'; }; __392c65eddae300e2aa67014b85884979 meta::meta('type::state', <<'__c1f29670be26f1df6100ffe4334e1202'); # 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'; meta::define_form 'state', \&meta::bootstrap::implementation; __c1f29670be26f1df6100ffe4334e1202 meta::meta('type::template', <<'__25f4d6eafb1d3eea6d5d3d9a71a5623e'); 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"); }; __25f4d6eafb1d3eea6d5d3d9a71a5623e meta::meta('type::watch', 'meta::functor::editable \'watch\', prefix => \'watch::\', inherit => 1, extension => \'.pl\', default => \'cat\';'); meta::alias('e', 'edit sdoc::js::core/caterwaul'); meta::alias('eb', 'edit sdoc::js::core/caterwaul.behaviors'); meta::alias('ec', 'edit sdoc::js::core/caterwaul.compiler'); meta::alias('ef', 'edit sdoc::js::core/caterwaul.configuration'); meta::alias('em', 'edit sdoc::js::core/caterwaul.macroexpander'); meta::alias('emj', 'edit sdoc::js::core/caterwaul.macroexpander.jit'); meta::alias('emjp', 'edit sdoc::js::core/caterwaul.macroexpander.jit-probabilistic'); meta::alias('emn', 'edit sdoc::js::core/caterwaul.macroexpander.naive'); meta::alias('ep', 'edit sdoc::js::core/caterwaul.parser'); meta::alias('et', 'edit sdoc::js::core/caterwaul.tree'); meta::alias('eu', 'edit sdoc::js::core/caterwaul.utilities'); meta::alias('me', 'macroexpand'); meta::alias('meopt', 'macroexpander-optimization'); meta::alias('rloc', 'loc js::(?!.*test|.*format|unit|minify)'); meta::bootstrap('html', <<'__aa347c4339e71e7acc9ea4fc3d593347'); __aa347c4339e71e7acc9ea4fc3d593347 meta::bootstrap('initialization', <<'__8774229a1a0ce7fd056d81ba0b077f79'); #!/usr/bin/perl # Run perldoc on this file for documentation. $|++; my %data; my %transient; my %externalized_functions; my %datatypes; my %locations; # Maps eval-numbers to attribute names 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")}; __8774229a1a0ce7fd056d81ba0b077f79 meta::bootstrap('perldoc', <<'__c63395cbc6f7160b603befbb2d9b6700'); =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 __c63395cbc6f7160b603befbb2d9b6700 meta::cache('parent-identification', <<'__c1b42b7ba418cde40af0086ff0ee16f1'); /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/sdoc a1e8480e579614c01dabeecf0f963bcc notes a9e5975593ed5d90d943ad98405c71e5 object 99aeabc9ec7fe80b1b39f5e53dc7e49e preprocessor 70dae4b46eb4e06798ec6f38d17d4c7b __c1b42b7ba418cde40af0086ff0ee16f1 meta::configuration('dependencies', <<'__e6452f707a2f0de738e51033b8e9d792'); # Named dependencies: #caterwaul.all.js: http://spencertipping.com/caterwaul/caterwaul.all.min.js #montenegro.server.js: http://spencertipping.com/montenegro/montenegro.server.js __e6452f707a2f0de738e51033b8e9d792 meta::data('author', 'Spencer Tipping'); meta::data('default-action', 'shell'); meta::data('edit::no-save', '1'); meta::data('libraries', <<'__cca38bdfe6664615b581ec6dff43cf46'); # URLs of libraries to be downloaded into the lib/ directory. http://spencertipping.com/caterwaul/caterwaul.all.js http://spencertipping.com/montenegro/montenegro.server.js __cca38bdfe6664615b581ec6dff43cf46 meta::data('license', <<'__3c6177256de0fddb721f534c3ad8c0ee'); 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. __3c6177256de0fddb721f534c3ad8c0ee 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('alias', <<'__28744564997657da45ab16cd5b441104'); my ($name, @stuff) = @_; return ls('-a', '^alias::') unless defined $name; @stuff ? around_hook('alias', @_, sub {associate("alias::$name", join ' ', @stuff)}) : retrieve("alias::$name") || "Undefined alias $name"; __28744564997657da45ab16cd5b441104 meta::function('bench-load', <<'__aba933cafd3b2b290fc4a2d36972911e'); # Just loads the standard library. render(); my $all = grep /^-a$/, @_; terminal::info('loading with node'); bench(sub {node([qw|js::core/debug/profile.start caterwaul.all.js js::core/debug/profile.stop|])}); if ($all) { terminal::info('loading with gjs'); bench(sub {gjs([qw|js::core/debug/profile.start caterwaul.all.js js::core/debug/profile.stop|])}); } __aba933cafd3b2b290fc4a2d36972911e meta::function('cat', 'join "\\n", retrieve(@_);'); meta::function('cc', <<'__c4d52b1d8f52a480f07b81b93c3aac7b'); # 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'); __c4d52b1d8f52a480f07b81b93c3aac7b meta::function('ccc', 'rm(\'data::current-continuation\');'); meta::function('child', <<'__f69646398c3123d3d939a7f2b3156606'); around_hook('child', @_, sub { my ($child_name) = @_; clone($child_name); enable(); qx($child_name update-from $0 -n); disable()}); __f69646398c3123d3d939a7f2b3156606 meta::function('cloc', 'loc(\'modules/caterwaul\\.(?!format)[^/]+$\', \'caterwaul$\')'); meta::function('clone', <<'__54e00ff2103e54423d4c9febb97ce063'); for (grep length, @_) { around_hook('clone', $_, sub { hypothetically(sub { rm('data::permanent-identity'); file::write($_, serialize(), noclobber => 1); chmod(0700, $_)})})} __54e00ff2103e54423d4c9febb97ce063 meta::function('cp', <<'__e5fee448a74ecbf4ae215e6b43dfc048'); my $from = shift @_; my $value = retrieve($from); associate($_, $value) for @_; __e5fee448a74ecbf4ae215e6b43dfc048 meta::function('create', <<'__090c342a2dc304b39c643d53350474a0'); 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}); __090c342a2dc304b39c643d53350474a0 meta::function('ct', 'create("sdoc::js::modules/caterwaul.$_[0].test/$_[1]");'); meta::function('current-state', <<'__d83ae43551c0f58d1d0ce576402a315a'); my @valid_keys = grep ! /^state::/, sort keys %data; my @ordered_keys = (grep(/^meta::/, @valid_keys), grep(! /^meta::/, @valid_keys)); join "\n", map serialize_single($_), @ordered_keys; __d83ae43551c0f58d1d0ce576402a315a meta::function('disable', 'hook(\'disable\', chmod_self(sub {$_[0] & 0666}));'); meta::function('dupdate', <<'__1c0273217c5b9f2756bb14a4a00aa7e2'); # Update the repository based on the dependencies it lists. use LWP::Simple (); rm(grep /^cached_dependency::/, keys %data); my %dependencies = dependencies(); for (keys %dependencies) { terminal::info("Retrieving $dependencies{$_} as $_"); associate("cached_dependency::$_", LWP::Simple::get($dependencies{$_}))} __1c0273217c5b9f2756bb14a4a00aa7e2 meta::function('edit', <<'__36bd3f2c1165d0c041b0fa9a96a85378'); my ($name, %options) = @_; my $extension = extension_for($name); 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') || exists $options{'-f'}; around_hook('edit', @_, sub { associate($name, invoke_editor_on($data{$name} || "# Attribute $name", %options, attribute => $name, extension => $extension), execute => $name !~ /^bootstrap::/)}); save() unless $data{'data::edit::no-save'}; ''; __36bd3f2c1165d0c041b0fa9a96a85378 meta::function('enable', 'hook(\'enable\', chmod_self(sub {$_[0] | $_[0] >> 2}));'); meta::function('export', <<'__388e0cc60507443cb1c0cc3e2658cfef'); # 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(@_)); __388e0cc60507443cb1c0cc3e2658cfef meta::function('extern', '&{$_[0]}(retrieve(@_[1 .. $#_]));'); meta::function('gjs', <<'__8c86893807ad07373b300cd5c6ff8878'); # 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); }); __8c86893807ad07373b300cd5c6ff8878 meta::function('grep', <<'__f2f28b81f4bcf4cf9fbe9e27bc9447b0'); # 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, @m_line_numbers, @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[$_] // ''}} if ($$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]); __f2f28b81f4bcf4cf9fbe9e27bc9447b0 meta::function('hash', 'fast_hash(@_);'); meta::function('hook', <<'__d74d8e2b611342af6a0897e0bd62e6e6'); my ($hook, @args) = @_; $transient{active_hooks}{$hook} = 1; dangerous('', sub {&$_(@args)}) for grep /^hook::${hook}::/, sort keys %data; @args; __d74d8e2b611342af6a0897e0bd62e6e6 meta::function('hooks', 'join "\\n", sort keys %{$transient{active_hooks}};'); meta::function('identity', 'retrieve(\'data::permanent-identity\') || associate(\'data::permanent-identity\', fast_hash(rand() . name() . serialize()));'); meta::function('import', <<'__ac86cbe9c9fb12fc8cef2cc88e80c01e'); my $name = pop @_; associate($name, @_ ? join('', map(file::read($_), @_)) : join('', )); __ac86cbe9c9fb12fc8cef2cc88e80c01e meta::function('import-bundle', <<'__4c7139ed5c9f65f38a33cf8f8a6cae27'); eval join '', ; die $@ if $@; __4c7139ed5c9f65f38a33cf8f8a6cae27 meta::function('initial-state', '$transient{initial};'); meta::function('is', <<'__1d3401965e4720ed972470b54b447db0'); my ($attribute, @criteria) = @_; my ($options, @stuff) = separate_options(@criteria); attribute_is($attribute, %$options); __1d3401965e4720ed972470b54b447db0 meta::function('line', <<'__9fbecf2ffad502e3c9502d14a81ec216'); # Prints a line, with some context, from caterwaul.all.js. This is useful when a test fails. my @lines = split /\n/, retrieve('pp::js::modules/caterwaul.all'); my ($line) = @_; for ($line - 5 .. $line + 5) { print "\033[1;32m" if $_ == $line; printf "%04d: %s\n", $_, $lines[$_ - 1]; print "\033[0;0m" if $_ == $line; } __9fbecf2ffad502e3c9502d14a81ec216 meta::function('load-state', <<'__ea18db867bd62a294e067f60e6975dcf'); 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()}); __ea18db867bd62a294e067f60e6975dcf meta::function('load-time', <<'__599109fa7c6ded3f10c9723dc2c1f53b'); render(); terminal::info('benchmarking GJS load time (SpiderMonkey)'); terminal::info('loading caterwaul.all.js'); bench(sub {sh('gjs caterwaul.all.js > /dev/null 2>&1')}); terminal::info('loading caterwaul.all.min.js'); bench(sub {sh('gjs caterwaul.all.min.js > /dev/null 2>&1')}); terminal::info('', 'benchmarking node load time (V8)'); terminal::info('loading caterwaul.all.js'); bench(sub {sh('node caterwaul.all.js > /dev/null 2>&1')}); terminal::info('loading caterwaul.all.min.js'); bench(sub {sh('node caterwaul.all.min.js > /dev/null 2>&1')}); __599109fa7c6ded3f10c9723dc2c1f53b meta::function('loc', <<'__36e0cabf1fe1c1bcaa3c8c708bd82ca0'); # Counts SLOC, whitespace, and total LOC in the codebase. hook('before-loc', @_); my $criteria = join '|', @_; my @attributes = grep s/^sdoc:://, select_keys('--criteria' => $criteria); my $tcomments = 0; my $twhitespace = 0; my $tsource = 0; my $line = sub { my ($source, $whitespace, $comments, $name) = @_; $source ||= 1; # Prevent divide-by-zero errors sprintf "%5d total, %4d SLOC, %5d[%4d%%] whitespace, %5d[%4d%%] comment [%s]", $source + $whitespace + $comments, $source, $whitespace, int($whitespace / $source * 100), $comments, int($comments / $source * 100), $name}; my $loc = sub { my @lines = map split(/\n/, $_), retrieve($_[0]); $tcomments += (my $comments = grep /^\s*\/\// || /^\s*#/, @lines); $twhitespace += (my $whitespace = grep /^\s*$/, @lines); $tsource += (my $source = @lines - $comments - $whitespace); &$line($source, $whitespace, $comments, $_[0])}; terminal::info(map &$loc($_), @attributes); terminal::info(&$line($tsource, $twhitespace, $tcomments, 'total')); hook('after-loc', @_); __36e0cabf1fe1c1bcaa3c8c708bd82ca0 meta::function('lock', 'hook(\'lock\', chmod_self(sub {$_[0] & 0555}));'); meta::function('ls', <<'__992087a0d63d41e0bb551803476cfe72'); my ($options, @criteria) = separate_options(@_); my ($all, $shadows, $sizes, $flags, $hashes) = @$options{qw(-a -s -z -l -h)}; $sizes ||= $flags; return table_display([grep ! exists $data{$externalized_functions{$_}}, sort keys %externalized_functions]) if $shadows; my $criteria = join('|', @criteria); my @definitions = select_keys('--criteria' => $criteria, %$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($_))), @{$all ? \@definitions : \@internals} if $sizes; my @hashes = map fast_hash(retrieve($_)), @definitions if $hashes; my %flag_hashes = map {$_ => {map {$_ => 1} select_keys("-$_" => 1)}} qw(m u i) if $flags; my @flags = map {my $k = $_; join '', map($flag_hashes{$_}{$k} ? $_ : '-', sort keys %flag_hashes)} @definitions if $flags; join "\n", map strip($_), split /\n/, table_display($all ? [@definitions] : [grep length, @externals], $sizes ? ([@sizes]) : (), $hashes ? ([@hashes]) : (), $flags ? ([@flags]) : ()); __992087a0d63d41e0bb551803476cfe72 meta::function('ls-a', 'ls(\'-ad\', @_);'); meta::function('m', <<'__3167c4c7bc087a3b0d524cada827fe55'); my @modules = select_keys('--criteria' => "sdoc::js::modules/caterwaul.$_[0]"); edit($modules[0]); __3167c4c7bc087a3b0d524cada827fe55 meta::function('macroexpand', <<'__ba8d14b1caf169f49834baa8018001c0'); # Macroexpands stuff. node(['caterwaul.all.js', 'modules/caterwaul.format.js', 'js::macroexpander'], @_); __ba8d14b1caf169f49834baa8018001c0 meta::function('minify', <<'__483938b52dbc38c3aed97bf1b524b436'); terminal::info("minifying $_[0]"); node([qw|js::core/debug/profile pp::js::core/caterwaul js::minify|], @_); __483938b52dbc38c3aed97bf1b524b436 meta::function('mv', <<'__52e95180e3c7019116bd798e0da0fdda'); my ($from, $to) = @_; die "'$from' does not exist" unless exists $data{$from}; associate($to, retrieve($from)); rm($from); __52e95180e3c7019116bd798e0da0fdda meta::function('name', <<'__6848cbc257e4b6d7441b25acb04e23c9'); my $name = $0; $name =~ s/^.*\///; $name; __6848cbc257e4b6d7441b25acb04e23c9 meta::function('node', <<'__3522fde8f76947a5f70bca33f1ee0016'); # Runs node on a collection of source files and arguments. The format is: # node([@source_strings], @process_args); my ($sources, @args) = @_; with_exported(@$sources, sub { hook('before-node', $_[0], @args); sh('node', $_[0], @args); hook('after-node', $_[0], @args); }); __3522fde8f76947a5f70bca33f1ee0016 meta::function('node-custom', <<'__c2f4063798c997ec7f78a3543b1240b3'); # Runs node on a collection of source files and arguments. The format is: # &{'node-custom'}([@source_strings], [@node_arguments], @process_args); my ($sources, $node_args, @args) = @_; with_exported(@$sources, sub { hook('before-node-custom', @$node_args, $_[0], @args); sh('node', @$node_args, $_[0], @args); hook('after-node-custom', @$node_args, $_[0], @args); }); __c2f4063798c997ec7f78a3543b1240b3 meta::function('note', <<'__bcbfeac6dd2112f47296265444570a6e'); # Creates a note with a given name, useful for jotting things down. create("note::$_[0]"); __bcbfeac6dd2112f47296265444570a6e meta::function('notes', 'ls(\'-a\', \'^note::\');'); meta::function('parents', 'join "\\n", grep s/^parent:://o, sort keys %data;'); meta::function('parse', <<'__d74e1dc365978bc2ce245d9c4d22e123'); # Parses stuff. node(['caterwaul.all.js', 'modules/caterwaul.format.js', 'js::parser'], @_); __d74e1dc365978bc2ce245d9c4d22e123 meta::function('perl', <<'__986a274c013b77fe08d29726ce3799fe'); my $result = eval(join ' ', @_); $@ ? terminal::error($@) : $result; __986a274c013b77fe08d29726ce3799fe meta::function('preprocess', <<'__66e539d29e9afa903569efad0eb7c886'); # 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; __66e539d29e9afa903569efad0eb7c886 meta::function('reload', 'around_hook(\'reload\', sub {execute($_) for grep ! /^bootstrap::/, keys %data});'); meta::function('render', <<'__d11b6e7522980db1022b1ccd575bdc30'); hook('before-render'); file::write(attribute($_) . '.js', retrieve($_), mkpath => 1) for grep s/^sdoc::js::/js::/ || /^js::/, keys %data; file::write(attribute(attribute($_)) . '.js.sdocp', retrieve($_), mkpath => 1) for grep s/^sdoc::js::/sdocp::js::/, keys %data; hook('after-render'); __d11b6e7522980db1022b1ccd575bdc30 meta::function('rm', <<'__7cecfb1691a7bf86741f00058bcc54ca'); around_hook('rm', @_, sub { exists $data{$_} or terminal::warning("$_ does not exist") for @_; delete @data{@_}}); __7cecfb1691a7bf86741f00058bcc54ca meta::function('run-forever', <<'__76175932a2d2692fc802856c28d0848d'); # Runs your application indefinitely, restarting each time it fails. # There's a one-second delay between restarts to prevent a tight loop. # Takes one argument, which is the function to run forever. my ($f, @args) = @_; hook('bin/before-run-forever'); &$f(@args) while sleep 0.1 && ! -f 'stop'; hook('bin/after-run-forever'); __76175932a2d2692fc802856c28d0848d meta::function('save', 'around_hook(\'save\', sub {dangerous(\'\', sub {file::write($0, serialize()); $transient{initial} = state()}) if verify()});'); meta::function('save-state', <<'__863e4d9fa75ca198ef7a755248d1002a'); # 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)}); __863e4d9fa75ca198ef7a755248d1002a meta::function('sdoc', <<'__060cfa349e629eb90a82b87a8ba00c1d'); # 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 /|; # 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 $_$e", split /\n/, $text)} sub paragraphs {map split(/(\n{2,})/, $_), @_} my ($filename) = @_; # 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) = $filename =~ /\.sdoc$/io ? $filename =~ /\.(\w+)\.sdoc$/igo : $filename =~ /\.(\w+)$/igo; my ($other_extension) = extension_for(attribute($filename)); $other_extension =~ s/^\.//o; my ($start, $end) = split /,/o, $comments_for_extension{lc($other_extension || $extension)}; join '', map(is_code($_) || is_blank($_) ? ($_ =~ /^\s*c\n(.*)$/so ? $1 : $_) : comment($_, $start, $end), paragraphs retrieve($filename)), "\n$start $generated_string $end\n"; __060cfa349e629eb90a82b87a8ba00c1d meta::function('sdoc-html', <<'__5fffe6139a81ff67ee063ebcbfadad2a'); # 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]; 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>)/</g}; my $code = sub {&$escape_all(); &$unindent(); push @markup, &$indent() . "
$_
"}; my $quoted = sub {&$escape_all(); &$unindent(); s/^\|\s?//; 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") - 10 < length($3); &$paragraph(); } &$close_section($section_level) while $section_level--; join "\n", @markup; __5fffe6139a81ff67ee063ebcbfadad2a meta::function('sdocp', <<'__8b7ed5bbd537234ae53c0691b6d02c97'); # 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) . "');"; __8b7ed5bbd537234ae53c0691b6d02c97 meta::function('serialize', <<'__5148e8ca46eeb3e297f76d098e496bcf'); my ($options, @criteria) = separate_options(@_); my $partial = $$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 : [retrieve('bootstrap::initialization'), @attributes, 'internal::main();', '', '__END__']}; join "\n", @final_array; __5148e8ca46eeb3e297f76d098e496bcf meta::function('serialize_single', <<'__ef0f63556d22816ed102d3bbe2172b28'); # Serializes a single attribute and optimizes for content. my $name = $_[0] || $_; my $contents = $data{$name}; my $meta_function = 'meta::' . namespace($name); my $invocation = attribute($name); my $escaped = $contents; $escaped =~ s/\\/\\\\/go; $escaped =~ s/'/\\'/go; return "$meta_function('$invocation', '$escaped');" unless $escaped =~ /\v/; my $delimiter = '__' . fast_hash($contents); return "$meta_function('$invocation', <<'$delimiter');\n$contents\n$delimiter"; __ef0f63556d22816ed102d3bbe2172b28 meta::function('sh', 'system(@_);'); meta::function('shell', <<'__dc8238ad70b1e02eaf230c4f1bd21690'); terminal::cc(retrieve('data::current-continuation')) if length $data{'data::current-continuation'}; shell::repl(); __dc8238ad70b1e02eaf230c4f1bd21690 meta::function('size', 'length(serialize());'); meta::function('snapshot', <<'__d3d84a364524eeb8ee90623f545187e8'); my ($name) = @_; file::write(my $finalname = temporary_name($name), serialize(), noclobber => 1); chmod 0700, $finalname; hook('snapshot', $finalname); __d3d84a364524eeb8ee90623f545187e8 meta::function('state', <<'__119111f84c3e32a5536838ac84bc6f10'); my @keys = sort keys %data; my $hash = fast_hash(fast_hash(scalar @keys) . join '|', @keys); $hash = fast_hash("$data{$_}|$hash") for @keys; $hash; __119111f84c3e32a5536838ac84bc6f10 meta::function('t', <<'__f23723801f5d89769169e40894b9dba6'); my @tests = select_keys('--criteria' => "sdoc::js::.*$_[0].*test/$_[1]"); edit($tests[0]); __f23723801f5d89769169e40894b9dba6 meta::function('test', <<'__4338344e86c1627fc556a7d8289a6c4c'); # Unit tests for caterwaul. # In the past this was done differently. It would run node once for each test, which involved a lot of startup overhead and took a while. Now the tests are all run in a single file, so node # gets run just once. This should be significantly faster. my ($options, @args) = separate_options(@_); my @tests = grep $_ !~ /^js::test\/lib/, grep s/^sdoc:://, select_keys('--criteria' => "^sdoc::js::.*$args[0]\\.?test/$args[1]"); render(); terminal::info('running ' . join(' ', @tests)); node([qw(caterwaul.all.js test/lib/unit.js), @tests]) unless $$options{'-N'}; gjs([qw(caterwaul.all.js test/lib/unit.js), @tests]) if $$options{'-a'}; __4338344e86c1627fc556a7d8289a6c4c 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', <<'__d86a78c26adf0414eedbceec62affb8e'); # 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('before-update-from-invocation', separate_options(@_), sub { my ($options, @targets) = @_; my %parent_id_cache = cache('parent-identification'); 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{'-n'} || $$options{'--no-save'}); 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'}; &{'save-state'}('before-update') if $save_state; for my $target (@targets) { dangerous("updating from $target", sub { around_hook('update-from', $target, sub { my $identity = $parent_id_cache{$target} ||= join '', qx($target identity); next if $already_seen{$identity}; $already_seen{$identity} = 1; my $attributes = join '', qx($target ls -ahiu); my %divergent; die "skipping unreachable $target" unless $attributes; 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); if (verify()) {hook('update-from-succeeded', $options, @targets); terminal::info("Successfully updated. Run 'load-state before-update' to undo this change.") if $save_state} elsif ($force) {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.', 'Run "load-state before-update" to undo the update and return to a working state.') if $save_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'.") if $save_state; return &{'load-state'}('before-update') if $save_state}}); __d86a78c26adf0414eedbceec62affb8e meta::function('usage', '"Usage: $0 action [arguments]\\nUnique actions (run \'$0 ls\' to see all actions):" . ls(\'-u\');'); meta::function('verify', <<'__d31b85fffd464ddf516d2afeb63dcbde'); 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; __d31b85fffd464ddf516d2afeb63dcbde meta::hook('after-render::cleanup', <<'__53d7a659ec1b2c1702f61651a956d38c'); # Nuke some things that get created by render(). unlink qw/macroexpander.js parser.js minify.js minify.js.sdocp/; __53d7a659ec1b2c1702f61651a956d38c meta::hook('after-render::hackery', <<'__049a9817dec529eff29f94917eb1ee83'); # Runs the hackery generation hooks (which happen after a render) #hook('hackery'); __049a9817dec529eff29f94917eb1ee83 meta::hook('after-render::minify', <<'__11068c68a72b7bec1f9b6733ad647dbf'); # Compile caterwaul.js by preprocessing sdoc::js::core/caterwaul. file::write('caterwaul.js', retrieve('pp::js::core/caterwaul')); file::write('caterwaul.all.js', retrieve('pp::js::modules/caterwaul.all')); file::write('caterwaul.node.js', retrieve('pp::js::modules/caterwaul.node')); file::write('caterwaul.js.sdocp', retrieve('sdocp::pp::js::core/caterwaul')); # Minify all of the generated files, but not those which contain preprocessor directives. file::read($_) =~ /^-\s*pinclude\s/m or minify($_) for grep !/\.min\.js$/, qw(caterwaul.js caterwaul.all.js), ; __11068c68a72b7bec1f9b6733ad647dbf meta::hook('after-render::notes', <<'__6a97d3ed9a596b2e5e0429553fdb83e8'); # Render each note into an SDoc file. file::write('doc/notes/' . attribute($_) . '.sdoc', retrieve($_)) for grep /^note::/, keys %data; __6a97d3ed9a596b2e5e0429553fdb83e8 meta::hook('after-render::test-page', <<'__45102e0cfa23cfa11c7f56f6dd4acbe9'); # Builds test/index.html my $scripts = join "\n", map "", grep s/^sdoc::js::(.*test\/(?!lib\/).*)$/\1.js/, sort keys %data; file::write('test/index.html', < Caterwaul unit tests
$scripts EOF __45102e0cfa23cfa11c7f56f6dd4acbe9 meta::hook('before-render::notify', 'terminal::info(\'rendering\');'); meta::hook('hackery::coarse-profiling', <<'__4056c8cc24a2c3e884837cc95efa0bba'); # Render versions of caterwaul.all.js, but leave out the core caterwaul. my $filename = 'hackery/coarse-profiling/caterwaul.configurations.js'; file::write($filename, cat(map "js::modules/caterwaul.$_", qw/heap memoize opt continuation seq parser/)); minify($filename); __4056c8cc24a2c3e884837cc95efa0bba meta::hook('hackery::partials', <<'__f84a0f54cf39f3fa5f294e44a925a8fb'); # Builds partial versions of Caterwaul to test the loading time of each. my @accepted_modules; sub build_caterwaul { my @modules = @_; my $modules = join '-', @modules; my $filename = "hackery/partials/caterwaul-$modules.js"; file::write($filename, cat('js::caterwaul', map "js::modules/caterwaul.$_", @modules)); minify($filename); } my @all_modules = qw/std heap memoize opt continuation seq parser/; for (@all_modules) { push @accepted_modules, $_; build_caterwaul(@accepted_modules); } __f84a0f54cf39f3fa5f294e44a925a8fb meta::internal_function('around_hook', <<'__e1cd17b80d4e8165df9c94facd9f239b'); # 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; __e1cd17b80d4e8165df9c94facd9f239b meta::internal_function('associate', <<'__fc4f785bcf3ffe3225a73a1fdd314703'); my ($name, $value, %options) = @_; die "Namespace does not exist" unless exists $datatypes{namespace($name)}; $data{$name} = $value; execute($name) if $options{'execute'}; $value; __fc4f785bcf3ffe3225a73a1fdd314703 meta::internal_function('attribute', <<'__62efb9f22157835940af1d5feae98d98'); my ($name) = @_; $name =~ s/^[^:]*:://; $name; __62efb9f22157835940af1d5feae98d98 meta::internal_function('attribute_is', <<'__7bf6fde9d247cbba37d5e0ff6a9dc1ee'); my ($a, %options) = @_; my %inherited = parent_attributes(grep /^parent::/o, sort keys %data) if $options{'-u'} or $options{'-U'}; my $criteria = $options{'--criteria'} || $options{'--namespace'} && "^$options{'--namespace'}::" || '.'; $a =~ /$criteria/ and ! $options{'-u'} || ! $inherited{$a} and ! $options{'-U'} || $inherited{$a} and ! $options{'-d'} || fast_hash(retrieve($a)) ne $inherited{$a} and ! $options{'-D'} || fast_hash(retrieve($a)) eq $inherited{$a} and ! $options{'-I'} || ! $transient{inherit}{namespace($a)} and ! $options{'-i'} || $transient{inherit}{namespace($a)} and ! $options{'-S'} || ! $a =~ /^state::/o and ! $options{'-M'} || ! /^meta::/o and ! $options{'-m'} || $a =~ /^meta::/o; __7bf6fde9d247cbba37d5e0ff6a9dc1ee meta::internal_function('bench', <<'__d77400a4154b5004d42b6086bbd9b02c'); use Time::HiRes qw/gettimeofday tv_interval/; my ($f) = @_; my $start_time = [gettimeofday]; &$f(); my $duration = tv_interval($start_time); terminal::info("$duration seconds elapsed"); __d77400a4154b5004d42b6086bbd9b02c meta::internal_function('cache', <<'__c119f9d7ea9a147c6d526a6283fb119a'); 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")} __c119f9d7ea9a147c6d526a6283fb119a meta::internal_function('chmod_self', <<'__b13487447c65f2dc790bd6b21dde89dd'); my ($mode_function) = @_; my (undef, undef, $mode) = stat $0; chmod &$mode_function($mode), $0; __b13487447c65f2dc790bd6b21dde89dd meta::internal_function('dangerous', <<'__4b8343178d6d4d1b760d61b1cfda008c'); # 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]; __4b8343178d6d4d1b760d61b1cfda008c meta::internal_function('debug_trace', <<'__77644ab45a770a6e172680f659911507'); terminal::debug(join ', ', @_); wantarray ? @_ : $_[0]; __77644ab45a770a6e172680f659911507 meta::internal_function('dep', <<'__bad9b934374b176318ed2295b63130bc'); # A variadic function to prepend cached_dependency:: onto things. # Used like this: dep(qw/caterwaul.all.js montenegro.server.js/) map "cached_dependency::$_", @_; __bad9b934374b176318ed2295b63130bc meta::internal_function('execute', <<'__4b4efc33bc6767a7aade7f427eedf83f'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(attribute($name), retrieve($name))}; warn $@ if $@ && $options{'carp'}; __4b4efc33bc6767a7aade7f427eedf83f meta::internal_function('exported', <<'__27414e8f2ceeaef3555b9726e690eb0f'); # 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; __27414e8f2ceeaef3555b9726e690eb0f meta::internal_function('extension_for', <<'__65e48f50f20bc04aa561720b03bf494c'); my $extension = $transient{extension}{namespace($_[0])}; $extension = &$extension($_[0]) if ref $extension eq 'CODE'; $extension || ''; __65e48f50f20bc04aa561720b03bf494c meta::internal_function('fast_hash', <<'__ac70f469e697725cfb87629833434ab1'); 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]; __ac70f469e697725cfb87629833434ab1 meta::internal_function('file::read', <<'__186bbcef8f6f0dd8b72ba0fdeb1de040'); my $name = shift; open my($handle), "<", $name; my $result = join "", <$handle>; close $handle; $result; __186bbcef8f6f0dd8b72ba0fdeb1de040 meta::internal_function('file::write', <<'__eb7b1efebe0db73378b0cce46681788d'); 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}; open my($handle), $options{append} ? '>>' : '>', $name or die "Can't open $name for writing"; print $handle $contents; close $handle; __eb7b1efebe0db73378b0cce46681788d meta::internal_function('fnv_hash', <<'__8d001a3a7988631bab21a41cee559758'); # 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; __8d001a3a7988631bab21a41cee559758 meta::internal_function('hypothetically', <<'__33ee2e1595d3877bd1d9accaa72305c8'); # Applies a temporary state and returns a serialized representation. # The original state is restored after this, regardless of whether the # temporary state was successful. my %data_backup = %data; my ($side_effect) = @_; my $return_value = eval {&$side_effect()}; %data = %data_backup; die $@ if $@; $return_value; __33ee2e1595d3877bd1d9accaa72305c8 meta::internal_function('internal::main', <<'__435a9e83ac803960745d9aa5aac6c75f'); disable(); $SIG{'INT'} = sub {snapshot(); exit 1}; $transient{initial} = state(); chomp(my $default_action = retrieve('data::default-action')); my $function_name = shift(@ARGV) || $default_action || 'usage'; terminal::warning("unknown action: '$function_name'") and $function_name = 'usage' unless $externalized_functions{$function_name}; around_hook('main-function', $function_name, @ARGV, sub { dangerous('', sub { chomp(my $result = &$function_name(@ARGV)); print "$result\n" if $result})}); save() unless state() eq $transient{initial}; END { enable(); } __435a9e83ac803960745d9aa5aac6c75f meta::internal_function('invoke_editor_on', <<'__1448132d5294a4b8390b4a684d8a78f9'); 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; __1448132d5294a4b8390b4a684d8a78f9 meta::internal_function('is_locked', '!((stat($0))[2] & 0222);'); meta::internal_function('namespace', <<'__93213d60cafb9627e0736b48cd1f0760'); my ($name) = @_; $name =~ s/::.*$//; $name; __93213d60cafb9627e0736b48cd1f0760 meta::internal_function('parent_attributes', <<'__480776f176ab728c6b7450287be5782a'); my $attributes = sub {my ($name, $value) = split /\s+/o, $_; $name => ($value || 1)}; map &$attributes(), split /\n/o, join("\n", retrieve(@_)); __480776f176ab728c6b7450287be5782a meta::internal_function('parent_ordering', <<'__61a829bfd725b367836b5342d26efce1'); # 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. # You can pass in a list of parents if you want; otherwise it uses all of them. my @parents = @_ ? @_ : grep /^parent::/, keys %data; 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; map @{$inverses{$_}}, sort keys %inverses; __61a829bfd725b367836b5342d26efce1 meta::internal_function('retrieve', <<'__0b6f4342009684fdfa259f45ac75ae37'); my @results = map defined $data{$_} ? $data{$_} : retrieve_with_hooks($_), @_; wantarray ? @results : $results[0]; __0b6f4342009684fdfa259f45ac75ae37 meta::internal_function('retrieve_with_hooks', <<'__5186a0343624789d08d1cc2084550f3d'); # 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; __5186a0343624789d08d1cc2084550f3d meta::internal_function('select_keys', <<'__1cd075b241905d9ab3199e680e86dced'); my %options = @_; grep attribute_is($_, %options), sort keys %data; __1cd075b241905d9ab3199e680e86dced meta::internal_function('separate_options', <<'__d47e8ee23fe55e27bb523c9fcb2f5ca1'); # 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; $options{$1} = $2 for grep /^([^=]+)=(.*)$/, @longs; ++$options{$_} for grep ! /=/, @singles, @longs; ({%options}, @others, @_); __d47e8ee23fe55e27bb523c9fcb2f5ca1 meta::internal_function('strip', 'wantarray ? map {s/^\\s*|\\s*$//g; $_} @_ : $_[0] =~ /^\\s*(.*?)\\s*$/ && $1;'); meta::internal_function('table_display', <<'__8a6897e093f36bf05477a3889b84a61d'); # 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; __8a6897e093f36bf05477a3889b84a61d meta::internal_function('temporary_name', <<'__0fb1402061581b69822f913631b4a9d9'); use File::Temp 'tempfile'; my (undef, $temporary_filename) = tempfile("$0." . 'X' x 4, OPEN => 0); $temporary_filename; __0fb1402061581b69822f913631b4a9d9 meta::internal_function('translate_backtrace', <<'__06fad3d85833a6484e426401b95e0206'); my ($trace) = @_; $trace =~ s/\(eval (\d+)\)/$locations{$1 - 1}/g; $trace; __06fad3d85833a6484e426401b95e0206 meta::internal_function('with_exported', <<'__fc4f32c46d95c6deed0414364d1c7410'); # 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; __fc4f32c46d95c6deed0414364d1c7410 meta::js('macroexpander', <<'__c21d8a4d0fee3d39aef360d077637bd1'); // A quick macroexpander REPL: console.log('%s', caterwaul.clone('format').format(caterwaul.clone('std seq opt continuation parser').macroexpand(caterwaul.parse(process.argv.slice(2).join(' '))))); __c21d8a4d0fee3d39aef360d077637bd1 meta::js('parser', <<'__ae8553a05bf29ed84554d0a426ca1406'); // A quick parser REPL: console.log('%s', caterwaul.parse(process.argv.slice(2).join(' ')).toString()); __ae8553a05bf29ed84554d0a426ca1406 meta::library('shell', <<'__528f486cc4d9eb390e4c350b8727c751'); # 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 $state = $options{state} // ::state(); my $other = $state ne $transient{initial} ? 33 : 30; my $locked = ::is_locked() ? "\033[1;31mlocked\033[0;0m" : ''; my $cc = length ::retrieve('data::current-continuation') ? "\033[1;36mcc\033[0;0m" : ''; "\033[1;32m$name\033[1;${other}m" . substr($state, 0, 4) . "\033[0;0m$cc$locked\033[1;34m$options{stuff}\033[0;0m "} 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, sort keys %externalized_functions]}; my $prompt = $options{prompt} || \&prompt; my $parse = $options{parse} || sub {parse(tokenize(@_))}; my $command = $options{command} || sub {my ($command) = @_; ::around_hook('shell-command', $command, sub {print ::dangerous('', sub {execute($command)}), "\n"})}; &$command(&$parse($_)) while ($attribs->{completion_word} = &$autocomplete(), defined($_ = $term->readline(&$prompt())))} __528f486cc4d9eb390e4c350b8727c751 meta::library('terminal', <<'__c52308d05ebb4ff61c5fc36e6d9c7a8a'); # 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; __c52308d05ebb4ff61c5fc36e6d9c7a8a meta::message_color('cc', '36'); meta::message_color('state', 'purple'); meta::message_color('states', 'yellow'); meta::message_color('test', 'purple'); meta::message_color('watch', 'blue'); meta::note('macroexpander-optimization', <<'__73d8ab136c386600bac133c292f4318e'); Optimization constraints for macroexpansion. This is a challenging problem. It looks like the total complexity of loading all modules is close to 7000, which isn't too bad by itself. However, there are more than 100 macros during some of these configurations, and that increases the amount of work by a factor of 100. It isn't possible to achieve sublinear macroexpansion time in the number of syntax nodes, but I think it is possible to achieve it in the number of macros. I'm not quite sure how this should work, but probably some kind of indexing strategy is in order. Masked indexing. This is probably the most obvious way to solve the problem. The idea is that macroexpand() first builds an index of nodes, then consults the index to determine which macros should be invoked. This should be roughly O(n) in the size of the tree. The implementation is made challenging by the fact that syntax node patterns aren't necessarily easy to index. The only way I can think of to go about this sensibly is to compile some kind of mixed mask from all of the macro patterns, and then hash each syntax node based on that mask. Another possibility is to arrange macro patterns by common factors. Maybe Caterwaul's macroexpander knows how to do things like identify structurally identical trees and dispatch on some hash of the data, or some such. This involves some fairly advanced decision-making on Caterwaul's part, but I'm fairly confident that a closed-form optimization strategy exists. Problem statement. Formally speaking, the problem is to minimize the number of pattern-node match operations. Scanning through all of the nodes up-front is a definite possibility, though each node visit is somewhat expensive. Match operations are worse, however, since they may have to dig down several layers before rejecting a match, and in the meantime they will have consed up and concatenated many arrays to build the partial match data. Potential algorithms for indexing. There are a lot of commonalities among patterns used for macros, and deriving the structure may be prohibitive. However, I'd like to try anyway. So to state it more clearly, there are a few possibilities: | 1. We use an algorithm that is invariant on its input. This is optimal if no structure can be derived from syntax patterns, which obviously isn't the case. 2. We use an algorithm that has heuristics about macros. This may work, but it breaks down for non-Javascript use cases (and it would be cool if Caterwaul were general-purpose). 3. We use an adaptive optimizer to find the best indexing strategy. We can either predict it or test it empirically. I'm axing (1) right off the bat just because it's no fun. This leaves (2) and (3). Let's see where (3) takes us. Adaptive optimization. In this case we know two things ahead of time. First, we know the list of macros that we'll need to apply to the syntax tree, and second we have a fixed number of scans over the tree (ideally just one) to compute whatever we want to about them. For the sake of comparison, the number to beat is 750000. This means that we can do up to 100 operations per syntax node and still beat the naive solution. Ideally we do better, but that's a high limit. (Hopefully whatever adaptation needs to happen will happen on the macro end, not the syntax node end -- there will be at most around 1000 macros, and that would be a pathological case.) Pattern hashing. It's fairly straightforward to identify high-information hash regions within the pattern space. All we have to do then is to build a table and do a very fast lookup on each node. However, what happens when we have two different schemas? For example: | ([] (let _ _)) ([] (foo _ _)) ([] (bar _ _)) ([] (bif _ _)) This is the first schema, where the path [1].data provides a perfect hash (reduces each tree to at most one match, and from there recursive descent is the only option). However, suppose we also have these macros: | ([] (let bif _)) ([] (let baz _)) ([] (let bok _)) Now the first scheme is perfect only for a subset of inputs. Because of the overlap (which there is in the real world too), we need a multi-stage algorithm to handle failover scenarios like this one. One important idea here is that we want to generalize to a /family/ of macros, not construct a perfect hash. It's acceptable to have to try a couple of different things; we just need to bound the number. Ideally we do the math to find the optimum; i.e. generate more hashes until the overhead of hashing each node outweighs the overhead of each test in a subset of cases. (There may not be such a moment, for example if two macros have the same pattern.) Along these lines, it's worth coming up with some simplifications. Pattern matching is transitive; if A matches pattern B and B matches pattern C, then A matches pattern C. This means that we can remove earlier macros that are matched by later macro-patterns. (Here the earlier macro is B, the later one is A, and C is the syntax tree we'll be matching.) This should simplify the hashing by removing all duplicate cases, and is O(n^2) in the number of macros. (In fact we can treat macro patterns as some kind of fully-ordered space to get O(n log n).) Syntactic heterogeneity. How heterogeneous is the syntax? Half of the nodes must be operators or other punctuation, and this is chosen out of a set of 20. The other half must be identifiers of some sort. There's probably a curve that dictates how likely each identifier is to be seen elsewhere, but I'm also guessing that the pattern is fractal; that is, each subtree has about the same amount of information density. If this is the case then it's reasonable to use this as a limit for how much information can be derived. Interrupting thought: Actually, it doesn't matter how much information is present in the syntax tree. The reason is that the macro patterns will end up dictating that; all we need to do is distinguish among them, and we're given at least that much information. Constraints. Because of terrible string-concat performance from some Javascript runtimes, it's important not to allocate new strings during this hashing process. So any hashing that's done will either (1) have to be on a single field, or (2) have to use numbers rather than strings. (2) is probably a more robust solution. Interning strings -> integers. This has some cost initially, and depending on the JS runtime it may or may not make sense. The idea is to unify symbols into hashable integers rather than leaving them as strings. It has the advantage of using little extra space and allowing strings to be used in low-overhead arithmetic ways (by their bijection onto numbers). Because we don't want to keep the global index table around forever, it's important to use only local indexes. This has important consequences, perhaps most significantly that we'll have to regenerate the index for each macroexpansion cycle. I don't consider this too much of a problem. It's nice to have optimizations not pervade the overall system design too much. Node ordering (irrelevant -- see below). If we want to skip the post-indexing traversal step altogether (which sounds attractive to me), then we need to know the right order to expand stuff. It goes outside-in and left-to-right, so a pre-order tree is required. The rule will be that as we're constructing the indexes we also maintain a counter that gets incremented for each node. (Conveniently, the depth-first traversal built in to nodes does what we need here.) Then each list of nodes to be transformed will be sorted by this counter. While the asymptotic complexity is worse than a regular depth-first search, it should be much faster in practice. Interrupting thought: Why not just transform the nodes as soon as we hit them? No need to build these lists at all. Therefore node ordering is irrelevant to the optimization problem. __73d8ab136c386600bac133c292f4318e meta::note('modules', <<'__92e7978739a04aa66d1b7859a730e367'); Modules. Modules are for extensions as much as they are for fundamentals. This sounds weird perhaps, but it's true. For example, the replication behavior of caterwaul is useful outside of just the caterwaul main library, so we could reasonably want to reuse it: | var replicator = caterwaul.replica(); replicator.clone(); In this case, caterwaul itself must be a replicating function. This is kind of interesting, because the publicly available caterwaul.replica assumes the existence of caterwaul already. Fortunately the initialization is guaranteed to be single-threaded. (And if you have created a world in which this isn't true, then you deserve what you get.) Fundamental toplevel modules. These modules are actually parts of Caterwaul's basis: | 1. Replica. This handles the inteface for configurable and replicable functions. 2. Macroexpansion. This handles the boilerplate involved in writing macroexpanders for array-like trees of syntax nodes. (It doesn't, however, handle the macro definitions themselves.) Replication is fairly straightforward; there isn't much of an interface required of the function being replicated. Macroexpansion, on the other hand, involves some inversion of control with respect to syntax trees. So, in order to work with this macroexpander, the tree should support an rmap() method. (More details in the code.) __92e7978739a04aa66d1b7859a730e367 meta::note('q', <<'__48c65f082344a2efd22342ecbffcd69b'); Bug list. Caterwaul does the right thing 99% of the time. This section is for the 1% when something goes wrong. Auto-parens for syntax nodes [won't fix]. It's possible to write this: | qs[x + y].replace({x: qs[z, t], y: qs[f, g]}) And end up with an expression whose semantics aren't reflected by the syntax tree (due to operator precedence). This is unfortunate. However, it can also be useful. Anytime there's ambiguity about this sort of thing you should wrap the expression in parens (which is sometimes required even when no particular ambiguity exists, e.g. at statement-level with function expressions in Firefox). Fix matching against flattened nodes [fixed in 0.5]. This is a reasonable bug. Nodes should be able to "un-flatten" back down into nested form, following their natural associativity. This should be done within macros to make sure that further macro patterns don't spuriously reject them. V8-based runtimes sometimes fail jQuery test [fixed in 0.5]. This is probably a V8 bug of some sort, though I'll have to isolate a suitable test case to submit it. Fortunately it only seems to have an effect when transforming jQuery (and it produces the same output), so maybe there is some nondeterminism that I need to address. Update: I think this was a product of using eval()-based functions. It's still a V8 bug, but at least there's a workaround. Remove uses of eval() inside Caterwaul [fixed in 0.5]. This is actually important for a couple of reasons. First, it's slow to use eval. Better is to avoid it by writing functions out longhand (much as I don't particularly like doing so). Second, it may be causing V8 some trouble. I'm not sure; will have to test this. Stops matching after an expansion rejection [fixed in 0.5.1]. If you have two macros whose patterns are identical, only the second will ever be used. This wouldn't be such a problem, except that the first won't be used even if the second declares a macroexpansion failure by returning false. Fails in IE7 [fixed in 0.5.4]. Something about constructors and how the sequence library implements inheritance from arrays. I'm not married to the idea of implementing proper inheritance here; it comes with enough problems. Important features. Caterwaul needs an offline precompiler. This is necessary mostly for performance reasons; it takes 7 seconds to load in GJS on a netbook, which is unacceptably slow. I should probably also profile the code to make sure the delays aren't caused by something else, for instance macroexpansion. Also, it would be cool to have a tracing compiler that can adaptively optimize. This could be really effective. Future modules. | 1. Recon module (but this has been in the works for ages). 2. Parser module. A good set of memoized parser combinators would benefit greatly from macro support. [done] Ideas. Unimportant things that might help at some point in the future. Hashed syntax nodes. This is useful for accelerating macroexpansion. I'm not sure whether it's relevant yet, but if done correctly it would give syntax nodes faster rejection (which is the most common case when macroexpanding). The challenge is incorporating wildcards. __48c65f082344a2efd22342ecbffcd69b meta::parent('/home/spencertipping/bin/configuration', <<'__9d2be9598330a82e97a313be6be67670'); meta::type::configuration d67e10a128e6b1d958c5b9d3bbe25aa4 parent::/home/spencertipping/bin/object 6b93679549b63f96ad5863923e0c4f1f __9d2be9598330a82e97a313be6be67670 meta::parent('/home/spencertipping/bin/node-base', <<'__ae5fd8bbce0d68ffe89f765751c60515'); function::loc 36e0cabf1fe1c1bcaa3c8c708bd82ca0 function::node 3522fde8f76947a5f70bca33f1ee0016 function::node-custom c2f4063798c997ec7f78a3543b1240b3 function::render d11b6e7522980db1022b1ccd575bdc30 function::run-forever 76175932a2d2692fc802856c28d0848d internal_function::dep bad9b934374b176318ed2295b63130bc message_color::test 14e993fdf2c62df353613c243dc9053b meta::type::js f7a0080116fecebf7abb030b0156f44d parent::/home/spencertipping/bin/repository 07e9a020871aea8085d81c6363c1aced parent::/home/spencertipping/conjectures/perl-objects/sdoc 17ae8eb5d9f395141bb54697e055d30b __ae5fd8bbce0d68ffe89f765751c60515 meta::parent('/home/spencertipping/bin/object', <<'__6b93679549b63f96ad5863923e0c4f1f'); bootstrap::html aa347c4339e71e7acc9ea4fc3d593347 bootstrap::initialization 8774229a1a0ce7fd056d81ba0b077f79 bootstrap::perldoc c63395cbc6f7160b603befbb2d9b6700 function::alias 28744564997657da45ab16cd5b441104 function::cat a19fdfda461f9a0aa01978dff2e2c2f7 function::cc c4d52b1d8f52a480f07b81b93c3aac7b function::ccc 2351344fc688518c75aa4ab3acec1c4a function::child f69646398c3123d3d939a7f2b3156606 function::clone 54e00ff2103e54423d4c9febb97ce063 function::cp e5fee448a74ecbf4ae215e6b43dfc048 function::create 090c342a2dc304b39c643d53350474a0 function::current-state d83ae43551c0f58d1d0ce576402a315a function::disable 49db26fcd680ca34c1f52629a7375d2f function::edit 36bd3f2c1165d0c041b0fa9a96a85378 function::enable 37af2d2e603e2455be227ae3c5a42c3a function::export 388e0cc60507443cb1c0cc3e2658cfef function::extern 6a9fa8c2a8a4eaae9fe8d38e402145e4 function::grep f2f28b81f4bcf4cf9fbe9e27bc9447b0 function::hash 7a903f90f2c8ed27af7e030f407d9f7b function::hook d74d8e2b611342af6a0897e0bd62e6e6 function::hooks 230122bdc8929884e45b2f78a7743e2e function::identity 37106ce13a0200af001d361ce7e81e57 function::import ac86cbe9c9fb12fc8cef2cc88e80c01e function::initial-state e21ba3519838b221a7b2d4e8a7544e7f function::is 1d3401965e4720ed972470b54b447db0 function::load-state ea18db867bd62a294e067f60e6975dcf function::lock 9bf21fee2f0f809131d43553bde82fa5 function::ls 992087a0d63d41e0bb551803476cfe72 function::mv 52e95180e3c7019116bd798e0da0fdda function::name 6848cbc257e4b6d7441b25acb04e23c9 function::parents f94c3a5addbc92fe7f90d198fa701484 function::perl 986a274c013b77fe08d29726ce3799fe function::reload c57ff432c3ffd91a5506cd3eb8bf50c9 function::rm 7cecfb1691a7bf86741f00058bcc54ca function::save 181da4858ac39c157dbf38e6bac7a0d2 function::save-state 863e4d9fa75ca198ef7a755248d1002a function::serialize 5148e8ca46eeb3e297f76d098e496bcf function::serialize_single ef0f63556d22816ed102d3bbe2172b28 function::sh 9647fa9227bef6c139a79d0dd4acc8b4 function::shell dc8238ad70b1e02eaf230c4f1bd21690 function::size ed32c644d604fdc61dda48bd3fbe5559 function::snapshot d3d84a364524eeb8ee90623f545187e8 function::state 119111f84c3e32a5536838ac84bc6f10 function::touch 819878bc64df094d3323c7050f2c3e97 function::unlock 8fc9bd69f3466f0b54ee2c6965f68cea function::update 4de1a6a4085836590a3b1ef997f9d5ea function::update-from d86a78c26adf0414eedbceec62affb8e function::usage b36ead828ad566c8e3919f3b40fb99e6 function::verify d31b85fffd464ddf516d2afeb63dcbde internal_function::around_hook e1cd17b80d4e8165df9c94facd9f239b internal_function::associate fc4f785bcf3ffe3225a73a1fdd314703 internal_function::attribute 62efb9f22157835940af1d5feae98d98 internal_function::attribute_is 7bf6fde9d247cbba37d5e0ff6a9dc1ee internal_function::cache c119f9d7ea9a147c6d526a6283fb119a internal_function::chmod_self b13487447c65f2dc790bd6b21dde89dd internal_function::dangerous 4b8343178d6d4d1b760d61b1cfda008c internal_function::debug_trace 77644ab45a770a6e172680f659911507 internal_function::execute 4b4efc33bc6767a7aade7f427eedf83f internal_function::exported 27414e8f2ceeaef3555b9726e690eb0f internal_function::extension_for 65e48f50f20bc04aa561720b03bf494c internal_function::fast_hash ac70f469e697725cfb87629833434ab1 internal_function::file::read 186bbcef8f6f0dd8b72ba0fdeb1de040 internal_function::file::write eb7b1efebe0db73378b0cce46681788d internal_function::fnv_hash 8d001a3a7988631bab21a41cee559758 internal_function::hypothetically 33ee2e1595d3877bd1d9accaa72305c8 internal_function::internal::main 435a9e83ac803960745d9aa5aac6c75f internal_function::invoke_editor_on 1448132d5294a4b8390b4a684d8a78f9 internal_function::is_locked 42c3c89625863a31105d1df49a2a762f internal_function::namespace 93213d60cafb9627e0736b48cd1f0760 internal_function::parent_attributes 480776f176ab728c6b7450287be5782a internal_function::parent_ordering 61a829bfd725b367836b5342d26efce1 internal_function::retrieve 0b6f4342009684fdfa259f45ac75ae37 internal_function::retrieve_with_hooks 5186a0343624789d08d1cc2084550f3d internal_function::select_keys 1cd075b241905d9ab3199e680e86dced internal_function::separate_options d47e8ee23fe55e27bb523c9fcb2f5ca1 internal_function::strip 4af6a470effeed94c0dd9800d01f7d66 internal_function::table_display 8a6897e093f36bf05477a3889b84a61d internal_function::temporary_name 0fb1402061581b69822f913631b4a9d9 internal_function::translate_backtrace 06fad3d85833a6484e426401b95e0206 internal_function::with_exported fc4f32c46d95c6deed0414364d1c7410 library::shell 528f486cc4d9eb390e4c350b8727c751 library::terminal c52308d05ebb4ff61c5fc36e6d9c7a8a message_color::cc 6249446f73b3c5af2404c322c150e57b message_color::state 14e993fdf2c62df353613c243dc9053b message_color::states 152a940086f7cee6110528a09af7dd78 meta::configure 25976e07665878d3fae18f050160343f meta::externalize 9141b4e8752515391385516ae94b23b5 meta::functor::editable e3d2ede6edf65ffe2123584b2bd5dab7 meta::type::alias 28fe15dd61f4902ed5180d8604d15d97 meta::type::bootstrap 297d03fb32df03b46ea418469fc4e49e meta::type::cache 52eac0d7b550a358cc86803fe1a9d921 meta::type::data 58d8027f20099b28a159eaac67314051 meta::type::function d93b3cc15693707dac518e3d6b1f5648 meta::type::hook f55a3f728ddfb90204dff3fe5d86845c meta::type::inc c95915391b969734305f2f492d5ca8e3 meta::type::internal_function 34abb44c67c7e282569e28ef6f4d62ab meta::type::library b6dd78120e6d787acdb5c1629f7f1896 meta::type::message_color 794bf137c425293738f07636bcfb5c55 meta::type::meta 640f25635ce2365b0648962918cf9932 meta::type::parent 607e9931309b1b595424bedcee5dfa45 meta::type::retriever 6e847a9d205e4a5589765a3366cdd115 meta::type::state c1f29670be26f1df6100ffe4334e1202 retriever::file b8e7aefc98b8341260d91f21dc61d749 retriever::id a791a5735e9b4f2cb8b99fd39dc17bc3 __6b93679549b63f96ad5863923e0c4f1f meta::parent('/home/spencertipping/bin/repository', <<'__07e9a020871aea8085d81c6363c1aced'); function::dupdate 1c0273217c5b9f2756bb14a4a00aa7e2 meta::type::cached_dependency e9455b403cbff27bbcc41d917fef482f parent::/home/spencertipping/bin/configuration 9d2be9598330a82e97a313be6be67670 __07e9a020871aea8085d81c6363c1aced meta::parent('/home/spencertipping/conjectures/perl-objects/sdoc', <<'__17ae8eb5d9f395141bb54697e055d30b'); function::sdoc 060cfa349e629eb90a82b87a8ba00c1d function::sdoc-html 5fffe6139a81ff67ee063ebcbfadad2a function::sdocp 8b7ed5bbd537234ae53c0691b6d02c97 meta::type::sdoc 392c65eddae300e2aa67014b85884979 parent::/home/spencertipping/bin/object 6b93679549b63f96ad5863923e0c4f1f retriever::html-sdoc 54d22ab83d9f3f2e27ef51033b4817a7 retriever::sdoc 6de8ec1f1436fb8e7477b533c321081b retriever::sdocp fef74cd94fa8761618662802f0bfc171 __17ae8eb5d9f395141bb54697e055d30b meta::parent('notes', <<'__9bd87fe8c3b24d930048afd7f1626853'); function::note bcbfeac6dd2112f47296265444570a6e function::notes 15b3aca0cba3b327a984928154eda2b5 meta::type::note 7ca6b375a66ecbfd14bef5bcefeb6643 parent::object 6b93679549b63f96ad5863923e0c4f1f __9bd87fe8c3b24d930048afd7f1626853 meta::parent('object', <<'__6b93679549b63f96ad5863923e0c4f1f'); bootstrap::html aa347c4339e71e7acc9ea4fc3d593347 bootstrap::initialization 8774229a1a0ce7fd056d81ba0b077f79 bootstrap::perldoc c63395cbc6f7160b603befbb2d9b6700 function::alias 28744564997657da45ab16cd5b441104 function::cat a19fdfda461f9a0aa01978dff2e2c2f7 function::cc c4d52b1d8f52a480f07b81b93c3aac7b function::ccc 2351344fc688518c75aa4ab3acec1c4a function::child f69646398c3123d3d939a7f2b3156606 function::clone 54e00ff2103e54423d4c9febb97ce063 function::cp e5fee448a74ecbf4ae215e6b43dfc048 function::create 090c342a2dc304b39c643d53350474a0 function::current-state d83ae43551c0f58d1d0ce576402a315a function::disable 49db26fcd680ca34c1f52629a7375d2f function::edit 36bd3f2c1165d0c041b0fa9a96a85378 function::enable 37af2d2e603e2455be227ae3c5a42c3a function::export 388e0cc60507443cb1c0cc3e2658cfef function::extern 6a9fa8c2a8a4eaae9fe8d38e402145e4 function::grep f2f28b81f4bcf4cf9fbe9e27bc9447b0 function::hash 7a903f90f2c8ed27af7e030f407d9f7b function::hook d74d8e2b611342af6a0897e0bd62e6e6 function::hooks 230122bdc8929884e45b2f78a7743e2e function::identity 37106ce13a0200af001d361ce7e81e57 function::import ac86cbe9c9fb12fc8cef2cc88e80c01e function::initial-state e21ba3519838b221a7b2d4e8a7544e7f function::is 1d3401965e4720ed972470b54b447db0 function::load-state ea18db867bd62a294e067f60e6975dcf function::lock 9bf21fee2f0f809131d43553bde82fa5 function::ls 992087a0d63d41e0bb551803476cfe72 function::mv 52e95180e3c7019116bd798e0da0fdda function::name 6848cbc257e4b6d7441b25acb04e23c9 function::parents f94c3a5addbc92fe7f90d198fa701484 function::perl 986a274c013b77fe08d29726ce3799fe function::reload c57ff432c3ffd91a5506cd3eb8bf50c9 function::rm 7cecfb1691a7bf86741f00058bcc54ca function::save 181da4858ac39c157dbf38e6bac7a0d2 function::save-state 863e4d9fa75ca198ef7a755248d1002a function::serialize 5148e8ca46eeb3e297f76d098e496bcf function::serialize_single ef0f63556d22816ed102d3bbe2172b28 function::sh 9647fa9227bef6c139a79d0dd4acc8b4 function::shell dc8238ad70b1e02eaf230c4f1bd21690 function::size ed32c644d604fdc61dda48bd3fbe5559 function::snapshot d3d84a364524eeb8ee90623f545187e8 function::state 119111f84c3e32a5536838ac84bc6f10 function::touch 819878bc64df094d3323c7050f2c3e97 function::unlock 8fc9bd69f3466f0b54ee2c6965f68cea function::update 4de1a6a4085836590a3b1ef997f9d5ea function::update-from d86a78c26adf0414eedbceec62affb8e function::usage b36ead828ad566c8e3919f3b40fb99e6 function::verify d31b85fffd464ddf516d2afeb63dcbde internal_function::around_hook e1cd17b80d4e8165df9c94facd9f239b internal_function::associate fc4f785bcf3ffe3225a73a1fdd314703 internal_function::attribute 62efb9f22157835940af1d5feae98d98 internal_function::attribute_is 7bf6fde9d247cbba37d5e0ff6a9dc1ee internal_function::cache c119f9d7ea9a147c6d526a6283fb119a internal_function::chmod_self b13487447c65f2dc790bd6b21dde89dd internal_function::dangerous 4b8343178d6d4d1b760d61b1cfda008c internal_function::debug_trace 77644ab45a770a6e172680f659911507 internal_function::execute 4b4efc33bc6767a7aade7f427eedf83f internal_function::exported 27414e8f2ceeaef3555b9726e690eb0f internal_function::extension_for 65e48f50f20bc04aa561720b03bf494c internal_function::fast_hash ac70f469e697725cfb87629833434ab1 internal_function::file::read 186bbcef8f6f0dd8b72ba0fdeb1de040 internal_function::file::write eb7b1efebe0db73378b0cce46681788d internal_function::fnv_hash 8d001a3a7988631bab21a41cee559758 internal_function::hypothetically 33ee2e1595d3877bd1d9accaa72305c8 internal_function::internal::main 435a9e83ac803960745d9aa5aac6c75f internal_function::invoke_editor_on 1448132d5294a4b8390b4a684d8a78f9 internal_function::is_locked 42c3c89625863a31105d1df49a2a762f internal_function::namespace 93213d60cafb9627e0736b48cd1f0760 internal_function::parent_attributes 480776f176ab728c6b7450287be5782a internal_function::parent_ordering 61a829bfd725b367836b5342d26efce1 internal_function::retrieve 0b6f4342009684fdfa259f45ac75ae37 internal_function::retrieve_with_hooks 5186a0343624789d08d1cc2084550f3d internal_function::select_keys 1cd075b241905d9ab3199e680e86dced internal_function::separate_options d47e8ee23fe55e27bb523c9fcb2f5ca1 internal_function::strip 4af6a470effeed94c0dd9800d01f7d66 internal_function::table_display 8a6897e093f36bf05477a3889b84a61d internal_function::temporary_name 0fb1402061581b69822f913631b4a9d9 internal_function::translate_backtrace 06fad3d85833a6484e426401b95e0206 internal_function::with_exported fc4f32c46d95c6deed0414364d1c7410 library::shell 528f486cc4d9eb390e4c350b8727c751 library::terminal c52308d05ebb4ff61c5fc36e6d9c7a8a message_color::cc 6249446f73b3c5af2404c322c150e57b message_color::state 14e993fdf2c62df353613c243dc9053b message_color::states 152a940086f7cee6110528a09af7dd78 meta::configure 25976e07665878d3fae18f050160343f meta::externalize 9141b4e8752515391385516ae94b23b5 meta::functor::editable e3d2ede6edf65ffe2123584b2bd5dab7 meta::type::alias 28fe15dd61f4902ed5180d8604d15d97 meta::type::bootstrap 297d03fb32df03b46ea418469fc4e49e meta::type::cache 52eac0d7b550a358cc86803fe1a9d921 meta::type::data 58d8027f20099b28a159eaac67314051 meta::type::function d93b3cc15693707dac518e3d6b1f5648 meta::type::hook f55a3f728ddfb90204dff3fe5d86845c meta::type::inc c95915391b969734305f2f492d5ca8e3 meta::type::internal_function 34abb44c67c7e282569e28ef6f4d62ab meta::type::library b6dd78120e6d787acdb5c1629f7f1896 meta::type::message_color 794bf137c425293738f07636bcfb5c55 meta::type::meta 640f25635ce2365b0648962918cf9932 meta::type::parent 607e9931309b1b595424bedcee5dfa45 meta::type::retriever 6e847a9d205e4a5589765a3366cdd115 meta::type::state c1f29670be26f1df6100ffe4334e1202 retriever::file b8e7aefc98b8341260d91f21dc61d749 retriever::id a791a5735e9b4f2cb8b99fd39dc17bc3 __6b93679549b63f96ad5863923e0c4f1f meta::parent('preprocessor', <<'__622cc4460c88a883baef674642c2e921'); function::preprocess 66e539d29e9afa903569efad0eb7c886 meta::type::template 25f4d6eafb1d3eea6d5d3d9a71a5623e parent::object 6b93679549b63f96ad5863923e0c4f1f retriever::pp f4a8c288d69963d6ebc5ce0bf7794777 template::comment 7f8e9be5cd7c865fa64efe3123f62b38 template::eval eb0b1058649eb2d833f348540516b358 template::failing_conditional 5c593329b434a7044f68cec4b77e8ed9 template::include e0624844a65ae41e0217dd871fc0dbfb template::pinclude 5ba61c3034a4b183881936aec30d2be9 __622cc4460c88a883baef674642c2e921 meta::retriever('file', '-f $_[0] ? file::read($_[0]) : undef;'); meta::retriever('html-sdoc', <<'__54d22ab83d9f3f2e27ef51033b4817a7'); my ($attribute) = @_; return undef unless $attribute =~ s/^html::/sdoc::/ and exists $data{$attribute}; &{'sdoc-html'}($attribute); __54d22ab83d9f3f2e27ef51033b4817a7 meta::retriever('id', '$_[0] =~ /^id::/ ? substr($_[0], 4) : undef;'); meta::retriever('pp', <<'__f4a8c288d69963d6ebc5ce0bf7794777'); return undef unless namespace($_[0]) eq 'pp'; my $attr = retrieve(attribute($_[0])); defined $attr ? preprocess($attr) : undef; __f4a8c288d69963d6ebc5ce0bf7794777 meta::retriever('sdoc', 'exists $data{"sdoc::$_[0]"} ? sdoc("sdoc::$_[0]") : undef;'); meta::retriever('sdocp', <<'__fef74cd94fa8761618662802f0bfc171'); my $attribute = attribute($_[0]); exists $data{"sdoc::$attribute"} ? sdocp("sdoc::$attribute") : undef; __fef74cd94fa8761618662802f0bfc171 meta::sdoc('js::core/caterwaul', <<'__85a9bcdb23aeb03be8168366fdf7fd59'); Caterwaul JS | Spencer Tipping Licensed under the terms of the MIT source code license (function (f) {return f(f, (function (x) {return function () {return ++x}})(1))}) (function (self, unique, undefined) { Introduction. Caterwaul implements a very small Lisp in Javascript syntax. The syntax ends up looking much more like McCarthy's M-expressions than traditional S-expressions, due to the ease of embedding those in a JS-compatible grammar. Also, Javascript convention makes square-bracket calls such as qs[foo] relatively uncommon, so I'm using that as the macro syntax (though of course you can define macros with other forms as well). The most important thing Caterwaul does is provide a quotation operator. For example: | caterwaul.clone('std')(function () { return qs[x + 1]; }); This function returns a syntax tree representing the expression 'x + 1'. Caterwaul also includes macro-definition and quasiquoting (not quite like Lisp, though I imagine you could write a macro for that): | caterwaul.configure('std')(function () { caterwaul.macro(qs[let (_ = _) in _], function (variable, value, expression) { return qs[(function (variable) {return expression}).call(this, value)].replace({variable: variable, expression: expression, value: value}); }); // Macro usable in future caterwaul()ed functions }); Or, more concisely (since macro definitions can be used inside other macro definitions when you define with rmacro): | var f = caterwaul.configure('std')(function () { caterwaul.rmacro(qs[let (_ = _) in _], fn[variable, value, expression] [qs[(fn[variable][expression]).call(this, value)].replace({variable: variable, expression: expression, value: value})]); }); Note that 'caterwaul' inside a transformed function refers to the transforming function, not to the global Caterwaul function. See the 'Macroexpansion' section some distance below for more information about defining macros. Coding style. I like to code using syntactic minimalism, and since this project is a hobby instead of work I've run with that style completely. This has some advantages and some disadvantages. Advantages include (1) a very small gzipped/minified footprint (especially since these comments make up most of the file), (2) few lines of code, though they are very long, and (3) lots of semantic factoring that should make modification relatively simple. Disadvantages are (1) completely impenetrable logic (especially without the comments) and (2) possibly suboptimal performance in the small scale (depending on whether your JS interpreter is optimized for statements or expressions). There are a couple of things worth knowing about as you're reading through this code. One is that invariants are generally coded as such; for example, the 'own' property lookup is factored out of the 'has' function even though it would be trivial to write it inside. This is to indicate to Javascript that Object.prototype.hasOwnProperty is relatively invariant, and that saves some lookups as the code is running. Another is that I use the (function (variable) {return expression})(value) form to emulate let-bindings. (Reading the code with this in mind will make it much more obvious what's going on.) Global management. Caterwaul creates a global symbol, caterwaul. Like jQuery, there's a mechanism to get the original one back if you don't want to replace it. You can call caterwaul.deglobalize() to return caterwaul and restore the global that was there when Caterwaul was loaded (might be useful in the unlikely event that someone else named their library Caterwaul). Note that deglobalize() is available only on the global caterwaul() function. It wouldn't make much sense for clones to inherit it. var _caterwaul = typeof caterwaul === 'undefined' ? undefined : caterwaul; - pinclude pp::js::core/caterwaul.utilities - pinclude pp::js::core/caterwaul.tree - pinclude pp::js::core/caterwaul.parser - pinclude pp::js::core/caterwaul.compiler - pinclude pp::js::core/caterwaul.macroexpander - pinclude pp::js::core/caterwaul.configuration - pinclude pp::js::core/caterwaul.behaviors var caterwaul_global = caterwaul = caterwaul_core(merge(replica(), {deglobalize: function () {caterwaul = _caterwaul; return this}})); return caterwaul_global}); __85a9bcdb23aeb03be8168366fdf7fd59 meta::sdoc('js::core/caterwaul.behaviors', <<'__7557891864eb5c739a462549771ad971'); Macroexpansion behavior. Caterwaul exposes macroexpansion as a contained interface. This lets you write your own compilers with macroexpansion functionality, even if the syntax trees weren't created by Caterwaul. In order for this to work, your syntax trees must: | 1. Look like arrays -- that is, have a .length property and be indexable by number (e.g. x[0], x[1], ..., x[x.length - 1]) 2. Implement an rmap() method. This should perform a depth-first traversal of the syntax tree, invoking a callback function on each node. If the callback returns a value, that value should be subsituted for the node passed in and traversal should continue on the next node (not the one that was grafted in). Otherwise traversal should descend into the unmodified node. The rmap() method defined for Caterwaul syntax trees can be used as a reference implementation. (It's fairly straightforward.) 3. Implement a .data property. This represents an equivalence class for syntax nodes under ===. Right now there is no support for using other equivalence relations. As of version 0.7.0 this compatibility may change without notice. The reason is that the macroexpansion logic used by Caterwaul is becoming more sophisticated to increase performance, which means that it may become arbitrarily optimized. Macro vs. rmacro. macro() defines a macro whose expansion is left alone. rmacro(), on the other hand, will macroexpand the expansion, letting you emit macro-forms such as fn[][]. Most of the time you will want to use rmacro(), but if you want to have a literal[] macro, for instance, you would use macro(): | caterwaul.configure(function () { // Using macro() instead of rmacro(), so no further expansion: this.macro(qs[literal[_]], fn[x][x]); }); While macro() is marginally faster than rmacro(), the difference isn't significant in most cases. var macroexpansion = function (f) {return f. shallow('macro_patterns', []).method('macro', function (pattern, expander) {if (! expander.apply) throw new Error('macro: Cannot define macro with non-function expander'); else return this.macro_patterns.push(pattern), this.macro_expanders.push(expander), this}). shallow('macro_expanders', []).method('macroexpand', function (t) {return this.baked_macroexpander ? macro_expand_baked(t, this.baked_macroexpander, this) : macro_expand_naive(t, this.macro_patterns, this.macro_expanders, this)}). method('bake_macroexpander', function () {return this.method('macro', function () {throw new Error('Cannot define new macros after baking the macroexpander')}). field('baked_macroexpander', jit_macroexpander(this.macro_patterns, this.macro_expanders))}). method('rmacro', function (pattern, expander) {if (! expander.apply) throw new Error('rmacro: Cannot define macro with non-function expander'); else return this.macro(pattern, function () {var t = expander.apply(this, arguments); return t && this.macroexpand(t)})})}, Composition behavior. New in 0.6.4 is the ability to compose caterwaul functions. This allows you to write distinct macroexpanders that might not be idempotent (as is the case for the Figment translator, for example: http://github.com/spencertipping/figment). Composition is achieved by invoking after(), which governs the behavior of the macroexpand() function. The list of functions to be invoked after a caterwaul function can be inspected by invoking after() with no arguments. | var f = caterwaul.clone(), g = caterwaul.clone(); f.after(g); // Causes g to be run on f's output; that is, g is an after-effect of f. f.after(h); // Adds another after function, to be run after all of the others. f.after(); // -> [g, h] The design for this feature is different in 0.6.5. The problem with the original design, in which after() returned a clone of the original function, was that you couldn't set up after-composition from within a configuration (since, reasonably enough, configuration is closed over the caterwaul instance). There is deliberately no before() method. The reason for this is that when you define a macro on a caterwaul function, it should take precedence over all other macros that get run. Obviously this doesn't happen for g if g comes after f, but generally that relationship is obvious from the setup code (which it might not be if a before() method could be invoked by configurations). composition = function (f) {return f. shallow('after_functions', []).method('after', function () {if (arguments.length) {for (var i = 0, l = arguments.length; i < l; ++i) this.after_functions.push(arguments[i]); return this} else return this.after_functions})}, Global Caterwaul setup. Now that we've defined lexing, parsing, and macroexpansion, we can create a global Caterwaul function that has the appropriate attributes. As of version 0.6.4, the init() property is polymorphic in semantics as well as structure. There are two cases: | 1. You invoke caterwaul on a syntax node. In this case only macroexpansion is performed. 2. You invoke caterwaul on anything else. In this case the object is decompiled, macroexpanded, and then compiled. This pattern is then closed under intent; that is, caterwaul functions compose both in the context of function -> function compilers (though composition here isn't advisable), and in the context of tree -> tree compilers (macroexpansion). Having such an arrangement is important for before() and after() to work properly. New in version 0.6.5 is the ability to bind closure variables during a tconfiguration(). This makes it simpler to close over non-globals such as node.js's require() function. caterwaul_core = function (f) {return configurable(f).configure(macroexpansion, composition). method('tconfiguration', function (configs, name, f, bindings) {this.configurations[name] = this.clone(configs)(f, bindings); return this}). field('syntax', syntax_node).field('ref', ref).field('parse', parse).field('compile', compile).field('gensym', gensym).field('map', map).field('self', self).field('unique', unique). field('macroexpansion', macroexpansion).field('replica', replica).field('configurable', configurable).field('caterwaul', caterwaul_core).field('decompile', parse). field('composition', composition).field('global', function () {return caterwaul_global}). method('init', function (f, environment) {var result = f.constructor === this.syntax ? this.macroexpand(f) : this.compile(this(this.decompile(f)), environment); if (f.constructor === this.syntax) for (var i = 0, l = this.after_functions.length; i < l; ++i) result = this.after_functions[i](result); return result}). method('reinitialize', function (transform, erase_configurations) {var c = transform(this.self), result = c(c, this.unique).deglobalize(); erase_configurations || (result.configurations = this.configurations); return result}). Utility library. Caterwaul uses and provides some design-pattern libraries to encourage extension consistency. This is not entirely selfless on my part; configuration functions have no access to the variables I've defined above, since the third-party ones are defined outside of the Caterwaul main function. So anything that they need access to must be accessible on the Caterwaul function that is being configured; thus a 'util' object that contains some useful stuff. For starters it contains some general-purpose methods: shallow('util', {extend: extend, merge: merge, se: se, id: id, bind: bind, map: map, qw: qw}). Baking things. Caterwaul can sometimes gain a performance advantage by precompiling pieces of itself to reflect its configuration. New in 0.7.0 is the ability to compile a decision-tree macroexpander function. To get it to do this, you use the bake() method, which freezes the caterwaul compiler's configuration and precompiles specialized functions to handle its configuration optimally. method('bake', function () {return this.bake_macroexpander()}). Magic. Sometimes you need to grab a unique value that is unlikely to exist elsewhere. Caterwaul gives you such a value given a string. These values are shared across all Caterwaul instances and are considered to be opaque. Because of the possibility of namespace collisions, you should name your magic after a configuration or otherwise prefix it somehow. method('magic', (function (table) {return function (name) {return table[name] || (table[name] = {})}})({}))}; __7557891864eb5c739a462549771ad971 meta::sdoc('js::core/caterwaul.compiler', <<'__e67397b6d19f677b6c0402a7c46732ef'); Environment-dependent compilation. It's possible to bind variables from 'here' (i.e. this runtime environment) inside a compiled function. The way we do it is to create a closure using a gensym. (Another reason that gensyms must really be unique.) Here's the idea. We use the Function constructor to create an outer function, bind a bunch of variables directly within that scope, and return the function we're compiling. The variables correspond to gensyms placed in the code, so the code will have closure over those variables. An optional second parameter 'environment' can contain a hash of variable->value bindings. These will be defined as locals within the compiled function. New in caterwaul 0.6.5 is the ability to specify a 'this' binding to set the context of the expression being evaluated. var compile = function (tree, environment) { // Despite the coincidence of 'tree' and 'environment' on this line, I'm seriously not pushing a green agenda :) var vars = [], values = [], bindings = merge({}, environment || {}, tree.bindings()), s = gensym(); for (var k in bindings) if (has(bindings, k)) vars.push(k), values.push(bindings[k]); var code = map(function (v) {return v === 'this' ? '' : 'var ' + v + '=' + s + '.' + v}, vars).join(';') + ';return(' + tree.serialize() + ')'; try {return (new Function(s, code)).call(bindings['this'], bindings)} catch (e) {throw new Error('Caught ' + e + ' while compiling ' + code)}}; __e67397b6d19f677b6c0402a7c46732ef meta::sdoc('js::core/caterwaul.configuration', <<'__7dbd96a9fb2c544be21176126049a5c4'); Configurations. Caterwaul is stateful in some ways, most particularly with macro definitions and compiler options. To prevent you from having to modify the global caterwaul() function, I've enabled replication. This works by giving you access to copies of caterwaul() (and copies of those copies, if you so choose) that you can customize independently. So, for example: | var copy = caterwaul.clone (function () { // This function is for customizations. Totally optional; can also customize at the toplevel. this.macro(qs[foo], fn_[qs[bar]]); }); | copy(function () { var bar = 6; return foo; }) (); // returns 6 Related to this is a configure() method that modifies and returns the original function: | caterwaul.configure (function () { // Global configuration using 'this' }); Core interface. The core API for replicable functions is exposed as 'caterwaul.replica'. This is primarily of use to API developers and not to end users. Also of use is the configuration 'caterwaul.configurable', which when applied to a replicable function will install Caterwaul's configurability onto it. For example: | var my_compiler = caterwaul.configurable(caterwaul.replica()); my_compiler.method('init', function () {/* custom compiler behavior */}); my_compiler.clone(); // A new instance You can then customize this function, which will have the same replication interface that Caterwaul has but won't have Caterwaul's default behavior. (A less elegant way to achieve the same thing is to clone caterwaul and give it a new 'init' method.) Attributes and methods. Function copying doesn't involve copying over every attribute indiscriminately, since different behaviors are required for different properties. For example, the macro table should be copied so that clones append to their local copies, methods should be rebound to the new function, and some attributes should just be referenced. These behaviors are encoded by way of an attribute table that keeps track of what to do with each. Attributes show up in this table when you call one of the attribute-association methods: | .field('attribute', value) Creates a reference-copying attribute. No copying is done at all; the attribute is cross-referenced between copies of the Caterwaul function. .shallow('attribute', value) Creates an attribute whose value is copied shallowly; for hashes or arrays. .method('name', f) Creates a method bound to the Caterwaul function. f will be bound to any copies on those copies. Naturally, attributes that don't appear in the table are left alone. You can add more of these attribute behaviors using the behavior() method: | .behavior('name', definition) Creates a new attribute behavior. definition() should take an original attribute value and return a new one, where 'this' is the new Caterwaul function. Underlying this mechanism is the associate() method: | .associate('attribute', 'behavior', value) Creates an attribute with the given behavior and assigns it a value. A couple of notes. First, these functions are bound to the function they modify; that is, you can eta-reduce them freely. Second, this is not a general purpose function replicator. All of the functions returned here call their own init() method rather than sharing a function body somewhere. (To be fair, the init() method gets referenced -- so it's almost as good I suppose.) A general-purpose way to do this would be to have g call f instead of g.init in the copy_of() function below. I'm not doing this in order to save stack frames; I want the function call performance to be constant-time in the number of copies. Another thing to be aware of is that this isn't a general-purpose metaclassing framework. I made a compromise by discouraging side-effecting initialization in the behavior-association methods -- these should just copy things, reference them, or transform them in some nondestructive way. This makes it easier to have extensional copies of objects, since there are fewer unknowns about the internal state. (e.g. we know that if 'foo' appears in the attribute table, we'll have something called 'foo' on the object itself and we can call its behavior -- we don't have to wonder about anything else.) var associator_for = function (f) {return function (name, behavior, value) {return f[name] = (f.behaviors[f.attributes[name] = behavior] || id).call(f, value), f}}, shallow_copy = function (x) {return x && (x.constructor === Array ? x.slice() : x.clone ? x.clone() : merge({}, x))}, copy_of = function (f) {var g = merge(function () {return g.init.apply(g, arguments)}, {behaviors: shallow_copy(f.behaviors), attributes: {}}); return se(g, function (g) {(g.associate = associator_for(g))('behavior', 'method', function (name, definition) {this.behaviors[name] = definition; return this.associate(name, 'method', function (attribute, value) {return this.associate(attribute, name, value)})}). behavior('method', g.behaviors.method); for (var k in f.attributes) has(f.attributes, k) && g.associate(k, f.attributes[k], f[k])})}, Bootstrapping method behavior. Setting up the behavior(), method(), field(), and shallow() methods. The behavior() and method() methods are codependent and are initialized in the copy_of function above, whereas the field() and shallow() methods are not core and are defined here. I'm also defining a 'configuration' function to allow quick definition of new configurations. (These are loadable by their names when calling clone() or configure() -- see 'Configuration and cloning' below.) A complement method, 'tconfiguration', is also available. This transforms the configuration function before storing it in the table, enabling you to use things like 'qs[]' without manually transforming stuff. The downside is that you lose closure state and can't bind variables. There's a convenience method called 'namespace', which is used when you have a shallow hash shared among different modules. It goes only one level deep. replica = se(function () {return copy_of({behaviors: {method: function (v) {return bind(v, this)}}}).behavior('field').behavior('shallow', shallow_copy)}, function (f) {f.init = f}), Configuration and cloning. Caterwaul ships with a standard library of useful macros, though they aren't activated by default. To activate them, you say something like this: | caterwaul.configure('std.fn'); // Longhand access to the function: caterwaul.configurations['std.fn'] You can also pass these libraries into a clone() call: | var copy = caterwaul.clone('std.fn', 'some_other_library', function () { ... }); Generally you will just configure with 'std', which includes all of the standard configurations (see caterwaul.std.js.sdoc in the modules/ directory). Note that functions passed to clone() and configure() are transformed using the existing caterwaul instance. This means that closure state is lost, so configuration at the toplevel is a good idea. Named configurations, on the other hand, are not explicitly transformed; so when you define a custom configuration in a named way, you will want to manually transform it. (The reason for this is that we don't want to force the configuration author to lose closure state, since it's arguably more important in a library setting than an end-user setting.) Alternatively you can use tconfigure(), which takes a series of configurations to use to transform your configuration function. (This makes more sense in code than in English; see how the configurations below are written...) Named configurations are made idempotent; that is, they cannot be applied twice. This is done through the 'has' hash, which can be manually reset if you actually do need to apply a configuration multiple times (though you're probably doing something wrong if you do need to do that). configurable = function (f) {return f. shallow('configurations', {}).shallow('has', {}).method('configuration', function (name, f) {this.configurations[name] = f; return this}). method('namespace', function (s) {return this[s] || this.shallow(s, {})[s]}). method('clone', function () {return arguments.length ? this.clone().configure.apply(null, arguments) : copy_of(this)}). method('configure', function () {for (var i = 0, l = arguments.length, _; _ = arguments[i], i < l; ++i) if (_.constructor === String) for (var cs = qw(arguments[i]), j = 0, lj = cs.length; _ = cs[j], j < lj; ++j) if (this.configurations[_]) this.has[_] || (this.has[_] = this.configurations[_].call(this, this) || this); else throw new Error('error: configuration "' + _ + '" does not exist'); else _ instanceof Array ? this.configure.apply(this, _.slice()) : _.call(this, this); return this})}; __7dbd96a9fb2c544be21176126049a5c4 meta::sdoc('js::core/caterwaul.macroexpander', <<'__9c3c260710a804e1a35aa04613f81af6'); Macroexpansion. Caterwaul is a Lisp, which in this case means that it provides the ability to transform code before that code is compiled. Lisp does macroexpansion inline; that is, as the code is being read (or compiled -- there are several stages I believe). Caterwaul provides offline macros instead; that is, you define them separately from their use. This gives Caterwaul some opportunity to optimize macro-rewriting. Defining offline macros is done in the normal execution path. For example: | caterwaul(function () { caterwaul.rmacro(qs[let (_ = _) in _], fn[n, v, e][qs[fn[args][body].call(this, values)].replace({args: n, body: e, values: v})]); }) (); // Must invoke the function | // Macro is usable in this function: caterwaul(function () { let (x = 5) in console.log(x); }); Wrapping the first function in caterwaul() wasn't necessary, though it was helpful to get the qs[] and fn[] shorthands. In this case, the macro is persistent to the caterwaul function that it was called on. (So any future caterwaul()ed functions would have access to it.) You can also define conditional macros, though they will probably be slower. For example: | caterwaul(function () { caterwaul.rmacro(qs[let (_) in _], fn[bs, e][bs.data === '=' && ...]); }) (); Here, returning a falsy value indicates that nothing should be changed about this syntax tree. It is replaced by itself and processing continues normally. You should try to express things in terms of patterns; there are theoretical optimizations that can cut the average-case runtime of pattern matching to a fraction of a full linear scan. The worst possible case is when you match on a universal pattern and restrict later: | caterwaul(function () { caterwaul.rmacro(qs[_], fn[x][...]); }) (); This will call your macroexpander once for every node in the syntax tree, which for large progams is costly. If you really do have such a variant structure, your best bet is to define separate macros, one for each case: | caterwaul(function () { var patterns = [qs[foo], qs[bar], qs[bif]]; patterns.map (function (p) { caterwaul.rmacro(p, fn[x][...]); }); }) (); Caterwaul implements several optimizations that make it much faster to macroexpand code when the macro patterns are easily identified. Pitfalls of macroexpansion. Macroexpansion as described here can encode a lambda-calculus. The whole point of having macros is to make them capable, so I can't complain about that. But there are limits to how far I'm willing to go down the pattern-matching path. Let's suppose the existence of the let-macro, for instance: | let (x = y) in z -> (function (x) {return z}) (y) If you write these macros: | foo[x, y] -> let (x = y) bar[x, y] -> x in y Caterwaul is not required to expand bar[foo[x, y], z] into (function (x) {return z}) (y). It might just leave it at let (x = y) in z instead. The reason is that while the individual macroexpansion outputs are macroexpanded, a fixed point is not run on macroexpansion in general. (That would require multiple-indexing, which in my opinion isn't worth the cost.) To get the extra macroexpansion you would have to wrap the whole expression in another macro, in this case called 'expand': | caterwaul.configure(function () { this.rmacro(expand[_], fn[expression][caterwaul.macroexpand(expression)]); }); This is an eager macro; by outputting the already-expanded contents, it gets another free pass through the macroexpander. Things that are not guaranteed: | 1. Reassembly of different pieces (see above). 2. Anything at all, if you modify the syntax tree in the macro code. Returning a replacement is one thing, but modifying one will break things. 3. Performance bounds. - pinclude pp::js::core/caterwaul.macroexpander.naive - pinclude pp::js::core/caterwaul.macroexpander.jit __9c3c260710a804e1a35aa04613f81af6 meta::sdoc('js::core/caterwaul.macroexpander.jit', <<'__71b5ce62f880b870b6a37642113313ec'); JIT macroexpander. This macroexpander examines the syntax trees used as macro patterns, constructs a breadth-first decision tree, and JITs a custom macroexpander function for those trees. Because this is a fairly tine-consuming operation the process is memoized in the macro list. At first I was using a context-free probabilistic model to optimize the order of decisions, but this requires knowing something about the syntax tree -- which means that each macroexpand() call involves generating a new function. This proved to be very expensive (worse than naive macroexpansion!), so I'm going with a model that can be used without knowing anything about the syntax trees being transformed. For history's sake I've left some of the theoretical notes involving probability, but in practice we don't know what the probability will be when we're building the decision tree. var jit_macroexpander = (function () { Irrelevance of discrimination. Suppose you have two macro trees, each with the same form (i.e. same arity of each node and same wildcard positions). I propose that the traversal order doesn't require any extensive optimization beyond one thing: How much information is being gained per comparison? This is very different from discrimination, which was the focus of the probabilistic JIT macroexpander design. Here is an example. | (, (_) (* (where) ([] ([ (= (_) (_)))))) <- the macro patterns (, (_) (* (where) ([] ([ (== (_) (_)))))) Suppose we run into a syntax tree that starts with ','. The likelihood of a comma occurring anyway is P(','), so we now have 1 / P(',') information. It doesn't matter that this fails to discriminate between the two macro patterns. We needed to know it anyway if we were going to perform a match, and it let us jump out early if it wasn't there. The left-hand side of each expansion tells us nothing, so we don't bother inspecting that yet. We instead go to the right-hand side, which we'll reject with probability (1 - P('*')). We then follow the right-hand side recursively downwards, continuing to match the non-wildcard nodes. Once all non-wildcard nodes are matched, we will have eliminated one macro pattern or the other. (This won't be true if we have overlapping macro definitions, but it is in this case.) At that point we will have both verified the macro pattern and reduced the macro-space as much as possible. Heterogeneous tree forms. Most of the time heterogeneity doesn't matter. The reason for this is that there are few variadic nodes. However, they do sometimes come up. One case is 'return', which sometimes occurs without a child. In this case we might have macro patterns like this: | (return (foo)) (return) We'll obviously have to compare the 'return' first, but what happens with the subtree in the first case? The answer is that the tree length is compared when the data is. This gives us an extra bailout condition. So comparing the 'return' will eliminate one possibility or the other, since the length check will fail for one of them. So we have a nice invariant: All trees under consideration will have the same shape. This check isn't reflected in the traversal path construction below, but it is generated in the final pattern-matching code. It's also generated in the intermediate treeset object representation. Traversal path. The pattern matcher uses both depth-first and breadth-first traversal of the pattern space. The idea is that each motion through the tree is fairly expensive, so we do a comparison at each point. However, we can decide which branch to progress down without losing progress. For example, suppose we have this: | (+ (* (a) (b)) (/ (a) (b))) The first comparison to happen is +, regardless of what the rest of the tree looks like. If we don't bail out, then without loss of generality suppose we take the * branch. At this point we have two nodes stored in local variables; one is the root + node, and the other is the child * node. The total child set is now (a), (b), and (/ (a) (b)). We take whichever one of these children is most likely to bail out, which let's suppose is (b). Because (b) has no children, we now have (a) and (/ (a) (b)). Now suppose that / is the next lowest-probability symbol; we then visit that node, producing the children (a) and (b). (b) is the lower-probability one, so we test that, leaving the total child list at (a), (a). The order of these last two comparisons doesn't matter. We need an intermediate representation for the path-finder decisions. The reason is that these decisions are used to both (1) partition the tree set, and (2) generate code. (2) isn't particularly difficult using just stack-local data, but (1) ends up being tricky because of the potential for breadth-first searching. The problem is a subtle one but is demonstrated by this example: | (+ (* (/ (a) (b)) (c)) (+ (a) (b))) (+ (* (* (a) (b)) (c)) (- (a) (b))) Suppose here that we search subtree [0] first. Further, suppose that we end up progressing into subtree [0][0]. At this point we'll create a branch, so we have partitioned the original tree space into two fragments. As such, we need to know to ask for just the probability of '+' or '-' on its own, not the sum, since we'll be able to bail out immediately if the wrong one is present. This kind of coupling means that we need to be able to query the original trees in their entirety and from the root point. This in turn requires a logical path representation that can be used to both partition trees, and to later generate code to do the same thing. (An interesting question is why we wouldn't use the JIT to do this for us. It would be a cool solution, but I think it would also be very slow to independently compile that many functions.) Paths are represented as strings, each of whose characters' charCodes is an index into a subtree. The empty string refers to the root. I'm encoding it this way so that paths can be used as hash keys, which makes it very fast to determine which paths have already been looked up. Also, most macro paths will be fewer than five characters, so even on eager-consing runtimes the quadratic nature of it isn't that bad. Treeset partitions are returned as objects that map the arity and data to an array of trees. The arity is encoded as a single character whose charCode is the actual arity. So, for example, partition_treeset() might return this object: | {'\002+': [(+ (x) (y)), (+ (x) (z))], '\002-': [(- (x) (y)), (- (x) (z))]} var resolve_tree_path = function (tree, path) {for (var i = 0, l = path.length; i < l; ++i) if (! (tree = tree[path.charCodeAt(i)])) return tree; return tree}, partition_treeset = function (trees, path) {for (var r = {}, i = 0, l = trees.length, t, ti; i < l; ++i) (t = resolve_tree_path(ti = trees[i], path)) ? (t = String.fromCharCode(t.length) + t.data) : (t = ''), (r[t] || (r[t] = [])).push(ti); return r}, Pathfinder logic. For optimization's sake the hash of visited paths maps each path to the arity of the tree that it points to. This makes it very easy to generate adjacent paths without doing a whole bunch of path resolution. Partitioning is done by visit_path, which first partitions the tree-space along the path, and then returns new visited[] hashes along with the partitions. The visited[] hash ends up being split because different partitions will have different traversal orders. In practice this means that we copy visited[] into several new hashes. The other reason we need to split visited[] is that the arity of the path may be different per partition. So the result of visit_path looks like this: | {'aritydata': {visited: {new_visited_hash}, trees: [...]}, 'aritydata': ...} The base case is when there is no visited history; then we return '' to get the process started with the root path. As explained in 'Full specification detection', next_path needs to skip over any paths that refer to wildcards. next_path = function (visited, trees) {if (! visited) return ''; for (var k in visited) if (visited.hasOwnProperty(k)) for (var i = 0, l = visited[k], p; i < l; ++i) if (! ((p = k + String.fromCharCode(i)) in visited)) { for (var j = 0, lj = trees.length, skip; j < lj; ++j) if (skip = resolve_tree_path(trees[j], p).data === '_') break; if (! skip) return p}}, visit_path = function (path, visited, trees) {var partitions = partition_treeset(trees, path), kv = function (k, v) {var r = {}; r[k] = v; return r}; for (var k in partitions) if (partitions.hasOwnProperty(k)) partitions[k] = {trees: partitions[k], visited: merge({}, visited, kv(path, k.charCodeAt(0)))}; return partitions}, Full specification detection. Sometimes no path resolves the treeset. At that point one or more trees are fully specified, so we need to find and remove those trees from the list. This will produce an array of results [treeset, treeset, treeset, ...]. The property is that each treeset will contain trees that either (1) are all fully specified with respect to the set of visited paths, or (2) are all not fully specified with respect to the paths. Order is also preserved from the original treeset. Note that the first treeset always represents trees which are not fully specified, then each subsequent treeset alternates in its specification. This way you can use a shorthand such as i&1 to determine whether a given treeset is final. (Because of all of this, the first treeset may be empty. All other ones, if they exist, will be populated.) This is actually a much more straightforward task than it sounds like, because the number of non-wildcard nodes for each tree is already stored in pattern_data. This means that we just need to find trees for which the number of non-wildcard nodes equals the number of visited paths. There's a kind of pathological case that also needs to be considered. Suppose you've got a couple of macro patterns like this: | (a (b) (_)) (a (_) (b)) In this case we may very well have to try both even though technically neither tree will be specified yet (and hence we don't think there's any ambiguity). The way to address this is to make sure that any trees we put into an 'unspecified' partition are all unspecified in the same place. So the two trees above would go into separate partitions, even though they're both unspecified. Then next_path() will be able to provide a single path that increases specificity, not one that hits a wildcard. This case can be recognized because non-wildcards will occur in different positions. A greedy algorithm will suffice; the idea is that we build a list of indexes that refer to non-wildcards and intersect it with each tree we consider. If the next tree results in a zero list then we defer it to the next partition. The way I'm doing this is finishing off the current partition, inserting an empty fully-specified partition, and then kicking off a new partition of unspecified trees. This probably isn't the most efficient way to go about it, but the code generator knows how to deal with empty partitions gracefully. split_treeset_on_specification = function (trees, pattern_data, visited) { var r = [], visited_count = 0, available_paths = {}, available_count = 0; if (visited != null) {for (var k in visited) if (visited.hasOwnProperty(k)) { ++visited_count; for (var i = 0, l = visited[k]; i < l; ++i) available_paths[k + String.fromCharCode(i)] = ++available_count}} else available_paths = {'': available_count = 1}; for (var p = [], s = false, remaining_paths = null, remaining_count = 0, i = 0, l = trees.length, t, td; i < l; ++i) if (((td = pattern_data[(t = trees[i]).id()]).non_wildcards === visited_count) !== s) r.push(p), p = [t], s = !s, remaining_paths = null; else if (s) p.push(t); else { if (remaining_paths === null) remaining_paths = merge({}, available_paths), remaining_count = available_count; for (var ps = td.wildcard_paths, j = 0, lj = ps.length, pj; j < lj; ++j) remaining_count -= remaining_paths.hasOwnProperty(pj = ps[j]), delete remaining_paths[pj]; if (remaining_count) p.push(t); else r.push(p), r.push([]), p = [t], remaining_paths = null} p.length && r.push(p); return r}, Pattern data. We end up with lots of subarrays of the original pattern list. However, we need to be able to get back to the original expander for a given pattern, so we keep a hash of pattern data indexed by the ID of the pattern tree. The pattern data consists of more than just the expander; we also store the number of non-wildcard nodes per pattern tree. This is used to determine which trees are fully resolved. We also need a list of wildcard paths for each tree; this is used to efficiently construct the arrays that are passed into the expander functions. By convention I call the result of this function pattern_data, which shadows this function definition. (Seems somehow appropriate to do it this way.) wildcard_paths = function (t) {for (var r = t.data === '_' ? [''] : [], i = 0, l = t.length; i < l; ++i) for (var ps = t[i] && wildcard_paths(t[i]), j = 0, lj = ps.length; j < lj; ++j) r.push(String.fromCharCode(i) + ps[j]); return r}, pattern_data = function (ps, es) {for (var r = {}, i = 0, l = ps.length, p; i < l; ++i) r[(p = ps[i]).id()] = {expander: es[i], non_wildcards: non_wildcard_node_count(p), wildcard_paths: wildcard_paths(p)}; return r}, Code generation. This is the last step and it's where the algorithm finally comes together. Two big things are going on here. One is the traversal process, which uses next_path to build the piecewise traversal order. The other is the code generation process, which conses up a code tree according to the treeset partitioning that guides the traversal. These two processes happen in parallel. The original JIT design goes over a lot of the code generation, but I'm duplicating it here for clarity. (There are also some changes in this design, though the ideas are the same since they're both fundamentally just decision trees.) Function body. This is largely uninteresting, except that it provides the base context for path dereferencing (see 'Variable allocation' below). It also provides a temporary 'result' variable, which is used by the macroexpander invocation code. pattern_match_function_template = parse('function (t) {var result; _body}'), empty_variable_mapping_table = function () {return {'': 't'}}, Partition encoding. Each time we partition the tree set, we generate a switch() statement. The switch operates both on arity and on the data, just like the partitions would suggest. (However these two things are separate conditionals, unlike their representation in the partition map.) The layout looks like this: | switch (tree.length) { case 0: switch (tree.data) { case 'foo': ... case 'bar': ... } break; case 1: switch (tree.data) { case 'bif': ... case 'baz': ... } break; } Note that we can't return false immediately after hitting a failing case. The reason has to do with overlapping macro definitions. If we have two macro definitions that would both potentially match the input, we have to proceed to the second if the first one rejects the match. partition_template = parse('switch (_value) {_cases}'), partition_branch_template = parse('case _value: _body; break'), Attempting a macro match is kind of interesting. We need a way to use 'break' to escape from a match, so we construct a null while loop that lets us do this. Any 'break' will then send the code into the sequential continuation, not escape from the function. single_macro_attempt_template = parse('do {_body} while (false)'), Variable allocation. Variables are allocated to hold temporary trees. This reduces the amount of dereferencing that must be done. If at any point we hit a variable that should have a value but doesn't, we bail out of the pattern match. A table keeps track of path -> variable name mappings. The empty path always maps to 't', which is the input tree. Incremental path references can be generated anytime we have a variable that is one dereference away from the given path. generate_incremental_path_reference does two things. First, it creates a unique temporary name and stashes it into the path -> variable mapping, and then it returns a syntax tree that uses that unique name and existing entries in the path -> variable mapping. The path's index is hard-coded. Note that if the path isn't properly adjacent you'll end up with an array instead of a syntax tree, and things will go downhill quickly from there. indexed_path_reference_template = parse('_base[_index]'), absolute_path_reference_template = parse('_base'), generate_path_reference = function (variables, path) { return variables[path] ? absolute_path_reference_template.replace({_base: variables[path]}) : indexed_path_reference_template .replace({_base: generate_path_reference(variables, path.substr(0, path.length - 1)), _index: '' + path.charCodeAt(path.length - 1)})}, path_variable_template = parse('var _temp = _value; if (! _temp) break'), path_exists_template = parse('null'), generate_path_variable = function (variables, path) {if (variables[path]) return path_exists_template; var name = 't' + genint(), replacements = {_value: generate_path_reference(variables, path), _temp: name}; return variables[path] = name, path_variable_template.replace(replacements)}, Macroexpander invocation encoding. The actual macroexpander functions are invoked by embedding ref nodes in the syntax tree. If one function fails, it's important to continue processing with whatever assumptions have been made. (This is actually one of the trickier points of this implementation.) Detecting this isn't too bad though. It's done above by split_treeset_on_specification. non_wildcard_node_count = function (tree) {var r = 0; tree.reach(function (node) {r += node.data !== '_'}); return r}, Invocations of the macroexpander should be fast, so there's some kind of interesting logic to quickly match wildcards with a minimum of array consing. This optimization requires a simplifying assumption that all _ nodes are leaf nodes, but this is generally true. (It's possible to build macro patterns that don't have this property, but they won't, and never would have, behaved properly.) The idea is that once we have a fully-specified macro pattern we can simply go through each visited path, grab the direct children of each node, and detect wildcards. We then encode these wildcard paths as hard-coded offsets from the tree variables. So, for example: | (+ (/ (_) (b)) (* (a) (_))) visited: [0], [0][1], [1], [1][0] children: (_), (b), (a), (_) parameters: [paths[0][0], paths[1][1]] This requires a lexicographic sort of the paths to make sure the tree is traversed from left to right. Note that a new array is consed per macroexpander invocation. I'm not reusing the array from last time because (1) it's too much work, and (2) the fallthrough-macro case is already fairly expensive and uncommon; a new array cons isn't going to make much difference at that point. path_reference_array_template = parse('[_elements]'), generate_path_reference_array = function (variables, paths) {for (var refs = [], i = 0, l = paths.length; i < l; ++i) refs.push(generate_path_reference(variables, paths[i])); return path_reference_array_template.replace({_elements: refs.length > 1 ? new syntax_node(',', refs) : refs[0]})}, macroexpander_invocation_template = parse('if (result = _expander.apply(this, _path_reference_array)) return result'), generate_macroexpander_invocation = function (pattern_data, pattern, variables) {return macroexpander_invocation_template.replace( {_expander: new ref(pattern_data[pattern.id()].expander), _path_reference_array: generate_path_reference_array(variables, pattern_data[pattern.id()].wildcard_paths)})}, Multiple match handling. When one or more macros are fully specified, we need to go through them in a particular order. Failover is handled gracefully; we just separate the macro patterns by a semicolon, since a success side-effects via return and a failure's side-effect is its sequential continuation. (This is why we needed 'break' instead of 'return false' when generating case statements above.) Here the pattern_data variable refers to a hash that maps each pattern's identity to some data about it, including which macroexpander belongs to the pattern in the first place. Note that because I'm using identities this way, you can't add the same pattern (referentially speaking) to map to two different macroexpanders. It would be a weird thing to do, so I don't anticipate that it would happen by accident. But it will cause bogus macroexpansion results if you do. First case: underspecified trees. In this case we create a switch on the tree length first. Then we subdivide into the data comparison. We create the tree-length switch() even if only one tree matches; the reason is that we still need to know that the tree we're matching against has the right length, even if it doesn't narrow down the macro space at all. length_reference_template = parse('_value.length'), data_reference_template = parse('_value.data'), generate_partitioned_switch = function (trees, visited, variables, pattern_data) { var path = next_path(visited, trees), partitions = visit_path(path, visited, trees), lengths = {}, length_pairs = []; for (var k in partitions) if (partitions.hasOwnProperty(k)) (lengths[k.charCodeAt(0)] || (lengths[k.charCodeAt(0)] = [])).push(k.substr(1)); for (var k in lengths) if (lengths.hasOwnProperty(k)) length_pairs.push([k, lengths[k]]); var new_variables = merge({}, variables), path_reference_variable = generate_path_variable(new_variables, path), variable = new_variables[path], length_reference = length_reference_template.replace({_value: variable}), data_reference = data_reference_template.replace({_value: variable}); for (var length_cases = new syntax_node(';'), i = 0, l = length_pairs.length, pair; i < l; ++i) { for (var data_cases = new syntax_node(';'), length = (pair = length_pairs[i])[0], values = pair[1], j = 0, lj = values.length, p, v; j < lj; ++j) p = partitions[String.fromCharCode(length) + (v = values[j])], data_cases.push(partition_branch_template.replace({_value: '"' + v.replace(/([\\"])/g, '\\$1') + '"', _body: generate_decision_tree(p.trees, path, p.visited, new_variables, pattern_data)})); lj && length_cases.push(partition_branch_template.replace({_value: '' + length_pairs[i][0], _body: partition_template.replace({_value: data_reference, _cases: data_cases})}))} return single_macro_attempt_template.replace({_body: new syntax_node(';', path_reference_variable, length_cases.length ? partition_template.replace({_value: length_reference, _cases: length_cases}) : [])})}, Second case: specified trees (base case). This is fairly simple. We just generate a sequence of invocations, since each tree has all of the constants assumed. generate_unpartitioned_sequence = function (trees, variables, pattern_data) {for (var r = new syntax_node(';'), i = 0, l = trees.length; i < l; ++i) r.push(generate_macroexpander_invocation(pattern_data, trees[i], variables)); return r}, Inductive step. This is where we delegate either to the partitioned switch logic or the sequential sequence logic. generate_decision_tree = function (trees, path, visited, variables, pattern_data) { for (var r = new syntax_node(';'), sts = split_treeset_on_specification(trees, pattern_data, visited), i = 0, l = sts.length; i < l; ++i) sts[i].length && r.push(i & 1 ? generate_unpartitioned_sequence(sts[i], variables, pattern_data) : generate_partitioned_switch(sts[i], visited, variables, pattern_data)); return r}; Macroexpansion generator. This is where all of the logic comes together. The only remotely weird thing we do here is reverse both the pattern and expansion lists so that the macros get applied in the right order. return function (patterns, expanders) {for (var i = patterns.length - 1, rps = [], res = []; i >= 0; --i) rps.push(patterns[i]), res.push(expanders[i]); return compile(pattern_match_function_template.replace( {_body: generate_decision_tree(rps, null, null, empty_variable_mapping_table(), pattern_data(rps, res))}))}})(), macro_expand_baked = function (t, f, context) {return t.rmap(function (n) {return f.call(context, n)})}; __71b5ce62f880b870b6a37642113313ec meta::sdoc('js::core/caterwaul.macroexpander.jit-probabilistic', <<'__f8e79b07d50f734a587e82b560e02729'); JIT macroexpander with probabilistic modeling. The naive macroexpander, implemented in sdoc::js::core/caterwaul.macroexpander.naive, takes linear time both in the number of syntax nodes and the number of macros. For potentially deep pattern trees this becomes too slow for regular use. This macroexpander uses just-in-time compilation to optimize lookups against the macro table, eliminating most of the checks that would have failed. Algorithm and use case analysis. For n syntax nodes and k macro patterns, a naive approach performs O(nk) tree-match operations. Each match is O(n) in the complexity of the pattern tree. At first it seemed like this would scale reasonably well, but version 0.6.7 of Caterwaul performed 750000 tree-match operations to load just the standard libraries. This ended up taking several seconds on some runtimes. Implementation approaches. Obviously it isn't terribly feasible to index every syntax node in every way, since we'll discover useful information about it just by querying its data. At that point we will have partitioned the macro-space to a smaller subset, and some other test will then serve to partition it further. In general this procedure would be quite slow; there's a lot of decision-making going on here. However, this overhead vanishes if, rather than using higher-order logic to construct the match function, we instead compile one tailored to the macro set. (Caterwaul /is/ a runtime compiler, after all :). In the case of the standard library (module 'std') we'd have something like this: | var t1 = tree; if (! t1) return false; // Fail quickly if nodes don't exist switch (t1.data) { // Check toplevel node -- this is the cheapest check we can do right now case '*': var t2 = t1[0]; if (! t2) return false; switch (t2.data) { // Check next node down case 'let': var t3 = t1[1]; if (! t3) return false; var t4 = t3[0]; if (! t4) return false; if (t3.data === '[]' && t4.data === '[') { // Just one pattern case left, so verify that the other elements match up var p = [t4[0], t3[1]]; // Either of these can be undefined return macroexpander_1.apply(this, p) || macroexpander_2.apply(this, p) || ...; } else return false; // ... This strategy is ideal because it performs inexpensive checks first, then dives down to do the more time-consuming ones. It also has the benefit that failover cases are still preserved; a macro that returns a falsy replacement will trigger the next macro in the series, finally failing over to no alteration. This is done in what I believe to be the fastest possible way. Note that I've omitted arity checks in the code above. This is generally safe to do; the alternative is to bail on unexpectedly undefined values, which is happening. However, it doesn't handle the case where there is extra data on a node for whatever reason. The real macroexpander has one final check to make sure that the arity of each non-wildcard node matches the pattern. Each decision costs a certain amount. Based on some benchmarks I've run that amount seems to be bounded in the length of the cases rather than the selector (see hackery/macroexpander-indexing/switch-strings.js for details), which is ideal. However, V8 has a minor pathological inefficiency case with long selectors and many cases. (It doesn't seem to optimize away some cases by using a failing length check.) Because of this, each switch statement needs to be guarded by a maximum-length check. Any strings longer than the maximum are automatically discarded, since they would be (1) expensive to check in the worst case, and (2) they would fail each check anyway. (It's also cheap to do a length check because all of the lengths are already known at compile-time.) Constructing the decisions. It would be fun to have some complex solver to build the decision tree, but I'm not convinced that one is necessary. Rather, I think a simple heuristic approach will work just fine. The reason is that each decision level involves the same amount of complexity (and it's fairly cheap), so doing one to save one is perfectly valid. This means that there isn't a particularly good reason to reduce the number of decisions when there are in fact a lot of cases. Here are the important scenarios to consider: | 1. The pattern space is uniform in its data. In this case, we skip the check (since it doesn't tell us anything) and descend into the next level. 2. The pattern space is partially decided by its data. In this case, construct a decisional and split the pattern space. 3. The pattern space is partially undefined. This happens if one of the patterns has '_' as its data, for instance. 4. The pattern space is completely undefined. This happens if each pattern has '_' as its data. Cases (3) and (4) are not as simple as (1) and (2). If two macros overlap, they need to be tried in the right order (which in this case is backwards from the order they appear in the macro array). So each wildcard within the pattern space partitions the patterns into those which should be matched before and those which should be matched after. For example, suppose we have three patterns in this order: | ([] (let) (_)) ([] (_) (_)) ([] (where) (_)) Note that unfortunately we can't remove the ([] (let) (_)) macro because the ([] (_) (_)) macro might reject the match for whatever reason. So despite the fact that technically the more specific pattern is shadowed by a more general one we can't optimize that case. They all start with the same thing so we generate a quick-failure check and build conditionals on [0]. Here's the condition tree after partitioning (arity checks and intermediate variables elided): | switch (t1.data) { // Compare the initial data first; this gives us a quick failure case for non-matching nodes case '[]': switch (t2.data) {case 'where': if (solution = macroexpander_for_where([t1[1]])) return solution} if (solution = macroexpander_for_wildcard([t1[1]])) return solution; switch (t2.data) {case 'let': if (solution = macroexpander_for_let([t1[1]])) return solution} } Pathological cases like this will sometimes happen, but most of the time macro patterns will be more sensible. In particular, most macros share forms in practice, which decreases the likelihood that anything bizarre like this will occur. So far I've portrayed the decisional tree construction as being a mostly linear process, but this isn't the case in practice. The reason is that at some point we have to decide which child to descend to next, or whether to do a parallel navigation or some such. To do this effectively we need an estimated-cost function. Computing estimated cost. Rather than introducing the variable of information-gained into the equation, I'm keeping it simple by assuming that we must reduce the search space down to a single item. Therefore the only relevant question is how much effort is required to do this. Luckily this is fairly straightforward to figure out. We just need to make some simplifying assumptions. | 1. Comparison on a node is about as expensive as visiting the node in the first place. 2. Identifier nodes have much higher information density than operator nodes, and identifier nodes never have children. For concreteness' sake, let's assume that there are 100 identifiers and 20 operators. (See 'Probabilistic modeling' for details about how a more realistic model is constructed.) It's possible to conclude a few things from these rules: | 1. It always makes sense to perform a comparison. If each possibility is equally likely, then odds are 1/20 that each case will be taken; so either (1) we partition the space (which is useful), or (2) we abandon the search with likelihood 19/20 (which is really useful). 2. We should perform comparisons in whichever order minimizes the expected number of future comparisons. An alternative to the uniformity assumption is to actually go through the syntax tree up-front and count the number of occurrences of each possibility. This way we have an order-1 probability model to work with to better judge the accuracy of each decisional. The cost of constructing this model is 2n in the tree size; one factor of n for visits, the other factor of n for the hashtable lookups involved in counting. (I'm deliberately being vague about the exact cost of these things, since it varies a lot depending on the runtime.) In order for this to be justifiable it would have to then save an expected two operations per node. Considering the highly nonuniform occurence of Javascript operators, I'd say this is easily possible. Probabilistic modeling. For simplicity's sake I don't want to dive down the context-ful rabbit hole yet. This probability model uses only two contexts: The node has children, or the node does not have children. (This actually is an important distinction, since in Javascript only operators can have children.) So we end up with two probability check functions: | P(node.data === 'whatever' | node has children) P(node.data === 'whatever' | node has no children) There are some optimizations that are possible when building these tables. One is that only the symbols mentioned in any of the macro patterns need to be counted; other ones can be added to the total but otherwise ignored. The other, which follows from the first, is that we can do a preliminary symbol length check to avoid unnecessary string comparisons. (The hash()/has() mechanism actually does this for us.) construct_symbol_table = function (macro_patterns) { var symbols = {}, max_length = 0; for (var i = 0, l = macro_patterns.length; i < l; ++i) node_patterns[i].reach(function (node) {var d = node.data; symbols[d] = true; d.length > max_length && (max_length = d.length)}); symbols[max_length_gensym] = max_length; return symbols}, construct_probability_models = function (macro_patterns, tree) { var symbols = construct_symbol_table(macro_patterns), l0 = {}, l0_total = 0, ln = {}, ln_total = 0; tree.reach(function (node) {var d = node.data; if (has(symbols, d)) if (node.length) {++ln_total; ln[d] = (ln[d] || 0) + 1} else {++l0_total; l0[d] = (l0[d] || 0) + 1}}); for (var k in l0) l0[k] /= l0_total; for (var k in ln) ln[k] /= ln_total; return {p0: l0, pn: ln}}, Note that I'm not checking for hasOwnProperty in the division loops. The reason is that IE has a bug that makes hasOwnProperty return false for members of the prototype. This means that for things like toString, hasOwnProperty, etc. we would fail to divide, yielding insane (> 1) probabilities for these attributes. The alternative case is to divide some functions by numbers, which results in NaN being assigned. (This happens in the pathological case that someone has extended Object.prototype.) Even this isn't that bad, though, because (1) we will never access those properties anyway, and (2) the objects with NaN properties don't escape for the user to access either. Tree weighting. Once we have an estimated cost function for a single level, we'll need to apply it recursively (and hopefully memoize the process so it isn't exponential-time). The idea is to, at each point, make a decision that will minimize the total expected number of decisions and macroexpand calls after that. So, for example: | ([] (let) (_)) ([] (where) (_)) The tree's weight is calculated like this. First, both start with a common prefix of [], which has probability p1. Given that the tree matches this, the [0] node will be 'let' with probability p2 and 'where' with probability p3. Each check involves exactly one decision, so the total weight is d1 + p1 * d2. (Remember that we're counting the expected number of decisions, so we add one for each comparison and ignore cases for which no further decisions need to be made.) A more interesting case arises here: | ([] (let) (= (_) (_))) ([] (let) (== (_) (_))) ([] (where) (_)) The weights of these trees are different. If p1 = p('[]'), p2 = p('let'), p3 = p('where'), p4 = p('='), and p5 = p('=='), then the total weight of deciding the set totals up to d1 + p1 * (d2 + p2 * d3). Here, d1 is the decision cost of ascertaining that the tree data is [], d2 is the decision cost of identifying 'let' vs 'where', and d3 is the cost of identifying '=' vs '=='. Here's how the algorithm gets applied. Suppose we want have these macro patterns: | ([] (let) (= (_) (_))) ([] (let) (== (_) (_))) ([] (where) (= (_) (_))) ([] (where) (in (_) (_))) And we want to find the optimal search strategy. We first accept a decision on the top node, producing a weight of d1. Then we consider the weight of the left and right subtrees independently. Deciding the left subtree yields two sets: | (= (_) (_)) and (== (_) (_)) with probability p('let') (= (_) (_)) and (in (_) (_)) with probability p('where') We then compute the weight of each of these sets and multiply by the probabilities: | d2 * p('let') d3 * p('where') This sum then is the weight of taking the [0] subtree. We then do the same process for the [1] subtree: | (let) and (where) with probability p('=') (let) with probability p('==') (where) with probability p('in') Assigning decisional costs: | d2 * p('=') d3 * p('==') d4 * p('in') And sum to get the total weight of taking the [1] subtree. Note that d3 and d4 are nonzero! This is because we have to verify them prior to knowing whether to accept the pattern or return false. It is easily possible that taking the [1] subtree is therefore more expensive than taking the [0] subtree, since switch() can be assumed to be slightly sublinear in the number of cases. Given these weights we take the [0] subtree. It has a cheaper initial decision and a cheaper progression from there. treeset_weight = function (trees, weight_of, p0, pn) { }, Tree partitioning. This is an awkward problem. In the case above, each tree had two sub-trees, which made the partitioning problem straightforward. However, sometimes trees have three sub-trees. This means that after we've decided one of those three we still have two left over. Even though this is arguably a pathological case, it occurs every now and then in regular Javascript. For example: | (? (a) (= (a) (_)) (= (b) (_))) (? (a) (== (c) (_)) (== (b) (_))) (? (b) (= (a) (_)) (== (a) (_))) Deciding subtree [0] produces two partitions: | (? (= (a) (_)) (= (b) (_))) and (? (== (c) (_)) (== (b) (_))) with probability p('a') (? (= (a) (_)) (== (a) (_))) with probability p('b') At this point we have two different decisional processes, as usual. The second case just requires verification. maximum_subtree_index = function (trees) {for (var r = 0, i = 0, l = trees.length, li; i < l; ++i) li = trees[i].data.length, li > r && (r = li); return r}, subtree_partitions = function (trees, subtree) {for (var r = {}, i = 0, l = trees.length, d, t; i < l; ++i) (d = (t = trees[i])[subtree]) ? (d = d.data) : (d = ''), (r[d] || (r[d] = [])).push(t); return r}, Decision weighting. Conditional structures need to be sorted to minimize the expected number of comparisons. For example, suppose P(foo) is 0.3 and P(bar) is 0.1. Then the switch() between them should list the 'foo' case before the 'bar' case. (Note that length, which bounds the cost of a single string comparison, is irrelevant; the number of characters we compare if the strings don't match can be assumed to be very small.) This provides a nice model for deciding how expensive a decision is too. For example: | switch (x) { case 'foo': // ... <- this happens with probability p('foo') case 'bar': // ... <- this happens with probability (1 - p('foo')) * p('bar') case 'bif': // ... <- this happens with probability (1 - p('foo')) * (1 - p('bar')) * p('bif') } The cost of this decision isn't 3. It's 1 * p('foo') + 2 * (1-p('foo'))*p('bar') + 3 * (1-p('foo'))*(1-p('bar'))*p('bif'). This has some interesting consequences, particularly that it is sometimes much cheaper to take a long decision than it is to take a short one. If the probabilities are exceptionally skewed then most decisional processes will be short. The decision_weight function takes an array of choice trees, a function to compute the weight (passed in first-class to permit memoization), the table of order-0 probabilities, and the table of order-N probabilities. (The latter two are computed by construct_probability_models above.) There's an accompanying constant, decision_weight_constant, that exists to approximate the overhead involved in making a decision in the first place. This includes generating/processing the switch() statement and doing the length check. optimal_decision_ordering = function (partitions, weight_of, p0, pn) { decision_weight_constant = 10, decision_weight = function (partitions, weight_of, p0, pn) { var p = function (x) {return x.length ? pn[p.data] : p0[p.data]}, sorted = Array.prototype.slice.call(choices).sort(function (x, y) {return p(y) - p(x)}), np = 1, e = 0; for (var i = 0, l = sorted.length, si, pi; i < l; ++i) si = sorted[i], pi = p(si), e += np * pi * weight_of(si), np *= (1 - pi); return decision_weight_constant + e} __f8e79b07d50f734a587e82b560e02729 meta::sdoc('js::core/caterwaul.macroexpander.naive', <<'__7f417e995fd77a32ec981042f15509d9'); Naive macroexpander implementation. This is the macroexpander used in Caterwaul 0.6.x and prior. It offers reasonable performance when there are few macros, but for high-macro cases it becomes prohibitive. Version 0.7.0 and forward use the optimizing JIT macroexpander defined in sdoc::js::core/caterwaul.macroexpander.jit. (Note that this macroexpander is still here to make the match() syntax tree method work.) Matching. macro_try_match returns null if two syntax trees don't match, or a possibly empty array of wildcards if the given tree matches the pattern. Wildcards are indicated by '_' nodes, as illustrated in the macro definition examples earlier in this section. Note that this function is O(n) in the number of nodes in the pattern. It is optimized, though, to reject invalid nodes quickly -- that is, if there is any mismatch in arity or data. var macro_array_push = Array.prototype.push, macro_try_match = function (pattern, t) {if (pattern.data === '_') return [t]; if (pattern.data !== t.data || pattern.length !== t.length) return null; for (var i = 0, l = pattern.length, wildcards = [], match = null; i < l; ++i) if (match = macro_try_match(pattern[i], t[i])) macro_array_push.apply(wildcards, match); else return null; return wildcards}, Expansion. Uses the straightforward brute-force algorithm to go through the source tree and expand macros. At first I tried to use indexes, but found that I couldn't think of a particularly good way to avoid double-expansion -- that is, problems like qs[qs[foo]] -- the outer must be expanded without the inner one. Most indexing strategies would not reliably (or if reliably, not profitably) index the tree in such a way as to encode containment. Perhaps at some point I'll find a faster macroexpander, especially if this one proves to be slow. At this point macroexpansion is by far the most complex part of this system, at O(nki) where n is the number of parse tree nodes, k is the number of macros, and i is the number of nodes in the macro pattern tree. (Though in practice it's generally not quite so bad.) Note! This function by default does not re-macroexpand the output of macros. That is handled at a higher level by Caterwaul's macro definition facility (see the 'rmacro' method). The fourth parameter, 'context', is used to hand a 'this' reference to the macroexpander. This is necessary to get defmacro[] to work properly, and in general lets macros be side-effectful. (Not that you should be in the habit of defining side-effectful macros, but I certainly won't stop you.) Note that as of version 0.5, macroexpansion proceeds backwards. This means that the /last/ matching macro is used, not the first. It's an important feature, as it lets you write new macros to override previous definitions. This ultimately lets you define sub-caterwaul functions for DSLs, and each can define a default case by matching on qs[_] (thus preventing access to other macro definitions that may exist). macro_expand_naive = function (t, macros, expanders, context) { return t.rmap(function (n) {for (var i = macros.length - 1, macro, match, replacement; i >= 0 && (macro = macros[i]); --i) if ((match = macro_try_match(macro, n)) && (replacement = expanders[i].apply(context, match))) return replacement})}; __7f417e995fd77a32ec981042f15509d9 meta::sdoc('js::core/caterwaul.parser', <<'__d03fdc55271075ed8b0a95edbe5fe5ca'); Parsing. There are two distinct parts to parsing Javascript. One is parsing the irregular statement-mode expressions such as 'if (condition) {...}' and 'function f(x) {...}'; the other is parsing expression-mode stuff like arithmetic operators. In Rebase I tried to model everything as an expression, but that failed sometimes because it required that each operator have fixed arity. In particular this was infeasible for keywords such as 'break', 'continue', 'return', and some others (any of these can be nullary or unary). It also involved creating a bizarre hack for 'case x:' inside a switch block. This hack made the expression passed in to 'case' unavailable, as it would be buried in a ':' node. Caterwaul fixes these problems by using a proper context-free grammar. However, it's much looser than most grammars because it doesn't need to validate anything. Correspondingly, it can be much faster as well. Instead of guessing and backtracking as a recursive-descent parser would, it classifies many different branches into the same basic structure and fills in the blanks. One example of this is the () {} pair, which occurs in a bunch of different constructs, including function () {}, if () {}, for () {}, etc. In fact, any time a () group is followed by a {} group we can grab the token that precedes () (along with perhaps one more in the case of function f () {}), and group that under whichever keyword is responsible. Syntax folding. The first thing to happen is that parenthetical, square bracket, and braced groups are folded up. This happens in a single pass that is linear in the number of tokens, and other foldable tokens (including unary and binary operators) are indexed by associativity. The following pass runs through these indexes from high to low precedence and folds tokens into trees. By this point all of the parentheticals have been replaced by proper nodes (here I include ?: groups in parentheticals, since they behave the same way). Finally, high-level rules are applied to the remaining keywords, which are bound last. This forms a complete parse tree. Doing all of this efficiently requires a linked list rather than an array. This gets built during the initial paren grouping stage. Arrays are used for the indexes, which are left-to-right and are later processed in the order indicated by the operator associativity. That is, left-associative operators are processed 0 .. n and right associative are processed n .. 0. Keywords are categorized by behavior and folded after all of the other operators. Semicolons are folded last, from left to right. There are some corner cases due to Javascript's questionable heritage from C-style syntax. For example, most constructs take either syntax blocks or semicolon-delimited statements. Ideally, else, while, and catch are associated with their containing if, do, and try blocks, respectively. This can be done easily, as the syntax is folded right-to-left. Another corner case would come up if there were any binary operators with equal precedence and different associativity. Javascript doesn't have them however, and it wouldn't make much sense to; it would render expressions such as 'a op1 b op2 c' ambiguous if op1 and op2 shared precedence but each wanted to bind first. (I mention this because at first I was worried about it, but now I realize it isn't an issue.) Notationally (for easier processing later on), a distinction is made between invocation and grouping, and between dereferencing and array literals. Dereferencing and function invocation are placed into their own operators, where the left-hand side is the thing being invoked or dereferenced and the right-hand side is the paren-group or bracket-group that is responsible for the operation. Also, commas inside these groups are flattened into a single variadic (possibly nullary) comma node so that you don't have to worry about the tree structure. This is the case for all left-associative operators; right-associative operators preserve their hierarchical folding. Parse/lex shared logic. Lexing Javascript is not entirely straightforward, primarily because of regular expression literals. The first implementation of the lexer got things right 99% of the time by inferring the role of a / by its preceding token. The problem comes in when you have a case like this: | if (condition) /foo/.test(x) In this case, (condition) will be incorrectly inferred to be a regular expression (since the close-paren terminates an expression, usually), and /foo/ will be interpreted as division by foo. We mark the position before a token and then just increment the position. The token, then, can be retrieved by taking a substring from the mark to the position. This eliminates the need for intermediate concatenations. In a couple of cases I've gone ahead and done them anyway -- these are for operators, where we grab the longest contiguous substring that is defined. I'm not too worried about the O(n^2) complexity due to concatenation; they're bounded by four characters. OK, so why use charAt() instead of regular expressions? It's a matter of asymptotic performance. V8 implements great regular expressions (O(1) in the match length for the (.*)$ pattern), but the substring() method is O(n) in the number of characters returned. Firefox implements O(1) substring() but O(n) regular expression matching. Since there are O(n) tokens per document of n characters, any O(n) step makes lexing quadratic. So I have to use the only reliably constant-time method provided by strings, charAt() (or in this case, charCodeAt()). Of course, building strings via concatenation is also O(n^2), so I also avoid that for any strings that could be long. This is achieved by using a mark to indicate where the substring begins, and advancing i independently. The span between mark and i is the substring that will be selected, and since each substring both requires O(n) time and consumes n characters, the lexer as a whole is O(n). (Though perhaps with a large constant.) Precomputed table values. The lexer uses several character lookups, which I've optimized by using integer->boolean arrays. The idea is that instead of using string membership checking or a hash lookup, we use the character codes and index into a numerical array. This is guaranteed to be O(1) for any sensible implementation, and is probably the fastest JS way we can do this. For space efficiency, only the low 256 characters are indexed. High characters will trigger sparse arrays, which may degrade performance. (I'm aware that the arrays are power-of-two-sized and that there are enough of them, plus the right usage patterns, to cause cache line contention on most Pentium-class processors. If we are so lucky to have a Javascript JIT capable enough to have this problem, I think we'll be OK.) The lex_op table indicates which elements trigger regular expression mode. Elements that trigger this mode cause a following / to delimit a regular expression, whereas other elements would cause a following / to indicate division. By the way, the operator ! must be in the table even though it is never used. The reason is that it is a substring of !==; without it, !== would fail to parse. (See test/lex-neq-failure for examples.) var lex_op = hash('. new ++ -- u++ u-- u+ u- typeof u~ u! ! * / % + - << >> >>> < > <= >= instanceof in == != === !== & ^ | && || ? = += -= *= /= %= &= |= ^= <<= >>= >>>= : , ' + 'return throw case var const break continue void else u; ;'), lex_table = function (s) {for (var i = 0, xs = [false]; i < 8; ++i) xs.push.apply(xs, xs); for (var i = 0, l = s.length; i < l; ++i) xs[s.charCodeAt(i)] = true; return xs}, lex_float = lex_table('.0123456789'), lex_decimal = lex_table('0123456789'), lex_integer = lex_table('0123456789abcdefABCDEFx'), lex_exp = lex_table('eE'), lex_space = lex_table(' \n\r\t'), lex_bracket = lex_table('()[]{}'), lex_opener = lex_table('([{'), lex_punct = lex_table('+-*/%&|^!~=<>?:;.,'), lex_eol = lex_table('\n\r'), lex_regexp_suffix = lex_table('gims'), lex_quote = lex_table('\'"/'), lex_slash = '/'.charCodeAt(0), lex_star = '*'.charCodeAt(0), lex_back = '\\'.charCodeAt(0), lex_x = 'x'.charCodeAt(0), lex_dot = '.'.charCodeAt(0), lex_zero = '0'.charCodeAt(0), lex_postfix_unary = hash('++ --'), lex_ident = lex_table('$_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), Parse data. The lexer and parser aren't entirely separate, nor can they be considering the complexity of Javascript's grammar. The lexer ends up grouping parens and identifying block constructs such as 'if', 'for', 'while', and 'with'. The parser then folds operators and ends by folding these block-level constructs. parse_reduce_order = map(hash, ['function', '( [ . [] ()', 'new delete', 'u++ u-- ++ -- typeof u~ u! u+ u-', '* / %', '+ -', '<< >> >>>', '< > <= >= instanceof in', '== != === !==', '&', '^', '|', '&&', '||', 'case', '?', '= += -= *= /= %= &= |= ^= <<= >>= >>>=', ':', ',', 'return throw break continue void', 'var const', 'if else try catch finally for switch with while do', ';']), parse_associates_right = hash('= += -= *= /= %= &= ^= |= <<= >>= >>>= ~ ! new typeof u+ u- -- ++ u-- u++ ? if else function try catch finally for switch case with while do'), parse_inverse_order = (function (xs) {for (var o = {}, i = 0, l = xs.length; i < l; ++i) for (var k in xs[i]) has(xs[i], k) && (o[k] = i); return annotate_keys(o)}) (parse_reduce_order), parse_index_forward = (function (rs) {for (var xs = [], i = 0, l = rs.length, _ = null; _ = rs[i], xs[i] = true, i < l; ++i) for (var k in _) if (has(_, k) && (xs[i] = xs[i] && ! has(parse_associates_right, k))) break; return xs}) (parse_reduce_order), parse_lr = hash('[] . () * / % + - << >> >>> < > <= >= instanceof in == != === !== & ^ | && || = += -= *= /= %= &= |= ^= <<= >>= >>>= , : ;'), parse_r_until_block = annotate_keys({'function':2, 'if':1, 'do':1, 'catch':1, 'try':1, 'for':1, 'while':1, 'with':1, 'switch':1}), parse_accepts = annotate_keys({'if':'else', 'do':'while', 'catch':'finally', 'try':'catch'}), parse_invocation = hash('[] ()'), parse_r_optional = hash('return throw break continue else'), parse_r = hash('u+ u- u! u~ u++ u-- new typeof finally case var const void delete'), parse_block = hash('; {'), parse_invisible = hash('i;'), parse_l = hash('++ --'), parse_group = annotate_keys({'(':')', '[':']', '{':'}', '?':':'}), parse_ambiguous_group = hash('[ ('), parse_ternary = hash('?'), parse_not_a_value = hash('function if for while catch'), parse_also_expression = hash('function'), Parse function. As mentioned earlier, the parser and lexer aren't distinct. The lexer does most of the heavy lifting; it matches parens and brackets, arranges tokens into a hierarchical linked list, and provides an index of those tokens by their fold order. It does all of this by streaming tokens into a micro-parser whose language is grouping and that knows about the oddities required to handle regular expression cases. In the same function, though as a distinct case, the operators are folded and the syntax is compiled into a coherent tree form. The input to the parse function can be anything whose toString() produces valid Javascript code. parse = function (input) { Lex variables. s, obviously, is the string being lexed. mark indicates the position of the stream, while i is used for lookahead. The difference is later read into a token and pushed onto the result. c is a temporary value used to store the current character code. re is true iff a slash would begin a regular expression. esc is a flag indicating whether the next character in a string or regular expression literal is escaped. exp indicates whether we've seen the exponent marker in a number. close is used for parsing single and double quoted strings; it contains the character code of the closing quotation mark. t is the token to be processed. Parse variables. grouping_stack and gs_top are used for paren/brace/etc. matching. head and parent mark two locations in the linked syntax tree; when a new group is created, parent points to the opener (i.e. (, [, ?, or {), while head points to the most recently added child. (Hence the somewhat complex logic in push().) indexes[] determines reduction order, and contains references to the nodes in the order in which they should be folded. invocation_nodes is an index of the nodes that will later need to be flattened. The push() function manages the mechanics of adding a node to the initial linked structure. There are a few cases here; one is when we've just created a paren group and have no 'head' node; in this case we append the node as 'head'. Another case is when 'head' exists; in that case we update head to be the new node, which gets added as a sibling of the old head. var s = input.toString(), mark = 0, c = 0, re = true, esc = false, dot = false, exp = false, close = 0, t = '', i = 0, l = s.length, cs = function (i) {return s.charCodeAt(i)}, grouping_stack = [], gs_top = null, head = null, parent = null, indexes = map(function () {return []}, parse_reduce_order), invocation_nodes = [], all_nodes = [], new_node = function (n) {return all_nodes.push(n), n}, push = function (n) {return head ? head._sibling(head = n) : (head = n._append_to(parent)), new_node(n)}; Main lex loop. This loop takes care of reading all of the tokens in the input stream. At the end, we'll have a linked node structure with paren groups. At the beginning, we set the mark to the current position (we'll be incrementing i as we read characters), munch whitespace, and reset flags. while ((mark = i) < l) { while (lex_space[c = cs(i)] && i < l) mark = ++i; esc = exp = dot = t = false; Miscellaneous lexing. This includes bracket resetting (the top case, where an open-bracket of any sort triggers regexp mode) and comment removal. Both line and block comments are removed by comparing against lex_slash, which represents /, and lex_star, which represents *. if (lex_bracket[c]) {t = !! ++i; re = lex_opener[c]} else if (c === lex_slash && cs(i + 1) === lex_star && (i += 2)) {while (++i < l && cs(i) !== lex_slash || cs(i - 1) !== lex_star); t = ! ++i} else if (c === lex_slash && cs(i + 1) === lex_slash) {while (++i < l && ! lex_eol[cs(i)]); t = false} Regexp and string literal lexing. These both take more or less the same form. The idea is that we have an opening delimiter, which can be ", ', or /; and we look for a closing delimiter that follows. It is syntactically illegal for a string to occur anywhere that a slash would indicate division (and it is also illegal to follow a string literal with extra characters), so reusing the regular expression logic for strings is not a problem. (This follows because we know ahead of time that the Javascript is valid.) else if (lex_quote[c] && (close = c) && re && ! (re = ! (t = s.charAt(i)))) {while (++i < l && (c = cs(i)) !== close || esc) esc = ! esc && c === lex_back; while (++i < l && lex_regexp_suffix[cs(i)]) ; t = true} Numeric literal lexing. This is far more complex than the above cases. Numbers have several different formats, each of which requires some custom logic. The reason we need to parse numbers so exactly is that it influences how the rest of the stream is lexed. One example is '0.5.toString()', which is perfectly valid Javascript. What must be output here, though, is '0.5', '.', 'toString', '(', ')'; so we have to keep track of the fact that we've seen one dot and stop lexing the number on the second. Another case is exponent-notation: 3.0e10. The hard part here is that it's legal to put a + or - on the exponent, which normally terminates a number. Luckily we can safely skip over any character that comes directly after an E or e (so long as we're really in exponent mode, which I'll get to momentarily), since there must be at least one digit after an exponent. The final case, which restricts the logic somewhat, is hexadecimal numbers. These also contain the characters 'e' and 'E', but we cannot safely skip over the following character, and any decimal point terminates the number (since '0x5.toString()' is also valid Javascript). The same follows for octal numbers; the leading zero indicates that there will be no decimal point, which changes the lex mode (for example, '0644.toString()' is valid). So, all this said, there are different logic branches here. One handles guaranteed integer cases such as hex/octal, and the other handles regular numbers. The first branch is triggered whenever a number starts with zero and is followed by 'x' or a digit (for conciseness I call 'x' a digit), and the second case is triggered when '.' is followed by a digit, or when a digit starts. A trivial change, using regular expressions, would reduce this logic significantly. I chose to write it out longhand because (1) it's more fun that way, and (2) the regular expression approach has theoretically quadratic time in the length of the numbers, whereas this approach keeps things linear. Whether or not that actually makes a difference I have no idea. Finally, in response to a recently discovered failure case, a period must be followed by a digit if it starts a number. The failure is the string '.end', which will be lexed as '.en', 'd' if it is assumed to be a floating-point number. (In fact, any method or property beginning with 'e' will cause this problem.) else if (c === lex_zero && lex_integer[cs(i + 1)]) {while (++i < l && lex_integer[cs(i)]); re = ! (t = true)} else if (lex_float[c] && (c !== lex_dot || lex_decimal[cs(i + 1)])) {while (++i < l && (lex_decimal[c = cs(i)] || (dot ^ (dot |= c === lex_dot)) || (exp ^ (exp |= lex_exp[c] && ++i)))); while (i < l && lex_decimal[cs(i)]) ++i; re = ! (t = true)} Operator lexing. The 're' flag is reused here. Some operators have both unary and binary modes, and as a heuristic (which happens to be accurate) we can assume that anytime we expect a regular expression, a unary operator is intended. The only exception are ++ and --, which are always unary but sometimes are prefix and other times are postfix. If re is true, then the prefix form is intended; otherwise, it is postfix. For this reason I've listed both '++' and 'u++' (same for --) in the operator tables; the lexer is actually doing more than its job here by identifying the variants of these operators. The only exception to the regular logic happens if the operator is postfix-unary. (e.g. ++, --.) If so, then the re flag must remain false, since expressions like 'x++ / 4' can be valid. else if (lex_punct[c] && (t = re ? 'u' : '', re = true)) {while (i < l && lex_punct[cs(i)] && has(lex_op, t + s.charAt(i))) t += s.charAt(i++); re = ! has(lex_postfix_unary, t)} Identifier lexing. If nothing else matches, then the token is lexed as a regular identifier or Javascript keyword. The 're' flag is set depending on whether the keyword expects a value. The nuance here is that you could write 'x / 5', and it is obvious that the / means division. But if you wrote 'return / 5', the / would be a regexp delimiter because return is an operator, not a value. So at the very end, in addition to assigning t, we also set the re flag if the word turns out to be an operator. else {while (++i < l && lex_ident[cs(i)]); re = has(lex_op, t = s.substring(mark, i))} Token unification. t will contain true, false, or a string. If false, no token was lexed; this happens when we read a comment, for example. If true, the substring method should be used. (It's a shorthand to avoid duplicated logic.) For reasons that are not entirely intuitive, the lexer sometimes produces the artifact 'u;'. This is never useful, so I have a case dedicated to removing it. if (i === mark) throw new Error('Caterwaul lex error at "' + s.substr(mark, 40) + '" with leading context "' + s.substr(mark - 40, 40) + '" (probably a Caterwaul bug)'); if (t === false) continue; t = t === true ? s.substring(mark, i) : t === 'u;' ? ';' : t; Grouping and operator indexing. Now that we have a token, we need to see whether it affects grouping status. There are a couple of possibilities. If it's an opener, then we create a new group; if it's a matching closer then we close the current group and pop out one layer. (We don't check for matching here. Any code provided to Caterwaul will already have been parsed by the host Javascript interpreter, so we know that it is valid.) All operator indexing is done uniformly, left-to-right. Note that the indexing isn't strictly by operator. It's by reduction order, which is arguably more important. That's what the parse_inverse_order table does: it maps operator names to parse_reduce_order subscripts. (e.g. 'new' -> 2.) t === gs_top ? (grouping_stack.pop(), gs_top = grouping_stack[grouping_stack.length - 1], head = head ? head.p : parent, parent = null) : (has(parse_group, t) ? (grouping_stack.push(gs_top = parse_group[t]), parent = push(new_node(new syntax_node(t))), head = null) : push(new_node(new syntax_node(t))), has(parse_inverse_order, t) && indexes[parse_inverse_order[t]].push(head || parent)); Regexp flag special cases. Normally a () group wraps an expression, so a following / would indicate division. The only exception to this is when we have a block construct; in this case, the next token appears in statement-mode, which means that it begins, not modifies, a value. We'll know that we have such a case if (1) the immediately-preceding token is a close-paren, and (2) a block-accepting syntactic form occurs to its left. With all this trouble over regular expressions, I had to wonder whether it was possible to do it more cleanly. I don't think it is, unfortunately. Even lexing the stream backwards fails to resolve the ambiguity: | for (var k in foo) /foo/g.test(k) && bar(); In this case we won't know it's a regexp until we hit the 'for' keyword (or perhaps 'var', if we're being clever -- but a 'with' or 'if' would require complete lookahead). A perfectly valid alternative parse, minus the 'for' and 'var', is this: | ((k in foo) / (foo) / (g.test(k))) && bar(); The only case where reverse-lexing is useful is when the regexp has no modifiers. re |= t === ')' && head.l && has(parse_r_until_block, head.l.data)} Operator fold loop. This is the second major part of the parser. Now that we've completed the lex process, we can fold operators and syntax, and take care of some exception cases. First step: fold function literals, function calls, dots, and dereferences. I'm treating this differently from the generalized operator folding because of the syntactic inference required for call and dereference detection. Nothing has been folded at this point (with the exception of paren groups, which is appropriate), so if the node to the left of any ( or [ group is an operator, then the ( or [ is really a paren group or array literal. If, on the other hand, it is another value, then the group is a function call or a dereference. This folding goes left-to-right. The reason we also process dot operators is that they share the same precedence as calls and dereferences. Here's what a () or [] transform looks like: | quux <--> foo <--> ( <--> bar quux <--> () <--> bar \ / \ <-- This can be done by saying _.l.wrap(new node('()')).p.fold_r(). bif <--> , <--> baz --> foo ( _.l.wrap() returns l again, .p gets the wrapping node, and fold_r adds a child to it. \ bif <--> , <--> baz This is actually merged into the for loop below, even though it happens before other steps do (see 'Ambiguous parse groups'). Second step: fold operators. Now we can go through the list of operators, folding each according to precedence and associativity. Highest to lowest precedence here, which is just going forwards through the indexes[] array. The parse_index_forward[] array indicates which indexes should be run left-to-right and which should go right-to-left. for (var i = 0, l = indexes.length, forward, _; _ = indexes[i], forward = parse_index_forward[i], i < l; ++i) for (var j = forward ? 0 : _.length - 1, lj = _.length, inc = forward ? 1 : -1, node, data; node = _[j], data = node && node.data, forward ? j < lj : j >= 0; j += inc) Binary node behavior. The most common behavior is binary binding. This is the usual case for operators such as '+' or ',' -- they grab one or both of their immediate siblings regardless of what they are. Operators in this class are considered to be 'fold_lr'; that is, they fold first their left sibling, then their right. if (has(parse_lr, data)) node._fold_lr(); Ambiguous parse groups. As mentioned above, we need to determine whether grouping constructs are invocations or real groups. This happens to take place before other operators are parsed (which is good -- that way it reflects the precedence of dereferencing and invocation). The only change we need to make is to discard the explicit parenthetical or square-bracket grouping for invocations or dereferences, respectively. It doesn't make much sense to have a doubly-nested structure, where we have a node for invocation and another for the group on the right-hand side of that invocation. Better is to modify the group in-place to represent an invocation. We can't solve this problem here, but we can solve it after the parse has finished. I'm pushing these invocation nodes onto an index for the end. else if (has(parse_ambiguous_group, data) && node.l && (node.l.data === '.' || ! (has(lex_op, node.l.data) || has(parse_not_a_value, node.l.data)))) invocation_nodes.push(node.l._wrap(new_node(new syntax_node(data + parse_group[data]))).p._fold_r()); Unary left and right-fold behavior. Unary nodes have different fold directions. In this case, it just determines which side we grab the node from. I'm glad that Javascript doesn't allow stuff like '++x++', which would make the logic here actually matter. Because there isn't that pathological case, exact rigidity isn't required. else if (has(parse_l, data)) node._fold_l(); else if (has(parse_r, data)) node._fold_r(); Ternary operator behavior. This is kind of interesting. If we have a ternary operator, then it will be treated first as a group; just like parentheses, for example. This is the case because the ternary syntax is unambiguous for things in the middle. So, for example, '3 ? 4 : 5' initially parses out as a '?' node whose child is '4'. Its siblings are '3' and '5', so folding left and right is an obvious requirement. The only problem is that the children will be in the wrong order. Instead of (3) (4) (5), we'll have (4) (3) (5). So after folding, we do a quick swap of the first two to set the ordering straight. else if (has(parse_ternary, data)) {node._fold_lr(); var temp = node[1]; node[1] = node[0]; node[0] = temp} Grab-until-block behavior. Not quite as simple as it sounds. This is used for constructs such as 'if', 'function', etc. Each of these constructs takes the form ' [identifier] () {}', but they can also have variants that include ' () {}', ' () statement;', and most problematically ' () ;'. Some of these constructs also have optional child components; for example, 'if () {} else {}' should be represented by an 'if' whose children are '()', '{}', and 'else' (whose child is '{}'). The tricky part is that 'if' doesn't accept another 'if' as a child (e.g. 'if () {} if () {}'), nor does it accept 'for' or any number of other things. This discrimination is encoded in the parse_accepts table. There are some weird edge cases, as always. The most notable is what happens when we have nesting without blocks: | if (foo) bar; else bif; In this case we want to preserve the semicolon on the 'then' block -- that is, 'bar;' should be its child; so the semicolon is required. But the 'bif' in the 'else' case shouldn't have a semicolon, since that separates top-level statements. Because desperate situations call for desperate measures, there's a hack specifically for this in the syntax tree serialization. One more thing. Firefox rewrites syntax trees, and one of the optimizations it performs on object literals is removing quotation marks from regular words. This means that it will take the object {'if': 4, 'for': 1, etc.} and render it as {if: 4, for: 1, etc.}. As you can imagine, this becomes a big problem as soon as the word 'function' is present in an object literal. To prevent this from causing problems, I only collapse a node if it is not followed by a colon. (And the only case where any of these would legally be followed by a colon is as an object key.) else if (has(parse_r_until_block, data) && node.r && node.r.data !== ':') {for (var count = 0, limit = parse_r_until_block[data]; count < limit && node.r && ! has(parse_block, node.r.data); ++count) node._fold_r(); node.r && node.r.data !== ';' && node._fold_r(); if (has(parse_accepts, data) && parse_accepts[data] === (node.r && node.r.r && node.r.r.data)) node._fold_r().pop()._fold_r(); else if (has(parse_accepts, data) && parse_accepts[data] === (node.r && node.r.data)) node._fold_r()} Optional right-fold behavior. The return, throw, break, and continue keywords can each optionally take an expression. If the token to the right is an expression, then we take it, but if the token to the right is a semicolon then the keyword should be nullary. else if (has(parse_r_optional, data)) node.r && node.r.data !== ';' && node._fold_r(); Third step. Find all elements with right-pointers and wrap them with semicolon nodes. This is necessary because of certain constructs at the statement-level don't use semicolons; they use brace syntax instead. (e.g. 'if (foo) {bar} baz()' is valid, even though no semicolon precedes 'baz()'.) By this point everything else will already be folded. Note that this does some weird things to associativity; in general, you can't make assumptions about the exact layout of semicolon nodes. Fortunately semicolon is associative, so it doesn't matter in practice. And just in case, these nodes are 'i;' rather than ';', meaning 'inferred semicolon' -- that way it's clear that they aren't original. (They also won't appear when you call toString() on the syntax tree.) for (var i = all_nodes.length - 1, _; _ = all_nodes[i], i >= 0; --i) _.r && _._wrap(new syntax_node('i;')).p._fold_r(); Fourth step. Flatten out all of the invocation nodes. As explained earlier, they are nested such that the useful data on the right is two levels down. We need to grab the grouping construct on the right-hand side and remove it so that only the invocation or dereference node exists. During the parse phase we built an index of all of these invocation nodes, so we can iterate through just those now. I'm preserving the 'p' pointers, though they're probably not useful beyond here. for (var i = 0, l = invocation_nodes.length, _, child; _ = invocation_nodes[i], i < l; ++i) (child = _[1] = _[1][0]) && (child.p = _); while (head.p) head = head.p; Fifth step. Prevent a space leak by clearing out all of the 'p' pointers. for (var i = all_nodes.length - 1; i >= 0; --i) delete all_nodes[i].p; return head}; __d03fdc55271075ed8b0a95edbe5fe5ca meta::sdoc('js::core/caterwaul.tree', <<'__67e6757fdd2e73fb415aba23836d68ce'); Syntax data structures. There are two data structures used for syntax trees. At first, paren-groups are linked into doubly-linked lists, described below. These are then folded into immutable array-based specific nodes. At the end of folding there is only one child per paren-group. Doubly-linked paren-group lists. When the token stream is grouped into paren groups it has a hierarchical linked structure that conceptually has these pointers: | +--------+ +------ | node | ------+ | +-> | | <--+ | first | | +--------+ | | last | | parent parent | | V | | V +--------+ +--------+ | node | --- r --> | node | --- r ---/ /--- l --- | | <-- l --- | | +--------+ +--------+ The primary operation performed on this tree, at least initially, is repeated folding. So we have a chain of linear nodes, and one by one certain nodes fold their siblings underneath them, breaking the children's links and linking instead to the siblings' neighbors. For example, if we fold node (3) as a binary operator: | (1) <-> (2) <-> (3) <-> (4) <-> (5) (1) <--> (3) <--> (5) / \ / \ / \ / \ / \ --> / \ / \ / \ / \ (2) (4) <- No link between children / \ / \ (see 'Fold nodes', below) Fold nodes. Once a node has been folded (e.g. (3) in the diagram above), none of its children will change and it will gain no more children. The fact that none of its children will change can be shown inductively: suppose you've decided to fold the '+' in 'x + y' (here x and y are arbitrary expressions). This means that x and y are comprised of higher-precedence operators. Since there is no second pass back to high-precedence operators, x and y will not change nor will they interact with one another. The fact that a folded node never gains more children arrives from the fact that it is folded only once; this is by virtue of folding by index instead of by tree structure. (Though a good tree traversal algorithm also wouldn't hit the same node twice -- it's just less obvious when the tree is changing.) Anyway, the important thing about fold nodes is that their children don't change. This means that an array is a completely reasonable data structure to use for the children; it certainly makes the structure simpler. It also means that the only new links that must be added to nodes as they are folded are links to new children (via the array), and links to the new siblings. Once we have the array-form of fold nodes, we can build a query interface similar to jQuery, but designed for syntactic traversal. This will make routine operations such as macro transformation and quasiquoting far simpler later on. Both grouping and fold nodes are represented by the same data structure. In the case of grouping, the 'first' pointer is encoded as [0] -- that is, the first array element. It doesn't contain pointers to siblings of [0]; these are still accessed by their 'l' and 'r' pointers. As the structure is folded, the number of children of each paren group should be reduced to just one. At this point the remaining element's 'l' and 'r' pointers will both be null, which means that it is in hierarchical form instead of linked form. After the tree has been fully generated and we have the root node, we have no further use for the parent pointers. This means that we can use subtree sharing to save memory. Once we're past the fold stage, push() should be used instead of append(). append() works in a bidirectionally-linked tree context (much like the HTML DOM), whereas push() works like it does for arrays (i.e. no parent pointer). var syntax_node_inspect = function (x) {return x ? x.inspect() : '(<>)'}, syntax_node_tostring = function (x) {return x ? x.serialize ? x.serialize() : x.toString() : ''}, Syntax node functions. These functions are common to various pieces of syntax nodes. Not all of them will always make sense, but the prototypes of the constructors can be modified independently later on if it turns out to be an issue. node_methods = { Mutability. These functions let you modify nodes in-place. They're used during syntax folding and shouldn't really be used after that (hence the underscores). _replace: function (n) {return (n.l = this.l) && (this.l.r = n), (n.r = this.r) && (this.r.l = n), this}, _append_to: function (n) {return n && n._append(this), this}, _reparent: function (n) {return this.p && this.p[0] === this && (this.p[0] = n), this}, _fold_l: function (n) {return this._append(this.l && this.l._unlink(this))}, _append: function (n) {return (this[this.length++] = n) && (n.p = this), this}, _fold_r: function (n) {return this._append(this.r && this.r._unlink(this))}, _sibling: function (n) {return n.p = this.p, (this.r = n).l = this}, _fold_lr: function () {return this._fold_l()._fold_r()}, _wrap: function (n) {return n.p = this._replace(n).p, this._reparent(n), delete this.l, delete this.r, this._append_to(n)}, _fold_rr: function () {return this._fold_r()._fold_r()}, _unlink: function (n) {return this.l && (this.l.r = this.r), this.r && (this.r.l = this.l), delete this.l, delete this.r, this._reparent(n)}, These methods are OK for use after the syntax folding stage is over (though because syntax nodes are shared it's generally dangerous to go modifying them): pop: function () {return --this.length, this}, push: function (x) {return this[this.length++] = x, this}, Identification. You can request that a syntax node identify itself, in which case it will give you an identifier if it hasn't already. The identity is not determined until the first time it is requested, and after that it is stable. As of Caterwaul 0.7.0 the mechanism works differently (i.e. isn't borked) in that it replaces the prototype definition with an instance-specific closure the first time it gets called. This may reduce the number of decisions in the case that the node's ID has already been computed. id: function () {var id = genint(); return (this.id = function () {return id})()}, Traversal functions. each() is the usual side-effecting shallow traversal that returns 'this'. map() distributes a function over a node's children and returns the array of results, also as usual. Two variants, reach and rmap, perform the process recursively. reach is non-consing; it returns the original as a reference. rmap, on the other hand, follows some rules to cons a new tree. If the function passed to rmap() returns the node verbatim then its children are traversed. If it returns a distinct node, however, then traversal doesn't descend into the children of the newly returned tree but rather continues as if the original node had been a leaf. For example: | parent Let's suppose that a function f() has these mappings: / \ node1 node2 f(parent) = parent f(node1) = q / \ | f(node2) = node2 c1 c2 c3 In this example, f() would be called on parent, node1, node2, and c3 in that order. c1 and c2 are omitted because node1 was replaced by q -- and there is hardly any point in going through the replaced node's previous children. (Nor is there much point in forcibly iterating over the new node's children, since presumably they are already processed.) If a mapping function returns something falsy, it will have exactly the same effect as returning the node without modification. Using the old s() to do gensym-safe replacement requires that you invoke it only once, and this means that for complex macroexpansion you'll have a long array of values. This isn't ideal, so syntax trees provide a replace() function that handles replacement more gracefully: | qs[(foo(_foo), _before_bar + bar(_bar))].replace({_foo: qs[x], _before_bar: qs[3 + 5], _bar: qs[foo.bar]}) There is a map() variant called nmap() (and a corresponding rnmap()) that lets you insert falsy nodes into the syntax tree. This is used by replace(), which lets you replace named nodes with falsy things if you want them to go away. (The exact behavior is that falsy nodes are not added to the syntax tree at all, rather than remaining in their original state.) each: function (f) {for (var i = 0, l = this.length; i < l; ++i) f(this[i], i); return this}, map: function (f) {for (var n = new this.constructor(this), i = 0, l = this.length; i < l; ++i) n.push(f(this[i], i) || this[i]); return n}, nmap: function (f) {for (var n = new this.constructor(this), i = 0, l = this.length, r; i < l; ++i) (r = f(this[i], i)) && n.push(r); return n}, reach: function (f) {f(this); this.each(function (n) {n && n.reach(f)}); return this}, rmap: function (f) {var r = f(this); return ! r || r === this ? this. map(function (n) {return n && n. rmap(f)}) : r.data === undefined ? new this.constructor(r) : r}, rnmap: function (f) {var r = f(this); return r === this ? this.nmap(function (n) {return n && n.rnmap(f)}) : r && r.data === undefined ? new this.constructor(r) : r}, clone: function () {return this.rmap(function () {return false})}, collect: function (p) {var ns = []; this.reach(function (n) {p(n) && ns.push(n)}); return ns}, replace: function (rs) {return this.rnmap(function (n) {return own.call(rs, n.data) ? rs[n.data] : n})}, Alteration. These functions let you make "changes" to a node by returning a modified copy. repopulated_with: function (xs) {return new this.constructor(this.data, xs)}, change: function (i, x) {return se(new this.constructor(this.data, Array.prototype.slice.call(this)), function (n) {n[i] = x})}, compose_single: function (i, f) {return this.change(i, f(this[i]))}, General-purpose traversal. This is a SAX-style traversal model, useful for analytical or scope-oriented tree traversal. You specify a callback function that is invoked in pre-post-order on the tree (you get events for entering and exiting each node, including leaves). Each time a node is entered, the callback is invoked with an object of the form {entering: node}, where 'node' is the syntax node being entered. Each time a node is left, the callback is invoked with an object of the form {exiting: node}. The return value of the function is not used. Any null nodes are not traversed, since they would fail any standard truthiness tests for 'entering' or 'exiting'. I used to have a method to perform scope-annotated traversal, but I removed it for two reasons. First, I had no use for it (and no tests, so I had no reason to believe that it worked). Second, Caterwaul is too low-level to need such a method. That would be more appropriate for an analysis extension. traverse: function (f) {f({entering: this}); f({exiting: this.each(function (n) {n && n.traverse(f)})}); return this}, Structural transformation. Having nested syntax trees can be troublesome. For example, suppose you're writing a macro that needs a comma-separated list of terms. It's a lot of work to dig through the comma nodes, each of which is binary. Javascript is better suited to using a single comma node with an arbitrary number of children. (This also helps with the syntax tree API -- we can use .map() and .each() much more effectively.) Any binary operator can be transformed this way, and that is exactly what the flatten() method does. (flatten() returns a new tree; it doesn't modify the original.) The tree flattening operation looks like this for a left-associative binary operator: | (+) / \ (+) (+) z -> / | \ / \ x y z x y This flatten() method returns the nodes along the chain of associativity, always from left to right. It is shallow, since generally you only need a localized flat tree. That is, it doesn't descend into the nodes beyond the one specified by the flatten() call. It takes an optional parameter indicating the operator to flatten over; if the operator in the tree differs, then the original node is wrapped in a unary node of the specified operator. The transformation looks like this: | (,) (+) | / \ .flatten(',') -> (+) x y / \ x y Because ',' is a binary operator, a ',' tree with just one operand will be serialized exactly as its lone operand would be. This means that plurality over a binary operator such as comma or semicolon degrades gracefully for the unary case (this sentence makes more sense in the context of macro definitions; see in particular 'let' and 'where' in std.bind). The unflatten() method performs the inverse transformation. It doesn't delete a converted unary operator in the tree case, but if called on a node with more than two children it will nest according to associativity. flatten: function (d) {d = d || this.data; return d !== this.data ? this.as(d) : ! (has(parse_lr, d) && this.length) ? this : has(parse_associates_right, d) ? se(new this.constructor(d), bind(function (n) {for (var i = this; i && i.data === d; i = i[1]) n.push(i[0]); n.push(i)}, this)) : se(new this.constructor(d), bind(function (n) {for (var i = this, ns = []; i.data === d; i = i[0]) i[1] && ns.push(i[1]); ns.push(i); for (i = ns.length - 1; i >= 0; --i) n.push(ns[i])}, this))}, unflatten: function () {var right = has(parse_associates_right, this.data); return this.length <= 2 ? this : se(new this.constructor(this.data), bind(function (n) { if (right) for (var i = 0, l = this.length - 1; i < l; ++i) n = n.push(this[i]).push(i < l - 2 ? new this.constructor(this.data) : this[i])[1]; else for (var i = this.length - 1; i >= 1; --i) n = n.push(i > 1 ? new this.constructor(this.data) : this[0]).push(this[i])[0]}, this))}, Wrapping. Sometimes you want your syntax tree to have a particular operator, and if it doesn't have that operator you want to wrap it in a node that does. Perhaps the most common case of this is when you have a possibly-plural node representing a variable or expression -- often the case when you're dealing with argument lists -- and you want to be able to assume that it's wrapped in a comma node. Calling node.as(',') will return the node if it's a comma, and will return a new comma node containing the original one if it isn't. as: function (d) {return this.data === d ? this : new this.constructor(d).push(this)}, Type detection and retrieval. These methods are used to detect the literal type of a node and to extract that value if it exists. You should use the as_x methods only once you know that the node does represent an x; otherwise you will get misleading results. (For example, calling as_boolean on a non-boolean will always return false.) Other methods are provided to tell you higher-level things about what this node does. For example, is_contextualized_invocation() tells you whether the node represents a call that can't be eta-reduced (if it were, then the 'this' binding would be lost). is_string: function () {return /['"]/.test(this.data.charAt(0))}, as_escaped_string: function () {return this.data.substr(1, this.data.length - 2)}, is_number: function () {return /^-?(0x|\d|\.\d+)/.test(this.data)}, as_number: function () {return Number(this.data)}, is_boolean: function () {return this.data === 'true' || this.data === 'false'}, as_boolean: function () {return this.data === 'true'}, is_regexp: function () {return /^\/./.test(this.data)}, as_escaped_regexp: function () {return this.data.substring(1, this.data.lastIndexOf('/'))}, has_grouped_block: function () {return has(parse_r_until_block, this.data)}, is_block: function () {return has(parse_block, this.data)}, is_blockless_keyword: function () {return has(parse_r_optional, this.data)}, is_null_or_undefined: function () {return this.data === 'null' || this.data === 'undefined'}, is_constant: function () {return this.is_number() || this.is_string() || this.is_boolean() || this.is_regexp() || this.is_null_or_undefined()}, left_is_lvalue: function () {return /=$/.test(this.data) || /\+\+$/.test(this.data) || /--$/.test(this.data)}, is_empty: function () {return !this.length}, has_parameter_list: function () {return this.data === 'function' || this.data === 'catch'}, has_lvalue_list: function () {return this.data === 'var' || this.data === 'const'}, is_dereference: function () {return this.data === '.' || this.data === '[]'}, is_invocation: function () {return this.data === '()'}, is_contextualized_invocation: function () {return this.is_invocation() && this[0] && this[0].is_dereference()}, is_invisible: function () {return has(parse_invisible, this.data)}, is_binary_operator: function () {return has(parse_lr, this.data)}, is_prefix_unary_operator: function () {return has(parse_r, this.data)}, is_postfix_unary_operator: function () {return has(parse_l, this.data)}, is_unary_operator: function () {return this.is_prefix_unary_operator() || this.is_postfix_unary_operator()}, accepts: function (e) {return parse_accepts[this.data] && this.accepts[parse.data] === (e.data || e)}, Value construction. Syntax nodes sometimes represent hard references to values instead of just syntax. (See 'References' for more information.) In order to compile a syntax tree in the right environment you need a mapping of symbols to these references, which is what the bindings() method returns. (It also collects references for all descendant nodes.) It takes an optional argument to populate, in case you already had a hash set aside for bindings -- though it always returns the hash. A bug in Caterwaul 0.5 and earlier failed to bind falsy values. This is no longer the case; nodes which bind values should indicate that they do so by setting a binds_a_value attribute (ref nodes do this on the prototype), indicating that their value should be read from the 'value' property. (This allows other uses of a 'value' property while making it unambiguous whether a particular node intends to bind something.) bindings: function (hash) {var result = hash || {}; this.reach(function (n) {if (n.binds_a_value) result[n.data] = n.value}); return result}, Matching. Syntax trees can use the Caterwaul match function to return a list of wildcards. match: function (pattern) {return macro_try_match(pattern, this)}, Inspection and syntactic serialization. Syntax nodes can be both inspected (producing a Lisp-like structural representation) and serialized (producing valid Javascript code). Each representation captures stray links via the 'r' pointer. In the serialized representation, it is shown as a comment /* -> */ containing the serialization of whatever is to the right. This has the property that it will break tests but won't necessarily break code (though if it happens in the field then it's certainly a bug). Block detection is required for multi-level if/else statements. Consider this code: | if (foo) for (...) {} else bif; A naive approach (the one I was using before version 0.6) would miss the fact that the 'for' was trailed by a block, and insert a spurious semicolon, which would break compilation: | if (foo) for (...) {}; // <- note! else bif; What we do instead is dig through the tree and find out whether the last thing in the 'if' case ends with a block. If so, then no semicolon is inserted; otherwise we insert one. This algorithm makes serialization technically O(n^2), but nobody nests if/else blocks to such an extent that it would matter. ends_with_block: function () {var block = this[parse_r_until_block[this.data]]; return this.data === '{' || has(parse_r_until_block, this.data) && (this.data !== 'function' || this.length === 3) && block && block.ends_with_block()}, There's a hack here for single-statement if-else statements. (See 'Grab-until-block behavior' in the parsing code below.) Basically, for various reasons the syntax tree won't munch the semicolon and connect it to the expression, so we insert one automatically whenever the second node in an if, else, while, etc. isn't a block. Update for Caterawul 0.6.6: I had removed mandatory spacing for unary prefix operators, but now it's back. The reason is to help out the host Javascript lexer, which can misinterpret postfix increment/decrement: x + +y will be serialized as x++y, which is invalid Javascript. The fix is to introduce a space in front of the second plus: x+ +y, which is unambiguous. toString: function () {return this.inspect()}, inspect: function () {return (this.l ? '(left) <- ' : '') + '(' + this.data + (this.length ? ' ' + map(syntax_node_inspect, this).join(' ') : '') + ')' + (this.r ? ' -> ' + this.r.inspect() : '')}, An improvement that could be made to serialize() is to use one big array that is then join()ed for serialization, rather than appending all of these little strings. Based on the benchmarking I've done, the compilation phase is fairly zippy; but if it ever ends up being a problem then I'll look into optimizations like this. serialize: function (xs) {var op = this.data, space = /\w/.test(op.charAt(op.length - 1)) ? ' ' : ''; return op === ';' ? this.length ? map(syntax_node_tostring, this).join(';\n') : ';' : has(parse_invisible, op) ? map(syntax_node_tostring, this).join(space) : has(parse_invocation, op) ? map(syntax_node_tostring, [this[0], op.charAt(0), this[1], op.charAt(1)]).join(space) : has(parse_ternary, op) ? map(syntax_node_tostring, [this[0], op, this[1], parse_group[op], this[2]]).join(space) : has(parse_group, op) ? op + map(syntax_node_tostring, this).join(space) + parse_group[op] : has(parse_lr, op) ? this.length ? map(syntax_node_tostring, this).join(space + op + space) : op : has(parse_r, op) || has(parse_r_optional, op) ? op.replace(/^u/, ' ') + space + (this[0] ? this[0].serialize() : '') : has(parse_r_until_block, op) ? has(parse_accepts, op) && this[1] && this[2] && parse_accepts[op] === this[2].data && ! this[1].ends_with_block() ? op + space + map(syntax_node_tostring, [this[0], this[1], ';\n', this[2]]).join('') : op + space + map(syntax_node_tostring, this).join('') : has(parse_l, op) ? (this[0] ? this[0].serialize() : '') + space + op : op}}, References. You can drop references into code that you're compiling. This is basically variable closure, but a bit more fun. For example: | caterwaul.compile(qs[fn_[_ + 1]].replace({_: new caterwaul.ref(3)})() // -> 4 What actually happens is that caterwaul.compile runs through the code replacing refs with gensyms, and the function is evaluated in a scope where those gensyms are bound to the values they represent. This gives you the ability to use a ref even as an lvalue, since it's really just a variable. References are always leaves on the syntax tree, so the prototype has a length of 0. ref = extend(function (value) {if (value instanceof this.constructor) {this.value = value.value; this.data = value.data} else {this.value = value; this.data = gensym()}}, {length: 0, binds_a_value: true}, node_methods), Syntax node constructor. Here's where we combine all of the pieces above into a single function with a large prototype. Note that the 'data' property is converted from a variety of types; so far we support strings, numbers, and booleans. Any of these can be added as children. Also, I'm using an instanceof check rather than (.constructor ===) to allow array subclasses such as Caterwaul finite sequences to be used. syntax_node = extend(function (data) {if (data instanceof this.constructor) this.data = data.data, this.length = 0; else {this.data = data && data.toString(); this.length = 0; for (var i = 1, l = arguments.length, _; _ = arguments[i], i < l; ++i) for (var j = 0, lj = _.length, it, itc; _ instanceof Array ? (it = _[j], j < lj) : (it = _, ! j); ++j) this._append((itc = it.constructor) === String || itc === Number || itc === Boolean ? new this.constructor(it) : it)}}, node_methods); __67e6757fdd2e73fb415aba23836d68ce meta::sdoc('js::core/caterwaul.utilities', <<'__8abb317799551d34715ca2175b6acca9'); Utility methods. Gensym is used to support qs[]. When we quote syntax, what we really intend to do is grab a syntax tree representing something; this entails creating a let-binding with the already-evaluated tree. (Note: Don't go and modify these qs[]-generated trees; you only get one for each qs[].) The ultimate code ends up looking like this (see 'Environment-dependent compilation' some distance below): | (function (a_gensym) { var v1 = a_gensym.gensym_1; var v2 = a_gensym.gensym_2; ... return ; }) ({gensym_1: v1, gensym_2: v2, ..., gensym_n: vn}); A note about gensym uniqueness. Gensyms are astronomically unlikely to collide, but there are some compromises made to make sure of this. First, gensyms are not predictable; the first one is randomized. This means that if you do have a collision, it may be intermittent (and that is probably a non-feature). Second, and this is a good thing, you can load Caterwaul multiple times without worrying about gensyms colliding between them. Each instance of Caterwaul uses its own system time and random number to seed the gensym generation, and the system time remains stable while the random number gets incremented. It is very unlikely that any collisions would happen. Bind() is the usual 'bind this function to some value' function. The only difference is that it supports rebinding; that is, if you have a function you've already bound to X, you can call bind on that function and some new value Y and get the original function bound to Y. The bound function has two attributes, 'original' and 'binding', that let bind() achieve this rebinding. Map() is an array map function, fairly standard really. I include it because IE doesn't provide Array.prototype.map. hash() takes a string, splits it on whitespace, and returns an object that maps each element to true. It's useful for defining sets. extend() takes a constructor function and zero or more extension objects, merging each extension object into the constructor function's prototype. The constructor function is then returned. It's a shorthand for defining classes. Se() stands for 'side-effect', and its purpose is to take a value and a function, pass the value into the function, and return either whatever the function returned or the value you gave it. It's used to initialize things statefully; for example: | return se(function () {return 5}, function (f) { f.sourceCode = 'return 5'; }); The Caterwaul standard library gives you an equivalent but much more refined form of se() called /se[]. var qw = function (x) {return x.split(/\s+/)}, id = function (x) {return x}, se = function (x, f) {return f && f.call(x, x) || x}, genval = (function (n, m, u) {return function () {return [u, n, ++m]}})(+new Date(), Math.random() * (1 << 30) >>> 0, unique()), genint = function () {var v = genval(); return (v[0] << 2) + v[0] + (v[1] << 1) + v[1] + v[2]}, gensym = function () {var v = genval(); return ['gensym', v[0].toString(36), v[1].toString(36), v[2].toString(36)].join('_')}, bind = function (f, t) {return f.binding === t ? f : f.original ? bind(f.original, t) : merge(function () {return f.apply(t, arguments)}, {original: f, binding: t})}, map = function (f, xs) {for (var i = 0, ys = [], l = xs.length; i < l; ++i) ys.push(f(xs[i], i)); return ys}, hash = function (s) {for (var i = 0, xs = qw(s), o = {}, l = xs.length; i < l; ++i) o[xs[i]] = true; return annotate_keys(o)}, merge = function (o) {for (var i = 1, l = arguments.length, _; i < l; ++i) if (_ = arguments[i]) for (var k in _) has(_, k) && (o[k] = _[k]); return o}, extend = function (f) {merge.apply(null, [f.prototype].concat(Array.prototype.slice.call(arguments, 1))); return f}, Optimizations. The parser and lexer each assume valid input and do no validation. This is possible because any function passed in to caterwaul will already have been parsed by the Javascript interpreter; syntax errors would have caused an error there. This enables a bunch of optimization opportunities in the parser, ultimately making it not in any way recursive and requiring only three linear-time passes over the token stream. (An approximate figure; it actually does about 19 fractional passes, but not all nodes are reached.) Also, I'm not confident that all Javascript interpreters are smart about hash indexing. Particularly, suppose a hashtable has 10 entries, the longest of whose keys is 5 characters. If we throw a 2K string at it, it might very well hash that whole thing just to find that, surprise, the entry doesn't exist. That's a big performance hit if it happens very often. To prevent this kind of thing, I'm keeping track of the longest string in the hashtable by using the 'annotate_keys' function. 'has()' knows how to look up the maximum length of a hashtable to verify that the candidate is in it, resulting in the key lookup being only O(n) in the longest key (generally this ends up being nearly O(1), since I don't like to type long keys), and average-case O(1) regardless of the length of the candidate. As of Caterwaul 0.7.0 the _max_length property has been replaced by a gensym. This basically guarantees uniqueness, so the various hacks associated with working around the existence of the special _max_length key are no longer necessary. max_length_key = gensym(), annotate_keys = function (o) {var max = 0; for (var k in o) own.call(o, k) && (max = k.length > max ? k.length : max); o[max_length_key] = max; return o}, has = function (o, p) {return p != null && ! (p.length > o[max_length_key]) && own.call(o, p)}, own = Object.prototype.hasOwnProperty; __8abb317799551d34715ca2175b6acca9 meta::sdoc('js::core/debug/profile.start', <<'__c57fde09ae1ed7924ae86e180a4077f7'); Profiling extensions for performance tuning | Spencer Tipping Licensed under the terms of the MIT source code license This is the code used to start profiling. You'll also need to include js::core/debug/profile.stop to print the results. var all_profiled_functions = {}; var recursion = {}; Function.prototype.profiled = function (name, no_recursion) { var f = this; recursion[name] || (recursion[name] = no_recursion ? NaN : 0); return function () { var k = name + ' (' + ++recursion[name] + ')'; var data = all_profiled_functions[k] || (all_profiled_functions[k] = {invocations: 0, total: 0, max: 0}); var t1 = +new Date(); var result = f.apply(this, arguments); var time = +new Date() - t1; ++data.invocations; data.total += time; if (recursion[name] > 1) all_profiled_functions[name + ' (' + (recursion[name] - 1) + ')'].total -= time; time > data.max && (data.max = time); --recursion[name]; return result; }; }; __c57fde09ae1ed7924ae86e180a4077f7 meta::sdoc('js::core/debug/profile.stop', <<'__990b468eb0612075050f2a50509c1c40'); Profile end code var fs = []; for (var k in all_profiled_functions) all_profiled_functions[k].average = (all_profiled_functions[k].total / all_profiled_functions[k].invocations).toFixed(3), all_profiled_functions[k].name = k, fs.push(all_profiled_functions[k]); fs.sort(function (x, y) {return x.total - y.total}); typeof console === 'undefined' ? print(fs) : console.log(fs); __990b468eb0612075050f2a50509c1c40 meta::sdoc('js::minify', <<'__050b10a052015886ecad866ded895049'); Minifies a JavaScript file by using the Caterwaul parse/deparse mechanism. This won't do identifier packing (which is unsafe in Caterwaul), but it will provide decent minification by removing comments and most whitespace. var code = require('fs').readFileSync(process.argv[2], 'utf8'), header = '// Caterwaul JS | Spencer Tipping\n// Licensed under the terms of the MIT source code license\n'; require('fs').writeFileSync(process.argv[2].replace(/\.js$/, '.min.js'), header + caterwaul.parse(code).serialize(), 'utf8'); __050b10a052015886ecad866ded895049 meta::sdoc('js::modules/caterwaul.all', <<'__89090c3bf49c11fe682f000912014455'); Caterwaul with all modules | Spencer Tipping Licensed under the terms of the MIT source code license - comment This is the template that produces caterwaul.all.js. - pinclude pp::js::core/caterwaul - pinclude pp::js::modules/caterwaul.std - pinclude pp::js::modules/caterwaul.macro - pinclude pp::js::modules/caterwaul.opt - pinclude pp::js::modules/caterwaul.continuation - pinclude pp::js::modules/caterwaul.seq - pinclude pp::js::modules/caterwaul.heap - pinclude pp::js::modules/caterwaul.memoize - pinclude pp::js::modules/caterwaul.parser __89090c3bf49c11fe682f000912014455 meta::sdoc('js::modules/caterwaul.continuation', <<'__d01f7225134d59279a11628ebc9fc7af'); Continuation manipulation module | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. This module provides macros to assist with continuations. The most widely-known case of continuation manipulation is probably continuation-passing-style conversion, which you use when you do nonblocking things such as AJAX. In this case the callback function is the continuation of the call. (I'm not going to fully explain continuations here, but http://en.wikipedia.org/wiki/Continuation is a good if intimidating place to start if you're into functional programming -- which I assume you are if you're using Caterwaul :).) caterwaul.configuration('continuation.core', function () {this.shallow('continuation', {})}). Unwind protection. This is how you can implement error handling. You can intercept both the normal and the escaping cases and specify a return value for each alternative. Unwind-protect ultimately compiles into a try/catch. Also provided is the unwind[] macro, which causes an unwind through any call/cc operations until an unwind-protect is hit or the toplevel is reached, in which case it shows up as an error. unwind[x] is exactly equivalent to (function () {throw x})(). Unwind-protect is of this form: | unwind_protect[][] // === caterwaul.continuation.unwind_protect(fn[e][], fn_[]) The escape block will be run if any abnormal escaping is being performed (e.g. escaping via call/cc, unwind[], or an exception). Body is executed regardless, and if it returns normally then its return value is the return value of the unwind_protect block. The escape block can refer to 'e', the escaping value. 'this' is preserved in the body and escape blocks. tconfiguration('std', 'continuation.unwind', function () { this.configure('std.fn continuation.core').continuation /se[_.unwind_protect = function (escape, f) {try {return f()} catch (e) {return escape(e)}}, _.unwind = function (e) {throw e}]; this.rmacro(qs[unwind_protect[_][_]], fn[escape, body][qse[_f(fb[e][_escape], fb_[_body])].replace({_f: qs[caterwaul.continuation.unwind_protect], _escape: escape, _body: body})]). rmacro(qs[unwind[_]], fn[e][qs[caterwaul.continuation.unwind(_e)].replace({_e: e})])}). CPS-conversion. Converting a whole program to CPS to get re-entrant continuations is a lot of work, so I'm not even trying that. But localized CPS is really useful, especially for nested AJAX calls and such. Here's a common example: | $.getJSON('some-url', fn[result] [$.getJSON('some-other-url-#{result.property}', fn[other_result][...])]); Rather than dealing with this nesting explicitly, it's more convenient to use normal l-notation. That's exactly what l/cps does: | l/cps[result <- $.getJSON('some-url', _), other_result <- $.getJSON('some-other-url-#{result.property}', _)] [console.log(result)]; There are a couple of things to note about this setup. First, the arrows. This is so that your continuations can be n-ary. (Javascript doesn't let you assign into a paren-list.) | l/cps[(x, y) <- binary_ajax_call('some-url', _)][...]; Second, and this is important: l/cps returns immediately with the result of the first continuation-producing expression (so in the above example, the return value of binary_ajax_call would be the value of the l/cps[][] block). This has some important ramifications, perhaps most importantly that the code in the block must be side-effectful to be productive. No magic is happening here; l/cps ultimately gets translated into the set of nested functions that you would otherwise write. As of version 0.5.5 the alternative "l/cps[x <- ...] in f(x)" notation is supported (basically, just like regular let-bindings). It's purely a stylistic thing. There's also a shorthand form to CPS-convert functions. If you care only about the first parameter (which is true for a lot of functions), you can use the postfix /cps[] form, like this: | $.getJSON('foo', _) /cps[alert(_)]; $.getJSON('foo', _) /cps.x[alert(x)]; // Also has named form Bound variants of both l/cps and /cps[] are also available: | $.getJSON('foo', _) /cpb[...]; l/cpb[x <- foo(_)][...]; The legacy let/cps and let/cpb forms are also supported for backwards compatibility. There's an interesting scoping bug in Caterwaul <= 0.5.1. Suppose you have a form that binds _ in some context, but doesn't intend for it to be a continuation; for example: | f(seq[xs *[_ + 1]], _) /cps[...]; In this case, _ + 1 is supposed to use the implicitly-bound _, not the outer continuation callback. However, the old continuation logic was perfectly happy to rewrite the term with two continuation functions, a semantic disaster. What happens now is a regular lexical binding for _, which has the added benefit that multiple _'s in continuation-rewriting positions will refer to the same callback function rather than multiply-evaluating it (though I'm not sure this actually matters...). tconfiguration('std', 'continuation.cps', function () { l*[cps_convert(v, f, b, bound) = qse[l[_ = _c][_f]].replace({_c: caterwaul.macroexpand(qs[_f[_v][_b]].replace({_f: bound ? qs[fb] : qs[fn]})).replace({_v: v.as('(')[0], _b: b}), _f: f}), l_cps_def(t, form, bound) = l[inductive(cs, v, f, b) = qs[l/cps[cs][_f]].replace({cs: cs, _f: cps_convert(v, f, b, bound)}), base(v, f, b) = cps_convert(v, f, b, bound)] in t.rmacro(qs[l/_form[_, _ <- _] in _].replace({_form: form}), inductive).rmacro(caterwaul.parse('let/#{form.serialize()}[_, _ <- _] in _'), inductive). rmacro(qs[l/_form[ _ <- _] in _].replace({_form: form}), base) .rmacro(caterwaul.parse('let/#{form.serialize()}[ _ <- _] in _'), base). rmacro(qs[l/_form[_, _ <- _][_]] .replace({_form: form}), inductive).rmacro(caterwaul.parse('let/#{form.serialize()}[_, _ <- _][_]'), inductive). rmacro(qs[l/_form[ _ <- _][_]] .replace({_form: form}), base) .rmacro(caterwaul.parse('let/#{form.serialize()}[ _ <- _][_]'), base), cps_def(t, form, bound) = t.rmacro(qs[_ /_form[_]]. replace({_form: form}), fn[f, b][qse[_f /_form._[_b]].replace({_form: form, _f: f, _b: b})]). rmacro(qs[_ /_form._[_]].replace({_form: form}), fn[f, v, b][qse[l[_ = _c][_f]].replace( {_c: caterwaul.macroexpand(qs[_f[_v][_b]].replace({_f: bound ? qs[fb] : qs[fn]})).replace({_v: v, _b: b}), _f: f})])] in this.configure('std.fn continuation.core') /se[cps_def(_, qs[cps], false), cps_def(_, qs[cpb], true), l_cps_def(_, qs[cps], false), l_cps_def(_, qs[cpb], true)]}). Escaping continuations and tail call optimization. The most common use for continuations besides AJAX is escaping. This library gives you a way to escape from a loop or other function by implementing a non-reentrant call/cc. You can also use tail-call-optimized recursion if your functions are written as such. | call/cc[fn[cc][cc(5)]] // returns 5 call/cc[fn[cc][cc(5), 6]] // still returns 5 call/cc[fn[cc][19]] // returns 19 Tail calls must be indicated explicitly with call/tail. (Otherwise they'll be regular calls.) For example: | var factorial_cps = fn[n, acc, cc][n > 0 ? call/tail[factorial_cps(n - 1, acc * n, cc)] : call/tail[cc(acc)]]; call/cc[fn[cc][factorial_cps(5, 1, cc)]]; // -> 120 In this example it's also legal to call the final continuation 'cc' normally: cc(acc). It's faster to use call/tail[cc(acc)] though. Importantly, continuations lose their bindings! This means that tail-calling a method won't do what you want: | call/tail[object.method(5)] // calls object.method with wrong 'this' What you can do instead is eta-expand or use Caterwaul's /mb notation (note the extra parens; they're necessary, just as they would be if you were invoking an /mb'd method directly): | call/tail[fn[x][object.method(x)](5)]; call/tail[(object/mb/method)(5)]; // At this rate you're probably better off using the call_tail function directly. Either of these will invoke object.method in the right context. Delimited continuations work because call/cc uses an internal while loop to forward parameters outside of the tail call. This keeps the stack bounded by a constant. Note that tail calls work only inside a call/cc context. You can use them elsewhere, but they will not do what you want. Also, tail calls really do have to be tail calls. You need to return the call/tail[...] expression in order for it to work, just like you'd have to do in Scheme or ML (except that in JS, return is explicit rather than implicit). Note that call/cc and call/tail are macros, not functions. The functions are available in normal Javascript form, however (no deep macro-magic is ultimately required to support delimited continuations). call/cc is stored as caterwaul.continuation.call_cc, and call/tail is caterwaul.continuation.call_tail. The invocation of call_tail is different from call/tail: | caterwaul.continuation.call_tail.call(f, arg1, arg2, ...); tconfiguration('std', 'continuation.delimited', function () { l[magic = this.configure('std.qg continuation.core').continuation.magic = this.magic('continuation.delimited')] in this.continuation /se[_.call_cc = function (f) {var escaped = false, cc = function (x) {escaped = true; throw x}, frame = {magic: magic, continuation: f, parameters: [cc]}; try {while ((frame = frame.continuation.apply(this, frame.parameters)) && frame && frame.magic === magic); return frame} catch (e) {if (escaped) return e; else throw e}}, _.call_tail() = {magic: magic, continuation: this, parameters: arguments}]; this.rmacro(qs[call/cc[_]], fn[f] [qs[qg[caterwaul.continuation.call_cc.call(this, _f)]]. replace({_f: f})]). rmacro(qs[call/tail[_(_)]], fn[f, args][qs[qg[caterwaul.continuation.call_tail.call(_f, _args)]].replace({_f: f, _args: args})])}). End-user library. configuration('continuation', function () {this.configure('continuation.core continuation.unwind continuation.cps continuation.delimited')}); __d01f7225134d59279a11628ebc9fc7af meta::sdoc('js::modules/caterwaul.continuation.test/cps', <<'__967fd04a90612fecae992c68dc57953e'); CPS-conversion tests. test('caterwaul.continuation.cps', function () { var c = caterwaul.clone('std continuation'); c(function (eq) { var x = 0; var cs = []; var f = cs/mb/push; var g = fn_[cs.shift().apply(this, arguments)]; eq(l/cps[y <- f(_)][x += y], 1); eq(x, 0); eq(cs.length, 1); g(10); eq(x, 10); eq(cs.length, 0); eq(l/cps[y <- f(_), z <- f(_)][x += y + z], 1); eq(x, 10); eq(cs.length, 1); g(7); eq(x, 10); eq(cs.length, 1); g(4); eq(x, 21); eq(cs.length, 0); var s = ''; eq(l/cps[(foo, bar) <- f(_)][s += foo + bar], 1); eq(s, ''); eq(cs.length, 1); g('one', 'two'); eq(s, 'onetwo'); eq(cs.length, 0); })(eq); c(function (eq) { var f = fn[x, g, y][g.call(10, x + y)]; var count = 0; var t = this; f(3, _, 5) /cps[++count, eq(_, 8), eq(this, 10)]; f(3, _, 5) /cpb[++count, eq(_, 8), eq(this, t)]; eq(count, 2); f(4, _, 9) /cps.n[++count, eq(n, 13), eq(this, 10)]; f(4, _, 9) /cpb.n[++count, eq(n, 13), eq(this, t)]; eq(count, 4); })(eq); c(function (eq) { // Sort of like the real I and C, but not quite... var i = fn[f][fn[x][f(x)]]; var c = fn[x, f, g][g(f(x))]; eq(c(5, i(_) /cps[_ + 1], _) /cps[_ + 1], 7); eq(c(5, i(_) /cps.x[x + 1], _) /cps[_ + 1], 7); eq(c(5, i(_) /cps[_ + 1], _) /cps.x[x + 1], 7); })(eq); }); __967fd04a90612fecae992c68dc57953e meta::sdoc('js::modules/caterwaul.continuation.test/delimited', <<'__3a506c5150e3570d7729f673f48ee686'); Delimited continuation tests. test('caterwaul.continuation.delimited', function () { var c = caterwaul.clone('std continuation'); c(function (eq) { eq(call/cc[fn[cc][cc(4)]], 4); eq(call/cc[fn[cc][cc(4), 5]], 4); eq(call/cc[fn[cc][5]], 5); })(eq); c(function (eq) { // This will fail if tail calls aren't being optimized. var successor_cps = fn[n, acc, cc][n > 0 ? call/tail[successor_cps(n - 1, acc + 1, cc)] : cc(acc)]; eq(call/cc[fn[cc][successor_cps(5, 0, cc)]], 5); eq(call/cc[fn[cc][successor_cps(100000, 0, cc)]], 100000); })(eq); c(function (eq) { var factorial_cps = fn[n, acc, cc][n > 0 ? call/tail[factorial_cps(n - 1, acc * n, cc)] : call/tail[cc(acc)]]; eq(call/cc[fn[cc][factorial_cps(5, 1, cc)]], 120); })(eq); }); __3a506c5150e3570d7729f673f48ee686 meta::sdoc('js::modules/caterwaul.format', <<'__7d67d57915c2722f359b8c24b594b451'); Code formatting module | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. Caterwaul's code generation is designed for compilation efficiency, but it isn't at all human-readable. This module provides a configuration that adds a 'format' method to Caterwaul. This method takes a function, string, or syntax tree and returns a string containing reasonably-formatted code. Formatting rules. Indentation is two spaces, and each block-style construct triggers additional indentation. Each semicolon triggers a line-wrap. caterwaul.tconfiguration('std', 'format', function () { var n_spaces = fn[n][n ? ' #{n_spaces(n - 1)}' : '']; this.field('format', function (tree, indentation) { var spaces = n_spaces(indentation << 1), c = this, op = tree.data, map = caterwaul.util.map, serialize = fn[n][fn[x][x ? x.constructor === String ? x : c.format(x, (indentation || 0) + (n || 0)) : '']]; return op === '()' || op === '[]' ? map(serialize(), [tree[0], op.charAt(0), tree[1], op.charAt(1)]).join('') : tree.is_invisible() ? map(serialize(), tree).join(' ') : op === '?' ? '#{serialize()(tree[0])}\n#{spaces} ? #{serialize(1)(tree[1])}\n#{spaces} : #{serialize(1)(tree[2])}' : op === '(' || op === '[' ? '#{op}#{serialize()(tree[0])}#{op === "(" ? ")" : "]"}' : op === '{' ? '{\n#{spaces} #{serialize(1)(tree[0])}\n#{spaces}}' : op === ';' ? '#{serialize()(tree[0])};\n#{spaces}#{serialize()(tree[1])}' : op === '.' ? '#{serialize()(tree[0])}.#{serialize()(tree[1])}' : op === ',' ? map(serialize(), tree).join(', ') : op === 'for' ? 'for #{c.format(tree[0]).replace(/\n/g, " ")} #{serialize()(tree[1])}' : tree.is_binary_operator() ? tree.length ? map(serialize(), tree).join(' #{op} ') : op : tree.is_prefix_unary_operator() ? '#{op.replace(/^u/, "")} #{serialize()(tree[0])}' : tree.is_postfix_unary_operator() ? '#{serialize()(tree[0])}#{op.replace(/^u/, "")}' : tree.is_blockless_keyword() ? '#{op} #{serialize()(tree[0])}' : tree.has_grouped_block() ? tree[1] && tree[1].data !== '{' && tree[2] && tree.accepts(tree[2]) ? '#{op} #{serialize()(tree[0])} #{serialize()(tree[1])};\n#{spaces}#{serialize()(tree[2])}' : '#{op} #{map(serialize(), tree).join(" ")}' : op})}); __7d67d57915c2722f359b8c24b594b451 meta::sdoc('js::modules/caterwaul.heap', <<'__0de58fc36044652470964f57fb7dd68f'); Heap implementation | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. This module provides a basic heap implementation on top of finite caterwaul sequences. The heap is parameterized by a function that orders elements, returning true if the element belongs more towards the root and false otherwise. (So a minheap on numbers or strings would use the function fn[x, y][x < y].) Usage. caterwaul.heap is a function that takes an order function and returns a constructor for heaps implementing that ordering. So, for example: | var minheap = caterwaul.heap(fn[x, y][x < y]); var h = new minheap(); h.insert(10).insert(20).root() // -> 10 h.rroot() // -> 10, removes the root caterwaul.tconfiguration('std seq', 'heap', function () { this.heap(less) = fc_[null] /se.c[c.prototype = new caterwaul.seq.finite() /se[_.constructor = c] /se[ _.insert(x) = this.push(x).heapify_up(this.size() - 1), _.root() = this[0], _.rroot() = this[0] /se[this.pop() /se[this[0] = _, this.heapify_down(0), when[this.size()]]], Implementation. There's some less-than-obvious math going on here. Down-heapifying requires comparing an element to its two children and finding the top of the three. We then swap that element into the top position and heapify the tree that we swapped. Normally in a heap the array indexes are one-based, but this is inconvenient considering that everything else in Javascript is zero-based. To remedy this, I'm using some makeshift offsets. We basically transform the index in one-based space, but then subtract one to get its zero-based offset. Normally the left and right offsets are 2i and 2i + 1, respectively; in this case, here's the math: | right = 2(i + 1) + 1 - 1 = 2(i + 1) left = 2(i + 1) - 1 = right - 1 _.swap(i, j) = this /se[_[j] = _[i], _[i] = temp, where[temp = _[j]]], _.heapify_up(i) = this /se[_.swap(i, p).heapify_up(p), when[less.call(_, _[i], _[p])], where[p = i >> 1]], _.heapify_down(i) = this /se[_.swap(lr, i).heapify_down(lr), unless[lr === i], where*[s = _.size(), r = i + 1 << 1, l = r - 1, ll = l < s && less.call(_, _[l], _[i]) ? l : i, lr = r < s && less.call(_, _[r], _[ll]) ? r : ll]]]]}); __0de58fc36044652470964f57fb7dd68f meta::sdoc('js::modules/caterwaul.heap.test/minheap', <<'__9fe45d54621677356c7e564a882f442b'); Minheap tests. test('caterwaul.heap.minheap', function () { var c = caterwaul.clone('std heap'); c(function (eq) { var minheap = caterwaul.heap(fn[x, y][x < y]); var h = new minheap(); eq(h.insert(8).root(), 8); eq(h.insert(5).root(), 5); eq(h.insert(3).root(), 3); eq(h.insert(7).root(), 3); eq(h.insert(2).root(), 2); eq(h.rroot(), 2); eq(h.rroot(), 3); eq(h.rroot(), 5); eq(h.rroot(), 7); eq(h.rroot(), 8); eq(h.size(), 0); })(eq); }); __9fe45d54621677356c7e564a882f442b meta::sdoc('js::modules/caterwaul.macro', <<'__06052fbb74a764a5b83bbf5adcb32372'); Macro-authoring configurations | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. These configurations used to belong to the Caterwaul standard library, but I've moved them here given how infrequently they are used in practice. caterwaul. Macro authoring tools (the 'defmacro' library). Lisp provides some handy macros for macro authors, including things like (with-gensyms (...) ...) and even (defmacro ...). Writing defmacro is simple because 'this' inside a macroexpander refers to the caterwaul function that is running. It is trivial to expand into 'null' and side-effectfully define a new macro on that caterwaul object. Another handy macro is 'with_gensyms', which lets you write hygienic macros. For example: | defmacro[forEach[_][_]][fn[xs, f][with_gensyms[i, l, xs][(function() {for (var i = 0, xs = _xs, l = xs.length, it; it = xs[i], it < l; ++it) {_body}})()].replace({_xs: xs, _body: f})]]; This will prevent 'xs', 'l', and 'i' from being visible; here is a sample (truncated) macroexpansion: | forEach[[1, 2, 3]][console.log(it)] -> (function() {for (var _gensym_gesr8o7u_10fo11_ = 0, _gensym_gesr8o7u_10fo12_ = [1, 2, 3], _gensym_gesr8o7u_10fo13_ = _gensym_gesr8o7u_10fo12_.length, it; it = _gensym_gesr8o7u_10fo12_[_gensym_...], _gensym_... < ...; ...) {console.log(it)}})() Since nobody in their right mind would name a variable _gensym_gesr8o7u_10fo11_, it is effectively collision-proof. (Also, even if you load Caterwaul twice you aren't likely to have gensym collisions. The probability of it is one-in-several-billion at least.) Note that macros defined with 'defmacro' are persistent; they outlast the function they were defined in. Presently there is no way to define scoped macros. Related to 'defmacro' is 'defsubst', which lets you express simple syntactic rewrites more conveniently. Here's an example of a defmacro and an equivalent defsubst: | defmacro[_ _][fn[left, right][qs[left === right].replace({left: left, right: right})]]; defsubst[_left _right][_left === _right]; Syntax variables are prefixed with underscores; other identifiers are literals. tconfiguration('std.qs std.fn std.bind', 'macro.defmacro', function () { l[wildcard = fn[n][n.data.constructor === String && n.data.charAt(0) === '_' && '_']] in this.macro(qs[defmacro[_][_]], fn[pattern, expansion][this.rmacro(pattern, this.compile(this.macroexpand(expansion))), qs[null]]). macro(qs[defsubst[_][_]], fn[pattern, expansion][this.rmacro(pattern.rmap(wildcard), l[wildcards = pattern.collect(wildcard)] in fn_[l[hash = {}, as = arguments] [this.util.map(fn[v, i][hash[v.data] = as[i]], wildcards), expansion.replace(hash)]]), qs[null]])}). tconfiguration('std.qs std.fn std.bind', 'macro.with_gensyms', function () { this.rmacro(qs[with_gensyms[_][_]], fn[vars, expansion][l[bindings = {}][vars.flatten(',').each(fb[v][bindings[v.data] = this.gensym()]), qs[qs[_]].replace({_: expansion.replace(bindings)})]])}). Compile-time eval (the 'compile_eval' library). This is one way to get values into your code (though you don't have closure if you do it this way). Compile-time evals will be bound to the current caterwaul function and the resulting expression will be inserted into the code as a reference. The evaluation is done at macro-expansion time, and any macros defined when the expression is evaluated are used. tconfiguration('std.qs std.fn', 'macro.compile_eval', function () { this.macro(qs[compile_eval[_]], fn[e][new this.ref(this.compile(this.macroexpand(qs[fn_[_]].replace({_: e}))).call(this))])}). Final configuration. This one loads the others. configuration('macro', function () {this.configure('macro.defmacro macro.with_gensyms macro.compile_eval')}); __06052fbb74a764a5b83bbf5adcb32372 meta::sdoc('js::modules/caterwaul.macro.test/compile-eval', <<'__c8273cdd5418074db7b758f47e82f6ab'); Compile-time eval tests. test('caterwaul.macro.compile-eval', function () { var c = caterwaul.clone('std macro'); c(function (eq) { var five = compile_eval[2 + 3]; var the_caterwaul = compile_eval[this]; var zero = compile_eval[this.count = 0]; var also_zero = compile_eval[this.count]; eq(five, 5); eq(the_caterwaul, caterwaul); eq(zero, 0); eq(also_zero, 0); })(eq); }); __c8273cdd5418074db7b758f47e82f6ab meta::sdoc('js::modules/caterwaul.macro.test/defmacro', <<'__9914b01f7b4244f4e00a03ded4072b08'); Caterwaul defmacro standard library tests test('caterwaul.macro.defmacro', function () { var c = caterwaul.clone('std.qs std.qg std.fn std.lvalue macro.defmacro macro.with_gensyms'); c(function (eq) { eq(defmacro[foo][fn_[qs[bar]]], null); var bar = 6; eq(foo, 6); var x = 3; eq(x + 5, 8, 'first'); eq(defmacro[_ + _][fn[l, r][qs[l * r].replace({l: l, r: r})]], null); eq(x + 5, 15, 'second'); defmacro[loop[_].over[_]][fn[expr, xs][(with_gensyms[i, l, xs][qg[function () { for (var i = 0, xs = _array, l = xs.length, it; it = xs[i], i < l; ++i) {_body}}]()]).replace({_array: xs, _body: expr})]]; var count = 0; loop[eq(it, ++count)].over[[1, 2, 3, 4, 5]]; eq(count, 5); var less = fn[x, y][qg[++count, x < y]]; defsubst[_left < _right][less(_left, _right)]; eq(5 < 6, true); eq(count, 6); eq(6 < 5, false); eq(count, 7); less(x, y) = x > y; eq(6 < 5, true); }) (eq); }); __9914b01f7b4244f4e00a03ded4072b08 meta::sdoc('js::modules/caterwaul.macro.test/with-gensyms', <<'__8827437acee16ed0aab3ed655d33c5b2'); Caterwaul with_gensyms tests test('caterwaul.macro.with-gensyms', function () { var c = caterwaul.clone('std.qs', 'std.qg', 'std.fn', 'macro.defmacro', 'macro.with_gensyms', 'std.string'); c(function (eq) { defmacro[foo][fn_[qs[qs[_]].replace({_: with_gensyms[bar][bar]})]]; defmacro[bif][fn_[qs[qs[_]].replace({_: with_gensyms[bif, baz, bar][bar]})]]; var symbol1 = foo; var symbol2 = bif; if (symbol1.data === symbol2.data) throw new Error('Gensyms not unique: #{symbol1.data} === #{symbol2.data}'); }) (eq); }); __8827437acee16ed0aab3ed655d33c5b2 meta::sdoc('js::modules/caterwaul.memoize', <<'__706a518f525bc0e31bb93f2d05c6083d'); Memoization module | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. This memoizer is implemented a bit differently from Perl's memoize module; this one leaves more up to the user. To mitigate the difficulty involved in using it I've written a couple of default proxy functions. The basic model is that functions are assumed to map some 'state' (which obviously involves their arguments, but could involve other things as well) into either a return value or an exception. This memoization library lets you introduce a proxy around a function call: | var null_proxy = fn[context, args, f][f.apply(context, args)]; // Identity memoizer (does no memoization) var memo_proxy = fn[context, args, f][this[args[0]] || (this[args[0]] = f.apply(context, args))]; // Memoizes on first argument var identity = caterwaul.memoize.from(null_proxy); var memoizer = caterwaul.memoize.from(memo_proxy); var fibonacci = memoizer(fn[x][x < 2 ? x : fibonacci(x - 1) + fibonacci(x - 2)]); // O(n) time Here the 'fstate' argument represents state specific to the function being memoized. 'f' isn't the real function; it's a wrapper that returns an object describing the return value. This object contains: | 1. The amount of time spent executing the function. This can be used later to expire memoized results (see the 'heap' module for one way to do this). 2. Any exceptions thrown by the function. 3. Any value returned by the function. Internals. The core interface is the proxy function, which governs the memoization process at a low level. It is invoked with three parameters: the invocation context, the original 'arguments' object, and the function being memoized (wrapped by a helper, explained below). It also receives an implicit fourth as 'this', which is bound to a generic object specific to the memoized function. The proxy function owns this object, so it's allowed to manipulate it in any way. Helpers. The function passed into the proxy isn't the original. It's been wrapped by a result handler that captures the full range of things the function can do, including throwing an exception. This guarantees the following things: | 1. The wrapped function will never throw an exception. 2. The wrapped function will always return a truthy value, and it will be an object. 3. Each invocation of the wrapped function is automatically timed, and the time is accessible via the .time attribute on its return value. The proxy function is expected to return one of these values, which will then be translated into the corresponding real action. caterwaul.tconfiguration('std seq continuation', 'memoize', function () { this.namespace('memoize') /se.m[m.wrap(f) = fn_[l[as = arguments, start = +new Date()] in unwind_protect[{error: e}][{result: f.apply(this, as)}] /se[_.time = +new Date() - start]] /se[_.original = f], m.perform(result) = result.error ? unwind[result.error] : result.result, m.from(proxy) = fn[f][l[state = {}, g = m.wrap(f)] in fn_[m.perform(proxy.call(state, this, arguments, g))]]]}); __706a518f525bc0e31bb93f2d05c6083d meta::sdoc('js::modules/caterwaul.memoize.test/fib', <<'__1cf835634927d2aca7f5252efd1a2276'); Fibonacci memoization test (trivial). test('caterwaul.memoize.fib', function () { var c = caterwaul.clone('std memoize'); c(function (eq) { var memo = caterwaul.memoize.from(fn[c, as, f][this[as[0]] || (this[as[0]] = f.apply(c, as))]); var c1 = 0; var c2 = 0; var fib = fn[x][++c1, x < 2 ? x : fib(x - 1) + fib(x - 2)]; var fibm = memo(fn[x][++c2, x < 2 ? x : fibm(x - 1) + fibm(x - 2)]); eq(fib(20), fibm(20)); eq(c2, 21); eq(c1 > c2, true); var c1old = c1; var c2old = c2; eq(fib(20), fibm(20)); eq(c1, c1old * 2); eq(c2, c2old); })(eq); }); __1cf835634927d2aca7f5252efd1a2276 meta::sdoc('js::modules/caterwaul.node', <<'__d4e5ce298895c0d59f093b80831556e4'); Node/CommonJS Caterwaul module | Spencer Tipping Licensed under the terms of the MIT source code license - pinclude pp::js::modules/caterwaul.all Expose the Caterwaul global function as an export. exports.caterwaul = caterwaul; __d4e5ce298895c0d59f093b80831556e4 meta::sdoc('js::modules/caterwaul.opt', <<'__eeda6a22d8304e62c118565efae2ee1a'); Caterwaul optimization library | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. JavaScript JIT technology has come a long way, but there are some optimizations that it still isn't very good at performing. One is loop unrolling, which can have a large impact on execution speed. Another is function inlining, which may be coming soon but for now also makes a difference. This library provides macros to transform well-factored code into high-performance code. Loop unrolling. This is probably the most straightforward family of optimizations in the library. If you're using the 'seq' library for iteration, then you will already benefit from these macros; but you can also use them directly. Counting loops. Loop unrolling is designed to optimize the most common use of 'for' loops, a loop from zero to some upper boundary. For example: | for (var i = 0, total = 0; i < xs.length; ++i) { console.log(xs[i]); total += xs[i]; } Using opt.unroll. The opt.unroll macro takes two bracketed expressions. The first describes the loop parameters and the second is the body to be executed on each iteration. The loop parameters are the variable representing the index, and its upper bound. (At present there is no way to specify a lower loop bound, nor custom incrementing. This may be added later.) Note that there isn't a good way to break out of a loop that's running. Using 'break' directly is illegal because of JavaScript's syntax rules. In the future there will be some mechanism that supports break and perhaps continue, in some form or another. Here is the unrolled version of the for loop described in 'Counting loops': | var total = 0; var x; opt.unroll[i, xs.length][ x = xs[i], console.log(x), total += x ]; And here is the generated code, reformatted for readability: | var total = 0; var x; (function (_gensym_iterations) { var _gensym_rounds = _gensym_iterations >>> 3; var _gensym_extra = _gensym_iterations & 7; for (var i = 0; i < _gensym_extra; ++i) x = xs[i], console.log(x), total += x; for (var _gensym_i = 0; _gensym_i < _gensym_rounds; ++_gensym_i) { x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; x = xs[i], console.log(x), total += x; i++; } return _gensym_iterations; }) (xs.length); Caveats. Caterwaul's optimizer is not smart about identifying loop invariants or non-side-effectful things about loops. In other words, it really exists only for the purpose of taking the work out of unrolling things or doing similarly mechanical low-level optimization. It also does not optimize algorithms or any other high-level aspects of your code that generally have a more significant performance impact than low-level stuff like loop unrolling. caterwaul.tconfiguration('std macro', 'opt.unroll', function () {this.rmacro(qs[opt.unroll[_, _][_]], fn[variable, iterations, body][ with_gensyms[l, rs, es, j][qg[function (l) {for (var rs = l >= 0 && l >> 3, es = l >= 0 && l & 7, _i_ = 0; _i_ < es; ++_i_) _body_; for (var j = 0; j < rs; ++j) {_body_; _i_++; _body_; _i_++; _body_; _i_++; _body_; _i_++; _body_; _i_++; _body_; _i_++; _body_; _i_++; _body_; _i_++}; return l}].call(this, _iterations_)]. replace({_i_: variable, _body_: body, _iterations_: iterations})])}); Opt module collection. Loading the 'opt' configuration will enable all of the individual macros in the optimization library. caterwaul.configuration('opt', function () {this.configure('opt.unroll')}); __eeda6a22d8304e62c118565efae2ee1a meta::sdoc('js::modules/caterwaul.opt.test/unroll', <<'__389added7daf92aea5679f58cd4b6940'); Tests for opt.unroll test('caterwaul.opt.unroll', function () { var f = caterwaul.clone('std', 'opt')(function (eq) { var iterations = 0; eq(opt.unroll[i, 10][++iterations], 10); eq(iterations, 10); eq(opt.unroll[i, 8][++iterations], 8); eq(iterations, 18); eq(opt.unroll[i, 7][++iterations], 7); eq(iterations, 25); eq(opt.unroll[i, 1][++iterations], 1); eq(iterations, 26); eq(opt.unroll[i, 24][++iterations], 24); eq(iterations, 50); eq(l[x = 10][eq(x, opt.unroll[i, x][++x]), x], 20); return iterations; }); var iterations = f(eq); eq(iterations, 50); }); __389added7daf92aea5679f58cd4b6940 meta::sdoc('js::modules/caterwaul.parser', <<'__9434f8f34406b5dbef76ea416c6098a3'); Caterwaul parser module | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. The caterwaul parser uses a combinatory approach, much like Haskell's parser combinator library. The parser consumes elements from a finite input stream and emits the parse tree at each step. Note that parsers generated here are not at all insightful or necessarily performant. In particular, left-recursion isn't resolved, meaning that the parser will loop forever in this case. Basis and acknowledgements. This parser library is based heavily on Chris Double's JSParse (available at github.com/doublec/jsparse), which implements a memoized combinatory PEG parser. If you are looking for a simple and well-designed parsing library, I highly recommend JSParse; it will be easier to use and more predictable than caterwaul.parser. Like JSParse, these parsers are memoized and use parsing expression grammars. However, this parser is probably quite a lot slower. Internals. Memoization is restricted to a session rather than being global. This prevents the space leak that would otherwise occur if the parser outlived the result. Behind the scenes the parser promotes the input into a parse-state (very much like the ps() function in JSParse). Like other caterwaul libraries, this one uses non-macro constructs behind the scenes. You can easily get at this by accessing stuff inside the caterwaul.parser namespace. caterwaul.tconfiguration('std seq continuation memoize', 'parser.core', function () { this.namespace('parser') /se[_.parse_state(input, i, result, memo) = undefined /se[this.input = input, this.i = i, this.result = result, this.memo = memo], _.parse_state /se.s[s.from_input(input) = new _.parse_state(input, 0, null, {}), s.prototype /se[_.accept(i, r) = new this.constructor(this.input, i, r, this.memo), _.has_input() = this.i < this.input.length, _.toString() = 'ps[#{this.input.substr(this.i)}, #{this.result}]']], _.memoize = caterwaul.memoize.from(fn[c, as, f][k in m ? m[k] : (m[k] = f.apply(c, as)), where[k = '#{f.original.memo_id}|#{as[0].i}', m = as[0].memo || (as[0].memo = {})]]), _.promote_non_states(f) = fn[state][state instanceof _.parse_state ? f.call(this, state) : f.call(this, _.parse_state.from_input(state)) /re[_ && _.result]], _.identify(f) = f /se[_.memo_id = caterwaul.gensym()], _.parser(f) = _.promote_non_states(_.memoize(_.identify(f))), _.defparser(name, f) = _.parsers[name]() = _.parser(f.apply(this, arguments)), _.parsers = {}]}). Notation. Parsers are written as collections of named nonterminals. Each nonterminal contains a mandatory expansion and an optional binding: | peg[c('a') % c('b')] // A grammar that recognizes the character 'a' followed by the character 'b' peg[c('a') % c('b') >> fn[ab][ab[0] + ab[1]]] // The same grammar, but the AST transformation step appends the two characters The >> notation is borrowed from Haskell (would have been >>=, but this requires a genuine lvalue on the left); the idea is that the optional binding is a monadic transform on the parse-state monad. (The only difference is that you don't have to re-wrap the result in a new parse state using 'return' as you would in Haskell -- the return here is implied.) The right-hand side of >> can be any expression that returns a function. It will be evaluated directly within its lexical context, so the peg[] macro is scope-transparent modulo gensyms and the namespace importing of caterwaul.parser.parsers. Parsers are transparent over parentheses. Only the operators described below are converted specially. Constants. Strings are parsable by using the c(x) function, which is named this because it matches a constant. | peg[c('x')] // Parses the string 'x' peg[c('foo bar')] // Parses the string 'foo bar' peg[c(['foo', 'bar'])] // Parses either the string 'foo' or the string 'bar', in mostly-constant time in the size of the array (see below) peg[c({foo: 1, bar: 2})] // Parses either the string 'foo' or the string 'bar'; returns 1 if 'foo' matched, 2 if 'bar' matched (also in mostly-constant time) peg[c(/\d+/, 1)] // Parses strings of digits with a minimum length of 1. The parse is greedy, and the regexp's exec() method output is returned. peg[c(fn[s][3])] // Always takes three characters, regardless of what they are. The c() function can take other arguments as well. One is an array of strings; in this case, it matches against any string in the array. (Matching time is O(l), where l is the number of distinct lengths of strings.) Another is an object; if any directly-contained (!) attribute of the key is parsed and consumed, then the value associated with that key is returned. The time for this algorithm is O(l), where l is the number of distinct lengths of the keys in the object. Another option is specifying a regular expression with a minimum length. The rule is that the parser fails immediately if the regexp doesn't match the minimum length of characters. If it does match, then the maximum matching length is found. This ends up performing O(log n) regexp-matches against the input, for a total runtime of O(n log n). (The algorithm here is an interesting one: Repeatedly double the match length until matching fails, then binary split between the last success and the first failure.) Because of the relatively low performance of this regexp approach, it may be faster to use a regular finite-automaton for routine parsing and lexing. Then again, O(log n) linear-time native code calls may be faster than O(n) constant-time calls in practice. In order for a regular expression to work sensibly in this context, it needs to have a couple of properties. One is that it partitions two contiguous ranges of substrings into matching and non-matching groups, and that the matching group, if it exists, contains shorter substrings than the non-matching group. Put differently, there exists some k such that substrings from length minimum (the minimum that you specify as the second argument to c()) to k all match, and substrings longer than k characters all fail to match. The other property is that the initial match length must be enough to accept or reject the regular expression. So, for example, c(/[a-zA-Z0-9]+/, 1) is a poor way to match against identifiers since it will also quite happily take an integer. Note that if you specify a regular expression, the parser library will recompile it into a new one that is anchored at both ends. This is necessary for sane behavior; nobody would ever want anything else. This means that matching on /foo/ will internally generate /^foo$/. This recompilation process preserves the flags on the original however. (Though you seriously shouldn't use /g -- I have no idea what this would do.) Finally, you can also specify a function. If you do this, the function will be invoked on the input and the current offset, and should return the number of characters it intends to consume. It returns a falsy value to indicate failure. tconfiguration('std seq continuation', 'parser.c', function () { this.configure('parser.core').parser.defparser('c', fn[x, l][ x.constructor === String ? fn[st][st.accept(st.i + x.length, x), when[x === st.input.substr(st.i, x.length)]] : x instanceof Array ? l[index = index_entries(x)] in fn[st][check_index(index, st.input, st.i) /re[_ && st.accept(st.i + _.length, _)]] : x.constructor === RegExp ? l[x = add_absolute_anchors_to(x)] in fn[st][fail_length(x, st.input, st.i, l) /re[_ > l && split_lengths(x, st.input, st.i, l, _) /re[st.accept(st.i + _, x.exec(st.input.substr(st.i, _)))]]] : x.constructor === Function ? fn[st][x.call(st, st.input, st.i) /re[_ && st.accept(st.i + _, st.input.substr(st.i, _))]] : l[index = index_entries(seq[sk[x]])] in fn[st][check_index(index, st.input, st.i) /re[_ && st.accept(st.i + _.length, x[_])]], where*[check_index(i, s, p) = seq[i |[_['@#{s}'] && s, where[s = s.substr(p, _.length)]]], index_entries(xs) = l*[xsp = seq[~xs], ls = seq[sk[seq[!(xsp *[[_.length, true]])]] *[Number(_)]]] in seq[~ls.slice().sort(fn[x, y][y - x]) *~l[!(xsp %[_.length === l] *[['@#{_}', true]] + [['length', l]])]], add_absolute_anchors_to(x) = l[parts = /^\/(.*)\/(\w*)$/.exec(x.toString())] in new RegExp('^#{parts[1]}$', parts[2]), fail_length(re, s, p, l) = re.test(s.substr(p, l)) ? p + (l << 1) <= s.length ? fail_length(re, s, p, l << 1) : l << 1 : l, split_lengths(re, s, p, l, u) = l*[b(l, u) = l + 1 < u ? (l + (u - l >> 1)) /re.m[re.test(s.substr(p, m)) ? b(m, u) : b(l, m)] : l] in b(l, u)]])}). Sequences. Denoted using the '%' operator. The resulting AST is flattened into a finite caterwaul sequence. For example: | peg[c('a') % c('b') % c('c')]('abc') // -> ['a', 'b', 'c'] peg[c('a') % c('b') >> fn[xs][xs.join('/')]]('ab') // -> 'a/b' tconfiguration('std opt seq continuation', 'parser.seq', function () { this.configure('parser.core').parser.defparser('seq', fn_[l[as = arguments] in fn[state][ call/cc[fn[cc][opt.unroll[i, as.length][(state = as[i](state)) ? result.push(state.result) : cc(false)], state.accept(state.i, result)]], where[result = []]]])}). Alternatives. Denoted using the '/' operator. Alternation is transparent; that is, the chosen entry is returned identically. Entries are tried from left to right without backtracking. For example: | peg[c('a') / c('b')]('a') // -> 'a' tconfiguration('std opt seq continuation', 'parser.alt', function () { this.configure('parser.core').parser.defparser('alt', fn_[l[as = seq[~arguments]] in fn[state][seq[as |[_(state)]]]])}). Repetition. Denoted using subscripted ranges, similar to the notation used in regular expressions. For example: | peg[c('a')[0]] // Zero or more 'a's peg[c('b')[1,4] // Between 1 and 4 'b's tconfiguration('std opt seq continuation', 'parser.times', function () { this.configure('parser.core').parser.defparser('times', fn[p, lower, upper][fn[state][ call/cc[fn[cc][opt.unroll[i, lower][++count, (state = p(state)) ? result.push(state.result) : cc(false)], true]] && call/cc[l*[loop(cc) = (! upper || count++ < upper) && state.has_input() && p(state) /se[state = _, when[_]] ? result.push(state.result) && call/tail[loop(cc)] : cc(state.accept(state.i, result))] in loop], where[count = 0, result = []]]])}). Optional things. Denoted using arrays. Returns a tree of undefined if the option fails to match. For example: | peg[c('a') % [c('b')] % c('c')] // a followed by optional b followed by c tconfiguration('std opt seq continuation', 'parser.opt', function () { this.configure('parser.core').parser.defparser('opt', fn[p][fn[state][state.accept(n, r), where*[s = p(state), n = s ? s.i : state.i, r = s && s.result]]])}). Positive and negative matches. Denoted using unary + and -, respectively. These consume no input but make assertions: | peg[c('a') % +c('b')] // Matches an 'a' followed by a 'b', but consumes only the 'a' peg[c('a') % -c('b')] // Matches an 'a' followed by anything except 'b', but consumes only the 'a' tconfiguration('std opt seq continuation', 'parser.match', function () { this.configure('parser.core').parser /se[_.defparser('match', fn[p][fn[state][p(state) /re[_ && state.accept(state.i, state.result)]]]), _.defparser('reject', fn[p][fn[state][p(state) /re[!_ && state.accept(state.i, null)]]])]}). Binding. This is fairly straightforward; a parser is 'bound' to a function by mapping through the function if it is successful. The function then returns a new result based on the old one. Binding is denoted by the >> operator. tconfiguration('std opt seq continuation', 'parser.bind', function () { this.configure('parser.core').parser /se[_.defparser('bind', fn[p, f][fn[state][p(state) /re[_ && _.accept(_.i, f.call(_, _.result))]]])]}). DSL macro. Most of the time you'll want to use the peg[] macro rather than hand-coding the grammar. The macro both translates the tree and introduces all of the parsers as local variables (like a with() block, but much faster and doesn't earn the wrath of Douglas Crockford). tconfiguration('std opt seq continuation', 'parser.dsl', function () { this.configure('parser.core').rmacro(qs[peg[_]], fn[x][qs[qg[l*[_bindings][_parser]]].replace({_bindings: new this.syntax(',', seq[sp[this.parser.parsers] *[qs[_x = _y].replace({_x: _[0], _y: new outer.ref(_[1])})]]), _parser: this.parser.dsl.macroexpand(x)}), where[outer = this]]), this.parser.dsl = caterwaul.global().clone() /se.dsl[dsl.macro /se[ _(qs[_(_)], fn[x, y][qs[_x(_y)].replace({_x: e(x), _y: y})]), _(qs[_ / _], fb('/', 'alt')), _(qs[_ % _], fb('%', 'seq')), _(qs[_ >> _], b('bind')), _(qs[[_]], u('opt')), _(qs[_].as('('), fn[x][e(x).as('(')]), _(qs[_[_]], fn[x, l][qs[times(_x, _l)].replace({_x: e(x), _l: l})]), _(qs[_[_, _]], fn[x, l, u][qs[times(_x, _l, _u)].replace({_x: e(x), _l: l, _u: u})]), where*[e = dsl.macroexpand, fb(op, name)(x, y) = qs[_name(_x, _y)].replace({_name: name, _x: x.flatten(op).map(e) /se[_.data = ','], _y: e(y)}), b(name)(x, y) = qs[_name(_x, _y)].replace({_name: name, _x: e(x), _y: y}), u(name)(x) = qs[_name(_x)] .replace({_name: name, _x: e(x)})]]]}). Final configuration. Loads both the classes and the peg[] macro. configuration('parser', function () { this.configure('parser.core parser.c parser.seq parser.alt parser.times parser.opt parser.match parser.bind parser.dsl')}); __9434f8f34406b5dbef76ea416c6098a3 meta::sdoc('js::modules/caterwaul.parser.test/basic', <<'__97627ef6b160d10fdc2de836f65c32d5'); Basic parser combinator tests. test('caterwaul.parser.basic', function () { var c = caterwaul.clone('std parser'); c(function (eq) { var p = caterwaul.parser.parsers; var a = p.c('a'); eq(a('a'), 'a'); var abc = p.seq(p.c('a'), p.c('b'), p.c('c')); eq(abc('abc').join(','), 'a,b,c'); eq(!! abc('abd'), false); eq(!! abc('ab'), false); var abBc = p.seq(p.c('a'), p.alt(p.c('b'), p.c('B')), p.c('c')); eq(abBc('abc').join(','), 'a,b,c'); eq(abBc('aBc').join(','), 'a,B,c'); eq(!! abBc('acc'), false); eq(!! abBc('aac'), false); eq(!! abBc('abb'), false); eq(!! abBc('abC'), false); eq(!! abBc('Abc'), false); var ab_c = p.seq(p.c('a'), p.opt(p.c('b')), p.c('c')); eq(ab_c('abc').join(','), 'a,b,c'); eq(ab_c('ac').join(','), 'a,false,c'); eq(!! ab_c('abbc'), false); eq(!! ab_c('adc'), false); eq(!! ab_c('aabc'), false); eq(p.times(p.c('abab'), 1, 2)('abab').join(','), 'abab'); eq(p.times(p.c('abab'), 1, 2)('abababab').join(','), 'abab,abab'); eq(p.times(p.c('ab'), 1, 3)('ababab').join(','), 'ab,ab,ab'); eq(p.times(p.c('ab'), 0, 3)('ab').join(','), 'ab'); eq(p.times(p.c('ab'), 0, 3)('abab').join(','), 'ab,ab'); var abbc = p.seq(p.c('a'), p.times(p.c('b'), 1, 3), p.c('c')); eq(abbc('abc')[1].join(','), 'b'); eq(abbc('abbc')[1].join(','), 'b,b'); eq(abbc('abbbc')[1].join(','), 'b,b,b'); eq(abbc('abbbbc'), false); eq(abbc('ac'), false); var ac = p.seq(p.c('a'), p.times(p.c('b'), 0, 2), p.c('c')); eq(ac('abc')[1].join(','), 'b'); eq(ac('abbc')[1].join(','), 'b,b'); eq(ac('abbbc'), false); eq(ac('abbbbc'), false); eq(ac('ac')[1].length, 0); })(eq); }); __97627ef6b160d10fdc2de836f65c32d5 meta::sdoc('js::modules/caterwaul.parser.test/c', <<'__4aa86f0e9bb2c74d7d126a17bcf9fa0a'); Tests for the constant parser. test('caterwaul.parser.c', function () { var c = caterwaul.clone('std seq parser'); c(function (eq) { eq(peg[c('foo')]('foo'), 'foo'); eq(peg[c('foo')]('fop'), false); eq(peg[c('foo')]('fOo'), false); eq(peg[c('foo')]('Foo'), false); eq(!! peg[c(['foo', 'bar'])], true); // Check for errors initializing the parser eq(peg[c(['foo', 'bar'])]('foo'), 'foo'); eq(peg[c(['foo', 'bar'])]('bar'), 'bar'); eq(peg[c(['foo', 'bar'])]('Bar'), false); eq(peg[c(['foo', 'bar'])]('Foo'), false); eq(peg[c(['foo', 'food'])]('foo'), 'foo'); eq(peg[c(['foo', 'food'])]('food'), 'food'); eq(peg[c(['foo', 'fooD']) % [c('d')] >> fn[xs][seq[~xs %[_]].join('')]]('food'), 'food'); eq(peg[c(['foo', 'fooD']) % [c('d')] >> fn[xs][seq[~xs %[_]].join('')]]('fooD'), 'fooD'); eq(peg[c({foo: 1, bar: 2})]('foo'), 1); eq(peg[c({foo: 1, bar: 2})]('bar'), 2); eq(peg[c({foo: 1, bar: 2})]('bif'), false); eq(peg[c({foo: 1, b: 2})]('foo'), 1); eq(peg[c({foo: 1, b: 2})]('b'), 2); eq(peg[c({foo: 1, b: 2})]('c'), false); eq(peg[c(/[ab]+/, 1)]('a'), 'a'); eq(peg[c(/[ab]+/, 1)]('ab'), 'ab'); eq(peg[c(/[ab]+/, 1)]('aba'), 'aba'); eq(peg[c(/[ab]+/, 1)]('ac'), 'a'); eq(peg[c(/[ab]+/, 1)]('abbc'), 'abb'); eq(peg[c(/[ab]+/, 1)]('abbac'), 'abba'); eq(peg[c(/[ab]+/, 1)]('abc'), 'ab'); eq(peg[c(/\d+/, 1)]('0')[0], '0'); eq(peg[c(/\d+/, 1)]('012345')[0], '012345'); eq(peg[c(/\d+/, 1)]('0abc')[0], '0'); eq(peg[c(/\d+/, 1)]('01bc')[0], '01'); eq(peg[c(/\d+/, 1)]('012bc')[0], '012'); eq(peg[c(/\d+/, 2)]('012bc')[0], '012'); eq(peg[c(/\d+/, 3)]('012bc')[0], '012'); eq(peg[c(/\d+/, 1)]('0123bc')[0], '0123'); eq(peg[c(/\d+/, 2)]('0123bc')[0], '0123'); eq(peg[c(/\d+/, 3)]('0123bc')[0], '0123'); eq(peg[c(/\d+/, 4)]('0123bc')[0], '0123'); eq(peg[c(/\d+/, 2)]('0abc'), false); eq(peg[c(/\d+/, 1)]('abc'), false); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('alkawemo + quux'), 'alkawemo'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_alkawemo + quux'), '_alkawemo'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_alkawemo+quux'), '_alkawemo'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_alkawemo2+quux'), '_alkawemo2'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_alkawemo2+quux'), '_alkawemo2'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_AlKaWeMo2+'), '_AlKaWeMo2'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_AlKa%wemo'), '_AlKa'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('$foo'), false); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar23'), '_foobar23'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar8++++++++++'), '_foobar8'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar7++++++++'), '_foobar7'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar6++++'), '_foobar6'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar5++'), '_foobar5'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar4+'), '_foobar4'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar2'), '_foobar2'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foobar'), '_foobar'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_fooba'), '_fooba'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foob'), '_foob'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('_foo'), '_foo'); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 1) >> fn[x][x[0]]]('3alkawemo2+quux'), false); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 2) >> fn[x][x[0]]]('+_alkawemo+quux'), false); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 2) >> fn[x][x[0]]]('+_alkawemo2+quux'), false); eq(peg[c(/[A-Za-z_][A-Za-z0-9_$]*/, 2) >> fn[x][x[0]]]('+_alkawemo2+quux'), false); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('Quotation'), 'uotation'); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('QuotatioN'), 'uotatio'); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('QuoTatioN'), 'uo'); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('FooBar'), 'oo'); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('Foobar'), 'oobar'); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('Foo bar'), 'oo'); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('F'), false); eq(peg[c(/[A-Z]([a-z]+)/, 2) >> fn[x][x[1]]]('oobar'), false); eq(peg[c(fn[s][3])]('abcdef'), 'abc'); eq(peg[c(fn[s][4])]('abcdef'), 'abcd'); eq(peg[c(fn[s][5])]('abcdef'), 'abcde'); eq(peg[c(fn[s][0])]('abcdef'), false); eq(peg[c(fn[s][Number(s.charAt(0))])]('1abcdef'), '1'); eq(peg[c(fn[s][Number(s.charAt(0))])]('2abcdef'), '2a'); eq(peg[c(fn[s][Number(s.charAt(0))])]('3abcdef'), '3ab'); eq(peg[c(fn[s][Number(s.charAt(0))])]('4abcdef'), '4abc'); eq(peg[c(fn[s][Number(s.charAt(0))])]('5abcdef'), '5abcd'); eq(peg[c(fn[s][Number(s.charAt(0))])]('6abcdef'), '6abcde'); eq(peg[c(fn[s][Number(s.charAt(0))])]('7abcdef'), '7abcdef'); eq(peg[c(fn[s][Number(s.charAt(0))])]('8abcdef'), '8abcdef'); })(eq); }); __4aa86f0e9bb2c74d7d126a17bcf9fa0a meta::sdoc('js::modules/caterwaul.parser.test/dsl', <<'__595f0a43936f994088d95cf2c2504923'); DSL tests for the caterwaul parser. test('caterwaul.parser.dsl', function () { var c = caterwaul.clone('std parser'); c(function (eq) { var bind_test = peg[c('a')]; eq(bind_test('a'), 'a'); eq(peg[c('a') % c('b')]('ab').join(','), 'a,b'); eq(peg[c('a') % (c('b') / c('c'))]('ab').join(','), 'a,b'); eq(peg[c('a') % (c('b') / c('c'))]('ac').join(','), 'a,c'); eq(peg[c('a') % c('b') % c('c')]('abc').join('|'), 'a|b|c'); eq(peg[c('a') % c('b')[0]]('abbb').join('|'), 'a|b,b,b'); eq(peg[c('a') % c('b')[0]]('ab').join('|'), 'a|b'); eq(peg[c('a') % c('b')[1]]('ab').join('|'), 'a|b'); eq(peg[c('a') % c('b')[2]]('ab'), false); eq(peg[c('a') % c('b')[0, 3]]('abbb').join('|'), 'a|b,b,b'); eq(peg[c('a') % c('b')[0, 2]]('abbb').join('|'), 'a|b,b'); eq(peg[c('a') % c('b')[0, 1]]('abbb').join('|'), 'a|b'); eq(peg[c('a') % c('b')[1, 1]]('abbb').join('|'), 'a|b'); eq(peg[c('a') % [c('b') / c('c')] % c('d') >> fn[xs][xs.join('.')]]('abd'), 'a.b.d'); eq(peg[c('a') % [c('b') / c('c')] % c('d') >> fn[xs][xs.join('.')]]('acd'), 'a.c.d'); eq(peg[c('a') % [c('b') / c('c')] % c('d') >> fn[xs][xs.join('.')]]('ad'), 'a.false.d'); eq(peg[c('a') % [c('b') / c('c')] % c('d') >> fn[xs][xs.join('.')]]('ab'), false); eq(peg[c('a') % [c('b') / c('c')] % c('d') >> fn[xs][xs.join('.')]]('ac'), false); eq(peg[c('a') >> fn[x][x.length]]('a'), 1); eq(peg[c('a') % c('b')[1] >> fn[xs]['#{xs.length}|#{xs.join("")}']]('abbbb'), '2|ab,b,b,b'); eq(peg[c('a') % c('b')[1] >> fn[xs]['#{xs.length}|#{xs.join("")}']]('ab'), '2|ab'); eq(peg[c('a') % c('b')[1] >> fn[xs]['#{xs.length}|#{xs.join("")}']]('a'), false); eq(peg[c('a') % ((c('b') / c('c'))[0] >> fn[xs]['[#{xs.join("")}]']) % c('d') >> fn[xs][xs.join('')]]('abd'), 'a[b]d'); eq(peg[c('a') % ((c('b') / c('c'))[0] >> fn[xs]['[#{xs.join("")}]']) % c('d') >> fn[xs][xs.join('')]]('acd'), 'a[c]d'); eq(peg[c('a') % ((c('b') / c('c'))[0] >> fn[xs]['[#{xs.join("")}]']) % c('d') >> fn[xs][xs.join('')]]('abcd'), 'a[bc]d'); eq(peg[c('a') % ((c('b') / c('c'))[0] >> fn[xs]['[#{xs.join("")}]']) % c('d') >> fn[xs][xs.join('')]]('abcbbccd'), 'a[bcbbcc]d'); eq(peg[c('a') % ((c('b') / c('c'))[0] >> fn[xs]['[#{xs.join("")}]']) % c('d') >> fn[xs][xs.join('')]]('abcbbcc'), false); })(eq); }); __595f0a43936f994088d95cf2c2504923 meta::sdoc('js::modules/caterwaul.parser.test/high-complexity', <<'__7205bca9c419ada3007d1ff029796c6b'); Observed failure case of exponential-time complexity to parse simple expressions. The Figment parser parses strings using the c(/ /, 1) construct. However, its complexity seems to be exponential. Update: The problem was the following regular expression, which does in fact have exponential time complexity when processing strings that end in a newline: | /(?:\n?[^\n]+)*/ test('caterwaul.parser.high-complexity', function () { caterwaul.clone('std macro seq continuation parser')(function (eq) { defsubst[time[_x]][l*[start = +new Date(), result = qg[_x], end = +new Date()] in end - start]; var parser = peg[c(/'([^\\']|\\.?)*/, 1) % c("'")]; var times = [time[parser("''")], time[parser("'f'")], time[parser("'fo'")], time[parser("'foobar'")], time[parser("'foobarbifbaz'")]]; // Uncomment this to run the actual test. I've commented it out because it fails nondeterministically. //eq(times[4] <= times[0] + times[1] + times[2] + times[3], true); // Another test, the Figment lexer: var figment_lex = l*[literate = peg[c(/[A-Z\|](?:\n?[^\n]+)*/, 1) >> fn_['']], paragraph = peg[c(/[^\n]+/, 1) % c(/(?:\n[^\n]+)*/, 1) >> fn[xs][xs[0][0] + xs[1][0]]], paragraphs = peg[(([c(/\n\n+/, 2)] >> fn_['']) % (literate / paragraph) >> fn[xs][xs.join('')])[0] >> fn[xs][seq[~xs %[_]].join('\n')]], line_comment = peg[c(/[-\/]\s*/, 1) % c(/[A-Z][^\n]*/, 1) % [c('\n')] >> fn_[' ']], code = peg[(line_comment / c(['-', '/']) / (c(/[^-\/]+/, 1) >> fn[xs][xs[0]]))[1] >> fn[xs][xs.join('')]]] in fn[s][code(paragraphs(s))]; var times2 = [time[figment_lex('foo bar bif\n')], time[figment_lex('foo bar bif baz bok\n')], time[figment_lex('foo bar bif baz bok boo quux hork bork bogus\n')]]; })(eq); }); __7205bca9c419ada3007d1ff029796c6b meta::sdoc('js::modules/caterwaul.parser.test/infinite-loop-failure', <<'__3828d90faa5416f15379fb065f994324'); An observed failure involving infinite looping. The original case is in the Figment parser: | l*[literate = peg[c(/[A-Z\|](?:[^\n]+\n?)*/, 1) >> fn_['']], paragraph = peg[c(/([^\n]+\n?)*/, 1) >> fn[xs][xs[0]]], paragraphs = peg[([c(/\n\n+/, 2)] % (literate / paragraph) >> fn[xs][xs.join('')])[0] >> fn[xs][xs.join('')]], line_comment = peg[c(/[-\/]\s*/, 1) % c(/[A-Z][^\n]*/, 1) % c('\n') >> fn_['']], code = peg[(line_comment / c(['-', '/']) / c(/[^-\/]+/, 1))[1] >> fn[xs][xs.join('')]]] in fn[s][code(paragraphs(s))] test('caterwaul.parser.infinite-loop-failure', function () { var c = caterwaul.clone('std parser'); c(function (eq) { l*[literate = peg[c(/[A-Z\|](?:[^\n]+\n?)+/, 2) >> fn_['']], paragraph = peg[c(/(?:[^\n]+\n?)+/, 1) >> fn[xs][xs[0]]], paragraphs = peg[(literate / paragraph % ([c(/\n\n+/, 2)] >> fn_['']) >> fn[xs][xs.join('')])[1] >> fn[xs][xs.join('')]], line_comment = peg[c(/[-\/]\s*/, 1) % c(/[A-Z][^\n]*/, 1) % c('\n') >> fn_['']], code = peg[(line_comment / c(['-', '/']) / c(/[^-\/]+/, 1))[1] >> fn[xs][xs.join('')]]] in eq /se[_(code(paragraphs('foo')), 'foo'), _(code(paragraphs('foo\n')), 'foo\n'), _(code(paragraphs('foo = "bar"\n')), 'foo = "bar"\n'), _(code(paragraphs('foo = "bar"\n\n')), 'foo = "bar"\n')]; // The failure case is triggered by this (the "one or more" combinator failed to recognize end of input): eq(peg[c(/.*/, 1)[1]]('abcdef').join(''), 'abcdef'); eq(peg[c(/.*/, 1)[1]]('abc').join(''), 'abc'); eq(peg[c(/.*/, 1)[1]]('a').join(''), 'a'); })(eq); }); __3828d90faa5416f15379fb065f994324 meta::sdoc('js::modules/caterwaul.parser.test/lookahead', <<'__612b755f1543d5784d525d14a8ac6d20'); Tests for lookahead assertions. test('caterwaul.parser.lookahead', function () { var c = caterwaul.clone('std parser'); c(function (eq) { var p = caterwaul.parser.parsers; var aa = p.seq(p.c('a'), p.match(p.c('a')), p.alt(p.c('a'), p.c('b'))); eq(aa('aa').join(','), 'a,a,a'); eq(aa('ab'), false); eq(aa('a'), false); var ab = p.seq(p.c('a'), p.reject(p.c('a')), p.alt(p.c('a'), p.c('b'))); eq(ab('ab').length, 3); eq(ab('ab')[0], 'a'); eq(ab('ab')[1], null); eq(ab('ab')[2], 'b'); eq(ab('aa'), false); eq(ab('a'), false); })(eq); }); __612b755f1543d5784d525d14a8ac6d20 meta::sdoc('js::modules/caterwaul.seq', <<'__5b173fc727700adbc93f88b45ef2f2d0'); Caterwaul JS sequence library | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. Javascript's looping facilities are side-effectful and more importantly operate in statement-mode rather than expression-mode. This sequence library moves finite and anamorphic looping into expression-mode using both methods and macros. Macros are used sparingly here; they provide comprehensions, but are ultimately just shorthands for sequence methods. All sequences ultimately inherit from Array, which may or may not work as desired. caterwaul.tconfiguration('std', 'seq.core', function () {this.shallow('seq', {core: fn_[null] /se[_.prototype = [] /se.p[p.constructor = _]]})}). There are two kinds of sequences represented here. One is a finite sequence, which is eager and acts like a Javascript array (though it has a different prototype). The other is an infinite stream; this is an anamorphism that generates new elements from previous ones. Because Javascript isn't required to optimize tail calls, any recursion done by the sequence library is coded in CPS using the continuation library. Finite sequence API. Finite sequences are assumed to have numbered elements and a 'length' field, just like a Javascript array or jQuery object. Any mapping, filtering, or folding on these sequences is done eagerly (put differently, most sequence/stream operations are closed under eagerness). There's a core prototype for finite sequences that contains eager implementations of each(), map(), filter(), foldl(), foldr(), zip(), etc. Note that because of an IE7 bug, all lengths are stored twice. Once in 'l' and once in 'length' -- the 'length' property is only updated for array compatibility on compliant platforms, but it will always be 0 on IE7. tconfiguration('std opt continuation', 'seq.finite.core', function () { this.configure('seq.core').seq.finite = fc[xs][this.length = this.l = xs ? opt.unroll[i, xs.size ? xs.size() : xs.length][this[i] = xs[i]] : 0] /se.c[c.prototype = new this.seq.core() /se[ _.size() = this.l || this.length, _.slice() = [] /se[opt.unroll[i, this.size()][_.push(this[i])]], _.constructor = c]]}). tconfiguration('std opt continuation', 'seq.finite.serialization', function () { this.configure('seq.finite.core').seq.finite.prototype /se[_.toString() = 'seq[#{this.slice().join(", ")}]', _.join(x) = this.slice().join(x)]}). Mutability. Sequences can be modified in-place. Depending on how Javascript optimizes this case it may be much faster than reallocating. Note that the methods here are not quite the same as the regular Javascript array methods. In particular, push() returns the sequence rather than its new length. Also, there is no shift()/unshift() API. These would each be linear-time given that we're using hard indexes. concat() behaves as it does for arrays; it allocates a new sequence rather than modifying either of its arguments. tconfiguration('std opt continuation', 'seq.finite.mutability', function () { l[push = Array.prototype.push, slice = Array.prototype.slice] in this.configure('seq.finite.core').seq.finite.prototype /se[_.push() = l[as = arguments] in opt.unroll[i, as.length][this[this.l++] = as[i]] /re[this.length = this.l, this], _.pop() = this[--this.l] /se[delete this[this.length = this.l]], _.concat(xs) = new this.constructor(this) /se[_.push.apply(_, slice.call(xs))]]}). Object interfacing. Sequences can be built from object keys, values, or key-value pairs. This keeps you from having to write for (var k in o) ever again. Also, you can specify whether you want all properties or just those which belong to the object directly (by default the latter is assumed). For example: | var keys = caterwaul.seq.finite.keys({foo: 'bar'}); // hasOwnProperty is used var all_keys = caterwaul.seq.finite.keys({foo: 'bar'}, true); // hasOwnProperty isn't used; you get every enumerable property Javascript, unlike Perl, fails to make the very useful parallel between objects and arrays. Because references are cheap in Javascript (both notationally and computationally), the representation of an object is slightly different from the one in Perl. You use an array of pairs, like this: [[k1, v1], [k2, v2], ..., [kn, vn]]. | object([o = {}]): Zips a sequence of pairs into an object containing those mappings. Later pairs take precedence over earlier ones if there is a collision. You can specify an optional object o to zip into; if you do this, then the pairs are added to o and o is returned instead of creating a new object and adding pairs to that. tconfiguration('std opt continuation', 'seq.finite.object', function () { l[own = Object.prototype.hasOwnProperty] in this.configure('seq.finite.core').seq.finite /se[_.keys (o, all) = new _() /se[(function () {for (var k in o) if (all || own.call(o, k)) _.push(k)})()], _.values(o, all) = new _() /se[(function () {for (var k in o) if (all || own.call(o, k)) _.push(o[k])})()], _.pairs (o, all) = new _() /se[(function () {for (var k in o) if (all || own.call(o, k)) _.push([k, o[k]])})()], _.prototype.object(o) = (o || {}) /se[this.each(fn[p][_[p[0]] = p[1]])]]}). Mapping and traversal. Sequences support the usual set of map/filter/fold operations. Unlike most sequence libraries, though, all of these functions used unrolled loops. This means that if your JS runtime has good hot-inlining support they should be really fast. (The same does not hold for the infinite stream library, which uses simulated continuations for lots of things and is probably quite slow.) If you fold on a sequence with too few elements (and you don't supply extras by giving it more arguments), it will return something falsy. tconfiguration('std opt continuation', 'seq.finite.traversal', function () { this.configure('seq.finite.core seq.finite.mutability').seq.finite.prototype /se[_.map(f) = new this.constructor() /se[opt.unroll[i, this.l][_.push(f.call(this, this[i], i))]], _.filter(f) = new this.constructor() /se[opt.unroll[i, this.l][_.push(this[i]), when[f.call(this, this[i], i)]]], _.each(f) = this /se[opt.unroll[i, _.l][f.call(_, _[i], i)]], _.reversed() = new this.constructor() /se[l[l = this.l] in opt.unroll[i, l][_.push(this[l - i - 1])]], _.flat_map(f) = new this.constructor() /se[this.each(fn[x, xi][(f.call(this, x, xi) /re.xs[xs.each ? xs : new this.constructor(xs)]).each(fn[x][_.push(x)])])], _.foldl(f, x) = l[x = arguments.length > 1 ? x : this[0], xi = 2 - arguments.length] [opt.unroll[i, this.l - xi][x = f.call(this, x, this[i + xi], i + xi)], x, when[this.l >= xi]], _.foldr(f, x) = l[x = arguments.length > 1 ? x : this[this.l - 1], xi = 3 - arguments.length, l = this.l] [opt.unroll[i, l - (xi - 1)][x = f.call(this, this[l - (i + xi)], x, l - (i + xi))], x, when[l >= xi - 1]]]}). Zipping. Zipping as a generalized construct has a few variants. One is the function used to zip (by default, [x, y]), another is the number of sequences to zip together, and the last one is whether you want an inner or outer product. Here's the full argument syntax for zip() with defaults: | xs.zip(xs1, xs2, ..., xsn, {f: fn_[new seq(arguments)], outer: false}) Each of xsi should be an array-ish object (i.e. should support .length and [i] attributes). If you specify the optional hash at the end, its 'f' attribute, if specified, will be invoked on every n-tuple of items, and if 'outer' is truthy then you will have the outer-product of all of your sequences (i.e. the longest sequence length is used, and undefined is specified when you run past the end of any other one). tconfiguration('std opt continuation', 'seq.finite.zip', function () { this.configure('seq.finite.traversal').seq.finite /se[_.prototype.zip() = l[as = new seq([this].concat(slice.call(arguments))), options = {f: fn_[new seq(arguments)], outer: false}] [caterwaul.util.merge(options, as.pop()), when[as[as.size() - 1].constructor === Object], l[l = as.map(fn[x][x.size ? x.size() : x.length]).foldl(options.outer ? fn[x, y][Math.max(x, y)] : fn[x, y][Math.min(x, y)]), f = options.f] in new this.constructor() /se[opt.unroll[i, l][_.push(f.apply({i: i}, as.map(fn[x][x[i]]).slice()))]]], where[seq = _, slice = Array.prototype.slice]]}). Quantification. Functions to determine whether all sequence elements have some property. If an element satisfying the predicate is found, exists() returns the output of the predicate for that element. (So, for example, xs.exists(fn[x][x.length]) would return the length of the nonempty item, which we know to be truthy.) forall() has no such behavior, since the quantifier is decided when the predicate returns a falsy value and there are only a few falsy values in Javascript. tconfiguration('std opt continuation', 'seq.finite.quantification', function () { this.configure('seq.finite.core').seq.finite.prototype /se[_.exists(f) = call/cc[fb[cc][opt.unroll[i, this.l][f.call(this, this[i], i) /re[_ && cc(_)]], false]], _.forall(f) = ! this.exists(fn_[! f.apply(this, arguments)])]}). Stream API. All streams are assumed to be infinite in length; that is, given some element there is always another one. Streams provide this interface with h() and t() methods; the former returns the first element of the stream, and the latter returns a stream containing the rest of the elements. tconfiguration('std opt continuation', 'seq.infinite.core', function () { this.configure('seq.core').seq.infinite = fn_[null] /se[_.prototype = new this.seq.core() /se[_.constructor = ctor], where[ctor = _]] /se[_.def(name, ctor, h, t) = i[name] = ctor /se[_.prototype = new i() /se[_.h = h, _.t = t, _.constructor = ctor]], where[i = _], _.def('cons', fn[h, t][this._h = h, this._t = t], fn_[this._h], fn_[this._t]), _.def('k', fn [x][this._x = x], fn_[this._x], fn_[this])]}). Anamorphisms via fixed-point. Anamorphic streams are basically unwrapped version of the Y combinator. An anamorphic stream takes a function f and an initial element x, and returns x, f(x), f(f(x)), f(f(f(x))), .... tconfiguration('std opt continuation', 'seq.infinite.y', function () { this.configure('seq.infinite.core').seq.infinite.def('y', fc[f, x][this._f = f, this._x = x], fn_[this._x], fn_[new this.constructor(this._f, this._f(this._x))])}). Lazy map and filter. These are implemented as separate classes that wrap instances of infinite streams. They implement the next() method to provide the desired functionality. map() and filter() are simple because they provide streams as output. filter() is eager on its first element; that is, it remains one element ahead of what is requested. tconfiguration('std opt continuation', 'seq.infinite.transform', function () { this.configure('seq.infinite.core').seq.infinite /se[_.prototype.map(f) = new _.map(f, this), _.def('map', fc[f, xs][this._f = f, this._xs = xs], fn_[this._f(this._xs.h())], fn_[new this.constructor(this._f, this._xs.t())]), _.prototype.filter(f) = new _.filter(f, this), _.def('filter', fc[f, xs][this._f = f, this._xs = l*[next(s)(cc) = f(s.h()) ? cc(s) : call/tail[next(s.t())(cc)]] in call/cc[next(xs)]], fn_[this._xs.h()], fn_[new this.constructor(this._f, this._xs.t())])]}). Traversal and forcing. This is where we convert from infinite streams to finite sequences. You can take or drop elements while a condition is true. take() always assumes it will return a finite sequence, whereas drop() assumes it will return an infinite stream. (In other words, the number of taken or dropped elements is assumed to be finite.) Both take() and drop() are eager. drop() returns a sequence starting with the element that fails the predicate, whereas take() returns a sequence for which no element fails the predicate. tconfiguration('std opt continuation', 'seq.infinite.traversal', function () { l[finite = this.configure('seq.finite.core seq.finite.mutability').seq.finite] in this.configure('seq.infinite.core').seq.infinite.prototype /se[_.drop(f) = l*[next(s)(cc) = f(s.h()) ? call/tail[next(s.t())(cc)] : cc(s)] in call/cc[next(this)], _.take(f) = l*[xs = new finite(), next(s)(cc) = l[h = s.h()][f(h) ? (xs.push(h), call/tail[next(s.t())(cc)]) : cc(xs)]] in call/cc[next(this)]]}). Sequence utilities. These methods are useful both for finite and for infinite sequences. Probably the most useful here is n(), which produces a bounded sequence of integers. You use n() like this: | caterwaul.seq.n(10) -> [0, 1, 2, ..., 8, 9] caterwaul.seq.n(1, 10) -> [1, 2, ..., 8, 9] caterwaul.seq.n(1, 10, 0.5) -> [1, 1.5, 2, 2.5, ..., 8, 8.5, 9, 9.5] caterwaul.seq.n(-10) -> [0, -1, -2, ..., -8, -9] caterwaul.seq.n(-1, -10) -> [-1, -2, ..., -8, -9] caterwaul.seq.n(-1, -10, 0.5) -> [-1, -1.5, -2, ..., -8, -8.5, -9, -9.5] Also useful is the infinite stream of natural numbers and its companion function naturals_from: | caterwaul.seq.naturals -> [0, 1, 2, 3, ...] caterwaul.seq.naturals_from(2) -> [2, 3, 4, 5, ...] tconfiguration('std opt continuation', 'seq.numeric', function () { this.configure('seq.infinite.core seq.infinite.y seq.finite.core').seq /se[ _.naturals_from(x) = new _.infinite.y(fn[n][n + 1], x), _.naturals = _.naturals_from(0), _.n(l, u, s) = l[lower = arguments.length > 1 ? l : 0, upper = arguments.length > 1 ? u : l] [l[step = Math.abs(s || 1) * (lower < upper ? 1 : -1)] in new _.infinite.y(fn[n][n + step], lower).take(fn[x][(upper - lower) * (upper - x) > 0])]]}). Sequence manipulation language. Using methods to manipulate sequences can be clunky, so the sequence library provides a macro to enable sequence-specific manipulation. You enter this mode by using seq[], and expressions inside the brackets are interpreted as sequence transformations. For example, here is some code translated into the seq[] macro: | var primes1 = l[two = naturals.drop(fn[x][x < 2])] in two.filter(fn[n][two.take(fn[x][x <= Math.sqrt(n)]).forall(fn[k][n % k])]); var primes2 = l[two = seq[naturals >>[_ < 2]] in seq[two %n[two[_ <= Math.sqrt(n)] &[n % _]]]; These operators are supported and take their normal Javascript precedence and associativity: | x *[_ + 2] // x.map(fn[_, _i][_ + 2]) x *~[_ + xs] // x.map(fn[_, _i][_.concat(xs)]) x *-[_ + 2] // x.map(fb[_, _i][_ + 2]) x *~-[_ + xs] // x.map(fb[_, _i][_.concat(xs)]) x *+(console/mb/log) // x.map(console/mb/log) x *!+f // x.each(f) x *![console.log(_)] // x.each(fn[_, _i][console.log(_)]) x /[_ + _0] // x.foldl(fn[_, _0, _i][_ + _0]) x /![_ + _0] // x.foldr(fn[_, _0, _i][_ + _0]) x %[_ >= 100] // x.filter(fn[_, _i][_ >= 100]) x %![_ >= 100] // x.filter(fn[_, _i][!(x >= 100)]) x *n[n + 2] // x.map(fn[n, ni][n + 2]) x *!n[console.log(n)] // x.each(fn[n, ni][console.log(n)]) x /n[n + n0] // x.foldl(fn[n, n0, ni][n + n0]) x /!n[n + n0] // x.foldr(fn[n, n0, ni][n + n0]) x %n[n % 100 === 0] // x.filter(fn[n, ni][n % 100 === 0]) x %!n[n % 100] // x.filter(fn[n, ni][!(n % 100 === 0)]) x <<[_ >= 10] // x.take(fn[_][_ >= 10]) x <= 10] // x.take(fn[n][n >= 10]) x >>[_ >= 10] // x.drop(fn[_][_ >= 10]) x >>n[n >= 10] // x.drop(fn[n][n >= 10]) x |[_ === 5] // x.exists(fn[_, _i][_ === 5]) x &[_ === 5] // x.forall(fn[_, _i][_ === 5]) x |n[n === 5] // x.exists(fn[n, ni][n === 5]) x &n[n === 5] // x.forall(fn[n, ni][n === 5]) x -~[~[_, _ + 1]] // x.flat_map(fn[_, _i][seq[~[_, _ + 1]]]) x -~i[~[i, i + 1]] // x.flat_map(fn[i, ii][seq[~[i, i + 1]]]) x >>>[_ + 1] // new caterwaul.seq.infinite.y(fn[_][_ + 1], x) x >>>n[n + 1] // new caterwaul.seq.infinite.y(fn[n][n + 1], x) x || y // x && x.size() ? x : y // except that each sequence is evaluated at most once, and if x && x.size() then y is not evaluated at all x && y // x && x.size() ? y : x // same here, except evaluation semantics are for && instead of || x > y // x.size() > y.size() x >= y // x.size() >= y.size() x < y // x.size() < y.size() x <= y // x.size() <= y.size() x == y // x.size() === y.size() x != y // x.size() !== y.size() x === y // x.size() === y.size() && x.zip(y).forall(fn[p][p[0] === p[1]]) x !== y // !(x === y) sk[x] // caterwaul.seq.finite.keys(x) sv[x] // caterwaul.seq.finite.values(x) sp[x] // caterwaul.seq.finite.pairs(x) n[...] // caterwaul.seq.n(...) N // caterwaul.seq.naturals N[x] // caterwaul.seq.naturals_from x ^ y // x.zip(y) x + y // x.concat(y) !x // x.object() ~x // new caterwaul.seq.finite(x) +(x) // x (this means 'literal', so no sequence transformations are applied to x) Method calls are treated normally and arguments are untransformed; so you can call methods normally. Also, each operator above makes sure to evaluate each operand at most once (basically mirroring the semantics of the Javascript operators in terms of evaluation). Modifiers. There are patterns in the above examples. For instance, x %[_ + 1] is the root form of the filter operator, but you can also write x %n[n + 1] to use a different variable name. The ~ modifier is available as well; this evaluates the expression inside brackets in sequence context rather than normal Javascript. (e.g. xs %~[_ |[_ === 1]] finds all subsequences that contain 1.) Finally, some operators have a ! variant (fully listed in the table above). In this case, the ! always precedes the ~. New in Caterwaul 0.6.7 is the unary - modifier, which preserves a function's binding. For example, seq[~xs *[this]] returns a sequence containing references to itself, whereas seq[~xs *-[this]] returns a sequence containing references to whatever 'this' was in the context containing the sequence comprehension. The difference is whether an fn[] or fb[] is used. Another modifier is +; this lets you use point-free form rather than creating a callback function. For example, xs %+divisible_by(3) expands into xs.filter(divisible_by(3)). This modifier goes where ~ would have gone. Bound variables. In addition to providing operator-to-method conversion, the seq[] macro also provides some functions. Prior to Caterwaul 0.6.7 you had to manually construct anamorphic sequences of integers, so a lot of code looked like seq[(0 >>>[_ + 1]) *![...]]. Caterwaul 0.6.7 introduces the following shorthands: | seq[N] // caterwaul.seq.naturals seq[N[10]] // caterwaul.seq.naturals_from(10) seq[n[10]] // caterwaul.seq.n(10), which becomes [0, 1, ..., 9] seq[n[5, 10]] // caterwaul.seq.n(5, 10), which becomes [5, 6, 7, 8, 9] seq[n[5, 8, 0.5]] // caterwaul.seq.n(5, 8, 0.5) These should eliminate most if not all occurrences of manual anamorphic generation. Inside the DSL code. This code probably looks really intimidating, but it isn't actually that complicated. The first thing I'm doing is setting up a few methods to help with tree manipulation. The prefix_substitute() method takes a prefix and a tree and looks for data items in the tree that start with underscores. It then changes their names to reflect the variable prefixes. For example, prefix_substitute(qs[fn[_, _x, _i][...]], 'foo') would return fn[foo, foox, fooi]. The next interesting thing is define_functional. This goes beyond macro() by assuming that you want to define an operator with a function body to its right; e.g. x >>[body]. It defines two forms each time you call it; the first form is the no-variable case (e.g. x *[_ + 1]), and the second is the with-variable case (e.g. x *n[n + 1]). The trees_for function takes care of the bang-variant of each operator. This gets triggered if you call define_functional on an operator that ends in '!'. After this is the expansion logic (which is just the regular Caterwaul macro logic). Any patterns that match are descended into; otherwise expansion returns its tree verbatim. Also, the expander-generator function rxy() causes expansion to happen on each side. This is what keeps expansion going. When I specify a custom function it's because either (1) rxy doesn't take enough parameters, or (2) I want to specify that only some subtrees should be expanded. (That's what happens when there's a dot or invocation, for instance.) Pattern matching always starts at the end of the arrays as of Caterwaul 0.5. This way any new patterns that you define will bind with higher precedence than the standard ones. tconfiguration('std opt continuation', 'seq.dsl', function () { this.configure('seq.core seq.infinite.y seq.finite.core seq.finite.zip seq.finite.traversal seq.finite.mutability').seq.dsl = caterwaul.global().clone() /se[_.prefix_substitute(tree, prefix) = tree.rmap(fn[n][new n.constructor('#{prefix}#{n.data.substring(1)}'), when[n.data.charAt(0) === '_']]), _.define_functional(op, expansion, xs) = trees_for(op).map(fn[t, i][_.macro(t, fn[l, v, r][expansion.replace({_x: _.macroexpand(l), _y: i >= 8 ? v : qs[fn[xs][y]].replace({fn: i & 2 ? qs[fb] : qs[fn], xs: _.prefix_substitute(xs, i & 1 ? v.data : '_'), y: (i & 4 ? _.macroexpand : fn[x][x])(r || v)})})])]), _.define_functional /se[_('%', qs[_x.filter(_y)], qs[_, _i]), _('*', qs[_x.map(_y)], qs[_, _i]), _('/', qs[_x.foldl(_y)], qs[_, _0, _i]), _('%!', qs[_x.filter(c(_y))].replace({c: not}), qs[_, _i]), _('*!', qs[_x.each(_y)], qs[_, _i]), _('/!', qs[_x.foldr(_y)], qs[_, _0, _i]), _('&', qs[_x.forall(_y)], qs[_, _i]), _('|', qs[_x.exists(_y)], qs[_, _i]), _('-', qs[_x.flat_map(_y)], qs[_, _i]), _('>>', qs[_x.drop(_y)], qs[_]), _('<<', qs[_x.take(_y)], qs[_]), _('>>>', qs[new caterwaul.seq.infinite.y(_y, _x)], qs[_])], seq(qw('> < >= <= == !=')).each(fn[op][_.macro(qs[_ + _].clone() /se[_.data = op], rxy(qs[qg[_x].size() + qg[_y].size()].clone() /se[_.data = op]))]), l[e(x) = _.macroexpand(x)] in _.macro /se[_(qs[_ && _], rxy(qse[qg[l[xp = _x][xp && xp.size() ? _y : xp]]])), _(qs[_ || _], rxy(qse[qg[l[xp = _x][xp && xp.size() ? xp : _y]]])), _(qs[_ === _], rxy(qs[qg[l[xp = _x, yp = _y][xp === yp || xp.size() === yp.size() && xp.zip(yp).forall(fn[p][p[0] === p[1]])]]])), _(qs[_ !== _], rxy(qs[qg[l[xp = _x, yp = _y][xp !== yp && (xp.size() !== yp.size() || xp.zip(yp).exists(fn[p][p[0] !== p[1]]))]]])), _(qs[_ ^ _], rxy(qs[_x.zip(_y)])), _(qs[_ + _], rxy(qs[_x.concat(_y)])), _(qs[!_], rxy(qs[_x.object()])), _(qs[_, _], rxy(qs[_x, _y])), _(qs[~_], rxy(qs[qg[new caterwaul.seq.finite(_x)]])), _(qs[_?_:_], fn[x, y, z][qs[x ? y : z].replace({x: e(x), y: e(y), z: e(z)})]), l[rx(t)(x, y) = t.replace({_x: e(x), _y: y})][_(qs[_(_)], rx(qs[_x(_y)])), _(qs[_[_]], rx(qs[_x[_y]])), _(qs[_._], rx(qs[_x._y])), _(qs[_].as('('), rx(qs[qg[_x]]))], _(qs[+_], fn[x][x]), l[rx(t)(x) = t.replace({x: x})][_(qs[N], fn_[qs[caterwaul.seq.naturals]]), _(qs[N[_]], rx(qs[caterwaul.seq.naturals_from(x)])), _(qs[n[_]], rx(qs[caterwaul.seq.n(x)]))], seq(qw('sk sv sp')).zip(qw('keys values pairs')).each(fb[p][_(qs[p[_]].replace({p: p[0]}), fn[x][qs[caterwaul.seq.finite.r(x)].replace({r: p[1], x: x})])])], this.rmacro(qs[seq[_]], _.macroexpand), where*[rxy(tree)(x, y) = tree.replace({_x: _.macroexpand(x), _y: y && _.macroexpand(y)}), seq = fb[xs][new this.seq.finite(xs)], prepend(operator)(x) = qs[-x].replace({x: x}) /se[_.data = operator], tree_forms = l*[base = seq([qs[[_]], qs[_[_]]]), mod(fs, op) = fs.concat(fs.map(prepend(op)))] in mod(mod(base, 'u-'), 'u~').concat(seq([qs[+_]])), template(op)(t) = qs[_ + x].replace({x: t}) /se[_.data = op], qw = caterwaul.util.qw, not = qs[qg[fn[f][fn_[!f.apply(this, arguments)]]]], trees_for(op) = tree_forms /re[op.charAt(op.length - 1) === '!' ? _.map(prepend('u!')) : _] /re[_.map(template(op.replace(/!$/, '')))]]]}). Final configuration. Rather than including individual configurations above, you'll probably just want to include this one. configuration('seq', function () {this.configure('seq.core seq.finite.core seq.finite.object seq.finite.mutability seq.finite.traversal seq.finite.zip seq.finite.quantification ' + 'seq.finite.serialization seq.infinite.core seq.infinite.y seq.infinite.transform seq.infinite.traversal ' + 'seq.numeric seq.dsl')}); __5b173fc727700adbc93f88b45ef2f2d0 meta::sdoc('js::modules/caterwaul.seq.test/dsl', <<'__3502609e09551b17c6963c7c4872f50c'); Sequence DSL tests. test('caterwaul.seq.dsl', function () { var c = caterwaul.clone('std continuation seq'); c(function (eq) { var f = fn[x][x + 1]; var xs = seq[~[1, 2, 3, 4, 5]]; var ys = seq[~xs *+f]; eq(ys.size(), xs.size()); eq(ys[0], 2); eq(ys[1], 3); eq(ys[2], 4); eq(ys[3], 5); eq(ys[4], 6); eq(seq[xs /+fn[x, y][x + y]], 15); eq(seq[xs %+fn[x][x % 2]].size(), 3); })(eq); c(function (eq) { var from_two = seq[2 >>>[_ + 1]]; var primes = seq[from_two %~n[from_two <<[_ <= Math.sqrt(n)] &[n % _]]]; var primef = fn_[primes]; var under_100 = seq[(primes <<[_ < 100]).join(',')]; var under_100_2 = seq[(~(primes <<[_ < 100])).join(',')]; var under_100_3 = seq[(~(primef() <<[_ < 100])).join(',')]; var primes2 = seq[2 >>>[_ + 1]] /re[seq[_ %~n[_ <<[_ <= Math.sqrt(n)] &[n % _]]]]; var count = 0; var one_to_ten = seq[1 >>>[_ + 1] <<[_ <= 10]]; l/cps[x <- seq[~one_to_ten *n[n * 3]].each(_)][count += x]; eq(count, 165); eq(seq[(primes2 <<[_ < 100]).join(',')], under_100); eq(under_100, '2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97'); eq(under_100_2, under_100); eq(under_100_3, under_100); // There are fewer primes below 100 than below 1000: eq(seq[primes <<[_ < 100] < primes <<[_ < 1000]], true); var keys = seq[sk[{foo: 'bar'}]]; eq(keys[0], 'foo'); eq(keys.size(), 1); var object = seq[!(sp[{foo: 'bar'}])]; eq(object.constructor, Object); eq(object.foo, 'bar'); eq(caterwaul.seq.finite.keys(object).size(), 1); })(eq); c(function (eq) { // In response to a failure case: var bytes = fn[x][seq[x >>>[_ >>> 8] <<[_]]]; var one = bytes(10); eq(one.size(), 1); eq(one[0], 10); })(eq); c(function (eq) { var zero = seq[(~[1, 2, 3, 4, 5] -~[~[_, -_]]) /[_ + _0]]; eq(zero, 0); var ten = seq[(~[1, 2, 3, 4] -[[_]]) /[_ + _0]]; eq(ten, 10); })(eq); c(function (eq) { var s1 = seq[~[1, 2, 3]]; var s2 = seq[s1 -[this]]; eq(s2.size(), 9); eq(s2.join(' '), '1 2 3 1 2 3 1 2 3'); var s3 = seq[s1 *-[this]]; eq(s3[0], this); eq(s3[1], this); eq(s3[2], this); })(eq); }); __3502609e09551b17c6963c7c4872f50c meta::sdoc('js::modules/caterwaul.seq.test/finite.core', <<'__4c1df949aae430af5eb950a2e943a4a2'); Finite sequence core method tests. test('caterwaul.seq.finite.core', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var s = new caterwaul.seq.finite(); eq(s.size(), 0); eq('0' in s, false); eq(s.push(1), s); eq('0' in s, true); eq(s.size(), 1); eq(s.pop(), 1); eq('0' in s, false); eq(s.size(), 0); })(eq); }); __4c1df949aae430af5eb950a2e943a4a2 meta::sdoc('js::modules/caterwaul.seq.test/finite.filter', <<'__489c5503235513ad2009f842729c3660'); Finite sequence filter tests. test('caterwaul.seq.finite.filter', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var xs = new caterwaul.seq.finite([1, 2, 3, 4, 5]); var evens = xs.filter(fn[x][! (x & 1)]); eq(evens.size(), 2); eq(evens[0], 2); eq(evens[1], 4); var odds = xs.filter(fn[x][x & 1]); eq(odds.size(), 3); eq(odds[0], 1); eq(odds[1], 3); eq(odds[2], 5); var first_three = xs.filter(fn[x, i][i < 3]); eq(first_three.size(), 3); eq(first_three[0], 1); eq(first_three[1], 2); eq(first_three[2], 3); })(eq); }); __489c5503235513ad2009f842729c3660 meta::sdoc('js::modules/caterwaul.seq.test/finite.fold', <<'__debbafd6cf5a0d1a61eeee5ed086c871'); Finite sequence folding tests test('caterwaul.seq.finite.fold', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var xs = new caterwaul.seq.finite([1, 2, 3, 4, 5]); eq(xs.foldl(fn[x, y][x + y]), 15); eq(xs.foldl(fn[x, y][x + y], ''), '12345'); eq(xs.foldl(fn[x, y]['(#{x + y})'], ''), '(((((1)2)3)4)5)'); var ys = new caterwaul.seq.finite([1, 2, 3, 4]); eq(ys.foldr(fn[x, y][x + y]), 10); eq(ys.foldr(fn[x, y][x + y], ''), '1234'); eq(ys.foldr(fn[x, y]['(#{x + y})'], ''), '(1(2(3(4))))'); var zs = new caterwaul.seq.finite([3, 4]); eq(zs.foldl(fn[x, y][Math.min(x, y)]), 3); eq(zs.foldr(fn[x, y][Math.min(x, y)]), 3); eq(zs.foldl(fn[x, y][Math.max(x, y)]), 4); eq(zs.foldr(fn[x, y][Math.max(x, y)]), 4); })(eq); }); __debbafd6cf5a0d1a61eeee5ed086c871 meta::sdoc('js::modules/caterwaul.seq.test/finite.map', <<'__8cfe868cf749d594d610870c90e94eb6'); Finite sequence mapping tests. test('caterwaul.seq.finite.map', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var xs = new caterwaul.seq.finite([1, 2, 3, 4, 5]); var ys = xs.map(fn[x][x + 1]); eq(xs.size(), 5); eq(xs[0], 1); eq(xs[1], 2); eq(xs[4], 5); eq(ys.size(), 5); eq(ys[0], 2); eq(ys[1], 3); eq(ys[4], 6); var zs = xs.map(fn[x, i][i]); eq(zs.size(), 5); eq(zs[0], 0); eq(zs[1], 1); eq(zs[4], 4); })(eq); }); __8cfe868cf749d594d610870c90e94eb6 meta::sdoc('js::modules/caterwaul.seq.test/finite.object', <<'__a26f106228e9d81ae11f1b9716b414ea'); Finite sequence object-related tests. test('caterwaul.seq.finite.object', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var base = {foo: 'bar', bif: 'baz'}; var ks = caterwaul.seq.finite.keys(base); var vs = caterwaul.seq.finite.values(base); var ps = caterwaul.seq.finite.pairs(base); eq(ks.size(), 2); eq(ks[0].length, 3); eq(ks[1].length, 3); eq(base[ks[0]].length, 3); eq(base[ks[1]].length, 3); eq(vs.size(), 2); eq(base[ks[0]], vs[0]); eq(base[ks[1]], vs[1]); eq(ps.size(), 2); eq(base[ps[0][0]], ps[0][1]); eq(base[ps[1][0]], ps[1][1]); var o = ps.object(); eq(o.foo, 'bar'); eq(o.bif, 'baz'); var o2 = {other: 'property'}; eq(ps.object(o2), o2); eq(o2.foo, 'bar'); eq(o2.bif, 'baz'); eq(o2.other, 'property'); })(eq); }); __a26f106228e9d81ae11f1b9716b414ea meta::sdoc('js::modules/caterwaul.seq.test/finite.quantifiers', <<'__5c9c8f33c5de6cba1f15ca28b149f8b9'); Finite sequence quantifier tests. test('caterwaul.seq.finite.quantifier', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var naturals = new caterwaul.seq.infinite.y(fn[x][x + 1], 0); var two = naturals.drop(fn[x][x < 2]); var primes = two.filter(fn[n][! two.take(fn[x][x <= Math.sqrt(n)]).exists(fn[k][n % k === 0])]); var below_100 = primes.take(fn[x][x < 100]); eq(below_100.toString(), 'seq[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]'); eq(below_100.forall(fn[x][x < 100]), true); eq(below_100.exists(fn[x][x > 10]), true); eq(below_100.exists(fn[x][x > 10 && x]), 11); eq(below_100.exists(fn[x][x > 20 && x]), 23); })(eq); }); __5c9c8f33c5de6cba1f15ca28b149f8b9 meta::sdoc('js::modules/caterwaul.seq.test/finite.zip', <<'__684cb7d12778460d4c8ebd5d6cf412cf'); Finite sequence zip tests. test('caterwaul.seq.finite.zip', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var xs = new caterwaul.seq.finite([1, 2, 3, 4, 5]); var ys = xs.map(fn[x][x * 2]); var zs = xs.map(fn[x][x * 3]); var triples = xs.zip(ys, zs); eq(triples.size(), 5); eq(triples[0].size(), 3); eq(triples[0][0], 1); eq(triples[0][1], 2); eq(triples[0][2], 3); eq(triples[0].toString(), 'seq[1, 2, 3]'); eq(triples[1].toString(), 'seq[2, 4, 6]'); eq(triples[4].toString(), 'seq[5, 10, 15]'); var sums = xs.zip(ys, zs, {f: fn[x, y, z][x + y + z]}); eq(sums.size(), 5); eq(sums[0], 6); eq(sums[1], 12); eq(sums[4], 30); var inner = xs.zip([1, 2, 3]); eq(inner.size(), 3); eq(inner[0].size(), 2); eq(inner[0].toString(), 'seq[1, 1]'); eq(inner[1].toString(), 'seq[2, 2]'); eq(inner[2].toString(), 'seq[3, 3]'); var outer = xs.zip([1, 2, 3], {outer: true}); eq(outer.size(), 5); eq(outer[0].toString(), 'seq[1, 1]'); eq(outer[1].toString(), 'seq[2, 2]'); eq(outer[2].toString(), 'seq[3, 3]'); eq(outer[3][0], 4); eq(outer[3][1], undefined); eq(outer[4][0], 5); eq(outer[4][1], undefined); })(eq); }); __684cb7d12778460d4c8ebd5d6cf412cf meta::sdoc('js::modules/caterwaul.seq.test/infinite.transform', <<'__7f20cdcf254854eba26004c2c7ee1750'); Map and filter tests on infinite streams. test('caterwaul.seq.infinite.transform', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var naturals = new caterwaul.seq.infinite.y(fn[x][x + 1], 0); var evens1 = naturals.map(fn[x][x * 2]); var evens2 = naturals.filter(fn[x][! (x & 1)]); eq(naturals.h(), 0); eq(evens1.h(), 0); eq(evens2.h(), 0); eq(naturals.t().h(), 1); eq(evens1.t().h(), 2); eq(evens2.t().h(), 2); eq(naturals.t().t().h(), 2); eq(evens1.t().t().h(), 4); eq(evens2.t().t().h(), 4); eq(naturals.t().t().t().h(), 3); eq(evens1.t().t().t().h(), 6); eq(evens2.t().t().t().h(), 6); var big = evens1.filter(fn[x][x > 20000]); eq(big.h(), 20002); eq(big.t().h(), 20004); })(eq); }); __7f20cdcf254854eba26004c2c7ee1750 meta::sdoc('js::modules/caterwaul.seq.test/infinite.traversal', <<'__3d8e3e2452d48433b3b7585b3235faf0'); Take/drop tests. test('caterwaul.seq.infinite.traversal', function () { var c = caterwaul.clone('std seq'); c(function (eq) { var naturals = new caterwaul.seq.infinite.y(fn[x][x + 1], 0); var big = naturals.drop(fn[x][x < 1000]); var small = naturals.take(fn[x][x < 1000]); eq(big.h(), 1000); eq(big.t().h(), 1001); eq(big.t().t().h(), 1002); eq(small.size(), 1000); eq(small[0], 0); eq(small[1], 1); eq(small[999], 999); })(eq); }); __3d8e3e2452d48433b3b7585b3235faf0 meta::sdoc('js::modules/caterwaul.seq.test/infinite.y', <<'__03fea126b6398edcb55191f34120c1f7'); Head/tail method tests. test('caterwaul.seq.infinite.y', function () { var c = caterwaul.clone('std continuation seq'); c(function (eq) { var naturals = new caterwaul.seq.infinite.y(fn[x][x + 1], 0); eq(naturals.h.constructor, Function); eq(naturals.t.constructor, Function); eq(naturals.constructor, caterwaul.seq.infinite.y); eq(naturals.h(), 0); eq(naturals.t().constructor, naturals.constructor); eq(naturals.t().h(), 1); eq(naturals.t().t().h(), 2); })(eq); }); __03fea126b6398edcb55191f34120c1f7 meta::sdoc('js::modules/caterwaul.seq.test/numeric', <<'__b0a2d54c0f5713cd919ec25076478def'); Numeric sequence tests. test('caterwaul.seq.numeric', function () { var c = caterwaul.clone('std seq continuation'); c(function (eq) { eq(caterwaul.seq.naturals.take(fn[x][x < 5]).join(','), '0,1,2,3,4'); eq(caterwaul.seq.naturals.h(), 0); eq(caterwaul.seq.naturals.t().h(), 1); eq(caterwaul.seq.naturals.t().t().h(), 2); eq(seq[N].h(), 0); eq(seq[N].t().h(), 1); eq(seq[N].t().t().h(), 2); eq(seq[N[10]].h(), 10); eq(seq[N[10]].t().h(), 11); eq(caterwaul.seq.n(5).join(','), '0,1,2,3,4'); eq(caterwaul.seq.n(1, 5).join(','), '1,2,3,4'); eq(caterwaul.seq.n(1, 5, 0.5).join(','), '1,1.5,2,2.5,3,3.5,4,4.5'); })(eq); }); __b0a2d54c0f5713cd919ec25076478def meta::sdoc('js::modules/caterwaul.std', <<'__5c27d8bd5506a3500b74d902abaae9bb'); Caterwaul standard library | Spencer Tipping Licensed under the terms of the MIT source code license caterwaul. Qs library. You really need to use this if you're going to write macros. It enables the qs[] construct in your code. This comes by default when you configure with 'std'. A variant, qse[], macroexpands the quoted code first and returns the macroexpansion. This improves performance while still enabling terse macro forms -- for example, if you write this: | this.rmacro(qs[foo[_]], function (tree) {return qse[fn_[x + 1]].replace({x: tree})}) The fn_[] will be expanded exactly once when the qse[] is processed, rather than each time as part of the macroexpansion. I don't imagine it improves performance that noticeably, but it's been bugging me for a while so I decided to add it. Finally, there's also a literal[] macro to preserve code forms. Code inside literal[] will not be macroexpanded in any way. configuration('std.qs', (function (qs_template, qse_template, literal_template) {return function () { this.macro(qs_template, function (tree) {return new this.ref(tree)}). macro(qse_template, function (tree) {return new this.ref(this.macroexpand(tree))}). macro(literal_template, function (tree) {return tree})}}) (caterwaul.parse('qs[_]'), caterwaul.parse('qse[_]'), caterwaul.parse('literal[_]'))). Qg library. The qg[] construct seems useless; all it does is parenthesize things. The reason it's there is to overcome constant-folding and rewriting Javascript runtimes such as SpiderMonkey. Firefox failed the unit tests when ordinary parentheses were used because it requires disambiguation for expression-mode functions only at the statement level; thus syntax trees are not fully mobile like they are ordinarily. Already-parenthesized expressions aren't wrapped. tconfiguration('std.qs', 'std.qg', function () {this.rmacro(qs[qg[_]], function (expression) {return expression.as('(')})}). Function abbreviations (the 'fn' library). There are several shorthands that are useful for functions. fn[x, y, z][e] is the same as function (x, y, z) {return e}, fn_[e] constructs a nullary function returning e. fb[][] and fb_[] are identical to fn[][] and fn_[], but they preserve 'this' across the function call. The fc[][] and fc_[] variants build constructor functions. These are just like regular functions, but they always return undefined. tconfiguration('std.qs std.qg', 'std.fn', function () { this.configure('std.qg'). rmacro(qs[fn[_][_]], function (vars, expression) {return qs[qg[function (vars) {return expression}]].replace({vars: vars, expression: expression})}). rmacro(qs[fn_[_]], function (expression) {return qs[qg[function () {return expression}]].replace({expression: expression})}). rmacro(qs[fb[_][_]], function (vars, expression) {return qse[fn[_t][fn_[fn[vars][e].apply(_t, arguments)]](this)].replace({_t: this.gensym(), vars: vars, e: expression})}). rmacro(qs[fb_[_]], function (expression) {return qse[fn[_t][fn_[fn_ [e].apply(_t, arguments)]](this)].replace({_t: this.gensym(), e: expression})}). rmacro(qs[fc[_][_]], function (vars, body) {return qse[qg[fn[vars][body, undefined]]].replace({vars: vars, body: body})}). rmacro(qs[fc_[_]], function (body) {return qse[qg[fn[vars][body, undefined]]].replace({ body: body})})}). Object abbreviations (the 'obj' library). Another useful set of macros is the /mb/ and the /mb[] notation. These return methods bound to the object from which they were retrieved. This is useful when you don't want to explicitly eta-expand when calling a method in point-free form: | xs.map(object/mb/method); // === xs.map(fn[x][object.method(x)]) xs.map(object/mb[method]); // === xs.map(fn[x][object[method](x)]) Note that undefined methods are returned as such rather than having a fail-later proxy. (i.e. if foo.bar === undefined, then foo/mb/bar === undefined too) Neither the object nor the property (for the indirected version) are evaluated more than once. Also useful is side-effecting, which you can do this way: | {} /se[_.foo = 'bar'] // === l[_ = {}][_.foo = 'bar', _] Conveniently, side-effects can be chained since / is left-associative. An alternative form of side-effecting is the 'right-handed' side-effect (which is still left-associative, despite the name), written x /re[y]. This returns the result of evaluating y, where _ is bound to x. Variants of /se and /re allow you to specify a variable name: | {} /se.o[o.foo = 'bar'] tconfiguration('std.qs std.qg std.fn', 'std.obj', function () { this.configure('std.qg std.fn'). rmacro(qs[_/mb/_], fn[object, method][qse[qg[fn[_o] [_o.m && fn_[_o.m.apply (_o, arguments)]]](o)] .replace({_o: this.gensym(), o: object, m: method})]). rmacro(qs[_/mb[_]], fn[object, method][qse[qg[fn[_o, _m][_o[_m] && fn_[_o[_m].apply(_o, arguments)]]](o, m)].replace({_o: this.gensym(), _m: this.gensym(), o: object, m: method})]). rmacro(qs[_/se._[_]], fn[v, n, b][qse[qg[fn[n][b, n]].call(this, v)].replace({b: b, n: n, v: v})]).rmacro(qs[_/se[_]], fn[v, b][qse[v /se._[b]].replace({b: b, v: v})]). rmacro(qs[_/re._[_]], fn[v, n, b][qse[qg[fn[n] [b]].call(this, v)].replace({b: b, n: n, v: v})]).rmacro(qs[_/re[_]], fn[v, b][qse[v /re._[b]].replace({b: b, v: v})])}). Binding abbreviations (the 'bind' library). Includes forms for defining local variables. One is 'l[bindings] in expression', and the other is 'expression, where[bindings]'. For the second, keep in mind that comma is left-associative. This means that you'll get the whole comma-expression placed inside a function, rendering it useless for expressions inside procedure calls. (You'll need parens for that.) Each of these expands into a function call; e.g. | l[x = 6] in x + y -> (function (x) {return x + y}).call(this, 6) You also get l* and where*, which define their variables in the enclosed scope: | l*[x = 6, y = x] in x + y // compiles into: (function () { var x = 6, y = x; return x + y; }).call(this); This form has a couple of advantages over the original. First, you can use the values of previous variables; and second, you can define recursive functions: | l*[f = fn[x][x > 0 ? f(x - 1) + 1 : x]] in f(5) You can also use the less English-like but more expressive l[...][...] syntax: | l[x = 5][x + 1] l*[f = fn[x][x > 0 ? f(x - 1) + 1 : x]][f(5)] This has the advantage that you no longer need to parenthesize any short-circuit, decisional, or relational logic in the expression. The legacy let and let* forms are also supported, but they will cause syntax errors in some Javascript interpreters (hence the change). tconfiguration('std.qs std.qg std.fn', 'std.bind', function () {this.configure('std.qg'); var lf = fb[form][this.rmacro(form, l_expander)], lsf = fb[form][this.rmacro(form, l_star_expander)], l_star_expander = fb[vars, expression][qs[qg[function () {var vars; return expression}].call(this)].replace({vars: this.macroexpand(vars), expression: expression})], l_expander = fb[vars, expression][vars = this.macroexpand(vars).flatten(','), qs[qg[function (vars) {return e}].call(this, values)].replace({vars: vars.map(fn[n][n[0]]).unflatten(), e: expression, values: vars.map(fn[n][n[1]]).unflatten()})]; lf (qs[l [_] in _]), lf (qs[l [_][_]]), lf (let_in), lf (let_brackets). rmacro(qs[_, where [_]], fn[expression, vars][l_expander(vars, expression)]); lsf(qs[l*[_] in _]), lsf(qs[l*[_][_]]), lsf(lets_in), lsf(lets_brackets).rmacro(qs[_, where*[_]], fn[expression, vars][l_star_expander(vars, expression)])}, {let_in: caterwaul.parse('let [_] in _'), let_brackets: caterwaul.parse('let [_][_]'), lets_in: caterwaul.parse('let*[_] in _'), lets_brackets: caterwaul.parse('let*[_][_]')}). Assignment abbreviations (the 'lvalue' library). Lets you create functions using syntax similar to the one supported in Haskell and OCaml -- for example, f(x) = x + 1. You can extend this too, though Javascript's grammar is not very easy to work with on this point. (It's only due to an interesting IE feature (bug) that assigning to a function call is possible in the first place.) tconfiguration('std.qs std.qg std.fn', 'std.lvalue', function () {this.rmacro(qs[_(_) = _], fn[base, params, value][qs[base = qg[function (params) {return value}]]. replace({base: base, params: params, value: value})])}). Conditional abbreviations (the 'cond' library). Includes forms for making decisions in perhaps a more readable way than using short-circuit logic. In particular, it lets you do things postfix; i.e. 'do X if Y' instead of 'if Y do X'. tconfiguration('std.qs std.qg std.fn', 'std.cond', function () {this.configure('std.qg').rmacro(qs[_, when[_]], fn[expr, cond][qs[ qg[l] && qg[r]].replace({l: cond, r: expr})]). rmacro(qs[_, unless[_]], fn[expr, cond][qs[! qg[l] && qg[r]].replace({l: cond, r: expr})])}). Self-reference (the 'ref' library). Sometimes you want to get a reference to 'this Caterwaul function' at runtime. If you're using the anonymous invocation syntax (which I imagine is the most common one), this is actually not possible without a macro. This macro provides a way to obtain the current Caterwaul function by writing 'caterwaul'. The expression is replaced by a closure variable that will refer to whichever Caterwaul function was used to transform the code. tconfiguration('std.qs std.qg std.fn', 'std.ref', function () {this.macro(qs[caterwaul], fn_[new this.ref(this)])}). String interpolation. Rebase provides interpolation of #{} groups inside strings. Caterwaul can do the same using a similar rewrite technique that enables macroexpansion inside #{} groups. It generates a syntax tree of the form (+ 'string' (expression) 'string' (expression) ... 'string') -- that is, a flattened variadic +. Strings that do not contain #{} groups are returned as-is. There is some weird stuff going on with splitting and bounds here. Most of it is IE6-related workarounds; IE6 has a buggy implementation of split() that fails to return elements inside match groups. It also fails to return leading and trailing zero-length strings (so, for example, splitting ':foo:bar:bif:' on /:/ would give ['foo', 'bar', 'bif'] in IE, vs. ['', 'foo', 'bar', 'bif', ''] in sensible browsers). So there is a certain amount of hackery that happens to make sure that where there are too few strings empty ones get inserted, etc. Another thing that has to happen is that we need to take care of any backslash-quote sequences in the expanded source. The reason is that while generally it's safe to assume that the user didn't put any in, Firefox rewrites strings to be double-quoted, escaping any double-quotes in the process. So in this case we need to find \" and replace them with ". In case the 'result.push' at the end looks weird, it's OK because result is a syntax node and syntax nodes return themselves when you call push(). If 'result' were an array the code would be seriously borked. tconfiguration('std.qs std.fn std.bind', 'std.string', function () { this.rmacro(qs[_], fn[string] [string.is_string() && /#\{[^\}]+\}/.test(string.data) && l*[q = string.data.charAt(0), s = string.as_escaped_string(), eq = new RegExp('\\\\' + q, 'g'), strings = s.split(/#\{[^\}]+\}/), xs = [], result = new this.syntax('+')] [s.replace(/#\{([^\}]+)\}/g, fn[_, s][xs.push(s), '']), this.util.map(fb[x, i][result.push(new this.syntax(q + (i < strings.length ? strings[i] : '') + q)).push(new this.syntax('(', this.parse(xs[i].replace(eq, q))))], xs), new this.syntax('(', result.push(new this.syntax(q + (xs.length < strings.length ? strings[strings.length - 1] : '') + q)).unflatten())]])}). Standard configuration. This loads all of the production-use extensions. configuration('std', function () {this.configure('std.qs std.qg std.bind std.lvalue std.cond std.fn std.obj std.ref std.string')}); __5c27d8bd5506a3500b74d902abaae9bb meta::sdoc('js::modules/caterwaul.std.test/falsy-refs', <<'__ee6542bd537959e722d2ad5d08cdf6f4'); Falsy ref tests. It's possible that Caterwaul 0.5 and earlier reject falsy references; e.g. | defmacro[foo][fn_[new this.ref(false)]]; foo // may result in undefined variable test('caterwaul.std.falsy-refs', function () { var c = caterwaul.clone('std macro'); c(function (eq) { defmacro[falsy][fn_[new this.ref(false)]]; eq(falsy, false); })(eq); }); __ee6542bd537959e722d2ad5d08cdf6f4 meta::sdoc('js::modules/caterwaul.std.test/fn', <<'__7bec921f8fd44a6fbdddf512f28a6226'); Caterwaul fn, bind, and cond standard library tests test('caterwaul.std.fn', function () { var fn = caterwaul.clone('std.fn std.bind std.cond'); fn(function (eq) { eq(fn[x][x + 1](6), 7); eq(fn[x, y][x + y](6, 7), 13); eq(fn_[10](), 10); eq(l[y = 5] in y + 1, 6); eq(l[y = 5, a = 6] in y + a, 11); eq(l*[x = 4, y = x] in x + y, 8); eq(l*[fact = fn[n][n > 1 ? n * fact(n - 1) : 1]] in fact(5), 120); eq(z + 1, where[z = 5], 6); eq(q + w, where[q = 10, w = 100], 110); eq(x + y, where*[x = 4, y = x], 8); eq(fact(5), where*[fact = fn[n][n > 1 ? n * fact(n - 1) : 1]], 120); eq(5, when[true], 5); eq(5, when[false], false); eq(5, unless[true], false); eq(5, unless[false], 5); }) (eq); }); __7bec921f8fd44a6fbdddf512f28a6226 meta::sdoc('js::modules/caterwaul.std.test/lvalue', <<'__5c3dc5fb4b8f2015070f760692845285'); Caterwaul lvalue macro tests test('caterwaul.std.lvalue', function () { var fn = caterwaul.clone('std.fn', 'std.bind', 'std.lvalue', 'std.cond'); fn(function (eq) { var f, g, h; f(x) = x + 1; g(x) = x + 2; h(x) = x + 3; eq(f(10), 11); eq(g(10), 12); eq(h(10), 13); eq(l[f(x) = 10] in f(5), 10); eq(l[g(x) = 11] in g(5), 11); eq(l[h(x) = 12] in h(5), 12); eq(l[f(x)(y) = x + y] in f(5)(6), 11); eq(l[f(x)(y)(z) = x + y + z] in f('1')('2')('3'), '123'); }) (eq); }); __5c3dc5fb4b8f2015070f760692845285 meta::sdoc('js::modules/caterwaul.std.test/macro-definition', <<'__091833e70addeb9353e6c0e38f1c8b45'); Macro definition tests. test('caterwaul.std.macro-definition', function () { var c = caterwaul.clone('std.qs'); c.configure(c(function () { this.rmacro(qs[fn[_][_]], function (params, expression) {return qs[function (_vars) {return _expression}].replace({_vars: params, _expression: expression})}); })); c.configure(c(function () { this.rmacro(qs[l(_ = _) in _], fn[v, e, n][qs[fn[variable][expression].call(this, value)].replace({variable: v, expression: n, value: e})]); })); eq(c(function () {return fn[x, y][x + y]})()(3, 5), 8); eq(c(function () {return l(x = 5) in x + 1})(), 6); }); __091833e70addeb9353e6c0e38f1c8b45 meta::sdoc('js::modules/caterwaul.std.test/overlapping-macros', <<'__715f3f544b78c2bd3b54fe18c48dd161'); Test for an uncommon but important macro case: | defmacro[_ + _][fn[x, y][..., when[x.data === 5]]]; // or some such defmacro[_ + _][fn[x, y][..., when[y.data === 10]]]; Macroexpansion works backwards, but the macroexpander should failover to the first defmacro if the second expander rejects the match. test('caterwaul.std.overlapping-macros', function () { var c = caterwaul.clone('std macro'); c(function (eq) { compile_eval[this.count1 = 0, this.count2 = 0]; defmacro[_ + _][fn[x, y][++this.count1, qs[_x < _y].replace({_x: x, _y: y}), when[x.is_number()]]]; defmacro[_ + _][fn[x, y][++this.count2, qs[_x * _y].replace({_x: x, _y: y}), when[y.is_number()]]]; defmacro[c._][fn[attribute][new this.ref(this[attribute.data])]]; eq(qs[3].is_number(), true); // Make sure this works... eq(qs[4].is_number(), true); var x = 3; var y = 'bar'; // Case 1. Should increment count2. eq(x * 4, 12); // Just checking... eq(x + 4, 12); eq(c.count1, 0); eq(c.count2, 1); // Case 2. Should fall through and increment count 1. eq(3 < y, false); // Just checking... eq(3 + y, false); eq(c.count1, 1); eq(c.count2, 1); }) (eq); }); __715f3f544b78c2bd3b54fe18c48dd161 meta::sdoc('js::modules/caterwaul.std.test/ref', <<'__a72d86ce8554b542f8a08a06a08987f5'); Caterwaul ref library tests test('caterwaul.std.ref', function () { var fn = caterwaul.clone('std macro'); fn.foo = 'bar'; var returned_this = fn(function (eq) { eq(caterwaul.foo, 'bar'); caterwaul.foo = 'bif'; return caterwaul; }) (eq); fn.macro(fn.parse('foo'), function () {return new this.ref('foo')}); var macro_value = fn(function (eq) { return foo; }) (eq); eq(macro_value, 'foo'); fn.rmacro(fn.parse('bar'), function () {return new this.ref('bar')}); var rmacro_value = fn(function (eq) { return bar; }) (eq); eq(rmacro_value, 'bar'); var defmacro_value = fn(function (eq) { defmacro[bif][fn_[new this.ref('bif')]]; return bif; }) (eq); eq(defmacro_value, 'bif'); eq(fn.foo, 'bif'); eq(returned_this, fn); }); __a72d86ce8554b542f8a08a06a08987f5 meta::sdoc('js::modules/caterwaul.std.test/string', <<'__fb17bd0301f5a5ba0b0461a9944d5e6e'); String interpolation tests. test('caterwaul.std.string', function () { caterwaul.clone('std.string')(function (eq) { eq('foo', 'foo'); eq('foo#{3 + 5}', 'foo8'); eq('foo#{10 + 1}#{4 + 5}#{3 + 3}', 'foo1196'); }) (eq); }); __fb17bd0301f5a5ba0b0461a9944d5e6e meta::sdoc('js::modules/caterwaul.trace', <<'__3a1c33101bf68061d3dd41ba5f3da257'); Code tracing module | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. The tracing configuration lets you create traces of code as it executes. It gives you a uniform interface to observe the evaluation of each expression in the program. To do this, first enable the 'trace' configuration, then add hooks. For example, here's a profiler: | var tracer = caterwaul.clone('trace'); var timings = {}; var timers = []; tracer.tracing.before(fn[expression, index] [timings[index] = timings[index] || 0, timers.push(+new Date())]). after(fn[expression, value, index][timings[index] += +new Date() - timers.pop()]); Interface details. Tracing things involves modifying the generated expressions in a specific way. First, the tracer marks that an expression will be evaluated. This is done by invoking a 'start' function, which then alerts all of the before-evaluation listeners. Then the tracer evaluates the original expression, capturing its output and alerting listeners in the process. Listeners are free to use and/or modify this value, but doing so may change how the program runs. (Note that value types are immutable, so in this case no modification will be possible.) There is currently no way to catch errors generated by the code. This requires a more aggressive and much slower method of tracing, and most external Javascript debuggers can give you a reasonable stack trace. (You can also deduce the error location by observing the discrepancy between before and after events.) Here is the basic transformation applied to the code: | some_expression -> (before_hook(qs[some_expression], some_index), after_hook(qs[some_expression], some_expression, some_index)) The index is an integer that uniquely identifies the expression. This prevents you from having to do anything too weird trying to use syntax trees as hash-keys. (This isn't safe anyway due to the intensionality of syntax nodes and their semantics.) Note that the tracer inserts itself as an after-step in the compilation process. This means that if you have other after-configurations, you should think about whether you want them to operate on the traced or untraced code. If untraced, then you should configure caterwaul with those configurations first: | caterwaul.clone('X trace') // X sees untraced code, then trace takes X's output caterwaul.clone('trace X') // X sees traced code, which is probably not what you want If for some reason you need to modify the listener lists, you can access the arrays directly using your_caterwaul.tracing.before_listeners and your_caterwaul.tracing.after_listeners. The hard part. If Javascript were any kind of sane language this module would be trivial to implement. Unfortunately, however, it is fairly challenging, primarily because of two factors. One is the role of statement-mode constructs, which can't be wrapped directly inside function calls. The other is method invocation binding, which requires either (1) no record of the value of the method itself, or (2) caching of the object. In this case I've written a special function to handle the caching to reduce the complexity of the generated code. Because certain forms can't be quoted directly, I call Caterwaul's parse function to get syntax trees. This shouldn't be too alarming, but there's a reason I'm not using qs[] or qse[]. Gory details. Here's the list of transformations broken down by construct. Transformation is denoted by T[], and the generic hook transformation is denoted by H[]. T is understood to be recursive, since it drives the tree descent. First, here are the statement-mode transformations: | T[function foo (x, y) {body}] -> function foo (x, y) {T[body]} T[var x = y, z = w] -> var x = T[y], z = T[w] T[var x] -> var x T[const x = y, z = w] -> const x = T[y], z = T[w] // Most people don't use this, but just in case... T[if (x) y; else z] -> if (T[x]) T[y]; else T[z]; T[if (x) y;] -> if (T[x]) T[y]; T[for (x; y; z) w;] -> for (T[x]; T[y]; T[z]) T[w]; T[for (x in y) z;] -> for (x in T[y]) T[z]; // Exceptional case: can't transform x because lvalue is implied T[for (var x in y) z;] -> for (var x in T[y]) T[z]; T[while (x) y;] -> while (T[x]) T[y]; T[do x; while (y);] -> do T[x]; while (T[y]); T[try {x} catch (e) {y} finally {z}] -> try {T[x]} catch (e) {T[y]} finally {T[z]} T[try {x} catch (e) {y}] -> try {T[x]} catch (e) {T[y]} T[return x] -> return T[x] T[return] -> return T[throw x] -> throw T[x] T[break label] -> break label // Exceptional case: labels aren't transformed T[break] -> break T[continue label] -> continue label T[continue] -> continue T[label: for ...] -> label: T[for ...] T[label: while ...] -> label: T[while ...] T[switch (x) {case v1: e1; break; ...; default: en}] -> switch (T[x]) {case v1: T[e1]; break; ...; default: T[en]} T[with (x) y;] -> with (T[x]) T[y]; T[x; y] -> T[x]; T[y] T[{x}] -> {T[x]} // Done by context on statement-level things (because we trace object literals) And here are the expression-mode transformations: | T[function (x, y) {body}] -> H[function (x, y) {T[body]}] T[x ? y : z] -> H[T[x] ? T[y] : T[z]] T[x + y] -> H[T[x] + T[y]] // For most binary operators; exceptions listed below T[+x] -> H[+T[x]] // For most unary operators; exceptions listed below T[++x], T[--x], T[x++], T[x--] -> H[++x], H[--x], H[x++], H[x--] // Lvalue, so can't trace the first value without disassembling the ++ or -- T[[x, y, z]] -> H[[T[x], T[y], T[z]]] T[{x: y, z: w}] -> H[{x: T[y], z: T[w]}] T[object.method(x, y)] -> M[T[object], T[method], [T[x], T[y]]] // M[] is like H[], but preserves invocation binding T[new f(x, y)] -> H[new T[f](T[x], T[y])] // No H[] around the function call T[delete x.y] -> H[delete T[x].y] // Lvalue, so can't trace y T[void x] -> H[void T[x]] // No point really, but capturing for completeness T[typeof x] -> H[typeof x] // Can't trace x due to potential ReferenceErrors if it isn't in scope T[f(x, y)] -> H[T[f](T[x], T[y])] T[x.y] -> H[T[x].y] T[x.y = z] -> H[T[x].y = T[z]] T[x[y] = z] -> H[T[x][T[y]] = T[z]] T[x = y] -> H[x = T[y]] // And all variants such as +=, -=, etc T['literal'] -> 'literal' // Literal syntax nodes aren't traced T[x, y] -> T[x], T[y] T[x] -> H[x] // For all identifiers x T[undefined] -> H[undefined] // undefined is a variable, not a literal (I'm reminding myself) caterwaul.tconfiguration('std seq continuation', 'trace', function () { this.namespace('tracing') /se[_.before_listeners = [], _.after_listeners = [], _.before(f) = _ /se[_.before_listeners.push(f)], _.after(f) = _ /se[_.after_listeners.push(f)]], this.after( __3a1c33101bf68061d3dd41ba5f3da257 meta::sdoc('js::test/after', <<'__fc15f6964f51604d56badbc1d46c94ea'); Tests for composition via the after() method. test('after', function () { caterwaul.clone('std')(function (eq) { var c1 = caterwaul.clone('std macro'), c2 = caterwaul.clone('std macro'); eq(c1.after.constructor, Function); c1.rmacro(qs[_ + _], fn[x, y][qs[_x * _y].replace({_x: x, _y: y})]); eq(c2.after.constructor, Function); c2.rmacro(qs[_ * _], fn[x, y][qs[_x + _y].replace({_x: x, _y: y})]); var c12 = c1.clone(); eq(c12.after(c2), c12); eq(c12.after().length, 1); eq(c12.after()[0], c2); eq(c12(qs[x + y]).data, '+'); eq(c12(qs[x * y]).data, '+'); var c21 = c2.clone(); eq(c21.after(c1), c21); eq(c21.after().length, 1); eq(c21.after()[0], c1); eq(c21(qs[x + y]).data, '*'); eq(c21(qs[x * y]).data, '*'); })(eq); }); __fc15f6964f51604d56badbc1d46c94ea meta::sdoc('js::test/environment', <<'__3b875a9598a9d647dc99d31e953b6c82'); Environment compilation test. Makes sure that Caterwaul compiles a function within the right environment. test('environment', function () { var c = caterwaul.clone('std'); eq(c(function () {return x}, {x: 5})(), 5); eq(c(function () {return x}, {x: false})(), false); eq(c(function () {return x}, {x: 'foo bar bif'})(), 'foo bar bif'); }); __3b875a9598a9d647dc99d31e953b6c82 meta::sdoc('js::test/lex-atom', <<'__07d87be64b62f3ca01c849fa138d66a5'); Atom lexing unit tests test('lex-atom', function () { var t = function (s, s2) {return eq(caterwaul.parse(s).inspect(), '(' + s2 + ')')}, s = function (s) {return t(s, s)}; Identifiers s('foo'); s('bar'); s('bif'); Strings s('"foo"'); s('"foo bar"'); t('"foo bar", "bif baz"', ', ("foo bar") ("bif baz")'); s('"foo"'); s("'foo bar'"); t('"foo bar", \'bif baz\'', ', ("foo bar") (\'bif baz\')'); Escaped strings s('"foo\\"bar"'); s('"foo\\"\\"bar\\"\\"bif"'); s('"\\"foo\\"\\"bar\\"\\"bif"'); s('"\\"foo\\"\\"bar\\"\\"bif\\""'); s('"\\"foo\\\\bar\\\\bif\\""'); Regular expressions s('/foo/'); s('/foo bar/'); s('/foo bar bif/'); s('/foo/gi'); s('/foo/gsim'); s('/foo bar bif/gim'); Escaped regular expressions s('/foo\\/bar/'); s('/foo\\/bar\\/bif/'); s('/foo\\/bar\\/bif/gi'); s('/foo\\/bar\\/bif/gims'); s('/foo\\/bar\\/bif\\\\/gims'); s('/foo\\/bar\\/bif\\\\\\//gims'); Numeric literals s('3'); s('3.'); s('.3'); s('3.0'); s('3.014'); s('3.141592653589793238462643383279502884197'); s('3e10'); s('3e3'); s('3.0e10'); s('3.0e+10'); s('3.0e-10'); s('3.0E+10'); s('3.0E-10'); s('3.0E-1'); s('3.0E1'); Regular expressions and parens t('(/foo/g)', '( (/foo/g)'); t('[/foo/g]', '[ (/foo/g)'); t('{/foo/g}', '{ (/foo/g)'); t('(foo)/foo/g', '/ (/ (( (foo)) (foo)) (g)'); t('[foo]/foo/g', '/ (/ ([ (foo)) (foo)) (g)'); t('{foo}/foo/g', '/ (/ ({ (foo)) (foo)) (g)'); }); __07d87be64b62f3ca01c849fa138d66a5 meta::sdoc('js::test/lex-end-failure', <<'__dfa47515b5923e12783ebceb398c6994'); Mysterious 'end()' bug -- caused by a lexer bug that interpreted '.e' as a number in scientific notation test('lex-end-failure', function () { var p = caterwaul.parse; eq(p('foo.bar()').inspect(), '(() (. (foo) (bar)) (<>))'); eq(p('foo.e()').inspect(), '(() (. (foo) (e)) (<>))'); eq(p('foo.end()').inspect(), '(() (. (foo) (end)) (<>))'); }); __dfa47515b5923e12783ebceb398c6994 meta::sdoc('js::test/lex-neq-failure', <<'__db5bc6e3197597074912c505be253979'); Proper parsing of != and !== (in response to a failure) test('lex-neq-failure', function () { eq(caterwaul.parse('foo[1] !== 5').inspect(), '(!== ([] (foo) (1)) (5))'); eq(caterwaul.parse('foo[1] != 5').inspect(), '(!= ([] (foo) (1)) (5))'); }); __db5bc6e3197597074912c505be253979 meta::sdoc('js::test/lex-op', <<'__523e2a2a2eff5911d23a76e399e03805'); Operation lexing tests. test('lex-op', function () { var s = function (e, i) {eq(caterwaul.parse(e).inspect(), i)}; Pathological numerical cases. s('1.2e+3+4', '(+ (1.2e+3) (4))'); s('1.2E10+5', '(+ (1.2E10) (5))'); s('1E10+2', '(+ (1E10) (2))'); s('1E+10+4E+10', '(+ (1E+10) (4E+10))'); s('0x14.serialize', '(. (0x14) (serialize))'); s('0xE+5', '(+ (0xE) (5))'); s('0644.serialize', '(. (0644) (serialize))'); s('0x511e+10e+10', '(+ (0x511e) (10e+10))'); Regular expression inference. s('return /foo/g', '(return (/foo/g))'); s('throw /foo/g', '(throw (/foo/g))'); s('case /foo/g:', '(: (case (/foo/g)) (<>))'); s('bar /foo/g', '(/ (/ (bar) (foo)) (g))'); s('3.5 /foo/g', '(/ (/ (3.5) (foo)) (g))'); s('false /foo/g', '(/ (/ (false) (foo)) (g))'); }); __523e2a2a2eff5911d23a76e399e03805 meta::sdoc('js::test/lex-regexp-in-for-failure', <<'__16a2dd041600b860c86d964cd53375d1'); Observed failure case: for (var k in hash) /foo/.test(k) || ... The problem here is that the lexer assumes that the parens surrounding the for-loop condition end an expression; there needs to be a lexer workaround for block-accepting keywords like this. test('lex-regexp-for-in-failure', function () { var f = function (s) {try {caterwaul.parse(s)} catch (e) {print('Chucked a wobbly parsing ' + s); throw e}}; f('for (var k in hash) /foo/.test(k) && bar()'); f('for (var k in hash) /foo/.test(k) && bar();'); f('if (k in hash) /foo/.test(k) && bar()'); f('if (k in hash) /foo/.test(k) && bar();'); f('while (k in hash) /foo/.test(k) && bar()'); f('while (k in hash) /foo/.test(k) && bar();'); f('with (k) /foo/.test(k) && toString()'); f('with (k) /foo/.test(k) && toString();'); f('if (k in hash) /foo/.test(k) && bar(); else /foo/.test(k) && bar()'); }); __16a2dd041600b860c86d964cd53375d1 meta::sdoc('js::test/lex-semicolon-failure', <<'__49490aff075a90b745e45ad9bad59b7a'); test('lex-semicolon-failure', function () { var t = function (s, s2) {return eq(caterwaul.parse(s).inspect(), s2)}; Semicolons (in response to a failure) t('x;y;z', '(; (; (x) (y)) (z))'); t('x;/foo/g', '(; (x) (/foo/g))'); t('/foo/g;/bar/g', '(; (/foo/g) (/bar/g))'); }); __49490aff075a90b745e45ad9bad59b7a meta::sdoc('js::test/lib/unit', <<'__67c1c04065c6451957f49497c6830d84'); Unit testing library functions var original_caterwaul = caterwaul.deglobalize(); var log_message = function (message) { if (typeof console === 'undefined') print(message); else console.log(message); }; var on_error = function (name, e) { log_message('Error at or shortly after eq count ' + eq_count + ' in test ' + name + ': ' + (e.description || e)); throw new Error(e); }; var eq_count = 0; var eq = function (x, y, message) { ++eq_count; if (x == y) return true; else return on_error(current_test, (message || '') + x + ' !== ' + y); }; var current_test = null; var test = function (name, f) { log_message('[unit] starting test ' + (current_test = name)); try { eq_count = 0; caterwaul = original_caterwaul.clone(); f(); eq_count = 0; caterwaul = caterwaul.reinitialize(caterwaul); f(); } catch (e) { on_error(name, e); } }; __67c1c04065c6451957f49497c6830d84 meta::sdoc('js::test/lib/unit.client', <<'__28f25ce7ee14450256238f801d4e6aac'); Client-side unit testing hooks. log = function (message) { var d = document.createElement('div'); d.appendChild(document.createTextNode(message)); document.getElementById('log').appendChild(d); }; __28f25ce7ee14450256238f801d4e6aac meta::sdoc('js::test/parse-arithmetic', <<'__b00daffcaa4ad72898ef2168448d686c'); Arithmetic/expression tests for the parser. test('parse-arithmetic', function () { var p = function (s, i) {return eq(caterwaul.parse(s).inspect(), i)}; Basic precedence p('3+5', '(+ (3) (5))'); p('3*5+6', '(+ (* (3) (5)) (6))'); p('3+5*6', '(+ (3) (* (5) (6)))'); Associativity p('3+4+5', '(+ (+ (3) (4)) (5))'); p('3*4*5', '(* (* (3) (4)) (5))'); p('3-4-5', '(- (- (3) (4)) (5))'); p('3/4/5', '(/ (/ (3) (4)) (5))'); p('3=4=5', '(= (3) (= (4) (5)))'); p('3+=4+=5', '(+= (3) (+= (4) (5)))'); p('3-=4+=5', '(-= (3) (+= (4) (5)))'); p('3*=4+=5', '(*= (3) (+= (4) (5)))'); p('3/=4+=5', '(/= (3) (+= (4) (5)))'); Parentheses/invocation p('(3+5)*6', '(* (( (+ (3) (5))) (6))'); p('6*(3+5)', '(* (6) (( (+ (3) (5))))'); p('((3))+((5))', '(+ (( (( (3))) (( (( (5))))'); p('((3+5))', '(( (( (+ (3) (5))))'); p('(3)(4)', '(() (( (3)) (4))'); p('(3)(4)(5)', '(() (() (( (3)) (4)) (5))'); p('3(4)', '(() (3) (4))'); p('(3)[4]', '([] (( (3)) (4))'); p('3[4]', '([] (3) (4))'); Array literals/dereferencing p('[1, 2, 3]', '([ (, (, (1) (2)) (3)))'); p('[1, 2][0]', '([] ([ (, (1) (2))) (0))'); p('[1][2, 3]', '([] ([ (1)) (, (2) (3)))'); p('[1][2][3]', '([] ([] ([ (1)) (2)) (3))'); p('foo.bar[bif]', '([] (. (foo) (bar)) (bif))'); p('foo.bar[3[bif]]', '([] (. (foo) (bar)) ([] (3) (bif)))'); p('foo.bar[3[4[bif]]]', '([] (. (foo) (bar)) ([] (3) ([] (4) (bif))))'); Object notation p('foo.bar', '(. (foo) (bar))'); p('foo.bar.bif', '(. (. (foo) (bar)) (bif))'); p('foo().bar.bif', '(. (. (() (foo) (<>)) (bar)) (bif))'); p('foo().bar().bif', '(. (() (. (() (foo) (<>)) (bar)) (<>)) (bif))'); p('foo().bar().bif()', '(() (. (() (. (() (foo) (<>)) (bar)) (<>)) (bif)) (<>))'); p('foo().bar().bif()()', '(() (() (. (() (. (() (foo) (<>)) (bar)) (<>)) (bif)) (<>)) (<>))'); }); __b00daffcaa4ad72898ef2168448d686c meta::sdoc('js::test/parse-empty-for-initializer-failure', <<'__bdeca27ccafe549bb65d437bbcda3ec4'); Test case for 'for (; foo; bar)' loops (in response to jQuery identity failure) test('parse-empty-for-initializer-failure', function () { var i = function (s) {eq(s.replace(/[\s\n]/g, ''), caterwaul.parse(s).serialize().replace(/[\s\n]/g, ''))}, p = function (s, i) {eq(i, caterwaul.parse(s).inspect())}; p('a;;;', '(; (; (a) (; (<>) (<>))) (<>))'); p(';a;;', '(; (; (<>) (a)) (; (<>) (<>)))'); p(';;a;', '(i; (; (<>) (; (<>) (<>))) (; (a) (<>)))'); p(';;;a', '(; (; (<>) (; (<>) (<>))) (a))'); p(';;', '(; (<>) (; (<>) (<>)))'); p('for (;a;b){}', '(for (( (; (; (<>) (a)) (b))) ({))'); p('for (a;;b){}', '(for (( (i; (; (a) (; (<>) (<>))) (b))) ({))'); i(';;'); i('for (;i)))'); p('if (foo) bar;', '(; (if (( (foo)) (bar)) (<>))'); p('if (foo);', '(; (if (( (foo))) (<>))'); p('for (var i = 0; i < 10; ++i) total += i;', '(; (for (( (; (; (var (= (i) (0))) (< (i) (10))) (u++ (i)))) (+= (total) (i))) (<>))'); p('for (var i = 0; i < 10; ++i);', '(; (for (( (; (; (var (= (i) (0))) (< (i) (10))) (u++ (i))))) (<>))'); p('for (var k in foo) console.log(k);', '(; (for (( (var (in (k) (foo)))) (() (. (console) (log)) (k))) (<>))'); p('for (var k in foo) for (var j in k) k + j;', '(; (for (( (var (in (k) (foo)))) (for (( (var (in (j) (k)))) (+ (k) (j)))) (<>))'); p('if(foo)for(bar);', '(; (if (( (foo)) (for (( (bar)))) (<>))'); p('while(foo)bar;', '(; (while (( (foo)) (bar)) (<>))'); p('while(bif)while(foo)bar;', '(; (while (( (bif)) (while (( (foo)) (bar))) (<>))'); p('for(var i = 0; i < 10; ++i) while(foo) bar;', '(; (for (( (; (; (var (= (i) (0))) (< (i) (10))) (u++ (i)))) (while (( (foo)) (bar))) (<>))'); Nested block-level statements p('if(foo)if(bar)bif', '(if (( (foo)) (if (( (bar)) (bif)))'); p('if(foo);if(bar)bif', '(; (if (( (foo))) (if (( (bar)) (bif)))'); p('if(foo) {bar} else bif;', '(; (if (( (foo)) ({ (bar)) (else (bif))) (<>))'); p('if(foo) bar; else bif;', '(; (if (( (foo)) (bar) (else (bif))) (<>))'); p('if(x)y; else if(z) a; else if(w) q;', '(; (if (( (x)) (y) (else (if (( (z)) (a) (else (if (( (w)) (q)))))) (<>))'); p('for(var i = 0, l = 10; i < l; ++i) while (i) while (j) foo;', '(; (for (( (; (; (var (, (= (i) (0)) (= (l) (10)))) (< (i) (l))) (u++ (i)))) (while (( (i)) (while (( (j)) (foo)))) (<>))'); Functions p('function foo (bar) {bif} function bar (bif) {baz}', '(i; (function (foo) (( (bar)) ({ (bif))) (function (bar) (( (bif)) ({ (baz))))'); p('var f = function (x) {return x}', '(var (= (f) (function (( (x)) ({ (return (x))))))'); }); __e545cf14101093e52bb21468c9517848 meta::sdoc('js::test/parse-ternary', <<'__cac941cdd6d24ed2c2d9cb469b11de7f'); Ternary operator tests. test('parse-ternary', function () { var p = function (s, i) {return eq(caterwaul.parse(s).inspect(), i)}, s = function (s, i) {return eq(caterwaul.parse(s).serialize(), i)}; p('3?4:5', '(? (3) (4) (5))'); p('3+4?5:6', '(? (+ (3) (4)) (5) (6))'); p('(3)?4:5', '(? (( (3)) (4) (5))'); p('3?(4):5', '(? (3) (( (4)) (5))'); p('var x = cond() === false ? y === true : y === false', '(var (= (x) (? (=== (() (cond) (<>)) (false)) (=== (y) (true)) (=== (y) (false)))))'); s('var x = foo() ? (foo + bar)() : (t + u)()', 'var x=foo()?(foo+bar)():(t+u)()'); }); __cac941cdd6d24ed2c2d9cb469b11de7f meta::sdoc('js::test/plus-plus-failure', <<'__3ab920bca03680a014abb07604264501'); A failure case involving prefix and infix plus. test('plus-plus-failure', function () { caterwaul.clone('std')(function (eq) { var x = 5; var y = x + +x; eq(y, 10); })(eq); }); __3ab920bca03680a014abb07604264501 meta::sdoc('js::test/replica', <<'__db2a7930c22d8a2dd292fc7927052f8c'); Replica interface tests. test('replica', function () { var r = caterwaul.replica(); eq(r.init, undefined); eq(r.foo, undefined); r.field('foo', 5); eq(r.foo, 5); eq(caterwaul.replica().foo, undefined); var r2 = caterwaul.configurable(caterwaul.replica()); eq(r2.foo, undefined); r2.field('foo', 10); eq(r2.foo, 10); var r2p = r2.clone(); eq(r2p.foo, 10); r2p.field('foo', 11); eq(r2p.foo, 11); eq(r2.foo, 10); var m = caterwaul.configurable(caterwaul.replica()).configure(caterwaul.macroexpansion); eq(m.rmacro.constructor, Function); eq(m.macro.constructor, Function); eq(m.macro_patterns.constructor, Array); eq(m.macro_expanders.constructor, Array); }); __db2a7930c22d8a2dd292fc7927052f8c meta::sdoc('js::test/serialize-semicolons', <<'__5674dc8262e3618cb04d828cb98bd81e'); Semicolon serialization test. test('serialize-semicolons', function () { eq(new caterwaul.syntax(';').inspect(), '(;)'); eq(new caterwaul.syntax(';').serialize(), ';'); }); __5674dc8262e3618cb04d828cb98bd81e meta::sdoc('js::test/tostring-bootstrap', <<'__ce6e5bffb3680096d95b86d776409ec6'); Serialization via serialize() test('tostring-bootstrap', function () { var n = function (s) {return s.replace(/\s+/g, ' ').replace(/;\s*\}/g, '}').replace(/\s+/g, '')}, s = function (s, i) {return eq(caterwaul.parse(s).serialize(), i)}, i = function (f) {return eq(n(caterwaul.decompile(f).serialize()), n(f.toString()))}; //.replace(/\s+/g, ''))}; Hardcore test: i(function () { var _caterwaul = this.caterwaul, _$c = this.$c, fn = function (x) {return new Function ('$0', '$1', '$2', '$3', '$4', 'return ' + x.replace(/@/g, 'this.'))}, $c = function () {return $c.init.apply(this, arguments)}, gensym = (function (n) {return function () {return 'gensym' + (++n).serialize(36)}})(0), qw = fn('$0.split(/\\s+/)'), own = Object.prototype.hasOwnProperty, has = function (o, p) {return own.call(o, p)}, map = function (f, xs) {for (var i = 0, ys = [], l = xs.length; i < l; ++i) ys.push(f(xs[i])); return ys}, hash = function (s) {for (var i = 0, xs = qw(s), o = {}, l = xs.length; i < l; ++i) o[xs[i]] = true; return o}, extend = function (f) {for (var i = 1, p = f.prototype, l = arguments.length, _ = null; _ = arguments[i], i < l; ++i) for (var k in _) has(_, k) && (p[k] = _[k]); return f}, lex_op = hash('. new ++ -- u++ u-- u+ u- typeof u~ u! ! * / % + - << >> >>> < > <= >= instanceof in == != === !== & ^ | && || ? = += -= *= /= %= &= |= ^= <<= >>= >>>= : , ' + 'return throw case var const break continue void ;'), lex_table = function (s) {for (var i = 0, xs = [false]; i < 8; ++i) xs = xs.concat(xs); for (var i = 0, l = s.length; i < l; ++i) xs[s.charCodeAt(i)] = true; return xs}, lex_float = lex_table('.0123456789'), lex_decimal = lex_table('0123456789'), lex_integer = lex_table('0123456789abcdefABCDEFx'), lex_exp = lex_table('eE'), lex_space = lex_table(' \n\r\t'), lex_bracket = lex_table('()[]{}'), lex_opener = lex_table('([{'), lex_punct = lex_table('+-*/%&|^!~=<>?:;.,'), lex_eol = lex_table('\n\r'), lex_regexp_suffix = lex_table('gims'), lex_quote = lex_table('\'"/'), lex_slash = '/'.charCodeAt(0), lex_star = '*'.charCodeAt(0), lex_back = '\\'.charCodeAt(0), lex_x = 'x'.charCodeAt(0), lex_dot = '.'.charCodeAt(0), lex_zero = '0'.charCodeAt(0), lex_postfix_unary = hash('++ --'), lex_ident = lex_table('$_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), lex = $c.lex = function (s) { var s = s.serialize(), mark = 0, cs = [], c = 0, re = true, esc = false, dot = false, exp = false, close = 0, t = '', ts = [], interned = {}, intern = function (s) {return interned['@' + s] || (interned['@' + s] = new String(s))}; for (var i = 0, l = s.length; i < l || (i = 0); ++i) cs.push(s.charCodeAt(i)); while ((mark = i) < l) { while (lex_space[c = cs[i]] && i < l) mark = ++i; esc = exp = dot = t = false; if (lex_bracket[c]) {t = !! ++i; re = lex_opener[c]} else if (c === lex_slash && cs[i + 1] === lex_star && (i += 2)) {while (++i < l && cs[i] !== lex_slash || cs[i - 1] !== lex_star); t = ! ++i} else if (c === lex_slash && cs[i + 1] === lex_slash) {while (++i < l && ! lex_eol[cs[i]]); t = false} else if (lex_quote[c] && (close = c) && re && ! (re = ! (t = s.charAt(i)))) {while (++i < l && (c = cs[i]) !== close || esc) esc = ! esc && c === lex_back; while (++i < l && lex_regexp_suffix[cs[i]]) ; t = true} else if (c === lex_zero && lex_integer[cs[i + 1]]) {while (++i < l && lex_integer[cs[i]]); re = ! (t = true)} else if (c === lex_dot && lex_decimal[cs[i + 1]] || lex_float[c]) {while (++i < l && (lex_decimal[c = cs[i]] || (dot ^ (dot |= c === lex_dot)) || (exp ^ (exp |= lex_exp[c] && ++i)))); while (i < l && lex_decimal[cs[i]]) ++i; re = ! (t = true)} else if (lex_punct[c] && (t = re ? 'u' : '', re = true)) {while (i < l && lex_punct[cs[i]] && has(lex_op, t + s.charAt(i))) t += s.charAt(i++); re = ! has(lex_postfix_unary, t)} else {while (++i < l && lex_ident[cs[i]]); re = has(lex_op, t = s.substring(mark, i))} t !== false && ts.push(intern(t === true ? s.substring(mark, i) : t)); } return ts}, parse_reduce_order = map(hash, ['[ . ( [] ()', 'function', 'new', 'u++ u-- ++ -- typeof u~ u! u+ u-', '* / %', '+ -', '<< >> >>>', '< > <= >= instanceof in', '== != === !==', '&', '^', '|', '&&', '||', 'case', '?', '= += -= *= /= %= &= |= ^= <<= >>= >>>=', ':', ',', 'return throw break continue', 'var const', 'if else try catch finally for switch with while do', ';']), parse_associates_right = hash('= += -= *= /= %= &= ^= |= <<= >>= >>>= ~ ! new typeof u+ u- -- ++ u-- u++ ? if else function try catch finally for switch case with while do'), parse_inverse_order = (function (xs) {for (var o = {}, i = 0, l = xs.length; i < l; ++i) for (var k in xs[i]) has(xs[i], k) && (o[k] = i); return o}) (parse_reduce_order), parse_index_forward = (function (rs) {for (var xs = [], i = 0, l = rs.length, _ = null; _ = rs[i], xs[i] = true, i < l; ++i) for (var k in _) if (has(_, k) && (xs[i] = xs[i] && ! has(parse_associates_right, k))) break; return xs}) (parse_reduce_order), parse_lr = hash('[] . () * / % + - << >> >>> < > <= >= instanceof in == != === !== & ^ | && || = += -= *= /= %= &= |= ^= <<= >>= >>>= , ;'), parse_r_until_block = {'function':2, 'if':1, 'do':1, 'catch':1, 'try':1, 'for':1, 'while':1}, parse_accepts = {'if':'else', 'do':'while', 'catch':'finally', 'try':'catch'}, parse_r_optional = hash('return throw break continue'), parse_l = hash('++ --'), parse_r = hash('u+ u- u! u~ u++ u-- new typeof else finally var const void'), parse_block = hash('; {'), parse_k_empty = fn('[]'), parse_group = {'(':')', '[':']', '{':'}', '?':':'}, parse_ambiguous_group = hash('[ ('), parse_ternary = hash('?'), parse_not_a_value = hash('function if for while catch'), parse_invisible = hash('() []'), syntax_node_inspect = fn('$0.inspect()'), syntax_node_tostring = fn('$0 ? $0.serialize() : ""'), syntax_node = $c.syntax_node = extend (fn('@data = $0, @length = 0, @l = @r = @p = null'), { replace: fn('($0.l = @l) && (@l.r = $0), ($0.r = @r) && (@r.l = $0), this'), append_to: fn('$0 && $0.append(this), this'), reparent: fn('@p && @p[0] === this && (@p[0] = $0), this'), fold_l: fn('@l && @append(@l.unlink(this)), this'), fold_lr: fn('@fold_l().fold_r()'), append: fn('(this[@length++] = $0).p = this'), fold_r: fn('@r && @append(@r.unlink(this)), this'), fold_rr: fn('@fold_r().fold_r()'), sibling: fn('$0.p = @p, (@r = $0).l = this'), unlink: fn('@l && (@l.r = @r), @r && (@r.l = @l), @l = @r = null, @reparent($0)'), wrap: fn('$0.p = @replace($0).p, @reparent($0), @l = @r = null, @append_to($0)'), pop: fn('--@length, this'), inspect: function () {return (this.l ? '(left) <- ' : '') + '(' + this.data + (this.length ? ' ' + map(syntax_node_inspect, this).join(' ') : '') + ')' + (this.r ? ' -> ' + this.r.inspect() : '')}, serialize: function () {var op = this.data, right = this.r ? '/* -> ' + this.r.serialize() + ' */' : '', s = has(parse_invisible, op) ? map(syntax_node_tostring, this).join('') : has(parse_ternary, op) ? map(syntax_node_tostring, [this[0], op, this[1], parse_group[op], this[2]]).join('') : has(parse_group, op) ? op + map(syntax_node_tostring, this).join('') + parse_group[op] : has(parse_lr, op) ? map(syntax_node_tostring, [this[0], op, this[1]]).join('') : has(parse_r, op) || has(parse_r_optional, op) ? op.replace(/^u/, '') + ' ' + this[0].serialize() : has(parse_r_until_block, op) ? op + ' ' + map(syntax_node_tostring, this).join('') : has(parse_l, op) ? this[0].serialize() + op : op; return right ? s + right : s}}), parse = $c.parse = function (ts) { var grouping_stack = [], gs_top = null, head = null, parent = null, indexes = map(parse_k_empty, parse_reduce_order), push = function (n) {return head ? head.sibling(head = n) : (head = n.append_to(parent)), n}; for (var i = 0, l = ts.length, _ = null; _ = ts[i], i < l; ++i) _ == gs_top ? (grouping_stack.pop(), gs_top = grouping_stack[grouping_stack.length - 1], head = head ? head.p : parent, parent = null) : (has(parse_group, _) ? (grouping_stack.push(gs_top = parse_group[_]), parent = push(new syntax_node(_)), head = null) : push(new syntax_node(_)), has(parse_inverse_order, _) && indexes[parse_inverse_order[_]].push(head || parent)); for (var i = 0, i0 = indexes[0], l = i0.length, _ = null, _d = null, _l = null; _ = i0[i], _d = _ && _.data, _l = _ && _.l, i < l; ++i) if (_d == '.') _.fold_lr(); else if (has(parse_ambiguous_group, _d) && _l && (_l.data == '.' || ! (has(lex_op, _l.data) || has(parse_not_a_value, _l.data)))) _l.wrap(new syntax_node(_d + parse_group[_d])).p.fold_r(); for (var i = 1, l = indexes.length, forward = null, _ = null; _ = indexes[i], forward = parse_index_forward[i], i < l; ++i) for (var j = forward ? 0 : _.length - 1, lj = _.length, inc = forward ? 1 : -1, node = null, data = null; node = _[j], data = node && node.data, forward ? j < lj : j >= 0; j += inc) if (has(parse_lr, data)) node.fold_lr(); else if (has(parse_l, data)) node.fold_l(); else if (has(parse_r, data)) node.fold_r(); else if (has(parse_ternary, data)) {node.fold_lr(); var temp = node[1]; node[1] = node[0]; node[0] = temp} else if (has(parse_r_until_block, data)) {for (var count = 0, limit = parse_r_until_block[data]; count < limit && node.r && ! has(parse_block, node.r.data); ++count) node.fold_r(); node.fold_r(); if (has(parse_accepts, data) && parse_accepts[data] == (node.r && node.r.r && node.r.r.data)) node.fold_r().pop().fold_r(); else if (has(parse_accepts, data) && parse_accepts[data] == (node.r && node.r.data)) node.fold_r(); if (node.r && node.r.data != ';') node.wrap(new syntax_node(';')).p.fold_r()} else if (has(parse_r_optional, data)) node.r && node.r.data != ';' && node.fold_r(); while (head.p) head = head.p; return head; }; this.caterwaul = this.$c = $c; }); }); __ce6e5bffb3680096d95b86d776409ec6 meta::sdoc('js::test/tostring-identity', <<'__299941a2e7a8dfb463dfa06056e23a16'); Serialization via serialize() test('tostring-identity', function () { var n = function (s) {return s.replace(/;\s*\}/g, '}').replace(/\s+/g, '')}, s = function (s, i) {return eq(caterwaul.parse(s).serialize(), i)}, i = function (f) {return eq(n(caterwaul.decompile(f).serialize()), n(f.toString()))}; Simple tests: i(function(){return 5}); i(function(x){return function(y) {return x + y}}); i(function () {var x = 5; return x}); }); __299941a2e7a8dfb463dfa06056e23a16 meta::sdoc('js::test/tostring-jquery', <<'__e81e19bd5b7575c1b844ddd0cf4fd4e8'); Serialization via serialize() test('tostring-jquery', function () { var n = function (s) {return s.replace(/\s+/g, '').replace(/;\s*\}/g, '}')}, s = function (s, i) {return eq(caterwaul.parse(s).serialize(), i)}, i = function (f) {return eq(n(caterwaul.decompile(f).serialize()), n(f.toString()))}; Does it preserve jQuery? i(function () { (function( window, undefined ) { var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }, _jQuery = window.jQuery, _$ = window.$, document = window.document, rootjQuery, quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, isSimple = /^.[^:#\[\.,]*$/, rnotwhite = /\S/, rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, userAgent = navigator.userAgent, browserMatch, readyBound = false, readyList = [], DOMContentLoaded, toString = Object.prototype.toString, hasOwnProperty = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, indexOf = Array.prototype.indexOf; jQuery.fn = jQuery.prototype = { init: function( selector, context ) { var match, elem, ret, doc; if ( !selector ) { return this; } if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } if ( selector === "body" && !context ) { this.context = document; this[0] = document.body; this.selector = "body"; this.length = 1; return this; } if ( typeof selector === "string" ) { match = quickExpr.exec( selector ); if ( match && (match[1] || !context) ) { if ( match[1] ) { doc = (context ? context.ownerDocument || context : document); ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = buildFragment( [ match[1] ], [ doc ] ); selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; } return jQuery.merge( this, selector ); } else { elem = document.getElementById( match[2] ); if ( elem ) { if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } } else if ( !context && /^\w+$/.test( selector ) ) { this.selector = selector; this.context = document; selector = document.getElementsByTagName( selector ); return jQuery.merge( this, selector ); } else if ( !context || context.jquery ) { return (context || rootjQuery).find( selector ); } else { return jQuery( context ).find( selector ); } } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if (selector.selector !== undefined) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }, selector: "", jquery: "1.4.2", length: 0, size: function() { return this.length; }, toArray: function() { return slice.call( this, 0 ); }, get: function( num ) { return num == null ? this.toArray() : ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); }, pushStack: function( elems, name, selector ) { var ret = jQuery(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { jQuery.merge( ret, elems ); } ret.prevObject = this; ret.context = this.context; if ( name === "find" ) { ret.selector = this.selector + (this.selector ? " " : "") + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } return ret; }, each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { jQuery.bindReady(); if ( jQuery.isReady ) { fn.call( document, jQuery ); } else if ( readyList ) { readyList.push( fn ); } return this; }, eq: function( i ) { return i === -1 ? this.slice( i ) : this.slice( i, +i + 1 ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ), "slice", slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, end: function() { return this.prevObject || jQuery(null); }, push: push, sort: [].sort, splice: [].splice }; jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; i = 2; } if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { if ( (options = arguments[ i ]) != null ) { for ( name in options ) { src = target[ name ]; copy = options[ name ]; if ( target === copy ) { continue; } if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src : jQuery.isArray(copy) ? [] : {}; target[ name ] = jQuery.extend( deep, clone, copy ); } else if ( copy !== undefined ) { target[ name ] = copy; } } } } return target; }; jQuery.extend({ noConflict: function( deep ) { window.$ = _$; if ( deep ) { window.jQuery = _jQuery; } return jQuery; }, isReady: false, ready: function() { if ( !jQuery.isReady ) { if ( !document.body ) { return setTimeout( jQuery.ready, 13 ); } jQuery.isReady = true; if ( readyList ) { var fn, i = 0; while ( (fn = readyList[ i++ ]) ) { fn.call( document, jQuery ); } readyList = null; } if ( jQuery.fn.triggerHandler ) { jQuery( document ).triggerHandler( "ready" ); } } }, bindReady: function() { if ( readyBound ) { return; } readyBound = true; if ( document.readyState === "complete" ) { return jQuery.ready(); } if ( document.addEventListener ) { document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); window.addEventListener( "load", jQuery.ready, false ); } else if ( document.attachEvent ) { document.attachEvent("onreadystatechange", DOMContentLoaded); window.attachEvent( "onload", jQuery.ready ); var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, isFunction: function( obj ) { return toString.call(obj) === "[object Function]"; }, isArray: function( obj ) { return toString.call(obj) === "[object Array]"; }, isPlainObject: function( obj ) { if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { return false; } if ( obj.constructor && !hasOwnProperty.call(obj, "constructor") && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } var key; for ( key in obj ) {} return key === undefined || hasOwnProperty.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw msg; }, parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; } data = jQuery.trim( data ); if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { return window.JSON && window.JSON.parse ? window.JSON.parse( data ) : (new Function("return " + data))(); } else { jQuery.error( "Invalid JSON: " + data ); } }, noop: function() {}, globalEval: function( data ) { if ( data && rnotwhite.test(data) ) { var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; if ( jQuery.support.scriptEval ) { script.appendChild( document.createTextNode( data ) ); } else { script.text = data; } head.insertBefore( script, head.firstChild ); head.removeChild( script ); } }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction(object); if ( args ) { if ( isObj ) { for ( name in object ) { if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} } } return object; }, trim: function( text ) { return (text || "").replace( rtrim, "" ); }, makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, inArray: function( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; }, merge: function( first, second ) { var i = first.length, j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, inv ) { var ret = []; for ( var i = 0, length = elems.length; i < length; i++ ) { if ( !inv !== !callback( elems[ i ], i ) ) { ret.push( elems[ i ] ); } } return ret; }, map: function( elems, callback, arg ) { var ret = [], value; for ( var i = 0, length = elems.length; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } return ret.concat.apply( [], ret ); }, guid: 1, proxy: function( fn, proxy, thisObject ) { if ( arguments.length === 2 ) { if ( typeof proxy === "string" ) { thisObject = fn; fn = thisObject[ proxy ]; proxy = undefined; } else if ( proxy && !jQuery.isFunction( proxy ) ) { thisObject = proxy; proxy = undefined; } } if ( !proxy && fn ) { proxy = function() { return fn.apply( thisObject || this, arguments ); }; } if ( fn ) { proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; } return proxy; }, uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || /(msie) ([\w.]+)/.exec( ua ) || !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, browser: {} }); browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } if ( indexOf ) { jQuery.inArray = function( elem, array ) { return indexOf.call( array, elem ); }; } rootjQuery = jQuery(document); if ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); }; } else if ( document.attachEvent ) { DOMContentLoaded = function() { if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }; } function doScrollCheck() { if ( jQuery.isReady ) { return; } try { document.documentElement.doScroll("left"); } catch( error ) { setTimeout( doScrollCheck, 1 ); return; } jQuery.ready(); } function evalScript( i, elem ) { if ( elem.src ) { jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); } else { jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } function access( elems, key, value, exec, fn, pass ) { var length = elems.length; if ( typeof key === "object" ) { for ( var k in key ) { access( elems, k, key[k], exec, fn, value ); } return elems; } if ( value !== undefined ) { exec = !pass && exec && jQuery.isFunction(value); for ( var i = 0; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } return elems; } return length ? fn( elems[0], key ) : undefined; } function now() { return (new Date).getTime(); } (function() { jQuery.support = {}; var root = document.documentElement, script = document.createElement("script"), div = document.createElement("div"), id = "script" + now(); div.style.display = "none"; div.innerHTML = "
a"; var all = div.getElementsByTagName("*"), a = div.getElementsByTagName("a")[0]; if ( !all || !all.length || !a ) { return; } jQuery.support = { leadingWhitespace: div.firstChild.nodeType === 3, tbody: !div.getElementsByTagName("tbody").length, htmlSerialize: !!div.getElementsByTagName("link").length, style: /red/.test( a.getAttribute("style") ), hrefNormalized: a.getAttribute("href") === "/a", opacity: /^0.55$/.test( a.style.opacity ), cssFloat: !!a.style.cssFloat, checkOn: div.getElementsByTagName("input")[0].value === "on", optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, deleteExpando: true, checkClone: false, scriptEval: false, noCloneEvent: true, boxModel: null }; script.type = "text/javascript"; try { script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); } catch(e) {} root.insertBefore( script, root.firstChild ); if ( window[ id ] ) { jQuery.support.scriptEval = true; delete window[ id ]; } try { delete script.test; } catch(e) { jQuery.support.deleteExpando = false; } root.removeChild( script ); if ( div.attachEvent && div.fireEvent ) { div.attachEvent("onclick", function click() { jQuery.support.noCloneEvent = false; div.detachEvent("onclick", click); }); div.cloneNode(true).fireEvent("onclick"); } div = document.createElement("div"); div.innerHTML = ""; var fragment = document.createDocumentFragment(); fragment.appendChild( div.firstChild ); jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; jQuery(function() { var div = document.createElement("div"); div.style.width = div.style.paddingLeft = "1px"; document.body.appendChild( div ); jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; document.body.removeChild( div ).style.display = 'none'; div = null; }); var eventSupported = function( eventName ) { var el = document.createElement("div"); eventName = "on" + eventName; var isSupported = (eventName in el); if ( !isSupported ) { el.setAttribute(eventName, "return;"); isSupported = typeof el[eventName] === "function"; } el = null; return isSupported; }; jQuery.support.submitBubbles = eventSupported("submit"); jQuery.support.changeBubbles = eventSupported("change"); root = script = div = all = a = null; })(); jQuery.props = { "for": "htmlFor", "class": "className", readonly: "readOnly", maxlength: "maxLength", cellspacing: "cellSpacing", rowspan: "rowSpan", colspan: "colSpan", tabindex: "tabIndex", usemap: "useMap", frameborder: "frameBorder" }; var expando = "jQuery" + now(), uuid = 0, windowData = {}; jQuery.extend({ cache: {}, expando:expando, noData: { "embed": true, "object": true, "applet": true }, data: function( elem, name, data ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { return; } elem = elem == window ? windowData : elem; var id = elem[ expando ], cache = jQuery.cache, thisCache; if ( !id && typeof name === "string" && data === undefined ) { return null; } if ( !id ) { id = ++uuid; } if ( typeof name === "object" ) { elem[ expando ] = id; thisCache = cache[ id ] = jQuery.extend(true, {}, name); } else if ( !cache[ id ] ) { elem[ expando ] = id; cache[ id ] = {}; } thisCache = cache[ id ]; if ( data !== undefined ) { thisCache[ name ] = data; } return typeof name === "string" ? thisCache[ name ] : thisCache; }, removeData: function( elem, name ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { return; } elem = elem == window ? windowData : elem; var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; if ( name ) { if ( thisCache ) { delete thisCache[ name ]; if ( jQuery.isEmptyObject(thisCache) ) { jQuery.removeData( elem ); } } } else { if ( jQuery.support.deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } delete cache[ id ]; } } }); jQuery.fn.extend({ data: function( key, value ) { if ( typeof key === "undefined" && this.length ) { return jQuery.data( this[0] ); } else if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } var parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else { return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { jQuery.data( this, key, value ); }); } }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); jQuery.extend({ queue: function( elem, type, data ) { if ( !elem ) { return; } type = (type || "fx") + "queue"; var q = jQuery.data( elem, type ); if ( !data ) { return q || []; } if ( !q || jQuery.isArray(data) ) { q = jQuery.data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } return q; }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(); if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { if ( type === "fx" ) { queue.unshift("inprogress"); } fn.call(elem, function() { jQuery.dequeue(elem, type); }); } } }); jQuery.fn.extend({ queue: function( type, data ) { if ( typeof type !== "string" ) { data = type; type = "fx"; } if ( data === undefined ) { return jQuery.queue( this[0], type ); } return this.each(function( i, elem ) { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; type = type || "fx"; return this.queue( type, function() { var elem = this; setTimeout(function() { jQuery.dequeue( elem, type ); }, time ); }); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); } }); var rclass = /[\n\t]/g, rspace = /\s+/, rreturn = /\r/g, rspecialurl = /href|src|style/, rtype = /(button|input)/i, rfocusable = /(button|input|object|select|textarea)/i, rclickable = /^(a|area)$/i, rradiocheck = /radio|checkbox/; jQuery.fn.extend({ attr: function( name, value ) { return access( this, name, value, true, jQuery.attr ); }, removeAttr: function( name, fn ) { return this.each(function(){ jQuery.attr( this, name, "" ); if ( this.nodeType === 1 ) { this.removeAttribute( name ); } }); }, addClass: function( value ) { if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); self.addClass( value.call(this, i, self.attr("class")) ); }); } if ( value && typeof value === "string" ) { var classNames = (value || "").split( rspace ); for ( var i = 0, l = this.length; i < l; i++ ) { var elem = this[i]; if ( elem.nodeType === 1 ) { if ( !elem.className ) { elem.className = value; } else { var className = " " + elem.className + " ", setClass = elem.className; for ( var c = 0, cl = classNames.length; c < cl; c++ ) { if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { setClass += " " + classNames[c]; } } elem.className = jQuery.trim( setClass ); } } } } return this; }, removeClass: function( value ) { if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); self.removeClass( value.call(this, i, self.attr("class")) ); }); } if ( (value && typeof value === "string") || value === undefined ) { var classNames = (value || "").split(rspace); for ( var i = 0, l = this.length; i < l; i++ ) { var elem = this[i]; if ( elem.nodeType === 1 && elem.className ) { if ( value ) { var className = (" " + elem.className + " ").replace(rclass, " "); for ( var c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[c] + " ", " "); } elem.className = jQuery.trim( className ); } else { elem.className = ""; } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this); self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); }); } return this.each(function() { if ( type === "string" ) { var className, i = 0, self = jQuery(this), state = stateVal, classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { jQuery.data( this, "__className__", this.className ); } this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; } }); }, hasClass: function( selector ) { var className = " " + selector + " "; for ( var i = 0, l = this.length; i < l; i++ ) { if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, val: function( value ) { if ( value === undefined ) { var elem = this[0]; if ( elem ) { if ( jQuery.nodeName( elem, "option" ) ) { return (elem.attributes.value || {}).specified ? elem.value : elem.text; } if ( jQuery.nodeName( elem, "select" ) ) { var index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; if ( index < 0 ) { return null; } for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { var option = options[ i ]; if ( option.selected ) { value = jQuery(option).val(); if ( one ) { return value; } values.push( value ); } } return values; } if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { return elem.getAttribute("value") === null ? "on" : elem.value; } return (elem.value || "").replace(rreturn, ""); } return undefined; } var isFunction = jQuery.isFunction(value); return this.each(function(i) { var self = jQuery(this), val = value; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call(this, i, self.val()); } if ( typeof val === "number" ) { val += ""; } if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { this.checked = jQuery.inArray( self.val(), val ) >= 0; } else if ( jQuery.nodeName( this, "select" ) ) { var values = jQuery.makeArray(val); jQuery( "option", this ).each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { this.selectedIndex = -1; } } else { this.value = val; } }); } }); jQuery.extend({ attrFn: { val: true, css: true, html: true, text: true, data: true, width: true, height: true, offset: true }, attr: function( elem, name, value, pass ) { if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { return undefined; } if ( pass && name in jQuery.attrFn ) { return jQuery(elem)[name](value); } var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), set = value !== undefined; name = notxml && jQuery.props[ name ] || name; if ( elem.nodeType === 1 ) { var special = rspecialurl.test( name ); if ( name === "selected" && !jQuery.support.optSelected ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } if ( name in elem && notxml && !special ) { if ( set ) { if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); } elem[ name ] = value; } if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { return elem.getAttributeNode( name ).nodeValue; } if ( name === "tabIndex" ) { var attributeNode = elem.getAttributeNode( "tabIndex" ); return attributeNode && attributeNode.specified ? attributeNode.value : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } return elem[ name ]; } if ( !jQuery.support.style && notxml && name === "style" ) { if ( set ) { elem.style.cssText = "" + value; } return elem.style.cssText; } if ( set ) { elem.setAttribute( name, "" + value ); } var attr = !jQuery.support.hrefNormalized && notxml && special ? elem.getAttribute( name, 2 ) : elem.getAttribute( name ); return attr === null ? undefined : attr; } return jQuery.style( elem, name, value ); } }); var rnamespaces = /\.(.*)$/, fcleanup = function( nm ) { return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { return "\\" + ch; }); }; jQuery.event = { add: function( elem, types, handler, data ) { if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { elem = window; } var handleObjIn, handleObj; if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; } if ( !handler.guid ) { handler.guid = jQuery.guid++; } var elemData = jQuery.data( elem ); if ( !elemData ) { return; } var events = elemData.events = elemData.events || {}, eventHandle = elemData.handle, eventHandle; if ( !eventHandle ) { elemData.handle = eventHandle = function() { return typeof jQuery !== "undefined" && !jQuery.event.triggered ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; }; } eventHandle.elem = elem; types = types.split(" "); var type, i = 0, namespaces; while ( (type = types[ i++ ]) ) { handleObj = handleObjIn ? jQuery.extend({}, handleObjIn) : { handler: handler, data: data }; if ( type.indexOf(".") > -1 ) { namespaces = type.split("."); type = namespaces.shift(); handleObj.namespace = namespaces.slice(0).sort().join("."); } else { namespaces = []; handleObj.namespace = ""; } handleObj.type = type; handleObj.guid = handler.guid; var handlers = events[ type ], special = jQuery.event.special[ type ] || {}; if ( !handlers ) { handlers = events[ type ] = []; if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } handlers.push( handleObj ); jQuery.event.global[ type ] = true; } elem = null; }, global: {}, remove: function( elem, types, handler, pos ) { if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, elemData = jQuery.data( elem ), events = elemData && elemData.events; if ( !elemData || !events ) { return; } if ( types && types.type ) { handler = types.handler; types = types.type; } if ( !types || typeof types === "string" && types.charAt(0) === "." ) { types = types || ""; for ( type in events ) { jQuery.event.remove( elem, type + types ); } return; } types = types.split(" "); while ( (type = types[ i++ ]) ) { origType = type; handleObj = null; all = type.indexOf(".") < 0; namespaces = []; if ( !all ) { namespaces = type.split("."); type = namespaces.shift(); namespace = new RegExp("(^|\\.)" + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") } eventType = events[ type ]; if ( !eventType ) { continue; } if ( !handler ) { for ( var j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( all || namespace.test( handleObj.namespace ) ) { jQuery.event.remove( elem, origType, handleObj.handler, j ); eventType.splice( j--, 1 ); } } continue; } special = jQuery.event.special[ type ] || {}; for ( var j = pos || 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( handler.guid === handleObj.guid ) { if ( all || namespace.test( handleObj.namespace ) ) { if ( pos == null ) { eventType.splice( j--, 1 ); } if ( special.remove ) { special.remove.call( elem, handleObj ); } } if ( pos != null ) { break; } } } if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { removeEvent( elem, type, elemData.handle ); } ret = null; delete events[ type ]; } } if ( jQuery.isEmptyObject( events ) ) { var handle = elemData.handle; if ( handle ) { handle.elem = null; } delete elemData.events; delete elemData.handle; if ( jQuery.isEmptyObject( elemData ) ) { jQuery.removeData( elem ); } } }, trigger: function( event, data, elem ) { var type = event.type || event, bubbling = arguments[3]; if ( !bubbling ) { event = typeof event === "object" ? event[expando] ? event : jQuery.extend( jQuery.Event(type), event ) : jQuery.Event(type); if ( type.indexOf("!") >= 0 ) { event.type = type = type.slice(0, -1); event.exclusive = true; } if ( !elem ) { event.stopPropagation(); if ( jQuery.event.global[ type ] ) { jQuery.each( jQuery.cache, function() { if ( this.events && this.events[type] ) { jQuery.event.trigger( event, data, this.handle.elem ); } }); } } if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { return undefined; } event.result = undefined; event.target = elem; data = jQuery.makeArray( data ); data.unshift( event ); } event.currentTarget = elem; var handle = jQuery.data( elem, "handle" ); if ( handle ) { handle.apply( elem, data ); } var parent = elem.parentNode || elem.ownerDocument; try { if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { event.result = false; } } } catch (e) {} if ( !event.isPropagationStopped() && parent ) { jQuery.event.trigger( event, data, parent, true ); } else if ( !event.isDefaultPrevented() ) { var target = event.target, old, isClick = jQuery.nodeName(target, "a") && type === "click", special = jQuery.event.special[ type ] || {}; if ( (!special._default || special._default.call( elem, event ) === false) && !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { try { if ( target[ type ] ) { old = target[ "on" + type ]; if ( old ) { target[ "on" + type ] = null; } jQuery.event.triggered = true; target[ type ](); } } catch (e) {} if ( old ) { target[ "on" + type ] = old; } jQuery.event.triggered = false; } } }, handle: function( event ) { var all, handlers, namespaces, namespace, events; event = arguments[0] = jQuery.event.fix( event || window.event ); event.currentTarget = this; all = event.type.indexOf(".") < 0 && !event.exclusive; if ( !all ) { namespaces = event.type.split("."); event.type = namespaces.shift(); namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); } var events = jQuery.data(this, "events"), handlers = events[ event.type ]; if ( events && handlers ) { handlers = handlers.slice(0); for ( var j = 0, l = handlers.length; j < l; j++ ) { var handleObj = handlers[ j ]; if ( all || namespace.test( handleObj.namespace ) ) { event.handler = handleObj.handler; event.data = handleObj.data; event.handleObj = handleObj; var ret = handleObj.handler.apply( this, arguments ); if ( ret !== undefined ) { event.result = ret; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } if ( event.isImmediatePropagationStopped() ) { break; } } } } return event.result; }, props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), fix: function( event ) { if ( event[ expando ] ) { return event; } var originalEvent = event; event = jQuery.Event( originalEvent ); for ( var i = this.props.length, prop; i; ) { prop = this.props[ --i ]; event[ prop ] = originalEvent[ prop ]; } if ( !event.target ) { event.target = event.srcElement || document; } if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } if ( !event.relatedTarget && event.fromElement ) { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } if ( event.pageX == null && event.clientX != null ) { var doc = document.documentElement, body = document.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { event.which = event.charCode || event.keyCode; } if ( !event.metaKey && event.ctrlKey ) { event.metaKey = event.ctrlKey; } if ( !event.which && event.button !== undefined ) { event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); } return event; }, guid: 1E8, proxy: jQuery.proxy, special: { ready: { setup: jQuery.bindReady, teardown: jQuery.noop }, live: { add: function( handleObj ) { jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); }, remove: function( handleObj ) { var remove = true, type = handleObj.origType.replace(rnamespaces, ""); jQuery.each( jQuery.data(this, "events").live || [], function() { if ( type === this.origType.replace(rnamespaces, "") ) { remove = false; return false; } }); if ( remove ) { jQuery.event.remove( this, handleObj.origType, liveHandler ); } } }, beforeunload: { setup: function( data, namespaces, eventHandle ) { if ( this.setInterval ) { this.onbeforeunload = eventHandle; } return false; }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } } }; var removeEvent = document.removeEventListener ? function( elem, type, handle ) { elem.removeEventListener( type, handle, false ); } : function( elem, type, handle ) { elem.detachEvent( "on" + type, handle ); }; jQuery.Event = function( src ) { if ( !this.preventDefault ) { return new jQuery.Event( src ); } if ( src && src.type ) { this.originalEvent = src; this.type = src.type; } else { this.type = src; } this.timeStamp = now(); this[ expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } jQuery.Event.prototype = { preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } if ( e.preventDefault ) { e.preventDefault(); } e.returnValue = false; }, stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } if ( e.stopPropagation ) { e.stopPropagation(); } e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; var withinElement = function( event ) { var parent = event.relatedTarget; try { while ( parent && parent !== this ) { parent = parent.parentNode; } if ( parent !== this ) { event.type = event.data; jQuery.event.handle.apply( this, arguments ); } } catch(e) { } }, delegate = function( event ) { event.type = event.data; jQuery.event.handle.apply( this, arguments ); }; jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { setup: function( data ) { jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); }, teardown: function( data ) { jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); } }; }); if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function( data, namespaces ) { if ( this.nodeName.toLowerCase() !== "form" ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { return trigger( "submit", this, arguments ); } }); jQuery.event.add(this, "keypress.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { return trigger( "submit", this, arguments ); } }); } else { return false; } }, teardown: function( namespaces ) { jQuery.event.remove( this, ".specialSubmit" ); } }; } if ( !jQuery.support.changeBubbles ) { var formElems = /textarea|input|select/i, changeFilters, getVal = function( elem ) { var type = elem.type, val = elem.value; if ( type === "radio" || type === "checkbox" ) { val = elem.checked; } else if ( type === "select-multiple" ) { val = elem.selectedIndex > -1 ? jQuery.map( elem.options, function( elem ) { return elem.selected; }).join("-") : ""; } else if ( elem.nodeName.toLowerCase() === "select" ) { val = elem.selectedIndex; } return val; }, testChange = function testChange( e ) { var elem = e.target, data, val; if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { return; } data = jQuery.data( elem, "_change_data" ); val = getVal(elem); if ( e.type !== "focusout" || elem.type !== "radio" ) { jQuery.data( elem, "_change_data", val ); } if ( data === undefined || val === data ) { return; } if ( data != null || val ) { e.type = "change"; return jQuery.event.trigger( e, arguments[1], elem ); } }; jQuery.event.special.change = { filters: { focusout: testChange, click: function( e ) { var elem = e.target, type = elem.type; if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { return testChange.call( this, e ); } }, keydown: function( e ) { var elem = e.target, type = elem.type; if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || type === "select-multiple" ) { return testChange.call( this, e ); } }, beforeactivate: function( e ) { var elem = e.target; jQuery.data( elem, "_change_data", getVal(elem) ); } }, setup: function( data, namespaces ) { if ( this.type === "file" ) { return false; } for ( var type in changeFilters ) { jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); } return formElems.test( this.nodeName ); }, teardown: function( namespaces ) { jQuery.event.remove( this, ".specialChange" ); return formElems.test( this.nodeName ); } }; changeFilters = jQuery.event.special.change.filters; } function trigger( type, elem, args ) { args[0].type = type; return jQuery.event.handle.apply( elem, args ); } if ( document.addEventListener ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { jQuery.event.special[ fix ] = { setup: function() { this.addEventListener( orig, handler, true ); }, teardown: function() { this.removeEventListener( orig, handler, true ); } }; function handler( e ) { e = jQuery.event.fix( e ); e.type = fix; return jQuery.event.handle.call( this, e ); } }); } jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { if ( typeof type === "object" ) { for ( var key in type ) { this[ name ](key, data, type[key], fn); } return this; } if ( jQuery.isFunction( data ) ) { fn = data; data = undefined; } var handler = name === "one" ? jQuery.proxy( fn, function( event ) { jQuery( this ).unbind( event, handler ); return fn.apply( this, arguments ); }) : fn; if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); } else { for ( var i = 0, l = this.length; i < l; i++ ) { jQuery.event.add( this[i], type, handler, data ); } } return this; }; }); jQuery.fn.extend({ unbind: function( type, fn ) { if ( typeof type === "object" && !type.preventDefault ) { for ( var key in type ) { this.unbind(key, type[key]); } } else { for ( var i = 0, l = this.length; i < l; i++ ) { jQuery.event.remove( this[i], type, fn ); } } return this; }, delegate: function( selector, types, data, fn ) { return this.live( types, data, fn, selector ); }, undelegate: function( selector, types, fn ) { if ( arguments.length === 0 ) { return this.unbind( "live" ); } else { return this.die( types, null, fn, selector ); } }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { if ( this[0] ) { var event = jQuery.Event( type ); event.preventDefault(); event.stopPropagation(); jQuery.event.trigger( event, data, this[0] ); return event.result; } }, toggle: function( fn ) { var args = arguments, i = 1; while ( i < args.length ) { jQuery.proxy( fn, args[ i++ ] ); } return this.click( jQuery.proxy( fn, function( event ) { var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); event.preventDefault(); return args[ lastToggle ].apply( this, arguments ) || false; })); }, hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); var liveMap = { focus: "focusin", blur: "focusout", mouseenter: "mouseover", mouseleave: "mouseout" }; jQuery.each(["live", "die"], function( i, name ) { jQuery.fn[ name ] = function( types, data, fn, origSelector ) { var type, i = 0, match, namespaces, preType, selector = origSelector || this.selector, context = origSelector ? this : jQuery( this.context ); if ( jQuery.isFunction( data ) ) { fn = data; data = undefined; } types = (types || "").split(" "); while ( (type = types[ i++ ]) != null ) { match = rnamespaces.exec( type ); namespaces = ""; if ( match ) { namespaces = match[0]; type = type.replace( rnamespaces, "" ); } if ( type === "hover" ) { types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); continue; } preType = type; if ( type === "focus" || type === "blur" ) { types.push( liveMap[ type ] + namespaces ); type = type + namespaces; } else { type = (liveMap[ type ] || type) + namespaces; } if ( name === "live" ) { context.each(function(){ jQuery.event.add( this, liveConvert( type, selector ), { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); }); } else { context.unbind( liveConvert( type, selector ), fn ); } } return this; } }); function liveHandler( event ) { var stop, elems = [], selectors = [], args = arguments, related, match, handleObj, elem, j, i, l, data, events = jQuery.data( this, "events" ); if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { return; } event.liveFired = this; var live = events.live.slice(0); for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { selectors.push( handleObj.selector ); } else { live.splice( j--, 1 ); } } match = jQuery( event.target ).closest( selectors, event.currentTarget ); for ( i = 0, l = match.length; i < l; i++ ) { for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; if ( match[i].selector === handleObj.selector ) { elem = match[i].elem; related = null; if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; } if ( !related || related !== elem ) { elems.push({ elem: elem, handleObj: handleObj }); } } } } for ( i = 0, l = elems.length; i < l; i++ ) { match = elems[i]; event.currentTarget = match.elem; event.data = match.handleObj.data; event.handleObj = match.handleObj; if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { stop = false; break; } } return stop; } function liveConvert( type, selector ) { return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); } jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { jQuery.fn[ name ] = function( fn ) { return fn ? this.bind( name, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } }); if ( window.attachEvent && !window.addEventListener ) { window.attachEvent("onunload", function() { for ( var id in jQuery.cache ) { if ( jQuery.cache[ id ].handle ) { try { jQuery.event.remove( jQuery.cache[ id ].handle.elem ); } catch(e) {} } } }); } (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true; [0, 0].sort(function(){ baseHasDuplicate = false; return 0; }); var Sizzle = function(selector, context, results, seed) { results = results || []; var origContext = context = context || document; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), soFar = selector; while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) { selector += parts.shift(); } set = posProcess( selector, set ); } } } else { if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { var ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { var ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray(set); } else { prune = false; } while ( parts.length ) { var cur = parts.pop(), pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function(results){ if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort(sortOrder); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[i-1] ) { results.splice(i--, 1); } } } } return results; }; Sizzle.matches = function(expr, set){ return Sizzle(expr, null, null, set); }; Sizzle.find = function(expr, context, isXML){ var set, match; if ( !expr ) { return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var type = Expr.order[i], match; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; match.splice(1,1); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = context.getElementsByTagName("*"); } return {set: set, expr: expr}; }; Sizzle.filter = function(expr, set, inplace, not){ var old = expr, result = [], curLoop = set, match, anyFound, isXMLFilter = set && set[0] && isXML(set[0]); while ( expr && set.length ) { for ( var type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { var filter = Expr.filter[ type ], found, item, left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw "Syntax error, unrecognized expression: " + msg; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function(elem){ return elem.getAttribute("href"); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !/\W/.test(part), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function(checkSet, part){ var isPartStr = typeof part === "string"; if ( isPartStr && !/\W/.test(part) ) { part = part.toLowerCase(); for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !/\W/.test(part) ) { var nodeCheck = part = part.toLowerCase(); checkFn = dirNodeCheck; } checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); }, "~": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !/\W/.test(part) ) { var nodeCheck = part = part.toLowerCase(); checkFn = dirNodeCheck; } checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); } }, find: { ID: function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? [m] : []; } }, NAME: function(match, context){ if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName(match[1]); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function(match, context){ return context.getElementsByTagName(match[1]); } }, preFilter: { CLASS: function(match, curLoop, inplace, result, not, isXML){ match = " " + match[1].replace(/\\/g, "") + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function(match){ return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ return match[1].toLowerCase(); }, CHILD: function(match){ if ( match[1] === "nth" ) { var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } match[0] = done++; return match; }, ATTR: function(match, curLoop, inplace, result, not, isXML){ var name = match[1].replace(/\\/g, ""); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function(match, curLoop, inplace, result, not){ if ( match[1] === "not" ) { if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function(match){ match.unshift( true ); return match; } }, filters: { enabled: function(elem){ return elem.disabled === false && elem.type !== "hidden"; }, disabled: function(elem){ return elem.disabled === true; }, checked: function(elem){ return elem.checked === true; }, selected: function(elem){ elem.parentNode.selectedIndex; return elem.selected === true; }, parent: function(elem){ return !!elem.firstChild; }, empty: function(elem){ return !elem.firstChild; }, has: function(elem, i, match){ return !!Sizzle( match[3], elem ).length; }, header: function(elem){ return /h\d/i.test( elem.nodeName ); }, text: function(elem){ return "text" === elem.type; }, radio: function(elem){ return "radio" === elem.type; }, checkbox: function(elem){ return "checkbox" === elem.type; }, file: function(elem){ return "file" === elem.type; }, password: function(elem){ return "password" === elem.type; }, submit: function(elem){ return "submit" === elem.type; }, image: function(elem){ return "image" === elem.type; }, reset: function(elem){ return "reset" === elem.type; }, button: function(elem){ return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; }, input: function(elem){ return /input|select|textarea|button/i.test(elem.nodeName); } }, setFilters: { first: function(elem, i){ return i === 0; }, last: function(elem, i, match, array){ return i === array.length - 1; }, even: function(elem, i){ return i % 2 === 0; }, odd: function(elem, i){ return i % 2 === 1; }, lt: function(elem, i, match){ return i < match[3] - 0; }, gt: function(elem, i, match){ return i > match[3] - 0; }, nth: function(elem, i, match){ return match[3] - 0 === i; }, eq: function(elem, i, match){ return match[3] - 0 === i; } }, filter: { PSEUDO: function(elem, match, i, array){ var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var i = 0, l = not.length; i < l; i++ ) { if ( not[i] === elem ) { return false; } } return true; } else { Sizzle.error( "Syntax error, unrecognized expression: " + name ); } }, CHILD: function(elem, match){ var type = match[1], node = elem; switch (type) { case 'only': case 'first': while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } if ( type === "first" ) { return true; } node = elem; case 'last': while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } return true; case 'nth': var first = match[2], last = match[3]; if ( first === 1 && last === 0 ) { return true; } var doneName = match[0], parent = elem.parentNode; if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent.sizcache = doneName; } var diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, ID: function(elem, match){ return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function(elem, match){ return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, CLASS: function(elem, match){ return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function(elem, match){ var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function(elem, match, i, array){ var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ return "\\" + (num - 0 + 1); })); } var makeArray = function(array, results) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; } catch(e){ makeArray = function(array, results) { var ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var i = 0, l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( var i = 0; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { if ( a == b ) { hasDuplicate = true; } return a.compareDocumentPosition ? -1 : 1; } var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } else if ( "sourceIndex" in document.documentElement ) { sortOrder = function( a, b ) { if ( !a.sourceIndex || !b.sourceIndex ) { if ( a == b ) { hasDuplicate = true; } return a.sourceIndex ? -1 : 1; } var ret = a.sourceIndex - b.sourceIndex; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } else if ( document.createRange ) { sortOrder = function( a, b ) { if ( !a.ownerDocument || !b.ownerDocument ) { if ( a == b ) { hasDuplicate = true; } return a.ownerDocument ? -1 : 1; } var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); aRange.setStart(a, 0); aRange.setEnd(a, 0); bRange.setStart(b, 0); bRange.setEnd(b, 0); var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } function getText( elems ) { var ret = "", elem; for ( var i = 0; elems[i]; i++ ) { elem = elems[i]; if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; } else if ( elem.nodeType !== 8 ) { ret += getText( elem.childNodes ); } } return ret; } (function(){ var form = document.createElement("div"), id = "script" + (new Date).getTime(); form.innerHTML = ""; var root = document.documentElement; root.insertBefore( form, root.firstChild ); if ( document.getElementById( id ) ) { Expr.find.ID = function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function(elem, match){ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); root = form = null; })(); (function(){ var div = document.createElement("div"); div.appendChild( document.createComment("") ); if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function(match, context){ var results = context.getElementsByTagName(match[1]); if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } div.innerHTML = ""; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function(elem){ return elem.getAttribute("href", 2); }; } div = null; })(); if ( document.querySelectorAll ) { (function(){ var oldSizzle = Sizzle, div = document.createElement("div"); div.innerHTML = "

"; if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function(query, context, extra, seed){ context = context || document; if ( !seed && context.nodeType === 9 && !isXML(context) ) { try { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } div = null; })(); } (function(){ var div = document.createElement("div"); div.innerHTML = "
"; if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; } div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) { return; } Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context, isXML) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem.sizcache = doneName; elem.sizset = i; } if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem.sizcache = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } var contains = document.compareDocumentPosition ? function(a, b){ return !!(a.compareDocumentPosition(b) & 16); } : function(a, b){ return a !== b && (a.contains ? a.contains(b) : true); }; var isXML = function(elem){ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function(selector, context){ var tmpSet = [], later = "", match, root = context.nodeType ? [context] : context; while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet ); } return Sizzle.filter( later, tmpSet ); }; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; jQuery.unique = Sizzle.uniqueSort; jQuery.text = getText; jQuery.isXMLDoc = isXML; jQuery.contains = contains; return; window.Sizzle = Sizzle; })(); var runtil = /Until$/, rparentsprev = /^(?:parents|prevUntil|prevAll)/, rmultiselector = /,/, slice = Array.prototype.slice; var winnow = function( elements, qualifier, keep ) { if ( jQuery.isFunction( qualifier ) ) { return jQuery.grep(elements, function( elem, i ) { return !!qualifier.call( elem, i, elem ) === keep; }); } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return (elem === qualifier) === keep; }); } else if ( typeof qualifier === "string" ) { var filtered = jQuery.grep(elements, function( elem ) { return elem.nodeType === 1; }); if ( isSimple.test( qualifier ) ) { return jQuery.filter(qualifier, filtered, !keep); } else { qualifier = jQuery.filter( qualifier, filtered ); } } return jQuery.grep(elements, function( elem, i ) { return (jQuery.inArray( elem, qualifier ) >= 0) === keep; }); }; jQuery.fn.extend({ find: function( selector ) { var ret = this.pushStack( "", "find", selector ), length = 0; for ( var i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { for ( var n = length; n < ret.length; n++ ) { for ( var r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; }, has: function( target ) { var targets = jQuery( target ); return this.filter(function() { for ( var i = 0, l = targets.length; i < l; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, not: function( selector ) { return this.pushStack( winnow(this, selector, false), "not", selector); }, filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, is: function( selector ) { return !!selector && jQuery.filter( selector, this ).length > 0; }, closest: function( selectors, context ) { if ( jQuery.isArray( selectors ) ) { var ret = [], cur = this[0], match, matches = {}, selector; if ( cur && selectors.length ) { for ( var i = 0, l = selectors.length; i < l; i++ ) { selector = selectors[i]; if ( !matches[selector] ) { matches[selector] = jQuery.expr.match.POS.test( selector ) ? jQuery( selector, context || this.context ) : selector; } } while ( cur && cur.ownerDocument && cur !== context ) { for ( selector in matches ) { match = matches[selector]; if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { ret.push({ selector: selector, elem: cur }); delete matches[selector]; } } cur = cur.parentNode; } } return ret; } var pos = jQuery.expr.match.POS.test( selectors ) ? jQuery( selectors, context || this.context ) : null; return this.map(function( i, cur ) { while ( cur && cur.ownerDocument && cur !== context ) { if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { return cur; } cur = cur.parentNode; } return null; }); }, index: function( elem ) { if ( !elem || typeof elem === "string" ) { return jQuery.inArray( this[0], elem ? jQuery( elem ) : this.parent().children() ); } return jQuery.inArray( elem.jquery ? elem[0] : elem, this ); }, add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context || this.context ) : jQuery.makeArray( selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? all : jQuery.unique( all ) ); }, andSelf: function() { return this.add( this.prevObject ); } }); function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; } jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return jQuery.nth( elem, 2, "nextSibling" ); }, prev: function( elem ) { return jQuery.nth( elem, 2, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( elem.parentNode.firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.makeArray( elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ); if ( !runtil.test( name ) ) { selector = until; } if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } ret = this.length > 1 ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } return this.pushStack( ret, name, slice.call(arguments).join(",") ); }; }); jQuery.extend({ filter: function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return jQuery.find.matches(expr, elems); }, dir: function( elem, dir, until ) { var matched = [], cur = elem[dir]; while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, nth: function( cur, result, dir, elem ) { result = result || 1; var num = 0; for ( ; cur; cur = cur[dir] ) { if ( cur.nodeType === 1 && ++num === result ) { break; } } return cur; }, sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, rtagName = /<([\w:]+)/, rtbody = /"; }, wrapMap = { option: [ 1, "" ], legend: [ 1, "
", "
" ], thead: [ 1, "", "
" ], tr: [ 2, "", "
" ], td: [ 3, "", "
" ], col: [ 2, "", "
" ], area: [ 1, "", "" ], _default: [ 0, "", "" ] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; if ( !jQuery.support.htmlSerialize ) { wrapMap._default = [ 1, "div
", "
" ]; } jQuery.fn.extend({ text: function( text ) { if ( jQuery.isFunction(text) ) { return this.each(function(i) { var self = jQuery(this); self.text( text.call(this, i, self.text()) ); }); } if ( typeof text !== "object" && text !== undefined ) { return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); } return jQuery.text( this ); }, wrapAll: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapAll( html.call(this, i) ); }); } if ( this[0] ) { var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); if ( this[0].parentNode ) { wrap.insertBefore( this[0] ); } wrap.map(function() { var elem = this; while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { elem = elem.firstChild; } return elem; }).append(this); } return this; }, wrapInner: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapInner( html.call(this, i) ); }); } return this.each(function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } }); }, wrap: function( html ) { return this.each(function() { jQuery( this ).wrapAll( html ); }); }, unwrap: function() { return this.parent().each(function() { if ( !jQuery.nodeName( this, "body" ) ) { jQuery( this ).replaceWith( this.childNodes ); } }).end(); }, append: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.appendChild( elem ); } }); }, prepend: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.insertBefore( elem, this.firstChild ); } }); }, before: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this ); }); } else if ( arguments.length ) { var set = jQuery(arguments[0]); set.push.apply( set, this.toArray() ); return this.pushStack( set, "before", arguments ); } }, after: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this.nextSibling ); }); } else if ( arguments.length ) { var set = this.pushStack( this, "after", arguments ); set.push.apply( set, jQuery(arguments[0]).toArray() ); return set; } }, remove: function( selector, keepData ) { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { if ( !keepData && elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); jQuery.cleanData( [ elem ] ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } } return this; }, empty: function() { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { if ( elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); } while ( elem.firstChild ) { elem.removeChild( elem.firstChild ); } } return this; }, clone: function( events ) { var ret = this.map(function() { if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { var html = this.outerHTML, ownerDocument = this.ownerDocument; if ( !html ) { var div = ownerDocument.createElement("div"); div.appendChild( this.cloneNode(true) ); html = div.innerHTML; } return jQuery.clean([html.replace(rinlinejQuery, "") .replace(/=([^="'>\s]+\/)>/g, '="$1">') .replace(rleadingWhitespace, "")], ownerDocument)[0]; } else { return this.cloneNode(true); } }); if ( events === true ) { cloneCopyEvent( this, ret ); cloneCopyEvent( this.find("*"), ret.find("*") ); } return ret; }, html: function( value ) { if ( value === undefined ) { return this[0] && this[0].nodeType === 1 ? this[0].innerHTML.replace(rinlinejQuery, "") : null; } else if ( typeof value === "string" && !rnocache.test( value ) && (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { value = value.replace(rxhtmlTag, fcloseTag); try { for ( var i = 0, l = this.length; i < l; i++ ) { if ( this[i].nodeType === 1 ) { jQuery.cleanData( this[i].getElementsByTagName("*") ); this[i].innerHTML = value; } } } catch(e) { this.empty().append( value ); } } else if ( jQuery.isFunction( value ) ) { this.each(function(i){ var self = jQuery(this), old = self.html(); self.empty().append(function(){ return value.call( this, i, old ); }); }); } else { this.empty().append( value ); } return this; }, replaceWith: function( value ) { if ( this[0] && this[0].parentNode ) { if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this), old = self.html(); self.replaceWith( value.call( this, i, old ) ); }); } if ( typeof value !== "string" ) { value = jQuery(value).detach(); } return this.each(function() { var next = this.nextSibling, parent = this.parentNode; jQuery(this).remove(); if ( next ) { jQuery(next).before( value ); } else { jQuery(parent).append( value ); } }); } else { return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); } }, detach: function( selector ) { return this.remove( selector, true ); }, domManip: function( args, table, callback ) { var results, first, value = args[0], scripts = [], fragment, parent; if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { return this.each(function() { jQuery(this).domManip( args, table, callback, true ); }); } if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); args[0] = value.call(this, i, table ? self.html() : undefined); self.domManip( args, table, callback ); }); } if ( this[0] ) { parent = value && value.parentNode; if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { results = { fragment: parent }; } else { results = buildFragment( args, this, scripts ); } fragment = results.fragment; if ( fragment.childNodes.length === 1 ) { first = fragment = fragment.firstChild; } else { first = fragment.firstChild; } if ( first ) { table = table && jQuery.nodeName( first, "tr" ); for ( var i = 0, l = this.length; i < l; i++ ) { callback.call( table ? root(this[i], first) : this[i], i > 0 || results.cacheable || this.length > 1 ? fragment.cloneNode(true) : fragment ); } } if ( scripts.length ) { jQuery.each( scripts, evalScript ); } } return this; function root( elem, cur ) { return jQuery.nodeName(elem, "table") ? (elem.getElementsByTagName("tbody")[0] || elem.appendChild(elem.ownerDocument.createElement("tbody"))) : elem; } } }); function cloneCopyEvent(orig, ret) { var i = 0; ret.each(function() { if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { return; } var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events; if ( events ) { delete curData.handle; curData.events = {}; for ( var type in events ) { for ( var handler in events[ type ] ) { jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); } } } }); } function buildFragment( args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { cacheable = true; cacheresults = jQuery.fragments[ args[0] ]; if ( cacheresults ) { if ( cacheresults !== 1 ) { fragment = cacheresults; } } } if ( !fragment ) { fragment = doc.createDocumentFragment(); jQuery.clean( args, doc, fragment, scripts ); } if ( cacheable ) { jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; } return { fragment: fragment, cacheable: cacheable }; } jQuery.fragments = {}; jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var ret = [], insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); return this; } else { for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = (i > 0 ? this.clone(true) : this).get(); jQuery.fn[ original ].apply( jQuery(insert[i]), elems ); ret = ret.concat( elems ); } return this.pushStack( ret, name, insert.selector ); } }; }); jQuery.extend({ clean: function( elems, context, fragment, scripts ) { context = context || document; if ( typeof context.createElement === "undefined" ) { context = context.ownerDocument || context[0] && context[0].ownerDocument || document; } var ret = []; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { elem += ""; } if ( !elem ) { continue; } if ( typeof elem === "string" && !rhtml.test( elem ) ) { elem = context.createTextNode( elem ); } else if ( typeof elem === "string" ) { elem = elem.replace(rxhtmlTag, fcloseTag); var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], div = context.createElement("div"); div.innerHTML = wrap[1] + elem + wrap[2]; while ( depth-- ) { div = div.lastChild; } if ( !jQuery.support.tbody ) { var hasBody = rtbody.test(elem), tbody = tag === "table" && !hasBody ? div.firstChild && div.firstChild.childNodes : wrap[1] === "" && !hasBody ? div.childNodes : []; for ( var j = tbody.length - 1; j >= 0 ; --j ) { if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { tbody[ j ].parentNode.removeChild( tbody[ j ] ); } } } if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); } elem = div.childNodes; } if ( elem.nodeType ) { ret.push( elem ); } else { ret = jQuery.merge( ret, elem ); } } if ( fragment ) { for ( var i = 0; ret[i]; i++ ) { if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); } else { if ( ret[i].nodeType === 1 ) { ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); } fragment.appendChild( ret[i] ); } } } return ret; }, cleanData: function( elems ) { var data, id, cache = jQuery.cache, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { id = elem[ jQuery.expando ]; if ( id ) { data = cache[ id ]; if ( data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); } else { removeEvent( elem, type, data.handle ); } } } if ( deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } delete cache[ id ]; } } } }); var rexclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, ralpha = /alpha\([^)]*\)/, ropacity = /opacity=([^)]*)/, rfloat = /float/i, rdashAlpha = /-([a-z])/ig, rupper = /([A-Z])/g, rnumpx = /^-?\d+(?:px)?$/i, rnum = /^-?\d/, cssShow = { position: "absolute", visibility: "hidden", display:"block" }, cssWidth = [ "Left", "Right" ], cssHeight = [ "Top", "Bottom" ], getComputedStyle = document.defaultView && document.defaultView.getComputedStyle, styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat", fcamelCase = function( all, letter ) { return letter.toUpperCase(); }; jQuery.fn.css = function( name, value ) { return access( this, name, value, true, function( elem, name, value ) { if ( value === undefined ) { return jQuery.curCSS( elem, name ); } if ( typeof value === "number" && !rexclude.test(name) ) { value += "px"; } jQuery.style( elem, name, value ); }); }; jQuery.extend({ style: function( elem, name, value ) { if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { return undefined; } if ( (name === "width" || name === "height") && parseFloat(value) < 0 ) { value = undefined; } var style = elem.style || elem, set = value !== undefined; if ( !jQuery.support.opacity && name === "opacity" ) { if ( set ) { style.zoom = 1; var opacity = parseInt( value, 10 ) + "" === "NaN" ? "" : "alpha(opacity=" + value * 100 + ")"; var filter = style.filter || jQuery.curCSS( elem, "filter" ) || ""; style.filter = ralpha.test(filter) ? filter.replace(ralpha, opacity) : opacity; } return style.filter && style.filter.indexOf("opacity=") >= 0 ? (parseFloat( ropacity.exec(style.filter)[1] ) / 100) + "": ""; } if ( rfloat.test( name ) ) { name = styleFloat; } name = name.replace(rdashAlpha, fcamelCase); if ( set ) { style[ name ] = value; } return style[ name ]; }, css: function( elem, name, force, extra ) { if ( name === "width" || name === "height" ) { var val, props = cssShow, which = name === "width" ? cssWidth : cssHeight; function getWH() { val = name === "width" ? elem.offsetWidth : elem.offsetHeight; if ( extra === "border" ) { return; } jQuery.each( which, function() { if ( !extra ) { val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; } if ( extra === "margin" ) { val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; } else { val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; } }); } if ( elem.offsetWidth !== 0 ) { getWH(); } else { jQuery.swap( elem, props, getWH ); } return Math.max(0, Math.round(val)); } return jQuery.curCSS( elem, name, force ); }, curCSS: function( elem, name, force ) { var ret, style = elem.style, filter; if ( !jQuery.support.opacity && name === "opacity" && elem.currentStyle ) { ret = ropacity.test(elem.currentStyle.filter || "") ? (parseFloat(RegExp.$1) / 100) + "" : ""; return ret === "" ? "1" : ret; } if ( rfloat.test( name ) ) { name = styleFloat; } if ( !force && style && style[ name ] ) { ret = style[ name ]; } else if ( getComputedStyle ) { if ( rfloat.test( name ) ) { name = "float"; } name = name.replace( rupper, "-$1" ).toLowerCase(); var defaultView = elem.ownerDocument.defaultView; if ( !defaultView ) { return null; } var computedStyle = defaultView.getComputedStyle( elem, null ); if ( computedStyle ) { ret = computedStyle.getPropertyValue( name ); } if ( name === "opacity" && ret === "" ) { ret = "1"; } } else if ( elem.currentStyle ) { var camelCase = name.replace(rdashAlpha, fcamelCase); ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { var left = style.left, rsLeft = elem.runtimeStyle.left; elem.runtimeStyle.left = elem.currentStyle.left; style.left = camelCase === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; style.left = left; elem.runtimeStyle.left = rsLeft; } } return ret; }, swap: function( elem, options, callback ) { var old = {}; for ( var name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } callback.call( elem ); for ( var name in options ) { elem.style[ name ] = old[ name ]; } } }); if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { var width = elem.offsetWidth, height = elem.offsetHeight, skip = elem.nodeName.toLowerCase() === "tr"; return width === 0 && height === 0 && !skip ? true : width > 0 && height > 0 && !skip ? false : jQuery.curCSS(elem, "display") === "none"; }; jQuery.expr.filters.visible = function( elem ) { return !jQuery.expr.filters.hidden( elem ); }; } var jsc = now(), rscript = //gi, rselectTextarea = /select|textarea/i, rinput = /color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i, jsre = /=\?(&|$)/, rquery = /\?/, rts = /(\?|&)_=.*?(&|$)/, rurl = /^(\w+:)?\/\/([^\/?#]+)/, r20 = /%20/g, _load = jQuery.fn.load; jQuery.fn.extend({ load: function( url, params, callback ) { if ( typeof url !== "string" ) { return _load.call( this, url ); } else if ( !this.length ) { return this; } var off = url.indexOf(" "); if ( off >= 0 ) { var selector = url.slice(off, url.length); url = url.slice(0, off); } var type = "GET"; if ( params ) { if ( jQuery.isFunction( params ) ) { callback = params; params = null; } else if ( typeof params === "object" ) { params = jQuery.param( params, jQuery.ajaxSettings.traditional ); type = "POST"; } } var self = this; jQuery.ajax({ url: url, type: type, dataType: "html", data: params, complete: function( res, status ) { if ( status === "success" || status === "notmodified" ) { self.html( selector ? jQuery("
") .append(res.responseText.replace(rscript, "")) .find(selector) : res.responseText ); } if ( callback ) { self.each( callback, [res.responseText, status, res] ); } } }); return this; }, serialize: function() { return jQuery.param(this.serializeArray()); }, serializeArray: function() { return this.map(function() { return this.elements ? jQuery.makeArray(this.elements) : this; }) .filter(function() { return this.name && !this.disabled && (this.checked || rselectTextarea.test(this.nodeName) || rinput.test(this.type)); }) .map(function( i, elem ) { var val = jQuery(this).val(); return val == null ? null : jQuery.isArray(val) ? jQuery.map( val, function( val, i ) { return { name: elem.name, value: val }; }) : { name: elem.name, value: val }; }).get(); } }); jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) { jQuery.fn[o] = function( f ) { return this.bind(o, f); }; }); jQuery.extend({ get: function( url, data, callback, type ) { if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = null; } return jQuery.ajax({ type: "GET", url: url, data: data, success: callback, dataType: type }); }, getScript: function( url, callback ) { return jQuery.get(url, null, callback, "script"); }, getJSON: function( url, data, callback ) { return jQuery.get(url, data, callback, "json"); }, post: function( url, data, callback, type ) { if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = {}; } return jQuery.ajax({ type: "POST", url: url, data: data, success: callback, dataType: type }); }, ajaxSetup: function( settings ) { jQuery.extend( jQuery.ajaxSettings, settings ); }, ajaxSettings: { url: location.href, global: true, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, xhr: window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject) ? function() { return new window.XMLHttpRequest(); } : function() { try { return new window.ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {} }, accepts: { xml: "application/xml, text/xml", html: "text/html", script: "text/javascript, application/javascript", json: "application/json, text/javascript", text: "text/plain", _default: "*/*" } }, lastModified: {}, etag: {}, ajax: function( origSettings ) { var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings); var jsonp, status, data, callbackContext = origSettings && origSettings.context || s, type = s.type.toUpperCase(); if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } if ( s.dataType === "jsonp" ) { if ( type === "GET" ) { if ( !jsre.test( s.url ) ) { s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; } } else if ( !s.data || !jsre.test(s.data) ) { s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; } s.dataType = "json"; } if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { jsonp = s.jsonpCallback || ("jsonp" + jsc++); if ( s.data ) { s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); } s.url = s.url.replace(jsre, "=" + jsonp + "$1"); s.dataType = "script"; window[ jsonp ] = window[ jsonp ] || function( tmp ) { data = tmp; success(); complete(); window[ jsonp ] = undefined; try { delete window[ jsonp ]; } catch(e) {} if ( head ) { head.removeChild( script ); } }; } if ( s.dataType === "script" && s.cache === null ) { s.cache = false; } if ( s.cache === false && type === "GET" ) { var ts = now(); var ret = s.url.replace(rts, "$1_=" + ts + "$2"); s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); } if ( s.data && type === "GET" ) { s.url += (rquery.test(s.url) ? "&" : "?") + s.data; } if ( s.global && ! jQuery.active++ ) { jQuery.event.trigger( "ajaxStart" ); } var parts = rurl.exec( s.url ), remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host); if ( s.dataType === "script" && type === "GET" && remote ) { var head = document.getElementsByTagName("head")[0] || document.documentElement; var script = document.createElement("script"); script.src = s.url; if ( s.scriptCharset ) { script.charset = s.scriptCharset; } if ( !jsonp ) { var done = false; script.onload = script.onreadystatechange = function() { if ( !done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") ) { done = true; success(); complete(); script.onload = script.onreadystatechange = null; if ( head && script.parentNode ) { head.removeChild( script ); } } }; } head.insertBefore( script, head.firstChild ); return undefined; } var requestDone = false; var xhr = s.xhr(); if ( !xhr ) { return; } if ( s.username ) { xhr.open(type, s.url, s.async, s.username, s.password); } else { xhr.open(type, s.url, s.async); } try { if ( s.data || origSettings && origSettings.contentType ) { xhr.setRequestHeader("Content-Type", s.contentType); } if ( s.ifModified ) { if ( jQuery.lastModified[s.url] ) { xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); } if ( jQuery.etag[s.url] ) { xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); } } if ( !remote ) { xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); } xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? s.accepts[ s.dataType ] + ", */*" : s.accepts._default ); } catch(e) {} if ( s.beforeSend && s.beforeSend.call(callbackContext, xhr, s) === false ) { if ( s.global && ! --jQuery.active ) { jQuery.event.trigger( "ajaxStop" ); } xhr.abort(); return false; } if ( s.global ) { trigger("ajaxSend", [xhr, s]); } var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { if ( !requestDone ) { complete(); } requestDone = true; if ( xhr ) { xhr.onreadystatechange = jQuery.noop; } } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { requestDone = true; xhr.onreadystatechange = jQuery.noop; status = isTimeout === "timeout" ? "timeout" : !jQuery.httpSuccess( xhr ) ? "error" : s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" : "success"; var errMsg; if ( status === "success" ) { try { data = jQuery.httpData( xhr, s.dataType, s ); } catch(err) { status = "parsererror"; errMsg = err; } } if ( status === "success" || status === "notmodified" ) { if ( !jsonp ) { success(); } } else { jQuery.handleError(s, xhr, status, errMsg); } complete(); if ( isTimeout === "timeout" ) { xhr.abort(); } if ( s.async ) { xhr = null; } } }; try { var oldAbort = xhr.abort; xhr.abort = function() { if ( xhr ) { oldAbort.call( xhr ); } onreadystatechange( "abort" ); }; } catch(e) { } if ( s.async && s.timeout > 0 ) { setTimeout(function() { if ( xhr && !requestDone ) { onreadystatechange( "timeout" ); } }, s.timeout); } try { xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null ); } catch(e) { jQuery.handleError(s, xhr, null, e); complete(); } if ( !s.async ) { onreadystatechange(); } function success() { if ( s.success ) { s.success.call( callbackContext, data, status, xhr ); } if ( s.global ) { trigger( "ajaxSuccess", [xhr, s] ); } } function complete() { if ( s.complete ) { s.complete.call( callbackContext, xhr, status); } if ( s.global ) { trigger( "ajaxComplete", [xhr, s] ); } if ( s.global && ! --jQuery.active ) { jQuery.event.trigger( "ajaxStop" ); } } function trigger(type, args) { (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args); } return xhr; }, handleError: function( s, xhr, status, e ) { if ( s.error ) { s.error.call( s.context || s, xhr, status, e ); } if ( s.global ) { (s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] ); } }, active: 0, httpSuccess: function( xhr ) { try { return !xhr.status && location.protocol === "file:" || ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 304 || xhr.status === 1223 || xhr.status === 0; } catch(e) {} return false; }, httpNotModified: function( xhr, url ) { var lastModified = xhr.getResponseHeader("Last-Modified"), etag = xhr.getResponseHeader("Etag"); if ( lastModified ) { jQuery.lastModified[url] = lastModified; } if ( etag ) { jQuery.etag[url] = etag; } return xhr.status === 304 || xhr.status === 0; }, httpData: function( xhr, type, s ) { var ct = xhr.getResponseHeader("content-type") || "", xml = type === "xml" || !type && ct.indexOf("xml") >= 0, data = xml ? xhr.responseXML : xhr.responseText; if ( xml && data.documentElement.nodeName === "parsererror" ) { jQuery.error( "parsererror" ); } if ( s && s.dataFilter ) { data = s.dataFilter( data, type ); } if ( typeof data === "string" ) { if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { data = jQuery.parseJSON( data ); } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { jQuery.globalEval( data ); } } return data; }, param: function( a, traditional ) { var s = []; if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } if ( jQuery.isArray(a) || a.jquery ) { jQuery.each( a, function() { add( this.name, this.value ); }); } else { for ( var prefix in a ) { buildParams( prefix, a[prefix] ); } } return s.join("&").replace(r20, "+"); function buildParams( prefix, obj ) { if ( jQuery.isArray(obj) ) { jQuery.each( obj, function( i, v ) { if ( traditional || /\[\]$/.test( prefix ) ) { add( prefix, v ); } else { buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v ); } }); } else if ( !traditional && obj != null && typeof obj === "object" ) { jQuery.each( obj, function( k, v ) { buildParams( prefix + "[" + k + "]", v ); }); } else { add( prefix, obj ); } } function add( key, value ) { value = jQuery.isFunction(value) ? value() : value; s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); } } }); var elemdisplay = {}, rfxtypes = /toggle|show|hide/, rfxnum = /^([+-]=)?([\d+-.]+)(.*)$/, timerId, fxAttrs = [ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], [ "opacity" ] ]; jQuery.fn.extend({ show: function( speed, callback ) { if ( speed || speed === 0) { return this.animate( genFx("show", 3), speed, callback); } else { for ( var i = 0, l = this.length; i < l; i++ ) { var old = jQuery.data(this[i], "olddisplay"); this[i].style.display = old || ""; if ( jQuery.css(this[i], "display") === "none" ) { var nodeName = this[i].nodeName, display; if ( elemdisplay[ nodeName ] ) { display = elemdisplay[ nodeName ]; } else { var elem = jQuery("<" + nodeName + " />").appendTo("body"); display = elem.css("display"); if ( display === "none" ) { display = "block"; } elem.remove(); elemdisplay[ nodeName ] = display; } jQuery.data(this[i], "olddisplay", display); } } for ( var j = 0, k = this.length; j < k; j++ ) { this[j].style.display = jQuery.data(this[j], "olddisplay") || ""; } return this; } }, hide: function( speed, callback ) { if ( speed || speed === 0 ) { return this.animate( genFx("hide", 3), speed, callback); } else { for ( var i = 0, l = this.length; i < l; i++ ) { var old = jQuery.data(this[i], "olddisplay"); if ( !old && old !== "none" ) { jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display")); } } for ( var j = 0, k = this.length; j < k; j++ ) { this[j].style.display = "none"; } return this; } }, _toggle: jQuery.fn.toggle, toggle: function( fn, fn2 ) { var bool = typeof fn === "boolean"; if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { this._toggle.apply( this, arguments ); } else if ( fn == null || bool ) { this.each(function() { var state = bool ? fn : jQuery(this).is(":hidden"); jQuery(this)[ state ? "show" : "hide" ](); }); } else { this.animate(genFx("toggle", 3), fn, fn2); } return this; }, fadeTo: function( speed, to, callback ) { return this.filter(":hidden").css("opacity", 0).show().end() .animate({opacity: to}, speed, callback); }, animate: function( prop, speed, easing, callback ) { var optall = jQuery.speed(speed, easing, callback); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete ); } return this[ optall.queue === false ? "each" : "queue" ](function() { var opt = jQuery.extend({}, optall), p, hidden = this.nodeType === 1 && jQuery(this).is(":hidden"), self = this; for ( p in prop ) { var name = p.replace(rdashAlpha, fcamelCase); if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; p = name; } if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { return opt.complete.call(this); } if ( ( p === "height" || p === "width" ) && this.style ) { opt.display = jQuery.css(this, "display"); opt.overflow = this.style.overflow; } if ( jQuery.isArray( prop[p] ) ) { (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; prop[p] = prop[p][0]; } } if ( opt.overflow != null ) { this.style.overflow = "hidden"; } opt.curAnim = jQuery.extend({}, prop); jQuery.each( prop, function( name, val ) { var e = new jQuery.fx( self, opt, name ); if ( rfxtypes.test(val) ) { e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); } else { var parts = rfxnum.exec(val), start = e.cur(true) || 0; if ( parts ) { var end = parseFloat( parts[2] ), unit = parts[3] || "px"; if ( unit !== "px" ) { self.style[ name ] = (end || 1) + unit; start = ((end || 1) / e.cur(true)) * start; self.style[ name ] = start + unit; } if ( parts[1] ) { end = ((parts[1] === "-=" ? -1 : 1) * end) + start; } e.custom( start, end, unit ); } else { e.custom( start, val, "" ); } } }); return true; }); }, stop: function( clearQueue, gotoEnd ) { var timers = jQuery.timers; if ( clearQueue ) { this.queue([]); } this.each(function() { for ( var i = timers.length - 1; i >= 0; i-- ) { if ( timers[i].elem === this ) { if (gotoEnd) { timers[i](true); } timers.splice(i, 1); } } }); if ( !gotoEnd ) { this.dequeue(); } return this; } }); jQuery.each({ slideDown: genFx("show", 1), slideUp: genFx("hide", 1), slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, callback ) { return this.animate( props, speed, callback ); }; }); jQuery.extend({ speed: function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? speed : { complete: fn || !fn && easing || jQuery.isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !jQuery.isFunction(easing) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default; opt.old = opt.complete; opt.complete = function() { if ( opt.queue !== false ) { jQuery(this).dequeue(); } if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } }; return opt; }, easing: { linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; } }, timers: [], fx: function( elem, options, prop ) { this.options = options; this.elem = elem; this.prop = prop; if ( !options.orig ) { options.orig = {}; } } }); jQuery.fx.prototype = { update: function() { if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); if ( ( this.prop === "height" || this.prop === "width" ) && this.elem.style ) { this.elem.style.display = "block"; } }, cur: function( force ) { if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { return this.elem[ this.prop ]; } var r = parseFloat(jQuery.css(this.elem, this.prop, force)); return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0; }, custom: function( from, to, unit ) { this.startTime = now(); this.start = from; this.end = to; this.unit = unit || this.unit || "px"; this.now = this.start; this.pos = this.state = 0; var self = this; function t( gotoEnd ) { return self.step(gotoEnd); } t.elem = this.elem; if ( t() && jQuery.timers.push(t) && !timerId ) { timerId = setInterval(jQuery.fx.tick, 13); } }, show: function() { this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.show = true; this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); jQuery( this.elem ).show(); }, hide: function() { this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.hide = true; this.custom(this.cur(), 0); }, step: function( gotoEnd ) { var t = now(), done = true; if ( gotoEnd || t >= this.options.duration + this.startTime ) { this.now = this.end; this.pos = this.state = 1; this.update(); this.options.curAnim[ this.prop ] = true; for ( var i in this.options.curAnim ) { if ( this.options.curAnim[i] !== true ) { done = false; } } if ( done ) { if ( this.options.display != null ) { this.elem.style.overflow = this.options.overflow; var old = jQuery.data(this.elem, "olddisplay"); this.elem.style.display = old ? old : this.options.display; if ( jQuery.css(this.elem, "display") === "none" ) { this.elem.style.display = "block"; } } if ( this.options.hide ) { jQuery(this.elem).hide(); } if ( this.options.hide || this.options.show ) { for ( var p in this.options.curAnim ) { jQuery.style(this.elem, p, this.options.orig[p]); } } this.options.complete.call( this.elem ); } return false; } else { var n = t - this.startTime; this.state = n / this.options.duration; var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); this.now = this.start + ((this.end - this.start) * this.pos); this.update(); } return true; } }; jQuery.extend( jQuery.fx, { tick: function() { var timers = jQuery.timers; for ( var i = 0; i < timers.length; i++ ) { if ( !timers[i]() ) { timers.splice(i--, 1); } } if ( !timers.length ) { jQuery.fx.stop(); } }, stop: function() { clearInterval( timerId ); timerId = null; }, speeds: { slow: 600, fast: 200, _default: 400 }, step: { opacity: function( fx ) { jQuery.style(fx.elem, "opacity", fx.now); }, _default: function( fx ) { if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } } } }); if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { return elem === fn.elem; }).length; }; } function genFx( type, num ) { var obj = {}; jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { obj[ this ] = type; }); return obj; } if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0]; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement, clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, top = box.top + (self.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ) - clientTop, left = box.left + (self.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft; return { top: top, left: left }; }; } else { jQuery.fn.offset = function( options ) { var elem = this[0]; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } jQuery.offset.initialize(); var offsetParent = elem.offsetParent, prevOffsetParent = elem, doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement, body = doc.body, defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, top = elem.offsetTop, left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { break; } computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; top -= elem.scrollTop; left -= elem.scrollLeft; if ( elem === offsetParent ) { top += elem.offsetTop; left += elem.offsetLeft; if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } prevOffsetParent = offsetParent, offsetParent = elem.offsetParent; } if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } prevComputedStyle = computedStyle; } if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { top += body.offsetTop; left += body.offsetLeft; } if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } return { top: top, left: left }; }; } jQuery.offset = { initialize: function() { var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0, html = "
"; jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); container.innerHTML = html; body.insertBefore( container, body.firstChild ); innerDiv = container.firstChild; checkDiv = innerDiv.firstChild; td = innerDiv.nextSibling.firstChild.firstChild; this.doesNotAddBorder = (checkDiv.offsetTop !== 5); this.doesAddBorderForTableAndCells = (td.offsetTop === 5); checkDiv.style.position = "fixed", checkDiv.style.top = "20px"; this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); checkDiv.style.position = checkDiv.style.top = ""; innerDiv.style.overflow = "hidden", innerDiv.style.position = "relative"; this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); body.removeChild( container ); body = container = innerDiv = checkDiv = table = td = null; jQuery.offset.initialize = jQuery.noop; }, bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; jQuery.offset.initialize(); if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0; left += parseFloat( jQuery.curCSS(body, "marginLeft", true) ) || 0; } return { top: top, left: left }; }, setOffset: function( elem, options, i ) { if ( /static/.test( jQuery.curCSS( elem, "position" ) ) ) { elem.style.position = "relative"; } var curElem = jQuery( elem ), curOffset = curElem.offset(), curTop = parseInt( jQuery.curCSS( elem, "top", true ), 10 ) || 0, curLeft = parseInt( jQuery.curCSS( elem, "left", true ), 10 ) || 0; if ( jQuery.isFunction( options ) ) { options = options.call( elem, i, curOffset ); } var props = { top: (options.top - curOffset.top) + curTop, left: (options.left - curOffset.left) + curLeft }; if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } } }; jQuery.fn.extend({ position: function() { if ( !this[0] ) { return null; } var elem = this[0], offsetParent = this.offsetParent(), offset = this.offset(), parentOffset = /^body|html$/i.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); offset.top -= parseFloat( jQuery.curCSS(elem, "marginTop", true) ) || 0; offset.left -= parseFloat( jQuery.curCSS(elem, "marginLeft", true) ) || 0; parentOffset.top += parseFloat( jQuery.curCSS(offsetParent[0], "borderTopWidth", true) ) || 0; parentOffset.left += parseFloat( jQuery.curCSS(offsetParent[0], "borderLeftWidth", true) ) || 0; return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }; }, offsetParent: function() { return this.map(function() { var offsetParent = this.offsetParent || document.body; while ( offsetParent && (!/^body|html$/i.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { offsetParent = offsetParent.offsetParent; } return offsetParent; }); } }); jQuery.each( ["Left", "Top"], function( i, name ) { var method = "scroll" + name; jQuery.fn[ method ] = function(val) { var elem = this[0], win; if ( !elem ) { return null; } if ( val !== undefined ) { return this.each(function() { win = getWindow( this ); if ( win ) { win.scrollTo( !i ? val : jQuery(win).scrollLeft(), i ? val : jQuery(win).scrollTop() ); } else { this[ method ] = val; } }); } else { win = getWindow( elem ); return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : jQuery.support.boxModel && win.document.documentElement[ method ] || win.document.body[ method ] : elem[ method ]; } }; }); function getWindow( elem ) { return ("scrollTo" in elem && elem.document) ? elem : elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false; } jQuery.each([ "Height", "Width" ], function( i, name ) { var type = name.toLowerCase(); jQuery.fn["inner" + name] = function() { return this[0] ? jQuery.css( this[0], type, false, "padding" ) : null; }; jQuery.fn["outer" + name] = function( margin ) { return this[0] ? jQuery.css( this[0], type, false, margin ? "margin" : "border" ) : null; }; jQuery.fn[ type ] = function( size ) { var elem = this[0]; if ( !elem ) { return size == null ? null : this; } if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); self[ type ]( size.call( this, i, self[ type ]() ) ); }); } return ("scrollTo" in elem && elem.document) ? elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || elem.document.body[ "client" + name ] : (elem.nodeType === 9) ? Math.max( elem.documentElement["client" + name], elem.body["scroll" + name], elem.documentElement["scroll" + name], elem.body["offset" + name], elem.documentElement["offset" + name] ) : size === undefined ? jQuery.css( elem, type ) : this.css( type, typeof size === "string" ? size : size + "px" ); }; }); window.jQuery = window.$ = jQuery; })(window); }); }); __e81e19bd5b7575c1b844ddd0cf4fd4e8 meta::template('comment', '\'\'; # A mechanism for line or block comments.'); meta::template('eval', <<'__eb0b1058649eb2d833f348540516b358'); my $result = eval $_[0]; terminal::warning("Error during template evaluation: $@") if $@; $result; __eb0b1058649eb2d833f348540516b358 meta::template('failing_conditional', <<'__5c593329b434a7044f68cec4b77e8ed9'); my ($commands) = @_; my $should_return = $commands =~ / if (.*)$/ && ! eval $1; terminal::warning("eval of template condition failed: $@") if $@; $should_return; __5c593329b434a7044f68cec4b77e8ed9 meta::template('include', <<'__e0624844a65ae41e0217dd871fc0dbfb'); my ($commands) = @_; return '' if template::failing_conditional($commands); join "\n", map retrieve($_), split /\s+/, $commands; __e0624844a65ae41e0217dd871fc0dbfb meta::template('pinclude', <<'__5ba61c3034a4b183881936aec30d2be9'); # 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"; __5ba61c3034a4b183881936aec30d2be9 internal::main(); __END__