#!/usr/bin/perl # 99aeabc9ec7fe80b1b39f5e53dc7e49e <- self-modifying Perl magic # This is a self-modifying Perl file. I'm sorry you're viewing the source (it's # really gnarly). If you're curious what it's made of, I recommend reading # http://github.com/spencertipping/writing-self-modifying-perl. # # If you got one of these from someone and don't know what to do with it, send # it to spencer@spencertipping.com and I'll see if I can figure out what it # does. # For the benefit of HTML viewers (this is hack): #
$|++; 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', <<'__'); # A function to configure transients. Transients can be used to store any number of # different things, but one of the more common usages is type descriptors. sub meta::configure { my ($datatype, %options) = @_; $transient{$_}{$datatype} = $options{$_} for keys %options; } __ meta::meta('externalize', <<'__'); # Function externalization. Data types should call this method when defining a function # that has an external interface. sub meta::externalize { my ($name, $attribute, $implementation) = @_; my $escaped = $name; $escaped =~ s/[^A-Za-z0-9:]/_/go; $externalized_functions{$name} = $externalized_functions{$escaped} = $attribute; *{"::$name"} = *{"::$escaped"} = $implementation || $attribute; } __ meta::meta('externalize_template', <<'__'); sub meta::externalize_template { my ($name, $implementation) = @_; meta::externalize "template::$name", "template::$name", $implementation; } __ meta::meta('functor::code-templates', <<'__'); package code; # Templates for generating named source files. sub template { my ($name, $implementation) = @_; $implementation ||= sub { my ($line, $block) = @_; my $append = $line =~ s/^\h*>>\h*//o; $line =~ s/\s+.*$//o; file::write(&{'::source-directory'}() . "/$line", ($append && "\n") . $block, mkpath => 1, append => $append); "\\lstset{caption={$line" . ($append && ' (continued)') . "},name={$line}}\\begin{${name}code}\n$block \\end{${name}code}"; }; meta::externalize_template $name, $implementation; } __ meta::meta('functor::editable', <<'__'); # An editable type. This creates a type whose default action is to open an editor # on whichever value is mentioned. This can be changed using different flags. sub meta::functor::editable { my ($typename, %options) = @_; meta::configure $typename, %options; meta::define_form $typename, sub { my ($name, $value) = @_; $options{on_bind} && &{$options{on_bind}}($name, $value); meta::externalize $options{prefix} . $name, "${typename}::$name", sub { my $attribute = "${typename}::$name"; my ($command, @new_value) = @_; return &{$options{default}}(retrieve($attribute)) if ref $options{default} eq 'CODE' and not defined $command; return edit($attribute) if $command eq 'edit' or $options{default} eq 'edit' and not defined $command; return associate($attribute, @new_value ? join(' ', @new_value) : join('', )) if $command eq '=' or $command eq 'import' or $options{default} eq 'import' and not defined $command; return retrieve($attribute)}}} __ meta::meta('functor::html-templates', <<'__'); my @html_elements = qw/html head title meta script style link body div/; # Very incomplete list for my $e (@html_elements) { meta::externalize "template::$e", "template::$e", sub { my ($line, $block) = @_; "<$e $line>\n$block\n"; }; } __ meta::meta('functor::tex-templates', <<'__'); package tex; # A wrapper for TeX templates. The idea is always the same, so I'm abstracting out the # common externalization logic here. sub template_for(&) { my ($implementation) = @_; sub { my %names = @_; for my $name (keys %names) { ::meta::externalize_template $name, sub { &$implementation($names{$name}, @_); }; } }; } sub id { map {$_ => $_} @_; } # Creates a one-line or multiline template based on normal TeX syntax. It's a straight # transfer into TeX with no preprocessing. *template = template_for { my ($name, $line, $block) = @_; $block ? "\\begin{$name}\n$block\n\\end{$name}" : "\\$name\{$line\}"; }; # Creates a labeled one-line template. This is just like normal TeX, but assumes the # specification of a label name after a pipe character. *labeled_template = template_for { my ($name, $line, undef) = @_; my ($real_stuff, $label) = split /\h*\|\h*/, $line; "\\$name\{$real_stuff\}" . ($label && "\\label{$label}"); }; __ meta::meta('template::beamer', <<'__'); tex::template tex::id(qw/frame pause item block alertblock exampleblock/); my $fframe_template = tex::template_for { my ($name, $line, $block) = @_; "\\begin{frame}[fragile]\n$block\n\\end{frame}\n"; }; &$fframe_template(tex::id('fframe')); __ meta::meta('template::code', <<'__'); code::template $_ for qw/java cpp asm javascript html resource perl ruby/; __ meta::meta('template::document', 'tex::template tex::id(qw/document tableofcontents maketitle title author date abstract documentclass verbatim/);'); meta::meta('template::enumeration', 'tex::template tex::id(qw/enumerate itemize description item/);'); meta::meta('template::math', <<'__'); tex::template align => 'align*', nalign => 'align'; tex::template tex::id(qw/theorem proof lemma corollary conjecture definition proposition/); __ meta::meta('template::quotations', 'tex::template tex::id(qw/quotation quote/);'); meta::meta('template::sections', <<'__'); tex::labeled_template(s1 => 'section', s2 => 'subsection', s3 => 'subsubsection', s4 => 'paragraph', s5 => 'subparagraph'); tex::labeled_template(sc => 'chapter', sp => 'part'); __ meta::meta('type::alias', <<'__'); 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(@_)); }; }; __ meta::meta('type::bootstrap', <<'__'); # Bootstrap attributes don't get executed. The reason for this is that because # they are serialized directly into the header of the file (and later duplicated # as regular data attributes), they will have already been executed when the # file is loaded. meta::configure 'bootstrap', extension => '.pl', inherit => 1; meta::define_form 'bootstrap', sub {}; __ meta::meta('type::cache', <<'__'); meta::configure 'cache', inherit => 0; meta::define_form 'cache', \&meta::bootstrap::implementation; __ meta::meta('type::cached_dependency', <<'__'); meta::configure 'cached_dependency', inherit => 0, extension => ''; meta::define_form 'cached_dependency', \&meta::bootstrap::implementation; __ meta::meta('type::configuration', <<'__'); meta::functor::editable 'configuration', inherit => 0, extension => '.conf', default => sub { # Any lines starting with #, with or without leading whitespace, are treated as comments. # Comments are not parsed in option text; that is, you could specify an option that contained # a # and the # and following text would be considered part of that option. my ($data) = @_; my @options = grep /:\h+/o && ! /^\h*#/o && ! /^\h*$/o, split(/\v+/o, $data); s/^\h+//o for @options; my @key_values = map split(/\h*:\h+/o, $_, 2), @options; $key_values[$_ << 1] and $key_values[$_ << 1] =~ s/\s/_/go for 0 .. @key_values >> 1; $key_values[$_ << 1] and $key_values[$_ << 1] = lc $key_values[$_ << 1] for 0 .. @key_values >> 1; @key_values; }; __ meta::meta('type::data', 'meta::functor::editable \'data\', extension => \'\', inherit => 0, default => \'cat\';'); meta::meta('type::function', <<'__'); meta::configure 'function', extension => '.pl', inherit => 1; meta::define_form 'function', sub { my ($name, $value) = @_; meta::externalize $name, "function::$name", meta::eval_in("sub {\n$value\n}", "function::$name"); }; __ meta::meta('type::hook', <<'__'); meta::configure 'hook', extension => '.pl', inherit => 0; meta::define_form 'hook', sub { my ($name, $value) = @_; *{"hook::$name"} = meta::eval_in("sub {\n$value\n}", "hook::$name"); }; __ meta::meta('type::inc', <<'__'); meta::configure 'inc', inherit => 1, extension => '.pl'; meta::define_form 'inc', sub { use File::Path 'mkpath'; use File::Basename qw/basename dirname/; my ($name, $value) = @_; my $tmpdir = basename($0) . '-' . $$; my $filename = "/tmp/$tmpdir/$name"; push @INC, "/tmp/$tmpdir" unless grep /^\/tmp\/$tmpdir$/, @INC; mkpath(dirname($filename)); unless (-e $filename) { open my $fh, '>', $filename; print $fh $value; close $fh; } }; __ meta::meta('type::indicator', <<'__'); # Shell indicator function. The output of each of these is automatically # appended to the shell prompt. meta::configure 'indicator', inherit => 1, extension => '.pl'; meta::define_form 'indicator', sub { my ($name, $value) = @_; *{"indicator::$name"} = meta::eval_in("sub {\n$value\n}", "indicator::$name"); }; __ meta::meta('type::internal_function', <<'__'); meta::configure 'internal_function', extension => '.pl', inherit => 1; meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = meta::eval_in("sub {\n$value\n}", "internal_function::$name"); }; __ meta::meta('type::library', <<'__'); meta::configure 'library', extension => '.pl', inherit => 1; meta::define_form 'library', sub { my ($name, $value) = @_; meta::eval_in($value, "library::$name"); }; __ meta::meta('type::message_color', <<'__'); meta::configure 'message_color', extension => '', inherit => 1; meta::define_form 'message_color', sub { my ($name, $value) = @_; terminal::color($name, $value); }; __ meta::meta('type::meta', <<'__'); # This doesn't define a new type. It customizes the existing 'meta' type # defined in bootstrap::initialization. Note that horrible things will # happen if you redefine it using the editable functor. meta::configure 'meta', extension => '.pl', inherit => 1; __ meta::meta('type::note', 'meta::functor::editable \'note\', extension => \'.sdoc\', inherit => 0, default => \'edit\';'); meta::meta('type::parent', <<'__'); meta::define_form 'parent', \&meta::bootstrap::implementation; meta::configure 'parent', extension => '', inherit => 1; __ meta::meta('type::resource', 'meta::functor::editable \'resource\', extension => \'.cltex\', inherit => 1, default => \'edit\';'); meta::meta('type::retriever', <<'__'); 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"); }; __ meta::meta('type::section', 'meta::functor::editable \'section\', extension => \'.cltex\', inherit => 0, default => \'edit\';'); meta::meta('type::state', <<'__'); # Allows temporary or long-term storage of states. Nothing particularly insightful # is done about compression, so storing alternative states will cause a large # increase in size. Also, states don't contain other states -- otherwise the size # increase would be exponential. # States are created with the save-state function. meta::configure 'state', inherit => 0, extension => '.pl'; meta::define_form 'state', \&meta::bootstrap::implementation; __ meta::meta('type::template', <<'__'); meta::configure 'template', extension => '.pl', inherit => 1; meta::define_form 'template', sub { my ($name, $value) = @_; meta::externalize "template::$name", "template::$name", meta::eval_in("sub {\n$value\n}", "template::$name"); }; __ meta::meta('type::vim_highlighter', <<'__'); meta::configure 'vim_highlighter', extension => '.vim', inherit => 1; meta::define_form 'vim_highlighter', \&meta::bootstrap::implementation; __ meta::bootstrap('html', <<'__'); __ meta::bootstrap('initialization', <<'__'); #!/usr/bin/perl # 99aeabc9ec7fe80b1b39f5e53dc7e49e <- self-modifying Perl magic # This is a self-modifying Perl file. I'm sorry you're viewing the source (it's # really gnarly). If you're curious what it's made of, I recommend reading # http://github.com/spencertipping/writing-self-modifying-perl. # # If you got one of these from someone and don't know what to do with it, send # it to spencer@spencertipping.com and I'll see if I can figure out what it # does. # For the benefit of HTML viewers (this is hack): #
$|++; 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::bootstrap('perldoc', <<'__'); =head1 Self-modifying Perl script =head2 Original implementation by Spencer Tipping L The prototype for this script is licensed under the terms of the MIT source code license. However, this script in particular may be under different licensing terms. To find out how this script is licensed, please contact whoever sent it to you. Alternatively, you may run it with the 'license' argument if they have specified a license that way. You should not edit this file directly. For information about how it was constructed, go to L. For quick usage guidelines, run this script with the 'usage' argument. =cut __ meta::cache('parent-identification', <<'__'); ./sdoc /home/spencertipping/bin/configuration aa772900bb5b925cb84346bd72a4249d /home/spencertipping/bin/literate-project cf9c87bde4f2e9aed1ea468d85b2ddad /home/spencertipping/bin/object 99aeabc9ec7fe80b1b39f5e53dc7e49e configuration aa772900bb5b925cb84346bd72a4249d development 3d94eaf9719db17882d02c1d1fe18718 notes a9e5975593ed5d90d943ad98405c71e5 object 99aeabc9ec7fe80b1b39f5e53dc7e49e preprocessor 70dae4b46eb4e06798ec6f38d17d4c7b repository 05bc3036c343fdb8aec5b0be12a9b19e vim-highlighters 902333a0bd6ed90ff919fe8477cb4e69 __ meta::data('author', 'Spencer Tipping'); meta::data('default-action', 'shell'); meta::data('license', <<'__'); MIT License Copyright (c) 2010 Spencer Tipping Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. __ meta::data('output-dir', '/tmp'); meta::data('pdf-output-file', '/tmp/writing-self-modifying-perl.9Yr3/document.pdf'); meta::data('pdf-reader', '/usr/bin/evince'); meta::data('pdftex-command', 'pdflatex -output-directory=__TEMPORARY_DIRECTORY__ __INPUT_FILE__'); meta::data('permanent-identity', '2572d38bedc92761b662fe6b546a01cf'); meta::data('source-directory', '.'); meta::data('table-of-contents', '1'); meta::data('tex-command', 'latex -output-directory=__TEMPORARY_DIRECTORY__ __INPUT_FILE__'); meta::data('watching', '0'); meta::function('ad', <<'__'); return @{$transient{path}} = () unless @_; push @{$transient{path}}, @_; __ meta::function('alias', <<'__'); my ($name, @stuff) = @_; @_ ? @stuff ? around_hook('alias', @_, sub {associate("alias::$name", join(' ', @stuff), execute => 1)}) : retrieve("alias::$name") // "Undefined alias $name" : table_display([select_keys('--namespace' => 'alias')], [map retrieve($_), select_keys('--namespace' => 'alias')]); __ meta::function('cat', 'join "\\n", retrieve(@_);'); meta::function('cc', <<'__'); # Stashes a quick one-line continuation. (Used to remind me what I was doing.) @_ ? associate('data::current-continuation', hook('set-cc', join(' ', @_))) : retrieve('data::current-continuation'); __ meta::function('ccc', 'rm(\'data::current-continuation\');'); meta::function('child', <<'__'); around_hook('child', @_, sub { my ($child_name) = @_; clone($child_name); enable(); qx($child_name update-from $0 -n); disable()}); __ meta::function('clean', <<'__'); my $output_directory = pdf_output_file(); $output_directory =~ s+/.*++g; unlink <$output_directory/*>; rmdir $output_directory; __ meta::function('clone', <<'__'); for (grep length, @_) { around_hook('clone', $_, sub { hypothetically(sub { rm('data::permanent-identity'); file::write($_, serialize(), noclobber => 1); chmod(0700, $_)})})} __ meta::function('compile', <<'__'); my $tex_command = tex_command(); my $pdftex_command = pdftex_command(); my $filename = 'document'; my $contents = tex(); my $output_directory = output_dir(); chomp $output_directory; my $temporary_directory = temporary_name(); $temporary_directory =~ s+^.*/++; $temporary_directory = "$output_directory/$temporary_directory"; $tex_command =~ s/__TEMPORARY_DIRECTORY__/$temporary_directory/g; $tex_command =~ s+__INPUT_FILE__+$temporary_directory/$filename.tex+g; $pdftex_command =~ s/__TEMPORARY_DIRECTORY__/$temporary_directory/g; $pdftex_command =~ s+__INPUT_FILE__+$temporary_directory/$filename.tex+g; mkdir $temporary_directory; file::write("$temporary_directory/$filename.tex", $contents); my $result = table_of_contents() ? system($tex_command) || system($tex_command) || system($pdftex_command) : system($pdftex_command); $transient{pdf_output_file} = "$temporary_directory/$filename.pdf"; $result; __ meta::function('cp', <<'__'); my $from = shift @_; my $value = retrieve($from); associate($_, $value) for @_; __ meta::function('create', <<'__'); my ($name, $value) = @_; around_hook('create', $name, $value, sub { return edit($name) if exists $data{$name}; associate($name, defined $value ? $value : ''); edit($name) unless defined $value}); __ meta::function('current-state', 'serialize(\'-pS\');'); meta::function('disable', 'hook(\'disable\', chmod_self(sub {$_[0] & 0666}));'); meta::function('dupdate', <<'__'); # Update the repository based on the dependencies it lists. These dependencies # can be anything that's retrievable. # If you have any ::s in the local name of the dependency, then the cached_dependency:: # prefix won't be added. This lets you import slices of other objects and have # those slices merge into any namespace you'd like. rm(grep /^cached_dependency::/, keys %data); my %dependencies = map &{attribute($_)}(), select_keys('--criteria' => "^configuration::depend.*"); for (keys %dependencies) { terminal::info("Retrieving $dependencies{$_} as $_"); my $prefix = /::/ ? '' : 'cached_dependency::'; associate("${prefix}$_", retrieve($dependencies{$_}))} reload(); __ meta::function('edit', <<'__'); 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') || is($name, '-d') || exists $options{'-f'}; around_hook('edit', @_, sub { associate($name, invoke_editor_on($data{$name} // '', %options, attribute => $name, extension => $extension), execute => 1)}); save() unless $data{'data::edit::no-save'}; ''; __ meta::function('enable', 'hook(\'enable\', chmod_self(sub {$_[0] | $_[0] >> 2}));'); meta::function('export', <<'__'); # Exports data into a text file. # export attr1 attr2 attr3 ... file.txt my $name = pop @_; @_ or die 'Expected filename'; file::write($name, join "\n", retrieve(@_)); __ meta::function('extern', '&{$_[0]}(retrieve(@_[1 .. $#_]));'); meta::function('grep', <<'__'); # Looks through attributes for a pattern. Usage is grep pattern [options], where # [options] is the format as provided to select_keys. my ($pattern, @args) = @_; my ($options, @criteria) = separate_options(@args); my @attributes = select_keys(%$options, '--criteria' => join('|', @criteria)); $pattern = qr/$pattern/; my @m_attributes; my @m_line_numbers; my @m_lines; for my $k (@attributes) { next unless length $k; my @lines = split /\n/, retrieve($k); for (0 .. $#lines) { next unless $lines[$_] =~ $pattern; push @m_attributes, $k; push @m_line_numbers, $_ + 1; push @m_lines, '' . ($lines[$_] // '')}} unless ($$options{'-C'}) { s/($pattern)/\033[1;31m\1\033[0;0m/g for @m_lines; s/^/\033[1;34m/o for @m_attributes; s/^/\033[1;32m/o && s/$/\033[0;0m/o for @m_line_numbers} table_display([@m_attributes], [@m_line_numbers], [@m_lines]); __ meta::function('hash', 'fast_hash(@_);'); meta::function('hook', <<'__'); my ($hook, @args) = @_; $transient{active_hooks}{$hook} = 1; dangerous('', sub {&$_(@args)}) for grep /^hook::${hook}::/, sort keys %data; @args; __ meta::function('hooks', 'join "\\n", sort keys %{$transient{active_hooks}};'); meta::function('identity', 'retrieve(\'data::permanent-identity\') || associate(\'data::permanent-identity\', fast_hash(rand() . name() . serialize()));'); meta::function('import', <<'__'); my $name = pop @_; associate($name, @_ ? join('', map(file::read($_), @_)) : join('', )); __ meta::function('initial-state', '$transient{initial};'); meta::function('is', <<'__'); my ($attribute, @criteria) = @_; my ($options, @stuff) = separate_options(@criteria); exists $data{$attribute} and attribute_is($attribute, %$options); __ meta::function('load-state', <<'__'); around_hook('load-state', @_, sub { my ($state_name) = @_; my $state = retrieve("state::$state_name"); terminal::state('saving current state into _...'); save_state('_'); delete $data{$_} for grep ! /^state::/, keys %data; %externalized_functions = (); terminal::state("restoring state $state_name..."); meta::eval_in($state, "state::$state_name"); terminal::error(hook('load-state-failed', $@)) if $@; reload(); verify()}); __ meta::function('lock', 'hook(\'lock\', chmod_self(sub {$_[0] & 0555}));'); meta::function('ls', <<'__'); my ($options, @criteria) = separate_options(@_); my ($external, $shadows, $sizes, $flags, $long, $hashes, $parent_hashes) = @$options{qw(-e -s -z -f -l -h -p)}; $sizes = $flags = $hashes = $parent_hashes = 1 if $long; return table_display([grep ! exists $data{$externalized_functions{$_}}, sort keys %externalized_functions]) if $shadows; my $criteria = join('|', @criteria); my @definitions = select_keys('--criteria' => $criteria, '--path' => $transient{path}, %$options); my %inverses = map {$externalized_functions{$_} => $_} keys %externalized_functions; my @externals = map $inverses{$_}, grep length, @definitions; my @internals = grep length $inverses{$_}, @definitions; my @sizes = map sprintf('%6d %6d', length(serialize_single($_)), length(retrieve($_))), @{$external ? \@internals : \@definitions} if $sizes; my @flags = map {my $k = $_; join '', map(is($k, "-$_") ? $_ : '-', qw(d i m u))} @definitions if $flags; my @hashes = map fast_hash(retrieve($_)), @definitions if $hashes; my %inherited = parent_attributes(grep /^parent::/o, keys %data) if $parent_hashes; my @parent_hashes = map $inherited{$_} || '-', @definitions if $parent_hashes; join "\n", map strip($_), split /\n/, table_display($external ? [grep length, @externals] : [@definitions], $sizes ? ([@sizes]) : (), $flags ? ([@flags]) : (), $hashes ? ([@hashes]) : (), $parent_hashes ? ([@parent_hashes]) : ()); __ meta::function('make', <<'__'); compile() || view(); clean(); __ meta::function('mv', <<'__'); my ($from, $to) = @_; die "'$from' does not exist" unless exists $data{$from}; associate($to, retrieve($from)); rm($from); __ meta::function('name', <<'__'); my $name = $0; $name =~ s/^.*\///; $name; __ meta::function('note', <<'__'); # Creates a note with a given name, useful for jotting things down. my $name = join('-', @_); create("note::$name"); __ meta::function('notes', 'ls(\'-a\', \'^note::\');'); meta::function('parents', 'join "\\n", grep s/^parent:://o, sort keys %data;'); meta::function('perl', <<'__'); my $result = eval(join ' ', @_); $@ ? terminal::error($@) : $result; __ meta::function('preprocess', <<'__'); # Implements a simple preprocessing language. # Syntax follows two forms. One is the 'line form', which gives you a way to specify arguments inline # but not spanning multiple lines. The other is 'block form', which gives you access to both one-line # arguments and a block of lines. The line parameters are passed in verbatim, and the block is # indentation-adjusted and then passed in as a second parameter. (Indentation is adjusted to align # with the name of the command.) # # Here are the forms: # # - line arguments to function # # - block line arguments << eof # block contents # block contents # ... # - eof my ($string, %options) = @_; my $expansions = 0; my $old_string = ''; my $limit = $options{expansion_limit} || 100; my @pieces = (); sub adjust_spaces { my ($spaces, $string) = @_; $string =~ s/^$spaces //mg; chomp $string; $string; } while ($old_string ne $string and $expansions++ < $limit) { $old_string = $string; while ((my @pieces = split /(^(\h*)-\h \S+ \h* \V* <<\h*(\w+)$ \n .*? ^\2-\h\3$)/xms, $string) > 1 and $expansions++ < $limit) { $pieces[1 + ($_ << 2)] =~ /^ (\h*)-\h(\S+)\h*(\V*)<<\h*(\w+)$ \n(.*?) ^\1-\h\4 $/xms && $externalized_functions{"template::$2"} and $pieces[1 + ($_ << 2)] = &{"template::$2"}($3, adjust_spaces($1, $5)) for 0 .. $#pieces / 4; @pieces[2 + ($_ << 2), 3 + ($_ << 2)] = '' for 0 .. $#pieces / 4; $string = join '', @pieces; } if ((my @pieces = split /^(\h*-\h \S+ \h* .*)$/xom, $string) > 1) { $pieces[1 + ($_ << 1)] =~ /^ \h*-\h(\S+)\h*(.*)$/xom && $externalized_functions{"template::$1"} and $pieces[1 + ($_ << 1)] = &{"template::$1"}($2) for 0 .. $#pieces >> 1; $string = join '', @pieces; } } $string; __ meta::function('rd', <<'__'); if (@_) {my $pattern = join '|', @_; @{$transient{path}} = grep $_ !~ /^$pattern$/, @{$transient{path}}} else {pop @{$transient{path}}} __ meta::function('reload', 'around_hook(\'reload\', sub {execute($_) for grep ! /^bootstrap::/, keys %data});'); meta::function('render', <<'__'); use File::Copy 'copy'; compile(); my $pdf = $transient{pdf_output_file}; my $tex = $pdf; my $directory = source_directory(); my $name = name(); $tex =~ s/\.pdf$/.tex/; copy($pdf, "$directory/$name.pdf") or die $!; copy($tex, "$directory/$name.tex") or die $!; __ meta::function('rm', <<'__'); around_hook('rm', @_, sub { exists $data{$_} or terminal::warning("$_ does not exist") for @_; delete @data{@_}}); __ meta::function('rmparent', <<'__'); # Removes one or more parents. my ($options, @parents) = separate_options(@_); my $clobber_divergent = $$options{'-D'} || $$options{'--clobber-divergent'}; my %parents = map {$_ => 1} @parents; my @other_parents = grep !$parents{$_}, grep s/^parent:://, select_keys('--namespace' => 'parent'); my %kept_by_another_parent; $kept_by_another_parent{$_} = 1 for grep s/^(\S+)\s.*$/\1/, split /\n/o, cat(@other_parents); for my $parent (@parents) { my $keep_parent_around = 0; for my $line (split /\n/, retrieve("parent::$parent")) { my ($name, $hash) = split /\s+/, $line; next unless exists $data{$name}; my $local_hash = fast_hash(retrieve($name)); if ($clobber_divergent or $hash eq $local_hash or ! defined $hash) {rm($name) unless $kept_by_another_parent{$name}} else {terminal::info("local attribute $name exists and is divergent; use rmparent -D $parent to delete it"); $keep_parent_around = 1}} $keep_parent_around ? terminal::info("not deleting parent::$parent so that you can run", "rmparent -D $parent if you want to nuke divergent attributes too") : rm("parent::$parent")} __ meta::function('save', 'around_hook(\'save\', sub {dangerous(\'\', sub {file::write($0, serialize()); $transient{initial} = state()}) if verify()});'); meta::function('save-state', <<'__'); # Creates a named copy of the current state and stores it. my ($state_name) = @_; around_hook('save-state', $state_name, sub { associate("state::$state_name", current_state(), execute => 1)}); __ meta::function('serialize', <<'__'); 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; __ meta::function('serialize-single', <<'__'); # 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); my $chars = 2; ++$chars until $chars >= length($delimiter) || index("\n$contents", "\n" . substr($delimiter, 0, $chars)) == -1; $delimiter = substr($delimiter, 0, $chars); "$meta_function('$invocation', <<'$delimiter');\n$contents\n$delimiter"; __ meta::function('serialize_single', <<'__'); # 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); my $chars = 2; ++$chars until $chars >= length($delimiter) || index("\n$contents", "\n" . substr($delimiter, 0, $chars)) == -1; $delimiter = substr($delimiter, 0, $chars); "$meta_function('$invocation', <<'$delimiter');\n$contents\n$delimiter"; __ meta::function('sh', 'system(@_);'); meta::function('shb', <<'__'); # Backgrounded shell job. exec(@_) unless fork; __ meta::function('shell', <<'__'); my ($options, @arguments) = separate_options(@_); $transient{repl_prefix} = $$options{'--repl-prefix'}; terminal::cc(retrieve('data::current-continuation')) if length $data{'data::current-continuation'}; around_hook('shell', sub {shell::repl(%$options)}); __ meta::function('size', <<'__'); my $size = 0; $size += length $data{$_} for keys %data; sprintf "% 7d % 7d % 7d", length(serialize()), $size, length(serialize('-up')); __ meta::function('snapshot', <<'__'); my ($name) = @_; file::write(my $finalname = temporary_name($name), serialize(), noclobber => 1); chmod 0700, $finalname; hook('snapshot', $finalname); __ meta::function('state', <<'__'); my @keys = sort keys %data; my $hash = fast_hash(fast_hash(scalar @keys) . join '|', @keys); $hash = fast_hash("$data{$_}|$hash") for @keys; $hash; __ meta::function('tex', <<'__'); my ($document) = @_; $document ||= 'main'; preprocess(retrieve("section::$document")); __ meta::function('touch', 'associate($_, \'\') for @_;'); meta::function('unlock', 'hook(\'unlock\', chmod_self(sub {$_[0] | 0200}));'); meta::function('update', <<'__'); update_from(@_, grep s/^parent:://o, sort keys %data); __ meta::function('update-from', <<'__'); # Upgrade all attributes that aren't customized. Customization is defined when the data type is created, # and we determine it here by checking for $transient{inherit}{$type}. # Note that this assumes you trust the remote script. If you don't, then you shouldn't update from it. around_hook('update-from-invocation', separate_options(@_), sub { my ($options, @targets) = @_; my %parent_id_cache = cache('parent-identification'); my %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}}); __ meta::function('usage', '"Usage: $0 action [arguments]\\nUnique actions (run \'$0 ls\' to see all actions):" . ls(\'-u\');'); meta::function('verify', <<'__'); file::write(my $other = $transient{temporary_filename} = temporary_name(), my $serialized_data = serialize()); chomp(my $observed = join '', qx|perl '$other' state|); unlink $other if my $result = $observed eq (my $state = state()); terminal::error("Verification failed; expected $state but got $observed from $other") unless $result; hook('after-verify', $result, observed => $observed, expected => $state); $result; __ meta::function('view', <<'__'); my $pdf_reader = pdf_reader(); chomp $pdf_reader; system("$pdf_reader '$transient{pdf_output_file}'"); __ meta::function('vim', <<'__'); # Installs VIM highlighters. file::write("$ENV{'HOME'}/.vim/syntax/$_.vim", retrieve("vim_highlighter::$_")) for grep s/^vim_highlighter:://o, keys %data; __ meta::hook('before-shell::ad', <<'__'); ad('section::'); __ meta::indicator('cc', 'length ::retrieve(\'data::current-continuation\') ? "\\033[1;36mcc\\033[0;0m" : \'\';'); meta::indicator('locked', 'is_locked() ? "\\033[1;31mlocked\\033[0;0m" : \'\';'); meta::indicator('path', <<'__'); join "\033[1;30m/\033[0;0m", @{$transient{path}}; __ meta::internal_function('around_hook', <<'__'); # around_hook('hookname', @args, sub { # stuff; # }); # Invokes 'before-hookname' on @args before the sub runs, invokes the # sub on @args, then invokes 'after-hookname' on @args afterwards. # The after-hook is not invoked if the sub calls 'die' or otherwise # unwinds the stack. my $hook = shift @_; my $f = pop @_; hook("before-$hook", @_); my $result = &$f(@_); hook("after-$hook", @_); $result; __ meta::internal_function('associate', <<'__'); my ($name, $value, %options) = @_; die "Namespace does not exist" unless exists $datatypes{namespace($name)}; $data{$name} = $value; execute($name) if $options{'execute'}; $value; __ meta::internal_function('attribute', <<'__'); my ($name) = @_; $name =~ s/^[^:]*:://; $name; __ meta::internal_function('attribute_is', <<'__'); my ($a, %options) = @_; my %inherited = parent_attributes(grep /^parent::/o, sort keys %data) if grep exists $options{$_}, qw/-u -U -d -D/; my $criteria = $options{'--criteria'} || $options{'--namespace'} && "^$options{'--namespace'}::" || '.'; my %tests = ('-u' => sub {! $inherited{$a}}, '-d' => sub {$inherited{$a} && fast_hash(retrieve($a)) ne $inherited{$a}}, '-i' => sub {$transient{inherit}{namespace($a)}}, '-s' => sub {$a =~ /^state::/o}, '-m' => sub {$a =~ /^meta::/o}); return 0 unless scalar keys %tests == scalar grep ! exists $options{$_} || &{$tests{$_}}(), keys %tests; return 0 unless scalar keys %tests == scalar grep ! exists $options{uc $_} || ! &{$tests{$_}}(), keys %tests; $a =~ /$_/ || return 0 for @{$options{'--path'}}; $a =~ /$criteria/; __ meta::internal_function('cache', <<'__'); my ($name, %pairs) = @_; if (%pairs) {associate("cache::$name", join "\n", map {$pairs{$_} =~ s/\n//g; "$_ $pairs{$_}"} sort keys %pairs)} else {map split(/\s/, $_, 2), split /\n/, retrieve("cache::$name")} __ meta::internal_function('chmod_self', <<'__'); my ($mode_function) = @_; my (undef, undef, $mode) = stat $0; chmod &$mode_function($mode), $0; __ meta::internal_function('dangerous', <<'__'); # Wraps a computation that may produce an error. my ($message, $computation) = @_; terminal::info($message) if $message; my @result = eval {&$computation()}; terminal::warning(translate_backtrace($@)), return undef if $@; wantarray ? @result : $result[0]; __ meta::internal_function('debug_trace', <<'__'); terminal::debug(join ', ', @_); wantarray ? @_ : $_[0]; __ meta::internal_function('execute', <<'__'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{$datatypes{$namespace}}(attribute($name), retrieve($name))}; warn $@ if $@ && $options{'carp'}; __ meta::internal_function('exported', <<'__'); # Allocates a temporary file containing the concatenation of attributes you specify, # and returns the filename. The filename will be safe for deletion anytime. my $filename = temporary_name(); file::write($filename, cat(@_)); $filename; __ meta::internal_function('extension_for', <<'__'); my $extension = $transient{extension}{namespace($_[0])}; $extension = &$extension($_[0]) if ref $extension eq 'CODE'; $extension || ''; __ meta::internal_function('fast_hash', <<'__'); my ($data) = @_; my $piece_size = length($data) >> 3; my @pieces = (substr($data, $piece_size * 8) . length($data), map(substr($data, $piece_size * $_, $piece_size), 0 .. 7)); my @hashes = (fnv_hash($pieces[0])); push @hashes, fnv_hash($pieces[$_ + 1] . $hashes[$_]) for 0 .. 7; $hashes[$_] ^= $hashes[$_ + 4] >> 16 | ($hashes[$_ + 4] & 0xffff) << 16 for 0 .. 3; $hashes[0] ^= $hashes[8]; sprintf '%08x' x 4, @hashes[0 .. 3]; __ meta::internal_function('file::read', <<'__'); my $name = shift; open my($handle), "<", $name; my $result = join "", <$handle>; close $handle; $result; __ meta::internal_function('file::write', <<'__'); use File::Path 'mkpath'; use File::Basename 'dirname'; my ($name, $contents, %options) = @_; die "Choosing not to overwrite file $name" if $options{noclobber} and -f $name; mkpath(dirname($name)) if $options{mkpath}; open my($handle), $options{append} ? '>>' : '>', $name or die "Can't open $name for writing"; print $handle $contents; close $handle; __ meta::internal_function('fnv_hash', <<'__'); # A rough approximation to the Fowler-No Voll hash. It's been 32-bit vectorized # for efficiency, which may compromise its effectiveness for short strings. my ($data) = @_; my ($fnv_prime, $fnv_offset) = (16777619, 2166136261); my $hash = $fnv_offset; my $modulus = 2 ** 32; $hash = ($hash ^ ($_ & 0xffff) ^ ($_ >> 16)) * $fnv_prime % $modulus for unpack 'L*', $data . substr($data, -4) x 8; $hash; __ meta::internal_function('hypothetically', <<'__'); # 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; __ meta::internal_function('internal::main', <<'__'); 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(); } __ meta::internal_function('invoke_editor_on', <<'__'); my ($data, %options) = @_; my $editor = $options{editor} || $ENV{VISUAL} || $ENV{EDITOR} || die 'Either the $VISUAL or $EDITOR environment variable should be set to a valid editor'; my $options = $options{options} || $ENV{VISUAL_OPTS} || $ENV{EDITOR_OPTS} || ''; my $attribute = $options{attribute}; $attribute =~ s/\//-/g; my $filename = temporary_name() . "-$attribute$options{extension}"; file::write($filename, $data); system("$editor $options '$filename'"); my $result = file::read($filename); unlink $filename; $result; __ meta::internal_function('is_locked', '!((stat($0))[2] & 0222);'); meta::internal_function('main', <<'__'); $SIG{'INT'} = sub { snapshot(); exit 1; }; my $initial_state = state(); chomp(my $default_action = retrieve('data::default-action')); my $function_name = shift(@ARGV) || $default_action || 'usage'; terminal::message('warning', "Unknown action: '$function_name'") and $function_name = 'usage' unless $externalized_functions{$function_name}; chomp(my $result = &$function_name(@ARGV)); print "$result\n" if $result; save() unless $initial_state eq state(); __ meta::internal_function('namespace', <<'__'); my ($name) = @_; $name =~ s/::.*$//; $name; __ meta::internal_function('parent_attributes', <<'__'); my $attributes = sub {my ($name, $value) = split /\s+/o, $_; $name => ($value || 1)}; map &$attributes(), split /\n/o, join("\n", retrieve(@_)); __ meta::internal_function('parent_ordering', <<'__'); # Topsorts the parents by dependency chain. The simplest way to do this is to # transitively compute the number of parents referred to by each parent. my @parents = @_; my %all_parents = map {$_ => 1} @parents; my %parents_of = map { my $t = $_; my %attributes = parent_attributes($_); $t => [grep /^parent::/, keys %attributes]} @parents; my %parent_count; my $parent_count; $parent_count = sub { my ($key) = @_; return $parent_count{$key} if exists $parent_count{$key}; my $count = 0; $count += $parent_count->($_) + exists $data{$_} for @{$parents_of{$key}}; $parent_count{$key} = $count}; my %inverses; push @{$inverses{$parent_count->($_)} ||= []}, $_ for @parents; grep exists $all_parents{$_}, map @{$inverses{$_}}, sort keys %inverses; __ meta::internal_function('retrieve', <<'__'); my @results = map defined $data{$_} ? $data{$_} : retrieve_with_hooks($_), @_; wantarray ? @results : $results[0]; __ meta::internal_function('retrieve_with_hooks', <<'__'); # Uses the hooks defined in $transient{retrievers}, and returns undef if none work. my ($attribute) = @_; my $result = undef; defined($result = &$_($attribute)) and return $result for map $transient{retrievers}{$_}, sort keys %{$transient{retrievers}}; return undef; __ meta::internal_function('select_keys', <<'__'); my %options = @_; grep attribute_is($_, %options), sort keys %data; __ meta::internal_function('separate_options', <<'__'); # Things with one dash are short-form options, two dashes are long-form. # Characters after short-form are combined; so -auv4 becomes -a -u -v -4. # Also finds equivalences; so --foo=bar separates into $$options{'--foo'} eq 'bar'. # Stops processing at the -- option, and removes it. Everything after that # is considered to be an 'other' argument. # The only form not supported by this function is the short-form with argument. # To pass keyed arguments, you need to use long-form options. my @parseable; push @parseable, shift @_ until ! @_ or $_[0] eq '--'; my @singles = grep /^-[^-]/, @parseable; my @longs = grep /^--/, @parseable; my @others = grep ! /^-/, @parseable; my @singles = map /-(.{2,})/ ? map("-$_", split(//, $1)) : $_, @singles; my %options; /^([^=]+)=(.*)$/ and $options{$1} = $2 for @longs; ++$options{$_} for grep ! /=/, @singles, @longs; ({%options}, @others, @_); __ meta::internal_function('strip', 'wantarray ? map {s/^\\s*|\\s*$//g; $_} @_ : $_[0] =~ /^\\s*(.*?)\\s*$/ && $1;'); meta::internal_function('table_display', <<'__'); # Displays an array of arrays as a table; that is, with alignment. Arrays are # expected to be in column-major order. sub maximum_length_in { my $maximum = 0; length > $maximum and $maximum = length for @_; $maximum; } my @arrays = @_; my @lengths = map maximum_length_in(@$_), @arrays; my @row_major = map {my $i = $_; [map $$_[$i], @arrays]} 0 .. $#{$arrays[0]}; my $format = join ' ', map "%-${_}s", @lengths; join "\n", map strip(sprintf($format, @$_)), @row_major; __ meta::internal_function('temporary_name', <<'__'); use File::Temp 'tempfile'; my (undef, $temporary_filename) = tempfile("$0." . 'X' x 4, OPEN => 0); $temporary_filename; __ meta::internal_function('translate_backtrace', <<'__'); my ($trace) = @_; $trace =~ s/\(eval (\d+)\)/$locations{$1 - 1}/g; $trace; __ meta::internal_function('with_exported', <<'__'); # Like exported(), but removes the file after running some function. # Usage is with_exported(@files, sub {...}); my $f = pop @_; my $name = exported(@_); my $result = eval {&$f($name)}; terminal::warning("$@ when running with_exported()") if $@; unlink $name; $result; __ meta::library('shell', <<'__'); # Functions for shell parsing and execution. package shell; use Term::ReadLine; sub tokenize {grep length, split /\s+|("[^"\\]*(?:\\.)?")/o, join ' ', @_}; sub parse { my ($fn, @args) = @_; s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args; {function => $fn, args => [@args]}} sub execute { my %command = %{$_[0]}; die "undefined command: $command{function}" unless exists $externalized_functions{$command{function}}; &{"::$command{function}"}(@{$command{args}})} sub run {execute(parse(tokenize(@_)))} sub prompt { my %options = @_; my $name = $options{name} // ::name(); my $indicators = join '', map &{"::$_"}(), ::select_keys('--namespace' => 'indicator'); my $prefix = $transient{repl_prefix} // ''; "$prefix\033[1;32m$name\033[0;0m$indicators "} sub repl { my %options = @_; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $attribs = $term->Attribs; $attribs->{completion_entry_function} = $attribs->{list_completion_function}; my $autocomplete = $options{autocomplete} || sub {[sort(keys %data), grep !/-/, sort keys %externalized_functions]}; my $prompt = $options{prompt} || \&prompt; my $parse = $options{parse} || sub {parse(tokenize(@_))}; my $command = $options{command} || sub {my ($command) = @_; ::around_hook('shell-command', $command, sub {print ::dangerous('', sub {execute($command)}), "\n"})}; length $_ && &$command(&$parse($_)) while ($attribs->{completion_word} = &$autocomplete(), defined($_ = $term->readline(&$prompt())))} __ meta::library('terminal', <<'__'); # Functions for nice-looking terminal output. package terminal; my $process = ::name(); sub message {print STDERR "[$_[0]] $_[1]\n"} sub color { my ($name, $color) = @_; *{"terminal::$name"} = sub {chomp($_), print STDERR "\033[1;30m$process(\033[1;${color}m$name\033[1;30m)\033[0;0m $_\n" for map join('', $_), @_}} my %preloaded = (info => 32, progress => 32, state => 34, debug => 34, warning => 33, error => 31); color $_, $preloaded{$_} for keys %preloaded; __ meta::message_color('cc', '36'); meta::message_color('state', 'purple'); meta::message_color('states', 'yellow'); meta::parent('./sdoc', <<'__'); function::sdoc f3f3f3127961399a4c38152771c966ab function::sdoc-html 7e7de47fe059a336309a4a0c06856401 function::sdocp c3d738d982ba87418a298ff58478a85b meta::type::sdoc 22cd7315641d38c9d536344e83c36bed meta::type::slibrary 95474943c4a5f8ff17d3cf66ddb7c386 parent::/home/spencertipping/bin/object 5788ae36d7790310e3b66d5ddd8b796e retriever::html-sdoc 2a5d5aa45e2d7576f79e045177d8705c retriever::sdoc 662061e9e41491e2a1debd6862ccf1e7 retriever::sdocp 330694ea14a23bb04b65c761075cd946 __ meta::parent('/home/spencertipping/bin/configuration', <<'__'); meta::type::configuration 7f5ba514d47ac29a3c226d0e331d9da4 parent::/home/spencertipping/bin/object 5788ae36d7790310e3b66d5ddd8b796e __ meta::parent('/home/spencertipping/bin/literate-project', <<'__'); function::clean 8754de0fa0d5fc535c47bca1183f6480 function::compile 3f3a53221fad7dba741d689603ff562d function::make 7234478722ac6a7cc9cf17c31a6e4283 function::render bcb18a54e479a3fad2f3c90d5c40e6e9 function::tex 43ac46c21e4f5fb6b29976a213c109c6 function::view b7f4f81d82cbd4051309e99659c0f661 internal_function::main fb128e779c38aeeb04c4a508db7176d4 meta::externalize_template ce51d3e099f0fad21cd01e5c53382e85 meta::functor::code-templates 2b8880f05f983ce5b8a19a01688b6001 meta::functor::tex-templates 6ac0ee994c9328fcfc41083083d97280 meta::template::beamer 3b73fe6a7ef271112a603b110630ad01 meta::template::code d29e41eab67368411a09753d6722f88d meta::template::document 18688cac3eab39087165931beb4f82de meta::template::enumeration 67d1cf0bb0e8946fc3b48cc30a4f5fa4 meta::template::math 057c8fe0d91c0b85138b01c1f6a1e3b3 meta::template::quotations f76a27311c8a9d09550dd669e98f9a7f meta::template::sections 25cb0a42bf1c2f403f8f9c2e1c5e4105 meta::type::resource c521d2de1dcd7f9f7ec4df3557572009 meta::type::section fb3b53eaebc1a2433c1763eccdb781c4 parent::development 79168a5886862b99577653bce0c45c31 resource::header 165b42e25edb90043f5d3b544adee521 resource::header-languages 96d12558e40d0df64ff3176b767f4f3b resource::header-listings 712557f5989b12ca325bdd2cd36020ef resource::header-packages 323964d33482e6099b0248bb4eddf2ae resource::header-refs e3999cbca586d5100eb782643071ee88 resource::header-resource a8df924fbb715de73b8e7518e0c9e0d6 template::item[] 2c2041b4c3b6584b6ae3610f5ab68939 vim_highlighter::cltex 40744d98b02b4b50fedb4d1e7e1fd151 __ meta::parent('/home/spencertipping/bin/object', <<'__'); bootstrap::html f44dd03cb0c904b3a5f69fbda5f018d0 bootstrap::initialization 1cf74e7209f32722a79b6e49e3907fd3 bootstrap::perldoc 5793df44bdd2526bb461272924abfd4b function::ad 77a05d9a6fef7871b2c3e8e94b56870a function::alias 8eeeeb4e064ef3aba7edf8f254427bc2 function::cat f684de6c8776617a437b76009114f52e function::cc 12ea9176e388400704d823433c209b7a function::ccc d151a9793edd83f80fb880b7f0ab9b34 function::child f5764adf0b4e892f147a9b6b68d4816f function::clone bb42e04e10a8e54e88786b6fbc4fb213 function::cp 3fe69d1b58d90045ad520048977538c4 function::create 3010d55f4dfa59a998742e07823ed54d function::current-state 6f03f86f1901e9ef07fdb5d4079a914c function::disable 53b449708cc2ffdefa352e53bb7d847d function::edit 9ce5ba1ae4607e8cf1975080bcde1cf4 function::enable 7de1cedc36841f5de8f9fdfbc3b65097 function::export 2374cd1dbf7616cb38cafba4e171075d function::extern 1290a5223e2824763eecfb3a54961eff function::grep 55c3cea8ff4ec2403be2a9d948e59f14 function::hash 6ee131d093e95b80039b4df9c7c84a02 function::hook 675cdb98b5dd8567bdd5a02ead6184b5 function::hooks 3d989899c616f7440429a2d9bf1cc44b function::identity 6523885762fcc2f354fc25cf6ed126ce function::import 5d0f0634cbd01274f2237717507198a2 function::initial-state 03d8ed608855a723124e79ca184d8e73 function::is 41564c8f21b12ab80824ac825266d805 function::load-state b6cf278a1f351f316fa6e070359b6081 function::lock 5d8db258704e6a8623fac796f62fac02 function::ls 01a23d51d5b529e03943bd57e33f92df function::mv 4a0e338a6edb89ad1e2c779d51d4d47b function::name 955ba2d1fe1d67cd78651a4042283b00 function::parents 3da9e63b5aae9e2f5dcc946a86d166aa function::perl a0f341ea54391b63b6195e7992b6a686 function::rd 2adb16d7e819d2e87a27201744a581e7 function::reload 1589f4cf8374e0011991cb8907afca3e function::rm 6f6fd7a6c25558eb469d78ea888f8551 function::rmparent fc2884910a6939a47898a778f277332c function::save 778c0e1043b9c6c96fb8f266f8061624 function::save-state 5af59ebc4ad8965767e4dc106d3b557e function::serialize a19ada2d2558ea9da3a7942fb913e15f function::serialize-single aa77af032272f5a2664e21713739a223 function::sh 1b2f542ca9dd63ad437058b7f6f61aac function::shb 7b2685a4041c25bc495816e472bdace5 function::shell a87f389b94713e5855e62241d649d01d function::size 8d4bd7a84ece556717f8ba3bf258d33c function::snapshot 56939a47f2758421669641e15ebd66eb function::state 8c68044dccae28f33244d0c7e9e9acfb function::touch 3991b1b7c7187566f50e5e58ce01fa06 function::unlock b4aac02f7f3fb700acf4acfd9b180ceb function::update ac391dc90e507e7586c81850e7c2ecdd function::update-from 631721c4dc30e11b2023a6703cbcef52 function::usage 5bdd370f5a56cfbf199e08d398091444 function::verify 0c0cc1dfeab7d705919df122f7850a4f indicator::cc 3db7509c521ee6abfedd33d5f0148ed3 indicator::locked fc2b4f4ca0d6a334b9ac423d06c8f18c indicator::path 9ec891df17cd45895f03a6124f9d065f internal_function::around_hook 7cc876e7c5f78c34654337fc95255587 internal_function::associate 05a75afb70daee635eefec8ae037f593 internal_function::attribute dd6f010f9688977464783f60f5b6d3dd internal_function::attribute_is 40bda8226322505e323ea6d405388f08 internal_function::cache eb9da45580a9ac0882baf98acd2ecd60 internal_function::chmod_self 2035e861eedab55ba0a9f6f5a068ca70 internal_function::dangerous 46c4baaa214ab3d05af43e28083d5141 internal_function::debug_trace 0faf9d9f4159d72dfe4481f6f3607ce1 internal_function::execute f0924e087d978ff2ab1e117124db3042 internal_function::exported ae35afef7d4762f2818aee5872c75be0 internal_function::extension_for 9de8261d69cc93e9b92072b89c89befd internal_function::fast_hash ee5eba48f837fda0fe472645fdd8899a internal_function::file::read e647752332c8e05e81646a3ff98f9a8e internal_function::file::write 3e290fdcb353c6f842eb5a40f2e575f8 internal_function::fnv_hash c36d56f1e13a60ae427afc43ba025afc internal_function::hypothetically b83e3f894a6df8623ccd370515dfd976 internal_function::internal::main f31f2945a19a668d92505f114ab29c78 internal_function::invoke_editor_on 5eb976796f0ec172d6ec036116a2f41e internal_function::is_locked da12ced6aa38295251f7e748ffd22925 internal_function::namespace 784d2e96003550681a4ae02b8d6d0a27 internal_function::parent_attributes f6ccfaa982ab1a4d066043981aaca277 internal_function::parent_ordering 57b6da88f76b59f3fed9abfa61280e5e internal_function::retrieve 8a34d1fe047fe1b40c3d2957c4a789eb internal_function::retrieve_with_hooks 0f1b0220ccd973d57a2e96ff00458cf2 internal_function::select_keys a5e3532ec6d58151d0ee24416ea1e2b5 internal_function::separate_options 34ec41a6edaa15adde607a0db3ccfa36 internal_function::strip 14f490b10ebd519e829d8ae20ea4d536 internal_function::table_display d575f4dc873b2e0be5bd7352047fd904 internal_function::temporary_name 6f548d101fc68356515ffd0fc9ae0c93 internal_function::translate_backtrace d77a56d608473b3cd8a3c6cb84185e10 internal_function::with_exported df345d5095d5ed13328ddd07ea922b36 library::shell 6b9f3befb61a01e9132a440601f8ea0a library::terminal 7e2d045782405934a9614fe04bcfe559 message_color::cc 2218ef0f7425de5c717762ffb100eb43 message_color::state 03621cd6ac0b1a40d703f41e26c5807f message_color::states ac66eeeff487b5f43f88a78ea18b3d56 meta::configure 69c2e727c124521d074fde21f8bbc4db meta::externalize aa44e27e0bbee6f0ca4de25d603a1fc7 meta::functor::editable 48246c608f363de66511400e00b26164 meta::type::alias 889d26d2df385e9ff8e2da7de4e48374 meta::type::bootstrap 51108ab2ddb8d966e927c8f62d9ef3e5 meta::type::cache 9267171f2eace476f64a1a670eaaf2c7 meta::type::data 120e1649a468d3b3fd3fb783b4168499 meta::type::function 8ea626198861dc59dd7f303eecb5ff88 meta::type::hook ff92aef328b6bdc6f87ddd0821f3e42f meta::type::inc 78e0375b6725487cb1f0deca41e96bbe meta::type::indicator feb54a2624e6983617685047c717427f meta::type::internal_function eff3cf31e2635f51c83836f116c99d2f meta::type::library 7622e8d65e03066668bade74715d65ad meta::type::message_color 557a1b44979cbf77a7251fbdc4c5b82c meta::type::meta c6250056816b58a9608dd1b2614246f8 meta::type::parent 09d1d03379e4e0b262e06939f4e00464 meta::type::retriever 71a29050bf9f20f6c71afddff83addc9 meta::type::state 84da7d5220471307f1f990c5057d3319 retriever::file 3bbc9d8a887a536044bafff1d54def7e retriever::id 4da6080168d32445150cc4200af7af6e retriever::object c7633990b4e01bdc783da7e545799f4f retriever::perl f41938e6dbad317f62abffc1e4d28cca __ meta::parent('configuration', <<'__'); meta::type::configuration d67e10a128e6b1d958c5b9d3bbe25aa4 parent::/home/spencertipping/bin/object 293415c4d45c10239298fa46b01cff17 __ meta::parent('development', <<'__'); parent::./sdoc ebc273b9984a53b0e3a491ac74272c9b parent::configuration 835c872a2163166e001c4e07b5191353 parent::notes a32dbfde9ecfcf3433dfd6575ffdf418 parent::preprocessor 3b4cea3c3d232d9cca0496a72968df65 parent::repository 9cb8c47c563910cc13f9f45ba31d732e parent::vim-highlighters 77c9231e0b6ba6e7aa6ed74193c02d2c __ meta::parent('notes', <<'__'); function::note c6d52ffe73cd48cd95fdc561c59d9f63 function::notes 7229b326ac8686b2db6de98496bc7527 meta::type::note f81bea58841a438e4ee34608ab4f54c0 parent::object 5788ae36d7790310e3b66d5ddd8b796e __ meta::parent('object', <<'__'); bootstrap::html f44dd03cb0c904b3a5f69fbda5f018d0 bootstrap::initialization 1cf74e7209f32722a79b6e49e3907fd3 bootstrap::perldoc 5793df44bdd2526bb461272924abfd4b function::ad 77a05d9a6fef7871b2c3e8e94b56870a function::alias 8eeeeb4e064ef3aba7edf8f254427bc2 function::cat f684de6c8776617a437b76009114f52e function::cc 12ea9176e388400704d823433c209b7a function::ccc d151a9793edd83f80fb880b7f0ab9b34 function::child f5764adf0b4e892f147a9b6b68d4816f function::clone bb42e04e10a8e54e88786b6fbc4fb213 function::cp 3fe69d1b58d90045ad520048977538c4 function::create 3010d55f4dfa59a998742e07823ed54d function::current-state 6f03f86f1901e9ef07fdb5d4079a914c function::disable 53b449708cc2ffdefa352e53bb7d847d function::edit 9ce5ba1ae4607e8cf1975080bcde1cf4 function::enable 7de1cedc36841f5de8f9fdfbc3b65097 function::export 2374cd1dbf7616cb38cafba4e171075d function::extern 1290a5223e2824763eecfb3a54961eff function::grep 55c3cea8ff4ec2403be2a9d948e59f14 function::hash 6ee131d093e95b80039b4df9c7c84a02 function::hook 675cdb98b5dd8567bdd5a02ead6184b5 function::hooks 3d989899c616f7440429a2d9bf1cc44b function::identity 6523885762fcc2f354fc25cf6ed126ce function::import 5d0f0634cbd01274f2237717507198a2 function::initial-state 03d8ed608855a723124e79ca184d8e73 function::is 41564c8f21b12ab80824ac825266d805 function::load-state b6cf278a1f351f316fa6e070359b6081 function::lock 5d8db258704e6a8623fac796f62fac02 function::ls 01a23d51d5b529e03943bd57e33f92df function::mv 4a0e338a6edb89ad1e2c779d51d4d47b function::name 955ba2d1fe1d67cd78651a4042283b00 function::parents 3da9e63b5aae9e2f5dcc946a86d166aa function::perl a0f341ea54391b63b6195e7992b6a686 function::rd 2adb16d7e819d2e87a27201744a581e7 function::reload 1589f4cf8374e0011991cb8907afca3e function::rm 6f6fd7a6c25558eb469d78ea888f8551 function::rmparent fc2884910a6939a47898a778f277332c function::save 778c0e1043b9c6c96fb8f266f8061624 function::save-state 5af59ebc4ad8965767e4dc106d3b557e function::serialize a19ada2d2558ea9da3a7942fb913e15f function::serialize-single aa77af032272f5a2664e21713739a223 function::sh 1b2f542ca9dd63ad437058b7f6f61aac function::shb 7b2685a4041c25bc495816e472bdace5 function::shell a87f389b94713e5855e62241d649d01d function::size 8d4bd7a84ece556717f8ba3bf258d33c function::snapshot 56939a47f2758421669641e15ebd66eb function::state 8c68044dccae28f33244d0c7e9e9acfb function::touch 3991b1b7c7187566f50e5e58ce01fa06 function::unlock b4aac02f7f3fb700acf4acfd9b180ceb function::update ac391dc90e507e7586c81850e7c2ecdd function::update-from 631721c4dc30e11b2023a6703cbcef52 function::usage 5bdd370f5a56cfbf199e08d398091444 function::verify 0c0cc1dfeab7d705919df122f7850a4f indicator::cc 3db7509c521ee6abfedd33d5f0148ed3 indicator::locked fc2b4f4ca0d6a334b9ac423d06c8f18c indicator::path 9ec891df17cd45895f03a6124f9d065f internal_function::around_hook 7cc876e7c5f78c34654337fc95255587 internal_function::associate 05a75afb70daee635eefec8ae037f593 internal_function::attribute dd6f010f9688977464783f60f5b6d3dd internal_function::attribute_is 40bda8226322505e323ea6d405388f08 internal_function::cache eb9da45580a9ac0882baf98acd2ecd60 internal_function::chmod_self 2035e861eedab55ba0a9f6f5a068ca70 internal_function::dangerous 46c4baaa214ab3d05af43e28083d5141 internal_function::debug_trace 0faf9d9f4159d72dfe4481f6f3607ce1 internal_function::execute f0924e087d978ff2ab1e117124db3042 internal_function::exported ae35afef7d4762f2818aee5872c75be0 internal_function::extension_for 9de8261d69cc93e9b92072b89c89befd internal_function::fast_hash ee5eba48f837fda0fe472645fdd8899a internal_function::file::read e647752332c8e05e81646a3ff98f9a8e internal_function::file::write 3e290fdcb353c6f842eb5a40f2e575f8 internal_function::fnv_hash c36d56f1e13a60ae427afc43ba025afc internal_function::hypothetically b83e3f894a6df8623ccd370515dfd976 internal_function::internal::main f31f2945a19a668d92505f114ab29c78 internal_function::invoke_editor_on 5eb976796f0ec172d6ec036116a2f41e internal_function::is_locked da12ced6aa38295251f7e748ffd22925 internal_function::namespace 784d2e96003550681a4ae02b8d6d0a27 internal_function::parent_attributes f6ccfaa982ab1a4d066043981aaca277 internal_function::parent_ordering 57b6da88f76b59f3fed9abfa61280e5e internal_function::retrieve 8a34d1fe047fe1b40c3d2957c4a789eb internal_function::retrieve_with_hooks 0f1b0220ccd973d57a2e96ff00458cf2 internal_function::select_keys a5e3532ec6d58151d0ee24416ea1e2b5 internal_function::separate_options 34ec41a6edaa15adde607a0db3ccfa36 internal_function::strip 14f490b10ebd519e829d8ae20ea4d536 internal_function::table_display d575f4dc873b2e0be5bd7352047fd904 internal_function::temporary_name 6f548d101fc68356515ffd0fc9ae0c93 internal_function::translate_backtrace d77a56d608473b3cd8a3c6cb84185e10 internal_function::with_exported df345d5095d5ed13328ddd07ea922b36 library::shell 6b9f3befb61a01e9132a440601f8ea0a library::terminal 7e2d045782405934a9614fe04bcfe559 message_color::cc 2218ef0f7425de5c717762ffb100eb43 message_color::state 03621cd6ac0b1a40d703f41e26c5807f message_color::states ac66eeeff487b5f43f88a78ea18b3d56 meta::configure 69c2e727c124521d074fde21f8bbc4db meta::externalize aa44e27e0bbee6f0ca4de25d603a1fc7 meta::functor::editable 48246c608f363de66511400e00b26164 meta::type::alias 889d26d2df385e9ff8e2da7de4e48374 meta::type::bootstrap 51108ab2ddb8d966e927c8f62d9ef3e5 meta::type::cache 9267171f2eace476f64a1a670eaaf2c7 meta::type::data 120e1649a468d3b3fd3fb783b4168499 meta::type::function 8ea626198861dc59dd7f303eecb5ff88 meta::type::hook ff92aef328b6bdc6f87ddd0821f3e42f meta::type::inc 78e0375b6725487cb1f0deca41e96bbe meta::type::indicator feb54a2624e6983617685047c717427f meta::type::internal_function eff3cf31e2635f51c83836f116c99d2f meta::type::library 7622e8d65e03066668bade74715d65ad meta::type::message_color 557a1b44979cbf77a7251fbdc4c5b82c meta::type::meta c6250056816b58a9608dd1b2614246f8 meta::type::parent 09d1d03379e4e0b262e06939f4e00464 meta::type::retriever 71a29050bf9f20f6c71afddff83addc9 meta::type::state 84da7d5220471307f1f990c5057d3319 retriever::file 3bbc9d8a887a536044bafff1d54def7e retriever::id 4da6080168d32445150cc4200af7af6e retriever::object c7633990b4e01bdc783da7e545799f4f retriever::perl f41938e6dbad317f62abffc1e4d28cca __ meta::parent('preprocessor', <<'__'); function::preprocess ab5526a02ff417d4c162357dc327e7c4 meta::functor::html-templates 2771200f87e9cbfeecfb5f8a0f796f18 meta::type::template bc4b0c80b5efc716b19e99b832c22bf3 parent::object 5788ae36d7790310e3b66d5ddd8b796e retriever::pp 3b5f5c5d30c5a04f72056dedaacfe7b7 template::comment dfe273d2dad3d8159b847545e4e5c309 template::eval 1a0e2124a05056be4abc11803883c294 template::failing_conditional e3a4523110dd859e828f342185de7c62 template::include 47b5552d609d97fe7f2522d5c1027014 template::pinclude c07ff79bf8d642cceaa9ef844bfcb189 template::script-include 76be051ad116449ddebd10e7c3729afd template::style-include 8e5a06b70e1b00379765f319bf6c8066 __ meta::parent('repository', <<'__'); function::dupdate 3203750417390913ae3892002b53bdc1 meta::type::cached_dependency b9dc0b20c2d3af0deb3b835b20cac4a7 parent::/home/spencertipping/bin/configuration 7df7136d772c7810d907d238e7cae3f2 retriever::http a23617a5787de41d1a89ad4496cacce3 __ meta::parent('vim-highlighters', <<'__'); function::vim cf9e37026f6cd1499a6dd258fbbcd060 meta::type::vim_highlighter 27990fddb6d7bd383b55dbdfee0d148d parent::object 5788ae36d7790310e3b66d5ddd8b796e __ meta::resource('header', <<'__'); - include resource::header-packages - include resource::header-listings - include resource::header-refs - include resource::header-languages - include resource::header-resource __ meta::resource('header-languages', <<'__'); \lstnewenvironment{asmcode} {}{} \lstnewenvironment{cppcode} {\lstset{language=c++}}{} \lstnewenvironment{javacode} {\lstset{language=java}}{} \lstnewenvironment{javascriptcode}{}{} \lstnewenvironment{htmlcode} {\lstset{language=html}}{} \lstnewenvironment{perlcode} {\lstset{language=perl}}{} \lstnewenvironment{rubycode} {\lstset{language=ruby}}{} __ meta::resource('header-listings', <<'__'); \definecolor{gray}{rgb}{0.6,0.6,0.6} \usepackage{caption} \DeclareCaptionFormat{listing}{\llap{\color{gray}#1\hspace{10pt}}\tt{}#3} \captionsetup[lstlisting]{format=listing, singlelinecheck=false, margin=0pt, font={bf}} \lstset{columns=fixed,basicstyle={\tt},numbers=left,firstnumber=auto,basewidth=0.5em,showstringspaces=false,numberstyle={\color{gray}\scriptsize}} __ meta::resource('header-packages', <<'__'); \usepackage[utf8]{inputenc} \usepackage{amsmath,amssymb,amsthm,pxfonts,listings,color} \usepackage[colorlinks]{hyperref} __ meta::resource('header-refs', '\\newcommand{\\Ref}[2]{\\hyperref[#2]{#1 \\ref*{#2}}}'); meta::resource('header-resource', '\\lstnewenvironment{resourcecode}{}{}'); meta::retriever('file', '-f $_[0] ? file::read($_[0]) : undef;'); meta::retriever('http', <<'__'); use LWP::Simple (); return undef unless $_[0] =~ /^(?:http:)?\/\/(\w+.*)$/; LWP::Simple::get("http://$1"); __ meta::retriever('id', '$_[0] =~ /^id::/ ? substr($_[0], 4) : undef;'); meta::retriever('object', <<'__'); # Fetch a property from another Perl object. This uses the 'cat' function. return undef unless $_[0] =~ /^object::(.*?)::(.*)$/ && -x $1 && qx|$1 is '$2'|; join '', qx|$1 cat '$2'|; __ meta::retriever('perl', <<'__'); # Lets you use the result of evaluating some Perl expression return undef unless $_[0] =~ /^perl::(.*)$/; eval $1; __ meta::retriever('pp', <<'__'); return undef unless namespace($_[0]) eq 'pp'; my $attr = retrieve(attribute($_[0])); defined $attr ? preprocess($attr) : undef; __ meta::section('a-big-quine', <<'__'); - sc A Big Quine | sec:a-big-quine At the core of things, a self-modifying Perl script is just a big quine.\footnote{A ``quine'' being a program that prints its own source.} There are only two real differences: - enumerate << end - item Self-modifying Perl scripts print into their own files rather than to standard output. - item They print modified versions of themselves, not the original source. - end \noindent If we're going to write such a script, it's good to start with a simple quine. - s1 A basic quine | sec:a-basic-quine Some languages make quine-writing easier than others. Perl actually makes it very simple. Here's one: - perl examples/quine << end my $code = <<'EOF'; print 'my $code = <<\'EOF\';', "\n", $code, "EOF\n"; print $code; EOF print 'my $code = <<\'EOF\';', "\n", $code, "EOF\n"; print $code; - end \noindent The logic is fairly straightforward, though it may not look like it. We're quoting a bunch of stuff using \verb|<<'EOF'|,\footnote{The single-quoted heredoc form doesn't do any interpolation inside the document, which is ideal since we don't want to worry about escaping stuff.} and storing that into a string. We then put the quoted content outside of the heredoc to let it execute. The duplication is necessary; we want to quote the content and then run it.\footnote{Later on I'll use {\tt eval} to reduce the amount of duplication.} The key is this line: - verbatim << end print 'my $code = <<\'EOF\';', "\n", $code, "EOF\n"; print $code; - end \noindent This code prints the setup to define a new variable \verb|$code| and prints its existing content after that. - s1 Reducing duplication | sec:reducing-duplication We don't want to write everything in our quine twice. Rather, we want to store most stuff just once and have a quine that scales well. The easiest way to do this is to use a hash to store the state, and serialize each key of the hash in the self-printing code. So instead of creating \verb|$code|, we'll create \verb|%data|: - perl examples/quine-with-data << end my %data; $data{code} = <<'EOF'; print 'my %data;', "\n"; print '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $data{code}; EOF print 'my %data;', "\n"; print '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $data{code}; - end \noindent This is a good start. Here's how to add attributes without duplication: - perl examples/quine-with-data-and-foo << end my %data; $data{foo} = <<'EOF'; a string EOF $data{code} = <<'EOF'; print 'my %data;', "\n"; print '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $data{code}; EOF print 'my %data;', "\n"; print '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $data{code}; - end - s1 Using {\tt eval} | sec:using-eval The business about duplicating \verb|$data{code}| is easily remedied by just {\tt eval}ing \verb|$data{code}| at the end. This requires the {\tt eval} section to be duplicated, but it's smaller than \verb|$data{code}|. Here's the quine with that transformation:\footnote{Note that these quines might not actually print themselves identically due to hash-key ordering. This is fine; all of the keys are printed before we use them.} - perl examples/quine-with-data-and-eval << end my %data; $data{foo} = <<'EOF'; a string EOF $data{code} = <<'EOF'; print 'my %data;', "\n"; print '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $data{bootstrap}; EOF $data{bootstrap} = <<'EOF'; eval $data{code}; EOF eval $data{code}; - end \noindent The advantage of this approach is that all we'll ever have to duplicate is \verb|eval $data{code}| and \verb|my %data;|, which is fairly trivial. It's important that you understand what's going on here, since this idea is integral to everything going forward.\footnote{Alternatively, it probably also works to accept the code so far as magic and take my word for it that future code snippets do what they should. But it's probably less fun without the ``aha!'' moment.} __ meta::section('adding-a-repl', <<'__'); - sc Adding a REPL | sec:adding-a-repl There are some ergonomic problems with the script as it stands. First, it should have a shebang line so that we don't have to use {\tt perl} explicitly. But more importantly, it should provide a REPL so that we don't have to keep calling it by name. The first question is how this should be invoked. It would be cool if we could run the script without arguments and get the REPL, but that will require some changes to the current {\tt code::main}. The ``right way'' to do it also requires a new data type. - s1 The {\tt data} data type | sec:adding-a-repl-the-data-data-type Sometimes we just want to store pieces of data without any particular meaning. We could use {\tt bootstrap::} for this, but it's cleaner to introduce a new data type altogether. - perl snippets/define-form-data << end meta::define_form 'data', sub { # Define a basic editing interface: my ($name, $value) = @_; *{$name} = sub { my ($command, $value) = @_; return $data{"data::$name"} unless @_; $data{"data::$name"} = $value if $command eq '='; }; }; - end This function we're defining lets us inspect and change a data attribute from the command line. Assuming {\tt data::foo}, for example: - verbatim << end $ perl script foo = bar bar $ perl script foo bar $ perl script foo = baz baz $ - end - s1 Setting up the default action | sec:adding-a-repl-setting-up-the-default-action The default action can be stored in a {\tt data::} attribute: - verbatim << end meta::data('default-action', <<'EOF'); shell EOF meta::code('main', <<'EOF'); ... my $command = shift @ARGV || $data{'data::default-action'}; print &$command(@ARGV); ... EOF - end Since all values are chomped already, we don't have to worry about the newline caused by the heredoc. - s1 Making the script executable | sec:adding-a-repl-making-the-script-executable This isn't hard at all. It means one extra line in the bootstrap logic, and another extra line in {\tt save}: - verbatim << end meta::bootstrap('initialization', <<'EOF'); #!/usr/bin/perl ... EOF meta::function('save', <<'EOF'); ... close $file; chmod 0744, $0; # Not perfect, but will fix later ... EOF - end - s1 The {\tt shell} function | sec:adding-a-repl-the-shell-function The idea here is to listen for commands from the user and simulate the \verb|@ARGV| interaction pattern. Readline is the simplest way to go about this: - perl snippets/shell-function-1 << end meta::function('shell', <<'EOF'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $output = $term->OUT || \*STDOUT; while (defined($_ = $term->readline("$0\$ "))) { my @args = grep length, split /\s+|("[^"\\]*(?:\\.)?")/o; my $function_name = shift @args; s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args; if ($function_name) { chomp(my $result = eval {&$function_name(@args)}); warn $@ if $@; print $output $result, "\n" unless $@; } } EOF - end This shell function does some minimal quotation-mark parsing so that you can use multi-word arguments, but otherwise it's fairly basic. The script's name is used as the shell prompt. It's OK to use {\tt use} inside of {\tt eval}ed functions. I think what happens is that it gets processed when the function is first created by {\tt meta::function}. But basically, Perl does the right thing and it works just fine as long as the module exists. - s1 Taking it to the max: tab-completion | sec:adding-a-repl-tab-completion If you have the GNU Readline library installed (Perl defaults to something else otherwise), you can get tab-autocompletion just like you can in Bash. Here's a {\tt complete} function written by my wife Joyce, modified slightly to make sense with this implementation: - perl snippets/complete-function-1 << end meta::function('complete', <<'EOF'); my @attributes = sort keys %data; sub match { my ($text, @options) = @_; my @matches = sort grep /^$text/, @options; if (@matches == 0) {return undef;} elsif (@matches == 1) {return $matches [0];} elsif (@matches > 1) { return ((longest ($matches [0], $matches [@matches - 1])), @matches); } } sub longest { my ($s1, $s2) = @_; return substr ($s1, 0, length $1) if ($s1 ^ $s2) =~ /^(\0*)/; return ''; } my ($text, $line) = @_; match ($text, @attributes); EOF - end Using this function is easy; we just add one line to {\tt shell}: - verbatim << end $term->Attribs->{attempted_completion_function} = \&complete; while (defined($_ = $term->readline("$0\$ "))) { ... - end - s1 Final result | sec:adding-a-repl-final-result Merging the shell and executable behavior in with the script from the last chapter, we now have:\footnote{You might notice that I'm still using {\tt EOF} as the marker in these scripts. As soon as the script is rewritten it will replace the {\tt EOF}s with hashes; in general, you can use any valid delimiter the first time around and the script will take it from there.} - perl examples/shell << end #!/usr/bin/perl my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'bootstrap', sub {}; meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; meta::define_form 'data', sub { # Define a basic editing interface: my ($name, $value) = @_; *{$name} = sub { my ($command, $value) = @_; return $data{"data::$name"} unless @_; $data{"data::$name"} = $value if $command eq '='; }; }; meta::bootstrap('initialization', <<'EOF'); #!/usr/bin/perl my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'bootstrap', sub {}; meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; meta::define_form 'data', sub { # Define a basic editing interface: my ($name, $value) = @_; *{$name} = sub { my ($command, $value) = @_; return $data{"data::$name"} unless @_; $data{"data::$name"} = $value if $command eq '='; }; }; EOF meta::data('default-action', <<'EOF'); shell EOF meta::function('serialize', <<'EOF'); my @keys = sort keys %data; join "\n", $data{'bootstrap::initialization'}, map(serialize_single($_), grep !/^code::/, @keys), map(serialize_single($_), grep /^code::/, @keys), "\n__END__"; EOF meta::function('serialize_single', <<'EOF'); my ($namespace, $name) = split /::/, $_[0], 2; my $marker = '__' . fast_hash($data{$_[0]}); "meta::$namespace('$name', <<'$marker');\n$data{$_[0]}\n$marker"; EOF meta::function('fnv_hash', <<'EOF'); 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; EOF meta::function('fast_hash', <<'EOF'); 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]; EOF meta::function('state', <<'EOF'); fast_hash(serialize()); EOF meta::function('verify', <<'EOF'); my $serialized_data = serialize(); my $state = state(); my $temporary_filename = "$0.$state"; open my $file, '>', $temporary_filename; print $file $serialized_data; close $file; chmod 0700, $temporary_filename; chomp(my $observed_state = join '', qx|perl '$temporary_filename' state|); my $result = $observed_state eq $state; unlink $temporary_filename if $result; $result; EOF meta::function('save', <<'EOF'); if (verify()) { open my $file, '>', $0; print $file serialize(); close $file; chmod 0744, $0; } else { warn 'Verification failed'; } EOF meta::function('cat', <<'EOF'); join "\n", @data{@_}; EOF meta::function('set', <<'EOF'); $data{$_[0]} = join '', ; EOF meta::function('complete', <<'EOF'); my @attributes = sort keys %data; sub match { my ($text, @options) = @_; my @matches = sort grep /^$text/, @options; if (@matches == 0) {return undef;} elsif (@matches == 1) {return $matches [0];} elsif (@matches > 1) { return ((longest ($matches [0], $matches [@matches - 1])), @matches); } } sub longest { my ($s1, $s2) = @_; return substr ($s1, 0, length $1) if ($s1 ^ $s2) =~ /^(\0*)/; return ''; } my ($text, $line) = @_; match ($text, @attributes); EOF meta::function('shell', <<'EOF'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $output = $term->OUT || \*STDOUT; $term->Attribs->{attempted_completion_function} = \&complete; while (defined($_ = $term->readline("$0\$ "))) { my @args = grep length, split /\s+|("[^"\\]*(?:\\.)?")/o; my $function_name = shift @args; s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args; if ($function_name) { chomp(my $result = eval {&$function_name(@args)}); warn $@ if $@; print $output $result, "\n" unless $@; } } EOF meta::function('edit', <<'EOF'); my $filename = '/tmp/' . rand(); open my $file, '>', $filename; print $file $data{$_[0]}; close $file; system($ENV{EDITOR} || $ENV{VISUAL} || '/usr/bin/nano', $filename); open my $file, '<', $filename; $data{$_[0]} = join '', <$file>; close $file; EOF meta::code('main', <<'EOF'); my $initial_state = state(); my $command = shift @ARGV || $data{'data::default-action'}; print &$command(@ARGV); save() if state() ne $initial_state; EOF __END__ - end __ meta::section('archiving-state', <<'__'); - sc Archiving state | sec:archiving-state Suppose you're about to do something risky with a script and you want to take a snapshot that you can restore to. You could copy into another file, but that's a brute-force approach and it requires you to exit the script's shell. Better is to have some kind of internal state management, and that's where explicit states come into play. Remember that \verb|%data| is just a variable; we can do all of the usual things with it. We can store a state by doing a partial serialization into an attribute, and we can restore from that state by {\tt eval}ing that attribute. To do this we're going to need another namespace. - perl snippets/state-type << end meta::meta('type::state', <<'EOF'); # No action when a state is defined meta::define_form 'state', \&meta::bootstrap::implementation; EOF - end - s1 Saving state | sec:archiving-state-saving It's tempting to think that this code would do what we want: - verbatim << end # Won't work: associate("state::$_[0]", serialize()); - end Unfortunately, {\tt serialize} generates three things that we don't want. These are the bootstrap section at the beginning, the call to {\tt internal::main()} at the end, and any attribute in the {\tt state::} namespace.\footnote{If some states contained others, the script size would grow exponentially in the number of states.} We'll need to write a separate function to serialize just what we want: - perl snippets/current-state-function << end meta::function('current-state', <<'EOF'); 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; EOF - end \noindent And here's a {\tt save-state} function to automate the state creation process: - perl snippets/save-state-function << end meta::function('save-state', <<'EOF'); my ($state_name) = @_; associate("state::$state_name", &{'current-state'}()); EOF - end - s1 Loading state | sec:archiving-state-loading This is not as straightforward as saving state. Because we're modifying \verb|%data| live, we have to be careful about what happens in the event that something goes wrong. We also don't want to have stray \verb|%data| elements or externalized functions. The easiest way to defend against errors is to save the current state before applying a new one. Here's the implementation of {\tt load-state}: - perl snippets/load-state-function << end meta::function('load-state', <<'EOF'); my ($state_name) = @_; my $state = retrieve("state::$state_name"); &{'save-state'}('_'); # Make a backup delete $data{$_} for grep ! /^state::/, keys %data; %externalized_functions = (); eval($state); # Apply the new state warn $@ if $@; verify(); # Make sure it worked EOF - end If the load failed for some reason, you can restore using \verb|load-state _|. If it failed badly enough to bork your {\tt load-state} function, then you have a problem. - s1 The {\tt hypothetically} function | sec:archiving-state-hypothetically Related to state management is a function called {\tt hypothetically}, which lets you try something out and then revert. It's used internally to examine the state of a modified copy without actually committing changes.\footnote{This is covered in \Ref{chapter}{sec:cloning-and-inheritance}.} Here's how it's defined: - perl snippets/hypothetically-function << end meta::internal_function('hypothetically', <<'EOF'); my %data_backup = %data; my ($side_effect) = @_; my $return_value = eval {&$side_effect()}; %data = %data_backup; die $@ if $@; $return_value; EOF - end You can use it like this: - verbatim << end my $x = hypothetically(sub { associate('data::foo', '10'); retrieve('data::foo'); }); my $y = retrieve('data::foo'); # now $x eq '10' and $y is undef - end __ meta::section('building-the-interface', <<'__'); - sc Building the interface | sec:building-the-interface Now that we've got attribute storage working, let's build a command-line interface so that we don't have to edit these files by hand anymore. There are a couple of things that need to happen. First, we need to get these scripts to overwrite themselves instead of printing to standard output. Second, we need a way to get and set entries in \verb|%data|. Starting with the quine from the last section, here's one way to go about it: - perl examples/cli-basic << end my %data; $data{cat} = <<'EOF'; sub cat { print join "\n", @data{@_}; } EOF $data{set} = <<'EOF'; sub set { $data{$_[0]} = join '', ; } EOF $data{code} = <<'EOF'; # Eval functions into existence: eval $data{cat}; eval $data{set}; # Run specified command: my $command = shift @ARGV; &$command(@ARGV); # Save new state: open my $fh, '>', $0; print $fh 'my %data;', "\n"; print $fh '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $fh $data{bootstrap}; close $fh; EOF $data{bootstrap} = <<'EOF'; eval $data{code}; EOF eval $data{code}; - end \noindent Now we can modify its state: - verbatim << end $ perl examples/cli-basic cat cat sub cat { print join "\n", @data{@_}; } $ perl examples/cli-basic set foo bar ^D $ perl examples/cli-basic cat foo bar $ - end Not bad for a first implementation. This is a very minimal self-modifying Perl file, though it's useless at this point. It also has some fairly serious deficiencies (other than being useless). I'll cover the serious problems later on, but first let's address the usability. - s1 Using an editor | sec:using-an-editor The first thing that would help this script be more useful is a function that let you edit things with a real text editor. Fortunately this isn't difficult: - verbatim << end $ cp examples/cli-basic temp $ perl temp set edit sub edit { my $filename = '/tmp/' . rand(); open my $file, '>', $filename; print $file $data{$_[0]}; close $file; system($ENV{EDITOR} || $ENV{VISUAL} || '/usr/bin/nano', $filename); open my $file, '<', $filename; $data{$_[0]} = join '', <$file>; close $file; } ^D $ - end It won't work yet though. The reason is that we aren't {\tt eval}ing {\tt edit} yet; we need to manually edit the {\tt code} section and insert this line: - verbatim << end ... eval $data{cat}; eval $data{set}; eval $data{edit}; # <- insert this ... - end Now you can invoke a text editor on any defined attribute:\footnote{Don't modify {\tt bootstrap} or break the print code though! This will possibly nuke your object.} - verbatim << end $ perl examples/cli-editor edit cat # hack away $ - end Here's the object at this point: - perl examples/cli-editor << end my %data; $data{cat} = <<'EOF'; sub cat { print join "\n", @data{@_}; } EOF $data{set} = <<'EOF'; sub set { $data{$_[0]} = join '', ; } EOF $data{edit} = <<'EOF'; sub edit { my $filename = '/tmp/' . rand(); open my $file, '>', $filename; print $file $data{$_[0]}; close $file; system($ENV{EDITOR} || $ENV{VISUAL} || '/usr/bin/nano', $filename); open my $file, '<', $filename; $data{$_[0]} = join '', <$file>; close $file; } EOF $data{code} = <<'EOF'; # Eval functions into existence: eval $data{cat}; eval $data{set}; eval $data{edit}; # Run specified command: my $command = shift @ARGV; &$command(@ARGV); # Save new state: open my $fh, '>', $0; print $fh 'my %data;', "\n"; print $fh '$data{', $_, '} = <<\'EOF\';', "\n$data{$_}EOF\n" for keys %data; print $fh $data{bootstrap}; close $fh; EOF $data{bootstrap} = <<'EOF'; eval $data{code}; EOF eval $data{code}; - end __ meta::section('cloning-and-inheritance', <<'__'); - sc Cloning and inheritance | sec:cloning-and-inheritance This is probably the single coolest thing about self-modifying Perl programs. You've probably had this looming feeling that propagating updated versions of functions between scripts was going to be a complete nightmare. For a long time this was indeed the case; I had shell scripts that copied attributes out of one script and into another. Luckily I got tired of doing things that way and came up with the inheritance mechanism that's used now. Inheritance isn't as simple as copying all of the attributes from one script into another. Certain namespaces like {\tt data::} are script-specific, for instance. We'll need to have some way to keep track of which namespaces should be inherited. Another issue is getting attributes from one script into another. My first implementation of inheritance retrieved each attribute individually. It used {\tt ls} and {\tt cat} for the transfer, which involved $O(n)$ runs of whichever script was being inherited from. Obviously it was really slow. $O(n)$ runs of a function containing $n$ functions means $O(n^2)$ total time, and Perl isn't blazingly fast at {\tt eval}ing functions. Later on I extended {\tt serialize} to return a bundle of attributes that the child then {\tt eval}ed. - s1 Tracking inheritability | sec:cloning-and-inheritance-tracking-inheritability We're going to need another toplevel field if we want to store data about data types. We can't use \verb|%data|, since we don't really want to save it (whatever we're storing would be regenerated automatically anyway). What we really need is a way to store transient information: - verbatim << end my %data; my %externalized_functions; my %datatypes; my %transient; - end \verb|%transient| does nothing except store stuff while the script is running, and all of its information is discarded when the script exits. It's basically just a temporary workspace where we can stash stuff. We can now use \verb|%transient| to store things about data types. For convenience let's define {\tt meta::configure} to do this for us:\footnote{For some reason I decided to store the keys in the odd order of {\tt option}-{\tt namespace} instead of the other way around. I'm still not sure why I did it this way, but it doesn't seem to cause problems.} - perl snippets/meta-configure << end meta::meta('configure', <<'EOF'); sub meta::configure { my ($datatype, %options) = @_; $transient{$_}{$datatype} = $options{$_} for keys %options; } EOF - end Now we can add a configuration to each datatype we define: - verbatim << end meta::meta('type::function', <<'EOF'); meta::configure 'function', inherit => 1; meta::define_form 'function', ...; EOF meta::meta('type::data', <<'EOF'); meta::configure 'data', inherit => 0; meta::define_form 'data', ...; EOF meta::meta('type::internal_function', <<'EOF'); meta::configure 'internal_function', inherit => 1; ... EOF meta::meta('type::bootstrap', <<'EOF'); meta::configure 'bootstrap', inherit => 1; ... EOF meta::meta('type::state', <<'EOF'); meta::configure 'state', inherit => 0; ... EOF - end - s1 Extensions to {\tt serialize} | sec:cloning-and-inheritance-extensions-to-serialize {\tt serialize} needs to be able to give us a bundle of code to create just the attributes that should be inherited. While we're at it, it would also be nice if it handed us just the {\tt meta::} attributes and then just the non-{\tt meta::} attributes. This way we can make sure that the {\tt meta::} attributes didn't break anything and bail out early if they did. None of this is particularly challenging, but given that we're going to invoke {\tt serialize} externally we should probably fix the \verb|%options| stuff. (The last thing we want to write is something like \verb|qx($script serialize partial 1 meta 1 inheritable 1)|). What we need is an adapter that turns command-line options into Perl hashes.\footnote{OK, I'm making a jump here. Later it will become clearer why it's good to do it this way.} Here's a function that uses {\tt Getopt}-style parsing: - perl snippets/separate-options-function << end meta::internal_function('separate_options', <<'EOF'); # 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, @_); EOF - end The output of this function is a reference to a hash of any keyword arguments (where short-form arguments are treated as increments) followed by any non-switch arguments (either because they came after \verb|--| or because they didn't start with a dash at all. For example, processing the arguments \verb|-xy --z=foo bar| would yield \verb|({-x => 1, -y => 1, --z => foo}, bar)|.\footnote{Given the similarity, I don't remember why I didn't just use {\tt Getopt::Long} for this stuff. I think I must have been having a NIH day.} Given the ability to pipe options into {\tt serialize} on the command-line, we just need to have it support a reasonably flexible selection interface. We'll later need to have {\tt ls} support the same options, so let's factor the key selector into its own function: - perl snippets/select-keys-function << end meta::internal_function('select_keys', <<'EOF'); my %options = @_; my $criteria = $options{'--criteria'} || $options{'--namespace'} && "^$options{'--namespace'}::" || '.'; grep /$criteria/ && (! $options{'-i'} || $transient{inherit}{namespace($_)}) && (! $options{'-I'} || ! $transient{inherit}{namespace($_)}) && (! $options{'-S'} || ! /^state::/o) && (! $options{'-m'} || /^meta::/o) && (! $options{'-M'} || ! /^meta::/o), sort keys %data; EOF - end This function takes the \verb|%options| hash output by \verb|separate_options| as input and returns a list of keys into \verb|%data|. The somewhat odd logical structure of the {\tt grep} predicate is just implication: ``if \verb|$options{'-i'}| is set, then the key's namespace must be inheritable.'' The new {\tt serialize} function is fairly simple; most of the heavy lifting is already done: - perl snippets/serialize-final << end meta::function('serialize', <<'EOF'); 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; EOF - end {\tt -m} and {\tt -M} select {\tt meta::} and non-{\tt meta::} attributes, respectively. We also provide criteria if the user has selected any. They're joined together with a pipe symbol because that forms a disjunction inside a regular expression (and criteria are regexps for attribute names). We also, somewhat importantly, have a {\tt -p} switch to produce a partial serialization. This leaves off the bootstrap code and the {\tt internal::main()} call. The only difference between this and {\tt current-state} is that {\tt current-state} also leaves out {\tt state::} attributes.\footnote{This version of {\tt serialize} also will do that for you if you pass the {\tt -S} option.} - s1 The {\tt update-from} function | sec:cloning-and-inheritance-the-update-from-function Here's where things start to get interesting. {\tt update-from} handles the case where you have two scripts {\tt base} and {\tt child}, and you want {\tt child} to inherit stuff from {\tt base}.\footnote{``Stuff'' is deliberately vague. Presumably we want to inherit every inheritable attribute though.} Here's a basic implementation: - perl snippets/update-from-function << end meta::function('update-from', <<'EOF'); my ($options, @targets) = separate_options(@_); my %options = %$options; @targets or die 'Must specify at least one target to update from'; my $save_state = ! ($options{'-n'} || $options{'--no-save'}); my $force = $options{'-f'} || $options{'--force'}; &{'save-state'}('before-update') if $save_state; for my $target (@targets) { eval qx($target serialize -ipm); eval qx($target serialize -ipM); reload(); # We're about to define this unless (verify()) { if ($force) { warn 'Verification failed, but keeping new state'; } else { warn "Verification failed after $target; reverting"; return &{'load-state'}('before-update') if $save_state; } } } EOF - end If {\tt child} has this function, you can update it this way (assuming you've set execute permissions): - verbatim << end $ ./child update-from ./base $ - end If all goes well it will execute without failing. {\tt base} will be run twice: once to grab inheritable {\tt meta::}-attributes, again to grab inheritable other attributes. The {\tt reload} function just calls {\tt execute} on each member of \verb|%data|. It's there to make sure that the new attributes and the old attributes work well together. Here's the definition: - perl snippets/reload-function << end meta::function('reload', <<'EOF'); execute($_) for grep ! /^bootstrap::/, keys %data; EOF - end - s1 Managing parents | sec:cloning-and-inheritance-managing-parents If you're using scripts as add-on modules, it gets tiring to issue $n$ {\tt update-from} commands when you're using $n$ modules. Further, you have to type out the whole path of the module, which might be in a separate directory. Finally, attributes that later get deleted or renamed in the addon modules won't be cleaned up in the child script. The way we've got inheritance set up is woefully incomplete. The missing piece is parent tracking. We need to keep track of two things: - enumerate << end - item Which scripts have I inherited from? - item Which properties did I inherit from each one? - end Fortunately this isn't too hard. We just need a new namespace and some new functions. - s2 The {\tt parent::} namespace | sec:cloning-and-inheritance-the-parent-namespace We can use the {\tt parent::} namespace to answer both of the above questions. Each attribute will take its name from the path to a script (e.g.~{\tt parent::/usr/bin/script1}), and its contents will be a newline-separated string of the attributes inherited from that script. Here's the type definition: - perl snippets/parent-type << end meta::meta('type::parent', <<'EOF'); meta::configure 'parent', inherit => 1; # Transitive parents (explained below) meta::define_form 'parent', \&meta::bootstrap::implementation; EOF - end Now we need to update {\tt update-from} to populate this namespace. Before we do, though, we need to define uniqueness. - s2 Uniqueness | sec:cloning-and-inheritance-uniqueness Let's suppose you've got three objects {\tt a}, {\tt b}, and {\tt c}, and each inherits from the previous one.\footnote{I'm presupposing that inheritance works automatically even though we haven't defined it quite yet.} Then {\tt b}'s parent is {\tt a}, and {\tt c}'s parent is {\tt b}. It's important for {\tt c} to know that {\tt a} is also its parent. The reason is that otherwise you're forced to update {\tt b} before {\tt c}, since {\tt c} inherits only from {\tt b}. It's ultimately for this reason that {\tt parent::} attributes get inherited. Inheritance is certainly transitive (if {\tt c} inherits from {\tt b}, which inherits from {\tt a}, then {\tt c} inherits from {\tt a}). There are, however, some logistical matters to be dealt with. The most important one has to do with ordering. It shouldn't matter in which order the parents are inherited from. This is an interesting requirement, because it means that the things an object inherits from each of its parents form disjoint sets. The only way to pull this off is if each object keeps track of how it's different from its own parents. The attributes that the child has but the parent doesn't are considered unique. - s2 Updating {\tt ls} and {\tt serialize} | sec:cloning-and-inheritance-updating-ls-and-serialize We need a way to ask {\tt ls} for the list of unique attributes for an object, and then ask {\tt serialize} to give us just those attributes. To do this, we'll add a {\tt -u} option (and for symmetry a complement {\tt -U}) to \verb|select_keys|: - perl snippets/select-keys-final << end meta::internal_function('select_keys', <<'EOF'); my %options = @_; my %inherited = map {$_ => 1} split /\n/o, join "\n", retrieve(grep /^parent::/o, sort keys %data) if $options{'-u'} or $options{'-U'}; my $criteria = $options{'--criteria'} || $options{'--namespace'} && "^$options{'--namespace'}::" || '.'; grep /$criteria/ && (! $options{'-u'} || ! $inherited{$_}) && (! $options{'-U'} || $inherited{$_}) && (! $options{'-i'} || $transient{inherit}{namespace($_)}) && (! $options{'-I'} || ! $transient{inherit}{namespace($_)}) && (! $options{'-S'} || ! /^state::/o) && (! $options{'-m'} || /^meta::/o) && (! $options{'-M'} || ! /^meta::/o), sort keys %data; EOF - end The extra logic here searches through all of the {\tt parent::} attributes to find properties that the parents also contain. If any parent contains one, then the property isn't unique. {\tt serialize} will get this new behavior automatically, since it just forwards its options to \verb|select_keys|. But we haven't modified {\tt ls} in a long time; it doesn't know anything about options. There are actually quite a few enhancements that we could make to {\tt ls}, but for now let's keep it simple and change it only as necessary: - perl snippets/ls-with-options << end meta::function('ls', <<'EOF'); my ($options, @criteria) = separate_options(@_); join "\n", select_keys('--criteria' => join '|', @criteria, %$options); EOF - end Now you can say things like {\tt ./script ls -iu} to get a listing of attributes that are both inheritable and unique. - s2 An updated {\tt update-from} function | sec:cloning-and-inheritance-an-updated-update-from-function {\tt update-from} now has three new responsibilities. One is to record the fact that we updated from another script, which involves creating a new {\tt parent::} attribute for each inheritance operation. Another is to ask each new parent which attributes it intends to define. The last thing it needs to do is clean up any attributes that some parent used to define but no longer does.\footnote{Due to how {\tt update-from} is structured, this step actually happens first.} Here's the new implementation: - perl snippets/update-from-final << end my ($options, @targets) = separate_options(@_); my %options = %$options; @targets or die 'Must specify at least one target to update from'; 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'}; &{'save-state'}('before-update') if $save_state; for my $target (@targets) { # The -a flag will become relevant once we add formatting to 'ls' my $attributes = join '', qx($target ls -aiu); warn "Skipping unreachable object $target" unless $attributes; if ($attributes) { # Remove keys that the parent used to define but doesn't anymore: rm(split /\n/, retrieve("parent::$target")) if $data{"parent::$target"}; associate("parent::$target", $attributes) unless $no_parents; eval qx($target serialize -ipmu); eval qx($target serialize -ipMu); warn $@ if $@; reload(); if (verify()) { print "Successfully updated from $_[0]. ", "Run 'load-state before-update' to undo this change.\n" if $save_state; } elsif ($force) { warn 'The object failed verification, but the failure state has been ' . 'kept because --force was specified.'; warn 'At this point your object will not save properly, though backup ' . 'copies will be created.'; print "Run 'load-state before-update' to undo the update and return to ", "a working state.\n" if $save_state; } else { warn 'Verification failed after the upgrade was complete.'; print "$0 has been reverted to its pre-upgrade state.\n" if $save_state; print "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; } } } - end - s2 The {\tt update} function | sec:cloning-and-inheritance-the-update-function This new {\tt update-from} function contains all of the logic to perform individual updates, but it still requires you to list the parent objects. There isn't any need to do this manually though, since if we look for {\tt parent::} attributes we can get the list. That's what {\tt update} does: - perl snippets/update-function << end meta::function('update', <<'EOF'); &{'update-from'}(@_, grep s/^parent:://o, sort keys %data); EOF - end - s1 {\tt clone} and {\tt child} | sec:cloning-and-inheritance-clone-and-child As things stand creating children from an object is a bit cumbersome. We have to manually copy the object and then run {\tt update-from} once to get the parent to work out, which seems like too much work. Let's take care of copying first by creating a {\tt clone} function: - perl snippets/clone-function << end meta::function('clone', <<'EOF'); open my $file, '>', $_[0]; print $file serialize(); close $file; chmod 0700, $_[0]; EOF - end Now you can clone an object as it exists at any given moment. More interesting, though, is the related {\tt child} function, which creates an object already setup for inheritance: - perl snippets/child-function << end meta::function('child', <<'EOF'); my ($child_name) = @_; clone($child_name); qx($child_name update-from $0 -n); EOF - end {\tt object} implements this function; so, for example, you could inherit from it like this: - verbatim << end $ /path/to/object child ./foo $ ./foo update -n $ ./foo ls -a parent:: parent::/path/to/object $ - end __ meta::section('detecting-divergence', <<'__'); - sc Detecting divergence | sec:detecting-divergence The inheritance system built in the last chapter has a couple of problems. One is a subtle race condition that can happen when a script has grandparents and out-of-date parents. The other is the very real deficiency that objects can't have divergent attributes without those attributes then being overwritten on the next update.\footnote{I ran into this when writing Caterwaul; its homepage is a self-modifying Perl file that needed a gray background instead of a white one when rendered as HTML. So I wrote divergence detection.} Neither of these problems is actually very difficult to solve. All we need to do is store not only the name of the attribute that we inherited, but also the state it was in when we last read it. We then make sure that the attribute we've got hasn't changed since we last read it before we update it. If it's in a different state, we assume that it was modified locally and preserve our local copy. - s1 Attribute hashing Right now, {\tt parent::} attributes are stored as lists of attributes defined by that parent. What we really want is not only the names of those attributes, but their hashes as well. So, for example, we inherited {\tt function::ls} from {\tt object} and its hash was {\tt 01a23d51d5b529e03943bd57e33f92df}. If our copy isn't the same, then we preserve our local one because it's divergent. {\tt function::update-from} uses {\tt ls} to get the attribute list that ends up making up the {\tt parent::} attribute. The last implementation used {\tt -aiu}; we'll add a new command-line option called {\tt -h} that tells {\tt ls} to include hashes. So the output would look like this: - verbatim << end $ ./object ls -ahiu ... meta::type::meta c6250056816b58a9608dd1b2614246f8 meta::type::parent 09d1d03379e4e0b262e06939f4e00464 meta::type::retriever 71a29050bf9f20f6c71afddff83addc9 meta::type::state 84da7d5220471307f1f990c5057d3319 ... $ - end This is saying that {\tt object}'s copy of {\tt meta::type::meta} hashes to {\tt c6250056816b58a9608dd1b2614246f8}, so if ours doesn't, then we have diverged. This divergence detection is taken care of by {\tt update-from}, which still evaluates everything the parent script sends but restores divergent attribute values after the fact. (I considered doing it the right way and having the parent script not send divergent attributes, but this was more complicated than I wanted to implement.) - s1 The code (lots of it) In order to implement all of this stuff, we'll need to modify {\tt ls} and {\tt update-from}. - s2 The new {\tt ls} Here's the monster {\tt ls} that {\tt object} defines:\footnote{Don't worry if you don't get what's going on here. Most of it is just display logic.} - resource snippets/updated-ls << end meta::function('ls', <<'EOF'); my ($options, @criteria) = separate_options(@_); my ($external, $shadows, $sizes, $flags, $long, $hashes, $parent_hashes) = @$options{qw(-e -s -z -f -l -h -p)}; $sizes = $flags = $hashes = $parent_hashes = 1 if $long; return table_display([grep ! exists $data{$externalized_functions{$_}}, sort keys %externalized_functions]) if $shadows; my $criteria = join('|', @criteria); my @definitions = select_keys('--criteria' => $criteria, '--path' => $transient{path}, %$options); my %inverses = map {$externalized_functions{$_} => $_} keys %externalized_functions; my @externals = map $inverses{$_}, grep length, @definitions; my @internals = grep length $inverses{$_}, @definitions; my @sizes = map sprintf('%6d %6d', length(serialize_single($_)), length(retrieve($_))), @{$external ? \@internals : \@definitions} if $sizes; my @flags = map {my $k = $_; join '', map(is($k, "-$_") ? $_ : '-', qw(d i m u))} @definitions if $flags; my @hashes = map fast_hash(retrieve($_)), @definitions if $hashes; my %inherited = parent_attributes(grep /^parent::/o, keys %data) if $parent_hashes; my @parent_hashes = map $inherited{$_} || '-', @definitions if $parent_hashes; join "\n", map strip($_), split /\n/, table_display($external ? [grep length, @externals] : [@definitions], $sizes ? ([@sizes]) : (), $flags ? ([@flags]) : (), $hashes ? ([@hashes]) : (), $parent_hashes ? ([@parent_hashes]) : ()); EOF - end There isn't as much going on here as it looks like. Most of the logic is just figuring out what to display, as this {\tt ls} implementation supports many different display modes. A few things I would like to point out, though, are: - enumerate << end - item On line 8, I reference a function called \verb|table_display|. This just puts things into aligned columns; here's how {\tt object} defines it: - resource snippets/table-display << end meta::internal_function('table_display', <<'EOF'); # 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; EOF - end - item On line 13, I reference \verb|$transient{path}|. This is a really useful shell feature that implicitly filters attributes when you type {\tt ls}, but otherwise has nothing to do with what's going on here. - item On line 21, I reference a function called {\tt is()}. Remember the gnarly {\tt grep} conditional in \verb|select_keys|? That's now factored into {\tt function::is}. Here's {\tt object}'s implementation: - resource snippets/function-is << end meta::function('is', <<'EOF'); my ($attribute, @criteria) = @_; my ($options, @stuff) = separate_options(@criteria); exists $data{$attribute} and attribute_is($attribute, %$options); EOF - end - resource snippets/internal-function-attribute-is << end meta::internal_function('attribute_is', <<'EOF'); my ($a, %options) = @_; my %inherited = parent_attributes(grep /^parent::/o, sort keys %data) if grep exists $options{$_}, qw/-u -U -d -D/; my $criteria = $options{'--criteria'} || $options{'--namespace'} && "^$options{'--namespace'}::" || '.'; my %tests = ('-u' => sub {! $inherited{$a}}, '-d' => sub {$inherited{$a} && fast_hash(retrieve($a)) ne $inherited{$a}}, '-i' => sub {$transient{inherit}{namespace($a)}}, '-s' => sub {$a =~ /^state::/o}, '-m' => sub {$a =~ /^meta::/o}); # These checks make sure all tests are satisfied (a test is just a # command-line option). I'm sure there's a clearer way to do it... return 0 unless scalar keys %tests == scalar grep ! exists $options{$_} || &{$tests{$_}}(), keys %tests; return 0 unless scalar keys %tests == scalar grep ! exists $options{uc $_} || ! &{$tests{$_}}(), keys %tests; $a =~ /$_/ || return 0 for @{$options{'--path'}}; # I'll explain this later $a =~ /$criteria/; EOF - end - end - s2 The new {\tt update-from} __ meta::section('eval-backtraces', <<'__'); - sc {\tt eval} backtraces | sec:eval-backtraces Our script is fairly awesome so far. It prevents us from creating attributes in namespaces that don't exist, since that would cause incorrect serialization, it verifies before it saves, etc. But there's one problem. Take a look at the error messages we get: - verbatim << end $ perl examples/some-improvements examples/some-improvements$ create foo::bar Namespace foo does not exist at (eval 9) line 4. examples/some-improvements$ - end If there's a problem in some attribute, we have no information about the location of the error other than ``eval $n$'' and the line number relative to that. {\tt object} solves this problem: - verbatim << end $ object object$ create foo::bar [error] Namespace foo does not exist at internal_function::associate line 4. object$ - end The key is to wrap {\tt eval} in such a way that we can later resolve the meaningless numbers into useful locations. And to do this, we're going to need to modify the bootstrap code again. - verbatim << end my %data; my %externalized_functions; my %datatypes; my %locations; # Maps eval-numbers to attribute names - end There's a beautiful hack to handle the {\tt eval} processing. Watch this (also in {\tt bootstrap::initialization}):\footnote{It actually doesn't have to be inside the bootstrap code, but it doesn't change often and is useful to have around, so I decided to put it there to save time.} - perl snippets/meta-eval-in << end sub meta::eval_in { my ($what, $where) = @_; # Obtain next eval-number and alias it to the designated location @locations{eval('__FILE__') =~ /\(eval (\d+)\)/} = ($where); # <- step 1 my $result = eval $what; # <- step 2 $@ =~ s/\(eval \d+\)/$where/ if $@; warn $@ if $@; $result; } - end By {\tt eval}ing \verb|__FILE__|, we get the current eval number. So the next one will be whatever we {\tt eval} next. This means that in the shell sessions above, \verb|%locations| contains a mapping from {\tt 9} to \verb|internal_function::associate|. Here's the function that converts an {\tt eval} index into an attribute name: - perl snippets/translate-backtrace-function << end meta::internal_function('translate_backtrace', <<'EOF'); my ($trace) = @_; $trace =~ s/\(eval (\d+)\)/$locations{$1 - 1}/g; $trace; EOF - end Notice that we're subtracting one. The {\tt eval} number that triggered the error will be one greater than the one we stored.\footnote{Good API design would resolve this ahead-of-time rather than at lookup time. I haven't gotten around to changing it yet though.} Now that we have this mechanism, we can go back and convert {\tt eval} calls into \verb|meta::eval_in|: - perl snippets/using-eval-in << end meta::define_form 'function', sub { my ($name, $value) = @_; meta::externalize $name, "function::$name", meta::eval_in("sub {\n$value\n}", "function::$name"); }; meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = meta::eval_in("sub {\n$value\n}", "internal_function::$name"); }; - end __ meta::section('introduction', <<'__'); - sc Introduction | sec:introduction I've gotten a lot of WTF's\footnote{\url{http://www.osnews.com/story/19266/WTFs_m}} about self-modifying Perl scripts. Rightfully so, too. There's no documentation (until now), the interface is opaque and not particularly portable, and they aren't even very human-readable when edited: - verbatim << end ... 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 ... - end Despite these shortcomings, though, I think they're fairly useful (this guide is a self-modifying Perl file, in fact). At the end, you'll have a script that is functionally equivalent to the {\tt object} script, which I use as the prototype for all of the other ones.\footnote{See \url{http://github.com/spencertipping/perl-objects} for the full source.} The full source code for this guide and accompanying examples is available at \url{http://github.com/spencertipping/writing-self-modifying-perl}. Proceed only with fortitude, determination, and Perl v5.10. __ meta::section('main', <<'__'); - documentclass report - include resource::header - title Writing Self-Modifying Perl - author Spencer Tipping - document << end - maketitle - tableofcontents - include section::introduction - sp The Basics - include section::a-big-quine - include section::building-the-interface - include section::namespaces - include section::serialization - include section::adding-a-repl - include section::some-improvements - sp The Fun Stuff - include section::rendering-as-html - include section::eval-backtraces - include section::archiving-state - include section::cloning-and-inheritance - include section::detecting-divergence - include section::virtual-attributes - end __ meta::section('namespaces', <<'__'); - sc Namespaces | sec:namespaces It's a bummer to have to add a new {\tt eval} line for every function we want to define. We could merge all of the functions into a single hash key, but that's too easy.\footnote{Aside from being a lame cop-out, it also limits extensibility, as I'll explain later.} More appropriate is to assign a type to each hash key. This can be encoded in the name. For example, we might convert the names like this: - verbatim << end set -> function::set cat -> function::cat edit -> function::edit code -> code::main - end For reasons that I'll explain in a moment, we no longer need {\tt bootstrap}. The rules governing these types are: - enumerate << end - item When we see a new {\tt function::} key, evaluate its contents. - item When we see a new {\tt code::} key, evaluate its contents. \label{item:run-code} - end \Ref{Rule}{item:run-code} is why we don't need {\tt bootstrap} anymore. Now you've probably noticed that these rules do exactly the same thing -- why are we differentiating between these types then? Two reasons. First, we need to make sure that functions are evaluated before the code section is evaluated (otherwise the functions won't exist when we need them). Second, it's because functions can be handled in a more useful way. - s1 Handling functions more usefully | sec:namespaces-handling-functions-more-usefully Remember how we had to write \verb|sub X {| and \verb|}| every time we wrote a function, despite the fact that the function name was identical to the name of the key in \verb|%data|? That's fairly lame, and it could become misleading if the names ever weren't the same. We really should have the script handle this for us. So instead of writing the function signature, we would just write its body: - verbatim << end # The body of 'cat': print join "\n", @data{@_}; - end \noindent and infer its name from the key. Perl is helpful here by giving us first-class access to the symbol table: - perl snippets/create-function << end sub create_function { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; } - end If we're going to handle functions this way, we need to change the rule for {\tt function::} keys: - quote << end When we see a new {\tt function::} key, call \verb|create_function| on the key name (without the {\tt function::} part) and the value. - end - s1 Catching attribute creation | sec:namespaces-catching-attribute-creation We can't observe when a new key is added to \verb|%data| as things are now. Fortunately this is easy to fix. Instead of writing lines that read \verb|$data{...} = ...|, we can write some functions that perform this assignment for us, and in the process we can handle any side-effects like function creation. Here's a naive implementation: - perl snippets/define-function-define-code << end sub define_function { my ($name, $value) = @_; $data{$name} = $value; create_function $name, $value; } sub define_code { my ($name, $value) = @_; $data{$name} = $value; } - end Since we're always going to assign into \verb|%data|, we can abstract that step out: - perl snippets/define-definer << end sub define_definer { my ($name, $handler) = @_; *{$name} = sub { my ($name, $value) = @_; $data{$name} = $value; &$handler($name, $value); } } define_definer 'define_function', \&create_function; define_definer 'define_code', sub { my ($name, $value) = @_; eval $value; }; - end To avoid the possibility of later collisions we should probably use a separate namespace for all of these functions, since really bad things happen if you inadvertently replace one. I use the {\tt meta::} namespace for this purpose in my scripts. At this point we've got the foundation for namespace creation. This is actually used with few modifications in the Perl objects I use on a regular basis. Here's \verb|meta::define_form| lifted from {\tt object}: - perl snippets/meta-define-form << end sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } - end The idea is the same as \verb|define_definer|, but with a few extra lines. We stash the delegate in a \verb|%datatypes| table for later reference. We also (redundantly, I notice) create a function in the {\tt meta::} package so that we can refer to it when defining other forms. This lets us copy the behavior of namespaces but still have them be separate. The third line that's different is \verb|chomp $value|, which is used because heredocs put an extra newline on the end of strings. \verb|meta::define_form| has the same interface as \verb|define_definer|: - perl snippets/meta-define-form-function-code << end meta::define_form 'function', \&create_function; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; - end Attribute definitions look a little different than they did before. The two \verb|define_form| calls above create the functions {\tt meta::function} and {\tt meta::code}, which will need to be called this way: - verbatim << end meta::function('cat', <<'EOF'); print join "\n", @data{@_}; EOF meta::code('main', <<'EOF'); # No more eval statements! # Run command ... # Save stuff ... EOF - end Notice that we don't specify the full name of the attributes being created. \verb|meta::function('x', ...)| creates a key called {\tt function::x}; this was handled in the \verb|define_form| logic. - s1 Putting it all together | sec:namespaces-putting-it-all-together At this point we're all set to write another script. The overall structure is still basically the same even though each piece has changed a little: - perl examples/namespace-basic << end my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; meta::function('cat', <<'EOF'); print join "\n", @data{@_}; EOF meta::code('main', <<'EOF'); # Run specified command: my $command = shift @ARGV; &$command(@ARGV); # Save new state: open my $file, '>', $0; # Copy above bootstrapping logic: print $file <<'EOF2'; my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; EOF2 # Serialize attributes (everything else before code): for (grep(!/^code::/, keys %data), grep(/^code::/, keys %data)) { my ($namespace, $name) = split /::/, $_, 2; print $file "meta::$namespace('$name', <<'EOF');\n$data{$_}\nEOF\n"; } # Just for good measure: print $file "\n__END__"; close $file; EOF __END__ - end The most substantial changes were: - enumerate << end - item We're defining two hashes at the beginning, though we still just use \verb|%data|. - item We're using delegate functions to define attributes rather than assigning directly into \verb|%data|. - item Quoted values now get {\tt chomp}ed. I've added another \verb|\n| in the serialization logic to compensate for this. - item The serialization logic is now order-specific; it puts {\tt code::} entries after other things. - item The file now has an \verb|__END__| marker on it. - end - s1 Separating bootstrap code | sec:namespaces-separating-bootstrap-code The bootstrap code is now large quoted string inside {\tt code::main}, which isn't optimal. Better is to break it out into its own attribute. To do this, we'll need a new namespace that has no side-effect.\footnote{We can't use {\tt code::} because then the code would be evaluated twice; once because it's printed directly, and again because of the {\tt eval} in the {\tt code::} delegate.} I'll call this namespace {\tt bootstrap::}. - verbatim << end meta::define_form 'bootstrap', sub {}; - end There's a special member of the {\tt bootstrap::} namespace that contains the code in the beginning of the file: - verbatim << end meta::bootstrap('initialization', <<'EOF'); my %data; my %datatypes; ... EOF - end This condenses {\tt code::main} by a lot: - perl snippets/bootstrapped-code-main << end meta::code('main', <<'EOF'); # Run specified command: my $command = shift @ARGV; &$command(@ARGV); # Save new state: open my $file, '>', $0; print $file $data{'bootstrap::initialization'}; # Serialize attributes (everything else before code): for (grep(!/^code::/, keys %data), grep(/^code::/, keys %data)) { my ($namespace, $name) = split /::/, $_, 2; print $file "meta::$namespace('$name', <<'EOF');\n$data{$_}\nEOF\n"; } # Just for good measure: print $file "\n__END__"; close $file; EOF - end Here's the final product, after adding the {\tt set} and {\tt edit} functions from before: - perl examples/namespace-full << end my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'bootstrap', sub {}; meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; meta::bootstrap('initialization', <<'EOF'); my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'bootstrap', sub {}; meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; EOF meta::function('cat', <<'EOF'); print join "\n", @data{@_}; EOF meta::function('set', <<'EOF'); $data{$_[0]} = join '', ; EOF meta::function('edit', <<'EOF'); my $filename = '/tmp/' . rand(); open my $file, '>', $filename; print $file $data{$_[0]}; close $file; system($ENV{EDITOR} || $ENV{VISUAL} || '/usr/bin/nano', $filename); open my $file, '<', $filename; $data{$_[0]} = join '', <$file>; close $file; EOF meta::code('main', <<'EOF'); # Run specified command: my $command = shift @ARGV; &$command(@ARGV); # Save new state: open my $file, '>', $0; print $file $data{'bootstrap::initialization'}; # Serialize attributes (everything else before code): for (grep(!/^code::/, keys %data), grep(/^code::/, keys %data)) { my ($namespace, $name) = split /::/, $_, 2; print $file "meta::$namespace('$name', <<'EOF');\n$data{$_}\nEOF\n"; } # Just for good measure: print $file "\n__END__"; close $file; EOF __END__ - end __ meta::section('rendering-as-html', <<'__'); - sc Rendering as HTML | sec:rendering-as-html It turns out that browsers are very lenient about what they're willing to render as HTML. From what I've seen, anything that ends with {\tt .html} will be rendered this way, with extra stuff inserted into the \verb|| as toplevel text. This is perfect for doing some fun stuff with self-modifying Perl files. First, I should note that the actual UI implementation is in Caterwaul 0.x and is beyond the scope of this guide. But the basic ideas still apply. There are two things you'll want to do to make your script HTML-renderable. The first is to create a {\tt bootstrap::} attribute with some markup in it. It's useful for it to be a {\tt bootstrap::} attribute because these are unevaluated. Here's {\tt bootstrap::html} from {\tt object}: - resource snippets/object/bootstrap::html << end - end If you copy this attribute into a Perl file, it should indeed render as a web page. There's really only one problem with it, which is that before {\tt bootstrap::html} is loaded you'll see a bunch of garbage on the screen. Rather than solve this in any sane and correct way, I just implemented this hack in {\tt bootstrap::initialization}:\footnote{The backslash before the end of the line isn't in the real {\tt object}, I just put it here to indicate that the line should be continued, not broken.} - resource snippets/object/bootstrap::initialization-div << end # For the benefit of HTML viewers (this is hack): #
- end This covers up the garbage until a function in {\tt interface.js} removes the covering \verb|
|. By the way, I highly recommend using the HTML view of Perl objects while reading the rest of this guide. You can get to them here: \url{http://spencertipping.com/perl-objects} (each {\tt .html} file is just a symlink to the Perl object of the same name). __ meta::section('serialization', <<'__'); - sc Serialization | sec:serialization Earlier I alluded to a glaring problem with these scripts as they stand. The issue is the {\tt EOF} marker we've been using. Here's what happens if we put a line containing {\tt EOF} into an attribute: - verbatim << end $ cp examples/basic-meta-with-functions temp $ perl temp set function::bif print <<'EOF'; uh-oh... EOF ^D $ perl temp cat function::bif Can't locate object method "EOF" via package "meta::function" at temp line 31. $ - end It's not hard to see what went wrong: {\tt temp} now has an attribute definition that looks like this: - verbatim << end meta::function('bif', <<'EOF'); print <<'EOF'; uh-oh... EOF EOF - end We need to come up with some end marker that isn't in the value being stored. For the moment let's use random numbers.\footnote{{\tt object} implements a simple FNV-hash and uses the hash of the contents. I'll go over how to implement this a bit later.} - s1 Fixing the {\tt EOF} markers | sec:serialization-fixing-the-eof-markers There isn't a particularly compelling reason to inline the serialization logic in {\tt code::main}. Since we have a low-overhead way of defining functions, let's make a {\tt serialize} function to return the state of a script as a string, along with a helper method \verb|serialize_single| to handle one attribute at a time: - perl snippets/serialize-and-serialize-single << end meta::function('serialize', <<'EOF'); my @keys = sort keys %data; join "\n", $data{'bootstrap::initialization'}, map(serialize_single($_), grep !/^code::/, @keys), map(serialize_single($_), grep /^code::/, @keys), "\n__END__"; EOF meta::function('serialize_single', <<'EOF'); my ($namespace, $name) = split /::/, $_[0], 2; my $marker = '__' . int(rand(1 << 31)); "meta::$namespace('$name', <<'$marker');\n$data{$_[0]}\n$marker"; EOF - end Sorting the keys is important. We'll be verifying the output of the serialization function, so it needs to be stable. Now {\tt code::main} is a bit simpler. With these new functions the file logic becomes: - verbatim << end open my $file, '>', $0; print $file serialize(); close $file; - end - s1 Verifying serialization | sec:serialization-verifying-serialization What we've been doing is very unsafe. There isn't a backup file, so if the serialization goes wrong then we'll blindly nuke our original script. This is a big problem, so let's fix it. The new strategy will be to serialize to a temporary file, have that file generate a checksum, and make sure that the checksum is what we expect. Before we can implement such a mechanism, though, we'll need a string hash function. - s2 Implementing the Fowler-Noll Vo hash | sec:serialization-verifying-fnv-hash At its core, the FNV-1a hash\footnote{\url{http://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function}} is just a multiply-xor in a loop. Generally it's written like this: - cpp snippets/fnv-hash.c << end int hash (char *s) { const int fnv_prime = 16777619; // Magic numbers const int fnv_offset = 2166136261; int result = fnv_offset; char c; while (c = *s++) { result ^= c; result *= fnv_prime; } return result; } - end In Perl it's advantageous to vectorize this function for performance reasons. It isn't necessarily sound to do this, but empirically the results seem reasonably well-distributed. Here's the function I ended up with: - perl snippets/fnv-hash-function << end meta::function('fnv_hash', <<'EOF'); 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; EOF - end This produces a 32-bit hash. Ideally we have something of at least 128 bits, just to reduce the likelihood of collision. When I was writing the 128-bit hash I went a bit overboard with hash chaining (which doesn't matter because it isn't a cryptographic hash), but here's the full hash: - perl snippets/fast-hash-function << end meta::function('fast_hash', <<'EOF'); 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]; EOF - end The convolutedness of this logic is partially to accommodate for very short strings. - s2 Fixing {\tt EOF} markers again | sec:serialization-verifying-fixing-eof-markers-again It's probably fine to use random numbers for EOF markers, but I prefer using a hash of the content. While it's probably about the same either way, it intuitively feels less likely that a string will contain its own hash.\footnote{And as we all know, intuition is key when making decisions in math and computer science...} - perl snippets/serialize-single-hash << end meta::function('serialize_single', <<'EOF'); my ($namespace, $name) = split /::/, $_[0], 2; my $marker = '__' . fast_hash($data{$_[0]}); "meta::$namespace('$name', <<'$marker');\n$data{$_[0]}\n$marker"; EOF - end We can also use the script state to get a tempfile in the {\tt edit} function.\footnote{{\tt object} uses {\tt File::Temp} to get temporary filenames. This is a better solution than anything involving pseudorandom names in {\tt /tmp}.} - s2 Implementing the {\tt state} function | sec:serialization-verifying-state-function The ``state'' of an object is just the hash of its serialization. (This is why it's useful to have the serialization logic factored out.) - perl snippets/state-function-hash << end meta::function('state', <<'EOF'); fast_hash(serialize()); EOF - end - s2 Implementing the {\tt verify} function | sec:serialization-verifying-verify-function {\tt verify} writes a temporary copy, checks its checksum, and returns {\tt 0} or {\tt 1} depending on whether the checksum came out invalid or valid, respectively. If invalid, it leaves the temporary file there for debugging purposes. - perl snippets/verify-function << end meta::function('verify', <<'EOF'); my $serialized_data = serialize(); my $state = state(); my $temporary_filename = "$0.$state"; open my $file, '>', $temporary_filename; print $file $serialized_data; close $file; chmod 0700, $temporary_filename; chomp(my $observed_state = join '', qx|perl '$temporary_filename' state|); my $result = $observed_state eq $state; unlink $temporary_filename if $result; $result; EOF - end - s1 Save logic | sec:serialization-save-logic Now we can use {\tt verify} before overwriting \verb|$0|. - perl snippets/save-function-and-broken-usage << end meta::function('save', <<'EOF'); if (verify()) { open my $file, '>', $0; print $file serialize(); close $file; } else { warn 'Verification failed'; } EOF meta::code('main', <<'EOF'); ... save(); EOF - end - s1 {\tt code::main} fixes | sec:serialization-code-main-fixes There's actually a fairly serious problem at this point. Every script saves itself unconditionally, which involves creating a temporary filename and verifying its contents. What happens when we run one then? Something like this: - verbatim << end $ perl some-script cat function::cat join "\n", @data{@_}; # Gets this much right # Now calls save(), which calls verify() to create a new temp script: > perl some-script.hash1 state hash1 # Gets this much right # Now calls save(), which calls verify() to create a new temp script: > perl some-script.hash1.hash2 state ... - end That's not what we want at all. There's no reason to call {\tt save} unless a modification has occurred, so we can make this modification to {\tt code::main}: - perl snippets/code-main-with-fixed-save << end meta::code('main', <<'EOF'); my $initial_state = state(); my $command = shift @ARGV; print &$command(@ARGV); # Also printing the result -- important for state save() if state() ne $initial_state; EOF - end - s1 Final result | sec:serialization-final-result At this point we have an extensible and reasonably robust script. Here's what we've got so far: - perl examples/verified << end my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'bootstrap', sub {}; meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; meta::bootstrap('initialization', <<'EOF'); my %data; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'bootstrap', sub {}; meta::define_form 'function', sub { my ($name, $body) = @_; *{$name} = eval "sub {\n$body\n}"; }; meta::define_form 'code', sub { my ($name, $value) = @_; eval $value; }; EOF meta::function('serialize', <<'EOF'); my @keys = sort keys %data; join "\n", $data{'bootstrap::initialization'}, map(serialize_single($_), grep !/^code::/, @keys), map(serialize_single($_), grep /^code::/, @keys), "\n__END__"; EOF meta::function('serialize_single', <<'EOF'); my ($namespace, $name) = split /::/, $_[0], 2; my $marker = '__' . fast_hash($data{$_[0]}); "meta::$namespace('$name', <<'$marker');\n$data{$_[0]}\n$marker"; EOF meta::function('fnv_hash', <<'EOF'); 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; EOF meta::function('fast_hash', <<'EOF'); 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]; EOF meta::function('state', <<'EOF'); fast_hash(serialize()); EOF meta::function('verify', <<'EOF'); my $serialized_data = serialize(); my $state = state(); my $temporary_filename = "$0.$state"; open my $file, '>', $temporary_filename; print $file $serialized_data; close $file; chmod 0700, $temporary_filename; chomp(my $observed_state = join '', qx|perl '$temporary_filename' state|); my $result = $observed_state eq $state; unlink $temporary_filename if $result; $result; EOF meta::function('save', <<'EOF'); if (verify()) { open my $file, '>', $0; print $file serialize(); close $file; } else { warn 'Verification failed'; } EOF meta::function('cat', <<'EOF'); join "\n", @data{@_}; EOF meta::function('set', <<'EOF'); $data{$_[0]} = join '', ; EOF meta::function('edit', <<'EOF'); my $filename = '/tmp/' . rand(); open my $file, '>', $filename; print $file $data{$_[0]}; close $file; system($ENV{EDITOR} || $ENV{VISUAL} || '/usr/bin/nano', $filename); open my $file, '<', $filename; $data{$_[0]} = join '', <$file>; close $file; EOF meta::code('main', <<'EOF'); my $initial_state = state(); my $command = shift @ARGV; print &$command(@ARGV); save() if state() ne $initial_state; EOF __END__ - end __ meta::section('some-improvements', <<'__'); - sc Some improvements | sec:some-improvements Let's step back for a minute and improve things a bit in preparation for some real awesomeness. There are few places that could use improvement. First, there isn't a way to get a list of defined attributes on an object without opening it by hand. Second, the interface exposes too many functions to the user; in particular, things like {\tt complete} aren't useful from the command line. Finally, every data type we define gets put into {\tt bootstrap::initialization}, which causes $O(n)$ redundancy in the size of the data type constructors. - s1 Useful functions | sec:some-improvements-useful-functions The most important thing to add is {\tt ls}, which gives you a listing of attributes:\footnote{{\tt object} contains a much more sophisticated version of {\tt ls}. It parses options and applies filters to the listing, much like the UNIX {\tt ls} command. I'll go over how to implement this stuff in a later chapter.} Related are {\tt cp} and {\tt rm}, which do what you would expect: - perl snippets/ls-cp-and-rm-functions << end meta::function('ls', <<'EOF'); join "\n", sort keys %data; EOF meta::function('cp', <<'EOF'); $data{$_[1]} = $data{$_[0]}; EOF meta::function('rm', <<'EOF'); delete @data{@_}; EOF - end Another useful function is {\tt create}, which opens an editor for a new attribute:\footnote{We can already do this with {\tt edit}, but {\tt object} doesn't let you edit attributes that don't exist. I'll include that behavior in these scripts before too long.} - perl snippets/create-function << end meta::function('create', <<'EOF'); return edit($_[0]) if exists $data{$_[0]}; $data{$_[0]} = $_[1] || "# Attribute $_[0]"; edit($_[0]); EOF - end Now we can create stuff from inside the shell or command-line and have a civilized text-editor interface to do it. - s1 Making some functions internal | sec:some-improvements-making-some-functions-internal It would be nice to have a distinction between functions meant for public consumption and functions used just inside the script. For example, nobody's going to call \verb|fnv_hash| from the command-line; they'd have to pass it a string in {\tt @ARGV}, which isn't practical. So it's time for a new toplevel mechanism, the \verb|%externalized_functions| table: - verbatim << end # In bootstrap::initialization: my %data; my %externalized_functions; my %datatypes; - end \verb|%externalized_functions| maps every callable function to the attribute that defines it, and only the listed functions will be usable directly from the shell or the command-line. This has an additional benefit of providing much better autocompletion, since the first word in the REPL always names a function. - verbatim << end meta::define_form 'data', sub { my ($name, $value) = @_; $externalized_functions{$name} = "data::$name"; *{$name} = ...; }; meta::define_form 'function', sub { my ($name, $value) = @_; $externalized_functions{$name} = "function::$name"; *{$name} = ...; }; - end \noindent And here's the new data type: - perl snippets/internal-function-type << end meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = eval "sub {\n$value\n}"; }; - end We can now move \verb|fnv_hash|, \verb|fast_hash|, and {\tt complete} into this namespace. We'll need to update {\tt shell} and {\tt complete} to leverage this new information: - perl snippets/shell-2 << end meta::function('shell', <<'EOF'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $output = $term->OUT || \*STDOUT; $term->Attribs->{attempted_completion_function} = \&complete; while (defined($_ = $term->readline("$0\$ "))) { my @args = grep length, split /\s+|("[^"\\]*(?:\\.)?")/o; my $function_name = shift @args; s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args; if ($function_name) { if ($externalized_functions{$function_name}) { chomp(my $result = eval {&$function_name(@args)}); warn $@ if $@; print $output $result, "\n" unless $@; } else { warn "Command not found: '$function_name' (use 'ls' to see available commands)"; } } } EOF - end - perl snippets/complete-2 << end meta::function('complete', <<'EOF'); my @functions = sort keys %externalized_functions; my @attributes = sort keys %data; sub match { my ($text, @options) = @_; my @matches = sort grep /^$text/, @options; if (@matches == 0) {return undef;} elsif (@matches == 1) {return $matches [0];} elsif (@matches > 1) { return ((longest ($matches [0], $matches [@matches - 1])), @matches); } } sub longest { my ($s1, $s2) = @_; return substr ($s1, 0, length $1) if ($s1 ^ $s2) =~ /^(\0*)/; return ''; } my ($text, $line) = @_; if ($line =~ / /) { # Start matching attribute names. match ($text, @attributes); } else { # Start of line, so it's a function. match ($text, @functions); } EOF - end - s1 Separate attributes for data types | sec:some-improvements-separate-attributes-for-data-types It's cumbersome to have all of the data types go in {\tt bootstrap::initialization}. Better is to break the code into separate attributes. To do this we'll need to restructure the scripts a little bit. Up until now the ``stuff first, code second'' approach has worked out all right. But now we want to evaluate stuff at the beginning and at the end, and if this keeps up it could get out of hand. Better is to have {\tt serialize} generate a call into some function that will be defined, and do away with {\tt code::} altogether. We can use a new namespace {\tt meta::} for stuff that needs to be evaluated at the beginning. So basically, instead of this: - verbatim << end bootstrap types functions code - end \noindent we'd have this: - verbatim << end bootstrap meta definitions functions call to internal::main() - end Here's what the new {\tt serialize} looks like: - perl snippets/serialize-with-internal-main << end my @keys = sort keys %data; join "\n", $data{'bootstrap::initialization'}, map(serialize_single($_), grep( /^meta::/, @keys), grep(!/^meta::/, @keys)), "internal::main();", "__END__"; - end And here's the definition for {\tt meta::} (it's identical to the one we used to have for {\tt code::}). This is the only \verb|define_form| invocation in {\tt bootstrap::initialization}; the others now reside in their own attributes. - perl snippets/define-form-meta << end meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; }; - end Here are the new type definitions: - verbatim << end meta::meta('type::data', <<'EOF'); meta::define_form 'data', sub {...}; EOF meta::meta('type::function', <<'EOF'); meta::define_form 'function', sub {...}; EOF meta::meta('type::bootstrap', <<'EOF'); meta::define_form 'bootstrap', sub {}; EOF ... - end - s2 Factoring externalization | sec:some-improvements-data-types-factoring-externalization While we're cleaning up meta-stuff, it's worth thinking about factoring out externalization. There isn't a particularly good reason to keep manually assigning to \verb|%externalized_functions|; better is to abstract this detail into a function. To do this, we'll want a meta-library: - perl snippets/meta-externalize << end meta::meta('externalize', <<'EOF'); sub meta::externalize { my ($name, $attribute, $implementation) = @_; $externalized_functions{$name} = $attribute; *{$name} = $implementation; } EOF - end This meta-definition is available to the others because it sorts first.\footnote{Which is a horrible way to manage dependencies, but it's worked so far.} Now instead of manually externalizing stuff, data types like {\tt function::} and {\tt data::} can just use {\tt meta::externalize}: - perl snippets/function-type-with-externalize << end meta::meta('type::function', <<'EOF'); meta::define_form 'function', sub { my ($name, $value) = @_; meta::externalize $name, "function::$name", eval "sub {\n$value\n}"; }; EOF - end - s1 Abstracting {\tt \%data} | sec:some-improvements-abstracting-data Another issue worth fixing is that you can assign into \verb|%data| arbitrarily, particularly in ways that end up breaking deserialization. For instance, nothing is stopping you from creating a key called {\tt foo::bar} even though there isn't a namespace called {\tt foo::}. This problem can be solved at the interface level (i.e.~inside {\tt edit}, {\tt set}, and such), but it's probably more useful to go a step further and abstract all access to \verb|%data|. Rather than writing to \verb|%data|, then, we'll use an internal function called {\tt associate}; and to read from it we'll use {\tt retrieve}. These two functions also benefit from a couple more to separate out namespace components. The {\tt namespace} function gives you the base part, and the {\tt attribute} function gives you the rest.\footnote{All four of these functions are taken directly from {\tt object}.} - perl snippets/namespace-attribute-retrieve-associate-functions << end meta::internal_function('namespace', <<'EOF'); my ($name) = @_; $name =~ s/::.*$//; $name; EOF meta::internal_function('attribute', <<'EOF'); my ($name) = @_; $name =~ s/^[^:]*:://; $name; EOF meta::internal_function('retrieve', <<'EOF'); my @results = map defined $data{$_} ? $data{$_} : file::read($_), @_; wantarray ? @results : $results[0]; EOF meta::internal_function('associate', <<'EOF'); my ($name, $value, %options) = @_; my $namespace = namespace($name); die "Namespace $namespace does not exist" unless $datatypes{$namespace}; $data{$name} = $value; execute($name) if $options{'execute'}; EOF - end - s2 Dynamic execution | sec:some-improvements-abstracting-data-dynamic-execution One problem with the way we've defined {\tt cp} is that you'll have to close and reopen the shell to get new functions to take effect. This is because while we're assigning into \verb|%data|, we're not calling the handler associated with the namespace. The simplest way to fix this is to dynamically invoke that handler: - perl snippets/execute-function << end meta::internal_function('execute', <<'EOF'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(attribute($name), retrieve($name))}; warn $@ if $@ && $options{'carp'}; EOF - end {\tt associate} is already hooked up to use this function; all you have to do is pass an extra option: - verbatim << end associate('function::foo', '...', execute => 1); - end - s1 Final result | sec:some-improvements-final-result Integrating all of these improvements into the previous chapter's script yields this monumental piece of work:\footnote{This is the last full listing I'll provide here. The remaining chapters cover the concepts required to get from here to {\tt object}. At this point the stuff going on in {\tt object} should more or less make sense, though you'll want to use {\tt ls-a} rather than {\tt ls} to get a full listing of attributes.} - perl examples/some-improvements << end #!/usr/bin/perl my %data; my %externalized_functions; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; }; meta::meta('externalize', <<'EOF'); sub meta::externalize { my ($name, $attribute, $implementation) = @_; $externalized_functions{$name} = $attribute; *{$name} = $implementation; } EOF meta::meta('type::bootstrap', <<'EOF'); meta::define_form 'bootstrap', sub {}; EOF meta::meta('type::function', <<'EOF'); meta::define_form 'function', sub { my ($name, $body) = @_; meta::externalize $name, "function::$name", eval "sub {\n$body\n}"; }; EOF meta::meta('type::internal_function', <<'EOF'); meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = eval "sub {\n$value\n}"; }; EOF meta::meta('type::data', <<'EOF'); meta::define_form 'data', sub { # Define a basic editing interface: my ($name, $value) = @_; meta::externalize $name, "data::$name", sub { my ($command, $value) = @_; return $data{"data::$name"} unless @_; $data{"data::$name"} = $value if $command eq '='; }; }; EOF meta::bootstrap('initialization', <<'EOF'); #!/usr/bin/perl my %data; my %externalized_functions; my %datatypes; sub meta::define_form { my ($namespace, $delegate) = @_; $datatypes{$namespace} = $delegate; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; }; EOF meta::data('default-action', <<'EOF'); shell EOF meta::internal_function('namespace', <<'EOF'); my ($name) = @_; $name =~ s/::.*$//; $name; EOF meta::internal_function('attribute', <<'EOF'); my ($name) = @_; $name =~ s/^[^:]*:://; $name; EOF meta::internal_function('retrieve', <<'EOF'); my @results = map defined $data{$_} ? $data{$_} : file::read($_), @_; wantarray ? @results : $results[0]; EOF meta::internal_function('associate', <<'EOF'); my ($name, $value, %options) = @_; my $namespace = namespace($name); die "Namespace $namespace does not exist" unless $datatypes{$namespace}; $data{$name} = $value; execute($name) if $options{'execute'}; EOF meta::internal_function('execute', <<'EOF'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(attribute($name), retrieve($name))}; warn $@ if $@ && $options{'carp'}; EOF meta::function('serialize', <<'EOF'); my @keys = sort keys %data; join "\n", $data{'bootstrap::initialization'}, map(serialize_single($_), grep( /^meta::/, @keys), grep(!/^meta::/, @keys)), "internal::main();", "__END__"; EOF meta::function('serialize_single', <<'EOF'); my ($namespace, $name) = split /::/, $_[0], 2; my $marker = '__' . fast_hash($data{$_[0]}); "meta::$namespace('$name', <<'$marker');\n$data{$_[0]}\n$marker"; EOF meta::function('fnv_hash', <<'EOF'); 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; EOF meta::function('fast_hash', <<'EOF'); 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]; EOF meta::function('state', <<'EOF'); fast_hash(serialize()); EOF meta::function('verify', <<'EOF'); my $serialized_data = serialize(); my $state = state(); my $temporary_filename = "$0.$state"; open my $file, '>', $temporary_filename; print $file $serialized_data; close $file; chmod 0700, $temporary_filename; chomp(my $observed_state = join '', qx|perl '$temporary_filename' state|); my $result = $observed_state eq $state; unlink $temporary_filename if $result; $result; EOF meta::function('save', <<'EOF'); if (verify()) { open my $file, '>', $0; print $file serialize(); close $file; chmod 0744, $0; } else { warn 'Verification failed'; } EOF meta::function('ls', <<'EOF'); join "\n", sort keys %data; EOF meta::function('cp', <<'EOF'); associate($_[1], retrieve($_[0])); EOF meta::function('rm', <<'EOF'); delete @data{@_}; EOF meta::function('cat', <<'EOF'); join "\n", @data{@_}; EOF meta::function('create', <<'EOF'); return edit($_[0]) if exists $data{$_[0]}; associate($_[0], $_[1] || "# Attribute $_[0]"); edit($_[0]); EOF meta::function('set', <<'EOF'); $data{$_[0]} = join '', ; EOF meta::function('complete', <<'EOF'); my @functions = sort keys %externalized_functions; my @attributes = sort keys %data; sub match { my ($text, @options) = @_; my @matches = sort grep /^$text/, @options; if (@matches == 0) {return undef;} elsif (@matches == 1) {return $matches [0];} elsif (@matches > 1) { return ((longest ($matches [0], $matches [@matches - 1])), @matches); } } sub longest { my ($s1, $s2) = @_; return substr ($s1, 0, length $1) if ($s1 ^ $s2) =~ /^(\0*)/; return ''; } my ($text, $line) = @_; if ($line =~ / /) { # Start matching attribute names. match ($text, @attributes); } else { # Start of line, so it's a function. match ($text, @functions); } EOF meta::internal_function('shell', <<'EOF'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $output = $term->OUT || \*STDOUT; $term->Attribs->{attempted_completion_function} = \&complete; while (defined($_ = $term->readline("$0\$ "))) { my @args = grep length, split /\s+|("[^"\\]*(?:\\.)?")/o; my $function_name = shift @args; s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args; if ($function_name) { if ($externalized_functions{$function_name}) { chomp(my $result = eval {&$function_name(@args)}); warn $@ if $@; print $output $result, "\n" unless $@; } else { warn "Command not found: '$function_name' (use 'ls' to see available commands)"; } } } EOF meta::function('edit', <<'EOF'); my $filename = '/tmp/' . rand(); open my $file, '>', $filename; print $file retrieve($_[0]); close $file; system($ENV{EDITOR} || $ENV{VISUAL} || '/usr/bin/nano', $filename); open my $file, '<', $filename; associate($_[0]}, join '', <$file>); close $file; EOF meta::internal_function('internal::main', <<'EOF'); my $initial_state = state(); my $command = shift @ARGV || retrieve('data::default-action'); print &$command(@ARGV); save() if state() ne $initial_state; EOF internal::main(); __END__ - end __ meta::section('virtual-attributes', <<'__'); - sc Virtual attributes In the last chapter, {\tt retrieve} was defined to grab either an attribute or a file. But there are a lot more things you might want to do with it, including creating views of existing data. This is exactly what virtual attributes are about. - s1 Core implementation Here's an updated {\tt retrieve}: - resource snippets/updated-retrieve << end meta::internal_function('retrieve', <<'EOF'); my @results = map defined $data{$_} ? $data{$_} : retrieve_with_hooks($_), @_; wantarray ? @results : $results[0]; EOF - end This is exactly the same as the one before except that the hard-coded {\tt file::read} call has been replaced by a call to \verb|retrieve_with_hooks|. This provides the indirection necessary to allow the user to introduce arbitrary retrievers. Before the user can do this, though, we'll need to make a new namespace: - resource snippets/meta-retriever << end meta::meta('type::retriever', <<'EOF'); 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"); }; EOF - end Nothing particularly interesting is going on here. We're just maintaining a ${\rm name} \rightarrow {\rm implementation}$ mapping in \verb|%transient|. This mapping is then used by \verb|retrieve_with_hooks|, which asks {\tt retriever::} functions for their values until one of them returns something besides {\tt undef}: - resource snippets/retrieve-with-hooks << end meta::internal_function('retrieve_with_hooks', <<'EOF'); # 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; EOF - end - s1 Retrievers in {\tt object} {\tt object} comes with four retrievers: - enumerate << end - item[] {\tt retriever::file} Retrieves contents from a filename. This lets you say things like {\tt cat foo}, where {\tt foo} is a file. By extension, you can also copy files into named attributes using {\tt function::cp}. - resource snippets/retriever-file << end meta::retriever('file', <<'EOF'); -f $_[0] ? file::read($_[0]) : undef; EOF - end - item[] {\tt retriever::id} Retrieves the string you are retrieving. This is primarily useful when you want a quick test value for something. For instance {\tt cat id::foobar} will print {\tt foobar}. Note that {\tt id::} is not defined as a namespace, it's just a name pattern detected by {\tt retriever::id}. - resource snippets/retriever-id << end meta::retriever('id', <<'EOF'); $_[0] =~ /^id::/ ? substr($_[0], 4) : undef; EOF - end - item[] {\tt retriever::object} This is a fun one. It lets you ask other Perl objects for their attributes. For example, {\tt cat object::./foo::function::ls} fetches {\tt function::ls} from {\tt ./foo}. If {\tt ./foo} is unreachable or doesn't contain {\tt function::ls}, then {\tt undef} is returned (which allows other retrievers to try to resolve your virtual attribute). - resource snippets/retriever-object << end meta::retriever('object', <<'EOF'); # Fetch a property from another Perl object. This uses the 'cat' function. return undef unless $_[0] =~ /^object::(.*?)::(.*)$/ && -x $1 && qx|$1 is '$2'|; join '', qx|$1 cat '$2'|; EOF - end - item[] {\tt retriever::perl} This returns the result of evaluating a given Perl expression. For example, {\tt cat perl::3+4} prints {\tt 7}. - resource snippets/retriever-perl << end meta::retriever('perl', <<'EOF'); # Lets you use the result of evaluating some Perl expression return undef unless $_[0] =~ /^perl::(.*)$/; eval $1; EOF - end - end As you can see, there's not much involved in writing a retriever. The only particularly counterintuitive thing about it is returning {\tt undef} instead of {\tt 0} or the empty string. - Fixing up {\tt edit} Right now, {\tt function::edit} will happily let you edit a virtual attribute and then attempt to {\tt associate} it, causing a failure. This is obviously lame; here's a better {\tt edit} function that takes care of this: - resource snippets/updated-edit << end meta::function('edit', <<'EOF'); my ($name, %options) = @_; my $extension = extension_for($name); die "$name is virtual or does not exist" unless exists $data{$name}; associate($name, invoke_editor_on($data{$name} // '', %options, attribute => $name, extension => $extension), execute => 1)}); save() unless $data{'data::edit::no-save'}; ''; EOF - end - More interesting retrievers I'm writing this document in a self-modifying Perl file that includes a simple preprocessor.\footnote{Unsurprisingly, it's called {\tt preprocessor} in the {\tt perl-objects} repository.} In order to make this work, there are a bunch of attributes in the {\tt section::} namespace, and each one includes some preprocessor directives to either include other attributes or to generate \TeX{} constructs (in some cases both). The easiest way to render the document as \TeX{} is to just have a virtual attribute I can retrieve that will contain the fully preprocessed source. Fortunately, {\tt preprocessor} provides {\tt retriever::pp} that does exactly this. Here's what that looks like: - resource snippets/retriever-pp << end meta::retriever('pp', <<'EOF'); return undef unless namespace($_[0]) eq 'pp'; my $attr = retrieve(attribute($_[0])); defined $attr ? preprocess($attr) : undef; EOF - end Notice that this retriever itself calls {\tt retrieve}! This is totally allowed as long as you don't create an infinite loop. Here, we're trimming the {\tt pp::} from the beginning of the attribute and are just retrieving the rest of it. This is good practice, as it lets you combine virtual retrievers. For example, you could preprocess an external file by saying {\tt cat pp::my-file}. Another interesting retriever is the {\tt http} retriever defined in {\tt repository}. This lets you retrieve arbitrary web content through Perl's LWP library: - resource snippets/retriever-http << end meta::retriever('http', <<'EOF'); use LWP::Simple (); return undef unless $_[0] =~ /^(?:http:)?\/\/(\w+.*)$/; LWP::Simple::get("http://$1"); EOF - end If you have this retriever, you can say things like {\tt cat http://google.com} (or shorter, {\tt cat //google.com}) and get back the content sent by the web server. This is especially useful when combined with the preprocessor, as it allows you to compile web-based resources into the final output. __ meta::template('comment', '\'\'; # A mechanism for line or block comments.'); meta::template('eval', <<'__'); my $result = eval $_[0]; terminal::warning("Error during template evaluation: $@") if $@; $result; __ meta::template('failing_conditional', <<'__'); my ($commands) = @_; my $should_return = $commands =~ / if (.*)$/ && ! eval $1; terminal::warning("eval of template condition failed: $@") if $@; $should_return; __ meta::template('include', <<'__'); my ($commands) = @_; return '' if template::failing_conditional($commands); join "\n", map retrieve($_), split /\s+/, $commands; __ meta::template('item[]', '"\\\\item[$_[0]]";'); meta::template('pinclude', <<'__'); # Just like the regular include, but makes sure to insert paragraph boundaries # (this is required for SDoc to function properly). my ($commands) = @_; return '' if template::failing_conditional($commands); my $text = join "\n\n", map retrieve($_), split /\s+/, $commands; "\n\n$text\n\n"; __ meta::template('script-include', <<'__'); my ($name) = @_; my $s = 'script'; my $script = retrieve($name); "<$s>\n$script\n"; __ meta::template('style-include', <<'__'); my ($name) = @_; my $s = 'style'; my $style = retrieve($name); "<$s>\n$style\n"; __ meta::vim_highlighter('cltex', <<'__'); " Cleaner TeX " Maintainer: Spencer Tipping " Language: Cleaner TeX (a variant of LaTeX with support for a bunch of embedded languages) if version < 600 syntax clear elseif exists("b:current_syntax") finish endif syn match cltEofMarker /<<\s*\w\+/ contained syn region cltLineComment matchgroup=cltCode start=/^\s*- comment / end=/$/ contained syn match cltLine /^\s*- .*$/ contains=cltEofMarker,cltLineComment syn case match | syn include @cpp syntax/cpp.vim | unlet b:current_syntax syn case match | syn include @java syntax/java.vim | unlet b:current_syntax syn case match | syn include @asm syntax/asm.vim | unlet b:current_syntax syn case match | syn include @javascript syntax/javascript.vim | unlet b:current_syntax syn case match | syn include @html syntax/html.vim | unlet b:current_syntax syn case match | syn include @perl syntax/perl.vim | unlet b:current_syntax syn case match | syn include @ruby syntax/ruby.vim | unlet b:current_syntax syn case match syn region cltCpp matchgroup=cltCode start=/^\z(\s*\)- cpp .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@cpp syn region cltJava matchgroup=cltCode start=/^\z(\s*\)- java .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@java syn region cltAsm matchgroup=cltCode start=/^\z(\s*\)- asm .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@asm syn region cltJavascript matchgroup=cltCode start=/^\z(\s*\)- javascript .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@javascript syn region cltHtml matchgroup=cltCode start=/^\z(\s*\)- html .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@html syn region cltPerl matchgroup=cltCode start=/^\z(\s*\)- perl .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@perl syn region cltRuby matchgroup=cltCode start=/^\z(\s*\)- ruby .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ contains=@ruby syn region cltResource matchgroup=cltCode start=/^\z(\s*\)- resource .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ syn region cltComment matchgroup=cltCode start=/^\z(\s*\)- comment .*<<\s*\z(\w\+\)$/ end=/^\z1- \z2$/ syn cluster cltStuff add=cltCpp,cltJava,cltAsm,cltJavascript,cltHtml,cltResource,cltComment,cltLine,cltPerl,cltRuby syn region cltDocument start=/\%^/ end=/\%$/ contains=@cltStuff hi link cltLine Special hi link cltKeyword String hi link cltResource String hi link cltEofMarker String hi link cltCode Special hi link cltDocument Comment hi link cltComment Type hi link cltLineComment Type let b:current_syntax = "cltex" __ internal::main(); __END__