Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 2166 lines (1719 sloc) 86.474 kB
#!/usr/bin/perl
use File::Temp 'tempfile';
use Carp 'carp';
use Digest::SHA 'sha256_base64';
$|++;
my %data;
my %transient;
my %externalized_functions;
my @data_types;
my @script_args;
sub meta::define_form {
my ($namespace, $delegate) = @_;
push @data_types, $namespace;
*{"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;
carp $@ if $@;
};
meta::meta('datatypes::bootstrap', <<'__guYWiOv4zBmdrlI3k3sW7f/q/xsX38Xvzz0dwwLCIRM');
meta::define_form 'bootstrap', sub {};
__guYWiOv4zBmdrlI3k3sW7f/q/xsX38Xvzz0dwwLCIRM
meta::meta('datatypes::code_filter', <<'__gIaVDqDoWT04Y/vzEYyoyadmT36MLhB1ERa09udQZ9M');
meta::define_form 'code_filter', sub {
my ($name, $value) = @_;
*{"code_filter::$name"} = eval "sub {\n$value\n}";
carp $@ if $@;
};
__gIaVDqDoWT04Y/vzEYyoyadmT36MLhB1ERa09udQZ9M
meta::meta('datatypes::data', <<'__j7lFraXGRfKk8ymj2mDJhNbCQMk9FSciN1hdDhzM99U');
meta::define_form 'data', sub {
my ($name, undef) = @_;
$externalized_functions{$name} = "data::$name";
*{$name} = sub {
associate("data::$name", $_[1] || join('', <STDIN>)) if @_ > 0 && $_[0] eq '=';
retrieve("data::$name");
};
};
__j7lFraXGRfKk8ymj2mDJhNbCQMk9FSciN1hdDhzM99U
meta::meta('datatypes::function', <<'__XSIHGGHv0Sh0JBj9KIrP/OzuuB2epyvn9pgtZyWE6t0');
meta::define_form 'function', sub {
my ($name, $value) = @_;
$externalized_functions{$name} = "function::$name";
*{$name} = eval "sub {\n$value\n}";
carp $@ if $@;
};
__XSIHGGHv0Sh0JBj9KIrP/OzuuB2epyvn9pgtZyWE6t0
meta::meta('datatypes::internal_function', <<'__heBxmlI7O84FgR+9+ULeiCTWJ4hqd079Z02rZnl9Ong');
meta::define_form 'internal_function', sub {
my ($name, $value) = @_;
*{$name} = eval "sub {\n$value\n}";
carp $@ if $@;
};
__heBxmlI7O84FgR+9+ULeiCTWJ4hqd079Z02rZnl9Ong
meta::meta('datatypes::library', <<'__Uu9nRiHABRn+x19zBWmHpJF8gbfMA5v5MlpNoSE8MqE');
meta::define_form 'library', sub {
my ($name, $value) = @_;
eval $value;
$externalized_functions{$name} = "library::$name";
*{$name} = sub {edit("library::$name")};
warn $@ if $@;
};
__Uu9nRiHABRn+x19zBWmHpJF8gbfMA5v5MlpNoSE8MqE
meta::meta('datatypes::line_filter', <<'__/k1BhEweDsgaEdqrALbIEjvKhsVrk/hv//e/KPydA6A');
meta::define_form 'line_filter', sub {
my ($name, $value) = @_;
*{"line_filter::$name"} = eval "sub {\n$value\n}";
carp $@ if $@;
};
__/k1BhEweDsgaEdqrALbIEjvKhsVrk/hv//e/KPydA6A
meta::meta('datatypes::list-type', <<'__OKczvJ+6wi8VPNFcZ9ohlXjw+ychodWCfcELdli9p+w');
meta::define_form '_list_type', sub {
my ($outer_name, $outer_value) = @_;
$externalized_functions{$outer_name} = "_list_type::$outer_name";
*{$outer_name} = sub {
associate("${outer_value}::$_", '') for @_;
};
meta::define_form $outer_value, sub {
my ($name, $value) = @_;
$externalized_functions{$name} = "${outer_value}::$name";
*{$name} = sub {
my ($command, @xs) = @_;
my $xs = join "\n", @xs;
return grep length, split /\n/, retrieve("${outer_value}::$name") if $command eq 'items';
associate("${outer_value}::$name", retrieve("${outer_value}::$name") . "\n$xs") if $command eq 'add' || $command eq '<<';
edit("${outer_value}::$name") if $command eq 'edit';
return retrieve("${outer_value}::$name");
};
};
};
__OKczvJ+6wi8VPNFcZ9ohlXjw+ychodWCfcELdli9p+w
meta::meta('datatypes::note', <<'__TGOjJwmj+QJp1giUQqg2bEaQe8RvqnrFEqyZhIpSC34');
meta::define_form 'note', sub {
my ($name, undef) = @_;
$externalized_functions{$name} = "note::$name";
*{$name} = sub {edit("note::$name")};
};
__TGOjJwmj+QJp1giUQqg2bEaQe8RvqnrFEqyZhIpSC34
meta::meta('datatypes::unlit_converter', <<'__2X1thh9LQ0oUWCfeWj297YwiGJA/klB7hVI2sWEFtEE');
meta::define_form 'unlit_converter', sub {
my ($name, $value) = @_;
*{"unlit_converter::$name"} = eval "sub {\n$value\n}";
carp $@ if $@;
};
__2X1thh9LQ0oUWCfeWj297YwiGJA/klB7hVI2sWEFtEE
meta::meta('datatypes::vim-highlighter', <<'__vsGBLVDC3S+pX/k/zl5CgXeAQz2QjpBkLgx0CJ4vcn0');
meta::define_form 'vim_highlighter', \&meta::bootstrap::implementation;
__vsGBLVDC3S+pX/k/zl5CgXeAQz2QjpBkLgx0CJ4vcn0
meta::meta('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA');
meta::define_form 'internal', \&meta::meta::implementation;
__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA
meta::_list_type('list', <<'__ozA5XMClOtEgdzZUav/0c1lAk3Vku/dc4e2tQHgNkTk');
list
__ozA5XMClOtEgdzZUav/0c1lAk3Vku/dc4e2tQHgNkTk
meta::bootstrap('initialization', <<'__plktoDCjGQioE48vwfrH0xL3ulcYnTWp+fUvaFwRnnc');
#!/usr/bin/perl
use File::Temp 'tempfile';
use Carp 'carp';
use Digest::SHA 'sha256_base64';
$|++;
my %data;
my %transient;
my %externalized_functions;
my @data_types;
my @script_args;
sub meta::define_form {
my ($namespace, $delegate) = @_;
push @data_types, $namespace;
*{"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;
carp $@ if $@;
};
__plktoDCjGQioE48vwfrH0xL3ulcYnTWp+fUvaFwRnnc
meta::bootstrap('pod', <<'__0h2CBA2cqa4qd6nox9dul6Jn9hcJFHw3uPdC89Xim7o');
=head1 NAME
object - Stateful file-based object
=head1 SYNOPSYS
object [options] action [arguments...]
object shell
=head1 DESCRIPTION
Stateful objects preserve their state between executions by rewriting themselves. Each time the script exits it replaces its contents with its new state. Thus
state management, for user-writable scripts, is completely transparent.
An object rewrites itself only if its state has changed. This may seem like a dangerous operation, but some checks are put into place to ensure that it goes
smoothly. First, the object is initially written to a separate file. Next, that file is executed and asked to provide a hashsum of its contents. The original
object is rewritten only if that hashsum is correct. This ensures that the replacement object is functional and has the right data.
Currently the only known way to lose your data is to edit the serialization-related functions in such a way that they no longer function. However, this is not
something most people will normally do. In the future there may be a locking mechanism to prevent unintentional edits of these attributes.
=cut
__0h2CBA2cqa4qd6nox9dul6Jn9hcJFHw3uPdC89Xim7o
meta::code_filter('verbatim', <<'__AON9bxWzZOMpERGSJVrit8dkijYb5Re8IIg2PEC5i1s');
my ($line, %settings) = @_;
unless ($settings{'name'}) {
return '\begin{verbatim}' if $settings{'begin'};
return '\end{verbatim}' if $settings{'end'};
}
return $line;
__AON9bxWzZOMpERGSJVrit8dkijYb5Re8IIg2PEC5i1s
meta::data('default-action', <<'__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc');
shell
__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc
meta::data('document', <<'__YGCi/syAMnXXbny13zipjKpx0WLvRajMexm2ilDCLe4');
= Javascript in Ten Minutes
a Spencer Tipping
begin
\newpage
- Introduction
This guide is for anyone who knows some Javascript but would like a quick\footnote{Longer than ten minutes, despite what the title says.} intro to its advanced features. It will be easier
reading if you also know another functional language such as Ruby, Perl, Python, ML, Scheme, etc, since I don't really explain how first-class functions work.
- Types
Javascript has nine types. They are:
e[
+ Null -- \verb|null|. Chucks a wobbly if you ask it for any attributes; e.g.~\verb|null.foo| fails. Never boxed.\footnote{Boxing is just a way of saying whether something has a pointer. A
boxed type is a reference type, and an unboxed type is a value type. In Javascript, this has additional ramifications as well -- see section \ref{sec:boxing}.}
+ Undefined -- \verb|undefined|. What you get if you ask an object for something it doesn't have; e.g.~\verb|document.nonexistent|. Also chucks a wobbly if you ask it for any attributes.
Never boxed.
+ Strings -- e.g. \verb|'foo'|, \verb|"foo"| (single vs. double quotation marks makes no difference). Sometimes boxed. Instance of \verb|String| when boxed.
+ Numbers -- e.g. \verb|5|, \verb|3e+10| (all numbers behave as floats -- significant for division, but can be truncated by \verb|x >>> 0|). Sometimes boxed. Instance of \verb|Number| when
boxed.
+ Booleans -- \verb|true| and \verb|false|. Sometimes boxed. Instance of \verb|Boolean| when boxed.
+ Arrays -- e.g. \verb|[1, 2, "foo", [3, 4]]|. Always boxed. Instance of \verb|Array|.
+ Objects -- e.g. \verb|{foo: 'bar', bif: [1, 2]}|, which are really just hashtables. Always boxed. Instance of \verb|Object|.
+ Regular expressions -- e.g. \verb|/foo\s*([bar]+)/|. Always boxed. Instance of \verb|RegExp|.
+ Functions -- e.g. \verb|function (x) {return x + 1}|. Always boxed. Instance of \verb|Function|.
]e
The value \verb|null| is actually almost never produced by Javascript. The only case you're likely to run across \verb|null| is if you assign it somewhere (most of the time you'll get
\verb|undefined| instead -- one notable exception is \verb|document.getElementById|, which returns \verb|null| if it can't find an element). Making sparing use of \verb|undefined| and
instead using \verb|null| can make bugs much easier to track down.
- Functions
Functions are first-class lexical closures,\footnote{First-class in the sense that you can pass them around as values at runtime. You can't reliably introspect them, however, because while
you can obtain their source code via {\tt toString} you won't be able to access the values they close over.} just like \verb|lambda|s in Ruby or \verb|sub|s in Perl.\footnote{Note that block
scoping isn't used -- the only scopes that get introduced are at function boundaries.} They behave pretty much like you'd expect, but there are several really cool things about functions and
one really egregious disaster.
- Variadic behavior (a cool thing)
Functions are always variadic.\footnote{The number of arguments a function accepts is referred to as its {\it arity}. So a unary function, which is monadic, takes one, a binary function,
which is dyadic, takes two, etc. A function that takes any number of arguments is said to be variadic.} Formal parameters are bound if they're present; otherwise they're \verb|undefined|.
For example:
::
(function (x, y) {return x + y}) ('foo') // => 'fooundefined'
:.
The arguments to your function can be accessed in a first-class way, too:
::
var f = function () {return arguments[0] + arguments[1]};
var g = function () {return arguments.length};
f ('foo') // => 'fooundefined'
g (null, false, undefined) // => 3
:.
{\it The {\tt arguments} keyword is not an array!} It just looks like one. In particular, doing any of these will cause problems:
::
arguments.concat ([1, 2, 3])
[1, 2, 3].concat (arguments)
arguments.push ('foo')
arguments.shift ()
:.
To get an array from the \verb|arguments| object, you can say \verb|Array.prototype.slice.call (arguments)|. As far as I know that's the best way to go about it.
- Lazy scoping (a cool thing)
Internally, functions use a lexical scoping chain. However, the variables inside a function body aren't resolved until the function is called. This has some really nice advantages,
perhaps foremost among them self-reference:
::
var f = function () {return f};
f () === f // => true
:.
\begin{quote}
{\bf Tidbit of pathology:} An important consequence of lazy scoping is that you can create functions that refer to variables that might never exist. This makes Javascript very difficult
to debug. The good part is that Javascript can be made to support syntactic macros via the \verb|toString| method:
::
var f = function () {return $0 + $1};
var g = eval (f.toString ().replace (/\$(\d+)/g,
function (_, digits) {return 'arguments[' + digits + ']'}));
g (5, 6) // => 11 (except on IE)
:.
Theoretically by extending this principle one could implement true structural macros, operator overloading, a type system,\footnote{God forbid.} or other things.
\end{quote}
- The meaning of {\tt this} (the egregious disaster)
One would think it is a simple matter to figure out what \verb|this| is, but it's apparently quite challenging, and Javascript makes it look nearly impossible. Outside of functions (in
the global scope, that is), the word \verb|this| refers to the {\it global object}, which is \verb|window| in a browser. The real question is how it behaves inside a function, and that
is determined entirely by how the function is called. Here's how that works:
e[
+ If the function is called alone, e.g.~\verb|foo(5)|, then inside that function's body the word \verb|this| will be equivalent to the global object.
+ If the function is called as a method, e.g.~\verb|x.foo(5)|, then inside that function's body the word \verb|this| refers to the object, in this case \verb|x|.
+ If the function starts off as a method and then is called alone:
::
var f = x.foo;
f (5);
:.
then \verb|this| will be the global object again. Nothing is remembered about where \verb|f| came from; it is all determined right at the invocation site. \label{itm:forget}
+ If the function is invoked using \verb|apply| or \verb|call|, then \verb|this| points to whatever you set it to (unless you try to set it to \verb|null| or \verb|undefined|, in
which case it will be the global object again):
::
var f = function () {return this};
f.call (4) // => 4
f.call (0) // => 0
f.call (false) // => false
f.call (null) // => [object global]
:.
]e
Given this unpredictability, most Javascript libraries provide a facility to set a function's \verb|this| binding (referred to within Javascript circles as just a function's binding)
to something invocation-invariant. The easiest way to do this is to define a function that proxies arguments using \verb|apply| and closes over the proper value (luckily, closure
variables behave normally):
::
var bind = function (f, this_value) {
return function () {return f.apply (this_value, arguments)};
};
:.
The difference between \verb|call| and \verb|apply| is straightforward: \verb|f.call (x, y, z)| is the same as \verb|f.apply (x, [y, z])|, which is the same as
\verb|bind (f, x) (y, z)|. That is, the first argument to both \verb|call| and \verb|apply| becomes \verb|this| inside the function, and the rest are passed through. In the case of
\verb|apply| the arguments are expected in an array-like thing (\verb|arguments| works here), and in the case of \verb|call| they're passed in as given.
- Important consequence: eta-reduction
In most functional programming languages, you can eta-reduce things; that is, if you have a function of the form \verb|function (x) {return f (x)}|, you can just use \verb|f| instead.
But in Javascript that's not always a safe transformation; consider this code:
::
Array.prototype.each = function (f) {
for (var i = 0, l = this.length; i < l; ++i)
f (this[i]);
};
var xs = [];
some_array.each (function (x) {xs.push (x)});
:.
It might be tempting to rewrite it more concisely as:
::
some_array.each (xs.push);
:.
\noindent however this latter form will result in a mysterious Javascript error when the native \verb|Array.push| function finds \verb|this| to be the global object instead of \verb|xs|.
The reason should be apparent: when the function is called inside \verb|each|, it is invoked as a function instead of a method. The fact that the function started out as a method on
\verb|xs| is forgotten. (Just like case \ref{itm:forget} above.)
The simplest way around this is to bind \verb|xs.push| to \verb|xs|:
::
some_array.each (bind (xs.push, xs));
:.
- Odd tidbit: {\tt this} is never falsy
\label{sec:this-is-never-falsy}
For reasons explained in section \ref{sec:boxing}, {\tt this} will never be set to a falsy value. If you try to set it to {\tt null} or {\tt undefined}, say by doing this:
::
var f = function () {
return this;
};
f.call (null); // returns null, right?
:.
\noindent it will in fact become the global {\tt this}, usually {\tt window} in the browser. If you use a falsy primitive, {\tt this} will refer to a boxed version of that primitive.
This has some counterintuitive consequences, covered in more detail in section \ref{sec:autoboxing}.
- Gotchas
Javascript is an awesome language just like Perl is an awesome language and Linux is an awesome operating system. If you know how to use it properly, it will solve all of your problems
trivially (well, almost), and if you miss one of its subtleties you'll spend hours hunting down bugs. I've collected the things I've run into here, which should cover most of Javascript's
linguistic pathology.\footnote{There is plenty of this pathology despite Javascript being generally an excellent language. This makes it ideal both for people who want to get things done,
and for bug-connoisseurs such as myself.}
- Semicolon inference
You won't run into any trouble if you always end lines with semicolons. However, most browsers consider it to be optional, and there is one potential surprise lurking if you choose to omit
them.
Most of the time Javascript does what you mean. The only case where it might not is when you start a line with an open-paren, like this:
::
var x = f
(y = x) (5)
:.
Javascript joins these two lines, forming:
::
var x = f (y = x) (5)
:.
The only way around this that I know of is to put a semicolon on the end of the first line.
- Void functions
Every function returns a value. If you don't use a \verb|return| statement, then your function returns \verb|undefined|; otherwise it returns whatever you tell it to. This can be a common
source of errors for people used to Ruby or Lisp; for instance,
::
var x = (function (y) {y + 1}) (5);
:.
\noindent results in \verb|x| being \verb|undefined|. If you're likely to make this slip, there's an Emacs mode called ``js2-mode'' that identifies functions with no side-effects or return
values, and it will catch most of these errors.\footnote{Of course, that's if you're an Emacs person. If you prefer a {\it real} editor (wink), I wrote a custom JS highlighter that handles
some cases better than the builtin one: \url{http://github.com/spencertipping/js-vim-highlighter}.}
- {\tt var}
Be careful how you define a variable. If you leave off the {\tt var} keyword, your variable will be defined in the global scope, which can cause some very subtle bugs:
::
var f = function () { // f is toplevel, so global
var x = 5; // x is local to f
y = 6; // y is global
};
:.
As far as I know, the same is true in both types of \verb|for| loop:
::
for (i = 0; i < 10; ++i) // i is global
for (var i = 0; i < 10; ++i) // i is local to the function
for (k in some_object) // k is global
for (var k in some_object) // k is local to the function
:.
- Lazy scoping and mutability
This is a beautiful disaster. Check this out:
::
var x = [];
for (var i = 0; i < 3; ++i)
x[i] = function () { return i; };
x[0](); // What will these be?
x[1]();
x[2]();
:.
What will our three functions return when they are eventually called? You might expect them to return 0, 1, and 2, respectively, since those were the values of \verb|i| when they were
created. However they will actually all return 3. This is because of Javascript's lazy scoping: Upon creation, each function receives only a variable name and a scope in which to search
for it; the value itself is not resolved until the time of invocation, at which point \verb|i| will equal 3.
The simplest way to fix this is to wrap our assignment in an anonymous function that is evaluated immediately, introducing another layer of scope. The following code works because within
the enclosing anonymous function, the value of \verb|new_i| never changes.
::
for (var i = 0; i < 3; ++i)
(function (new_i) {
x[new_i] = function () { return new_i; };
})(i);
:.
By the way, you might be tempted to do this:
::
for (var i = 0; i < 3; ++i) {
var j = i;
x[j] = function () { return j; };
}
:.
This won't work for same reason that our original example failed: \verb|j| will be scoped to the nearest enclosing function (remember that Javascript's scoping is function level, not block
level!), so its value is changing just as frequently as \verb|i|'s.
- Equality
Because the \verb|==| is lame, these are all true in Javascript:
::
null == undefined
false == 0
false == ''
'' == 0
true == 1
true == '1'
'1' == 1
:.
So, {\it never use the {\tt ==} operator unless you really want this behavior}. Instead, use \verb|===| (whose complement is \verb|!==|), which behaves sensibly. In particular, \verb|===|
requires both operands to not only be the same-ish, but also be of the same type. It does referential comparison for boxed values and structural comparison for unboxed values. If one side
is boxed and the other is unboxed, \verb|===| will always return false. Because string literals are unboxed, though, you can use it there: \verb|'foo' === 'fo' + 'o'|.
There is one case in particular where {\tt ==} is more useful than {\tt ===}. If you want to find out whether something has a property table (i.e.~isn't {\tt null} or {\tt undefined}), the
easiest way to go about it is {\tt (x == null)} rather than the more explicit \verb+(x === null || x === undefined)+. Apart from this I can't imagine using {\tt ==} very
often.\footnote{And, in fact, there are good security reasons not to do so; see section \ref{sec:numeric-coercion} for all the gory details.}
\begin{quote}
{\bf Tidbit of pathology:} It turns out that {\tt ==} isn't even stable under truthiness. If {\tt x = 0} and {\tt y = new Number(0)}, then {\tt x == y}, {\tt !!x} is {\tt false}, and
{\tt !!y} is {\tt true}. Section \ref{sec:boxing} talks more about why this kind of thing happens.
\end{quote}
- Boxed vs.~unboxed
\label{sec:boxing}
Boxed values are always truthy and can store properties. Unboxed values will silently fail to store them; for example:\footnote{There are other consequences of boxing; see sections
\ref{sec:careful-with-typeof} and \ref{sec:careful-with-instanceof} for some examples.}
::
var x = 5;
x.foo = 'bar';
x.foo // => undefined; x is an unboxed number.
var x = new Number (5);
x.foo = 'bar';
x.foo // => 'bar'; x is a pointer.
:.
How does a sometimes-boxed value acquire a box? When you do one of these things:
e[
+ Call its constructor directly, as we did above
+ Set a member of its prototype and refer to \verb|this| inside that method (see section \ref{sec:prototypes})
+ Pass it as the first argument to a function's {\tt call} or {\tt apply} method (see section \ref{sec:this-is-never-falsy})
]e
All HTML objects, whether or not they're somehow native, will be boxed.
- Things that will silently fail or misbehave
Javascript is very lenient about what you can get away with. In particular, the following are all perfectly legal:
::
[1, 2, 3].foo // => undefined
[1, 2, 3][4] // => undefined
1 / 0 // => Infinity
0 * 'foo' // => NaN
:.
This can be very useful. A couple of common idioms are things like these:
::
e.nodeType || (e = document.getElementById (e));
options.foo = options.foo || 5;
:.
Also, the language will convert {\it anything} to a string or number if you use \verb|+|. All of these expressions are strings:
::
null + [1, 2] // => 'null1,2'
undefined + [1, 2] // => 'undefined1,2'
3 + {} // => '3[object Object]'
'' + true // => 'true'
:.
And all of these are numbers:
::
undefined + undefined // => NaN
undefined + null // => NaN
null + null // => 0
{} + {} // => NaN
true + true // => 2
0 + true // => 1
:.
And some of my favorites:
::
null * false + (true * false) + (true * true) // => 1
true << true << true // => 4
true / null // => Infinity
[] == [] // => false
[] == ![] // => true
:.
- Numeric coercion
\label{sec:numeric-coercion}
This one caught me off guard recently. Javascript's type coercions sometimes have inconsistent properties. For example:
::
{} // truthy
!!{} // coerce to boolean, truthy
+{} // coerce to number, NaN, which is falsy
[] // truthy
!![] // coerce to boolean, truthy
+[] // coerce to number, 0, which is falsy
[] == false // true (because [] is really zero, or something)
[] == 0 // true
[] == '' // true (because 0 == '')
[] == [] // false (different references, no coercion)
[1] == [1] // false (different references, no coercion)
[1] == +[1] // true (right-hand side is number, coercion)
:.
You need to watch out for things like this when you're using certain operators with non-numeric things. For example, this function will not tell you whether an array contains any truthy
values:
::
var has_truthy_stuff = function (xs) {
var result = 0;
for (var i = 0, l = xs.length; i < l; ++i)
result |= xs[i];
return !!result;
};
has_truthy_stuff([{}, {}, 0]) // returns false
:.
The reason \verb|has_truthy_stuff| returns false is because when \verb|{}| is coerced to a number, it becomes {\tt NaN}, which is falsy in Javascript. Using \verb+|=+ with {\tt NaN} is
just like using it with {\tt 0}; nothing happens. So {\tt result} remains {\tt 0} for all values of the array, and the function fails.
By the way, you can change what numeric coercion does by (re)defining the {\tt valueOf} method:
::
+{valueOf: function () {return 42}} // -> 42
Object.prototype.valueOf = function () {
return 15;
};
Array.prototype.valueOf = function () {
return 91;
};
+{} // -> 15
+[] // -> 91
+[1] // -> 91
:.
It's worth thinking about this a little bit because it has some interesting implications. First, {\tt valueOf()} may not halt. For example:
::
Object.prototype.valueOf = function () {
while (true);
};
{} == 5 // never returns; {} is coerced to a number
+{} // never returns
!{} // returns false; this bypasses valueOf()
:.
Second, {\tt valueOf} is just a regular Javascript function, so it can create security holes. In particular, suppose you're using {\tt eval()} as a JSON parser (not a good idea, by the
way) and didn't check the input for well-formedness first. If someone sends you \verb|{valueOf: function () {while (true);}}|, then your app will hang the first time it coerces the object
to a number (and this coercion can be implicit, like the {\tt == 5} case above).
\begin{quote}
{\bf Tidbit of pathology:} The numeric value of an array depends on its contents:
::
+[0] // 0
+[1] // 1
+[2] // 2
+[[1]] // 1
+[[[[[[[1]]]]]]] // 1
+[1, 2] // NaN
+[true] // NaN
+['4'] // 4
+['0xff'] // 255
+[' 0xff'] // 255
-[] // 0
-[1] // -1
-[1, 2] // NaN
:.
The built-in numeric coercion on arrays will fail with a stack overflow error if your array is deeply nested enough. For example:
::
for (var x = [], a = x, tmp, i = 0; i < 100000; ++i) {
a.push(tmp = []);
a = tmp;
}
a.push(42); // the value we want, 100000 levels deep
x == 5 // stack overflow in V8
:.
Fortunately, at least in V8, numeric coercion is still well-defined when you have an array that contains itself; so this example isn't nearly as much fun as it could be:\footnote{Though
this could easily change if you redefine {\tt valueOf()}.}
::
var a = [];
a.push(a);
+a // 0
:.
\end{quote}
- Things that will loudly fail
There is a point where Javascript will complain. If you call a non-function, ask for a property of \verb|null| or \verb|undefined|, or refer to a global variable that doesn't
exist,\footnote{To get around the error for this case, you can say {\tt typeof foo}, where {\tt foo} is the potentially nonexistent global. It will return {\tt 'undefined'} if
{\tt foo} hasn't been defined (or contains the value {\tt undefined}).} then Javascript will throw a \verb|TypeError| or \verb|ReferenceError|. By extension, referring to local variables
that don't exist causes a \verb|ReferenceError|, since Javascript thinks you're talking about a global variable.
- Throwing things
You can \verb|throw| a lot of different things, including unboxed values. This can have some advantages; in this code for instance:
::
try {
...
throw 3;
} catch (n) {
// n has no stack trace!
}
:.
\noindent the \verb|throw|/\verb|catch| doesn't compute a stack trace, making exception processing quite a bit faster than usual. But for debugging, it's much better to throw a proper
error:
::
try {
...
throw new Error(3);
} catch (e) {
// e has a stack trace, useful in Firebug among other things
}
:.
- Be careful with {\tt typeof}
\label{sec:careful-with-typeof}
Because it behaves like this:
::
typeof function () {} // => 'function'
typeof [1, 2, 3] // => 'object'
typeof {} // => 'object'
typeof null // => 'object'
typeof typeof // hangs forever in Firefox
:.
{\tt typeof} is a really lame way to detect the type of something in many cases.\footnote{And because it returns a string, it's marginally slower than using {\tt .constructor}.} Better is
to use an object's \verb|constructor| property, like this:
::
(function () {}).constructor // => Function
[1, 2, 3].constructor // => Array
({}).constructor // => Object
true.constructor // => Boolean
null.constructor // TypeError: null has no properties
:.
In order to defend against \verb|null| and \verb|undefined| (neither of which let you ask for their constructor), you might try to rely on the falsity of these values:
::
x && x.constructor
:.
But in fact that will fail for \verb|''|, \verb|0|, \verb|false|, \verb|NaN|, and possibly others. The only way I know to get around this is to just do the comparison:
::
x === null || x === undefined ? x : x.constructor
x == null ? x : x.constructor // same thing, but more concise
:.
Alternatively, if you just want to find out whether something is of a given type, you can just use \verb|instanceof|, which never throws an exception.\footnote{Well, almost. If you ask for
it by putting {\tt null}, {\tt undefined}, or similarly inappropriate things on the right-hand side you'll get a {\tt TypeError}.}
- Also be careful with {\tt instanceof}
\label{sec:careful-with-instanceof}
{\tt instanceof} is generally more useful than {\tt typeof}, but it only works with boxed values. For example, these are all false:
::
3 instanceof Number
'foo' instanceof String
true instanceof Boolean
:.
\noindent However, these are all true:
::
[] instanceof Array
({}) instanceof Object
[] instanceof Object // Array inherits from Object
/foo/ instanceof RegExp // regular expressions are always boxed
(function () {}) instanceof Function
:.
One way to work around the first problem is to wrap primitives:
::
new Number(3) instanceof Number // true
new String('foo') instanceof String // also true
new Boolean(true) instanceof Boolean // also true
:.
In general, {\tt (new x.constructor(x) instanceof x.constructor)} will be true for all primitive {\tt x}. However, this doesn't hold for {\tt null} or {\tt undefined}. These will throw
errors if you ask for their constructors, and as far as I know are never returned from the result of a constructor invocation (using {\tt new}, that is).
- Browser incompatibilities
Generally browsers since IE6 have good compatibility for core language stuff. One notable exception, however, is an IE bug that affects \verb|String.split|:
::
var xs = 'foo bar bif'.split (/(\s+)/);
xs // on reasonable browsers: ['foo', ' ', 'bar', ' ', 'bif']
xs // on IE: ['foo', 'bar', 'bif']
:.
A more subtle bug that took me several hours to find is that IE6 also doesn't return functions from \verb|eval()|:
::
var f = eval('function() {return 5}');
f() // on reasonable browsers: 5
f() // on IE6: 'Object expected' (because f is undefined)
:.
I'm sure there are other similar bugs out there, though the most common ones to cause problems are generally in the DOM.\footnote{jQuery is your friend here. It's branded as a Javascript
library, but in fact it's a set of enhancements to the DOM to (1) achieve a uniform cross-browser API, and (2) make it easier to retrieve and manipulate nodes.}
- Prototypes
\label{sec:prototypes}
I used to have a very anti-OOP comment here, but considering that I occasionally use prototypes I removed it. Despite my obvious and probably unfair vendetta against Javascript's linguistic
compromises to pander to Java-inspired marketing pressure,\footnote{Hence its name, {\it Java}script, despite all of the dissimilarities.} prototype-based programming can be useful on
occasion. This section contains my subjective and biased view of it.
Whenever you define a function, it serves two purposes. It can be what every normal programmer assumes a function is -- that is, it can take values and return values, or it can be a
mutant instance-generating thing that does something completely different. Here's an example:
::
// A normal function:
var f = function (x) {return x + 1};
f (5) // => 6
:.
This is what most people expect. Here's the mutant behavior that no rational person would ever imagine:
::
// A constructor function
var f = function (x) {this.x = x + 1}; // no return!
var i = new f (5); // i.x = 6
:.
The following things are true at this point:
::
i.constructor === f
i.__proto__ === i.constructor.prototype // on Firefox, anyway
i instanceof f
typeof i === 'object'
:.
The \verb|new| keyword is just a right-associative (prefix) unary operator, so you can instantiate things first-class:
::
var x = 5;
new x.constructor (); // Creates a boxed version of x, regardless of what x is
new new Function('x', 'this.x = 5');
:.
If you are going to program using this questionable design pattern, then you'll probably want to add methods to things:\footnote{This section used to say that {\tt i.x} would evaluate to
{\tt 7}. That isn't true though. It's actually 6, as indicated. (Thanks to Daniel Gasparotto for pointing this out.)}
::
var f = function (x) {this.x = x};
f.prototype.add_one = function () {++this.x};
var i = new f (5);
i.add_one ();
i.x // => 6
:.
You can find tons of information about this kind of prototype programming online.
- Why {\tt new} is awful
{\tt new} has some cool features (such as being first-class), but it has a really horrible shortcoming. Most functions in Javascript can be {\it forwarded} -- that is, you can write a new
function to wrap an existing one and the function being called will never know the difference. For example:
::
var to_be_wrapped = function (x) {return x + 1};
var wrapper = function () {
return to_be_wrapped.apply (this, arguments);
};
// for all x, wrapper(x) === to_be_wrapped(x)
:.
However, {\tt new} has no such mechanism. You can't forward a constructor in the general case, because {\tt new} has no equivalent of {\tt apply}. (Though this isn't the whole story; see
the next section for a brilliant workaround.)
- Why {\tt new} isn't quite so awful
I recently received an e-mail from Ondrej Zara explaining that my bias against {\tt new} was ill-founded, and containing a remarkably elegant workaround for the problem I complained about
in the previous section. Here's his implementation verbatim:
::
var Forward = function(ctor /*, args... */) {
var tmp = function(){};
tmp.prototype = ctor.prototype;
var inst = new tmp();
var args = [];
for (var i=1;i<arguments.length;i++) { args.push(arguments[i]); }
ctor.apply(inst, args);
return inst;
}
:.
\noindent And the use case:
::
var Class = function(a, b, c) {}
var instance = Forward(Class, a, b, c);
instance instanceof Class; // true
:.
At first I was very skeptical that this approach would work, but I have yet to find a case where it fails. So constructors can indeed be forwarded in Javascript, despite my previous claims
to the contrary.
- Why you should use prototypes
If you need a dynamic-dispatch pattern, then prototypes are probably your best bet and you should use them rather than a roll-your-own approach. Google's V8 has a bunch of
prototype-specific optimizations, as do later releases of Firefox. Also, prototypes save memory; having a pointer to a prototype is much cheaper than having $n$ pointers to $n$ attributes.
If, on the other hand, you find yourself implementing actual inheritance hierarchies, then you're probably making a mistake.\footnote{OK, I'm being biased about this point. I tend to treat
Javascript more like Scheme than like Smalltalk, so I don't think much in terms of classical object-oriented modeling. Also, since closures are really fast, it's OK to use functional
abstraction instead of inheritance. Javascript tends to be better suited to metaprogramming than inheritance.} I have found prototypes to be an effective way to program in Javascript, but
inheritance in Javascript is (1) slow,\footnote{In some cases really slow. The difference between single-level and multiple-level prototype lookups in Firefox 3.5, for instance, is
enormous.} and (2) poorly representative of Javascript's ``everything is public'' model.
- Autoboxing
\label{sec:autoboxing}
You might be tempted to try something like this:\footnote{{\tt !!~x} is just an idiom to make sure that {\tt x} ends up being a boolean. It's a double-negation, and {\tt !}~always
returns either {\tt true} or {\tt false}.}
::
Boolean.prototype.xor = function (rhs) {return !! this !== !! rhs};
:.
And, upon running this code, you'd run into this tragically unfortunate property:
::
false.xor (false) // => true
:.
The reason is that when you treat an unboxed value as an object (e.g.~invoke one of its methods), it gets temporarily promoted into a boxed value for the purposes of that method call.
This doesn't change its value later, but it does mean that it loses whatever falsity it once had. Depending on the type you're working with, you can convert it back to an unboxed value:
::
function (rhs) {return !! this.valueOf () !== !! rhs};
:.
- A Really Awesome Equality
\label{sec:a-really-awesome-equality}
There is something really important about Javascript that isn't at all obvious from the way it's used. It is this: The syntax \verb|foo.bar| is, in all situations, identical to
\verb|foo['bar']|. You could safely make this transformation to your code ahead of time, whether on value-properties, methods, or anything else. By extension, you can assign non-identifier
things to object properties:
::
var foo = [1, 2, 3];
foo['@snorkel!'] = 4;
foo['@snorkel!'] // => 4
:.
You can also read properties this way, of course:
::
[1, 2, 3]['length'] // => 3
[1, 2, 3]['push'] // => [native function]
:.
In fact, this is what the \verb|for (var ... in ...)| syntax was built to do: Enumerate the properties of an object. So, for example:
::
var properties = [];
for (var k in document) properties.push (k);
properties // => a boatload of strings
:.
However, \verb|for ... in| has a dark side. It will do some very weird things when you start modifying prototypes. For example:
::
Object.prototype.foo = 'bar';
var properties = [];
for (var k in {}) properties.push (k);
properties // => ['foo']
:.
To get around this, you should do two things. First, never modify \verb|Object|'s prototype, since everything is an instance of \verb|Object| (including arrays and all other boxed things);
and second, use \verb|hasOwnProperty|:\footnote{OK, so you're probably wondering why we don't see the {\tt hasOwnProperty} method from a {\tt for ... in} loop, since it's obviously a
property. The reason is that Javascript's attributes have invisible flags (as defined by the ECMAScript standard), one of which is called {\tt DontEnum}. If {\tt DontEnum} is set for some
attribute, then a {\tt for ... in} loop will not enumerate it. Javascript doesn't provide a way to set the {\tt DontEnum} flag on anything you add to a prototype, so using {\tt
hasOwnProperty} is a good way to prevent looping over other people's prototype extensions. Note that it fails sometimes on IE6; I believe it always returns false if the prototype supplies an
attribute of the same name.}
::
Object.prototype.foo = 'bar';
var properties = [], obj = {};
for (var k in obj) obj.hasOwnProperty (k) && properties.push (k);
properties // => []
:.
And very importantly, never use \verb|for ... in| to iterate through arrays (it returns string indices, not numbers, which can cause problems) or strings. Either of these will fail if you
add methods to {\tt Array} or {\tt String} (or {\tt Object}, but you shouldn't do that).
- If You Have 20 Minutes...
Javascript can do almost anything that other languages can do. However, it might not be very obvious how to go about it.
- Iterators for cool people
Because languages like Ruby showed the world just how pass\'e \verb|for| loops really are, a lot of self-respecting functional programmers don't like to use them. If you're on Firefox, you
won't have to; the \verb|Array| prototype includes \verb|map| and \verb|forEach| functions already. But if you're writing cross-browser code and aren't using a library that provides them
for you, here is a good way to implement them:
::
Array.prototype.each = Array.prototype.forEach || function (f) {
for (var i = 0, l = this.length; i < l; ++i)
f (this[i]);
return this; // convenient for chaining
};
Array.prototype.map = Array.prototype.map || function (f) {
var ys = [];
for (var i = 0, l = this.length; i < l; ++i)
ys.push (f (this[i]));
return ys;
};
:.
As far as I know this is (almost) the fastest way to write these functions. We declare two variables up-front (\verb|i| and \verb|l|) so that the length is cached; Javascript won't know
that \verb|this.length| is invariant with the \verb|for| loop, so it will check it every time if we fail to cache it. This is expensive because due to boxing we'd have a failed hash-lookup
on \verb|this| that then dropped down to \verb|this.__proto__|, where it would find the special property \verb|length|. Then, a method call would happen to retrieve
\verb|length|.\footnote{This gets into how Javascript presents certain APIs. Internally it has a notion of gettable and settable properties, though there isn't a cross-browser way to
create them. But properties such as {\tt length}, {\tt childNodes}, etc.~are all really method calls and not field lookups. (Try assigning to one and you'll see.)}
The only further optimization that could be made is to go through the array backwards (which only works for \verb|each|, since \verb|map| is assumed to preserve order):
::
Array.prototype.each = function (f) {
for (var i = this.length - 1; i >= 0; --i)
f (this[i]);
};
:.
This ends up being very slightly faster than the first implementation because it changes a floating-point subtraction (required to evaluate \verb|<| for non-zero quantities) into a sign
check, which internally is a bitwise \verb|and| and a zero-predicated jump. Unless your Javascript engine inlines functions and you're really determined to have killer performance (at
which point I would ask why you're using Javascript in the first place), you probably never need to consider the relative overhead of a non-zero \verb|<| vs.~a zero \verb|>=|.
You can also define an iterator for objects, but not like this:
::
// NO NO NO!!! Don't do it this way!
Object.prototype.each = function (f) {
for (var k in this) this.hasOwnProperty (k) && f (k);
};
:.
Much better is to implement a separate \verb|keys| function to avoid polluting the \verb|Object| prototype:
::
var keys = function (o) {
var xs = [];
for (var k in o) o.hasOwnProperty (k) && xs.push (k);
return xs;
};
:.
- Java classes and interfaces
No sane person would ever want to use these. But if you're insane or are being forced to, then the Google Web Toolkit will give you a way to shoot yourself in the foot and turn it into
Javascript.
- Recursive metaclasses
There are different ways to approach this, but a straightforward way is to do something like this:\footnote{Remember that a class is just a function that produces instances. Nothing about
the {\tt new} keyword is necessary to write object-oriented code (thank goodness).}
::
var metaclass = {methods: {
add_to: function (o) {
var t = this;
keys (this.methods).each (function (k) {
o[k] = bind (t.methods[k], o); // can't use /this/ here
});
return o}}};
metaclass.methods.add_to.call (metaclass, metaclass);
:.
At this point, \verb|metaclass| is now itself a \verb|metaclass|. We can start to implement instances of it:
::
var regular_class = metaclass.add_to ({methods: {}});
regular_class.methods.def = function (name, value) {
this.methods[name] = value;
return this;
};
regular_class.methods.init = function (o) {
var instance = o || {methods: {}};
this.methods.init && this.methods.init.call (instance);
return this.add_to (instance);
};
regular_class.add_to (regular_class);
:.
This is a Ruby-style class where you can define public methods and a constructor. So, for example:
::
var point = regular_class.init ();
point.def ('init', function () {this.x = this.y = 0});
point.def ('distance', function () {
return Math.sqrt (this.x * this.x + this.y * this.y)});
:.
We're using the rather verbose \verb|this.x|, which may offend some Python-eschewing Rubyists. Fortunately, we can use dynamic rewriting to use the \verb|$| where Rubyists would use
\verb|@|:\footnote{And, in fact, we could bake this {\tt ruby()} transformation into a metaclass to make it totally transparent if we wanted to.}
::
var ruby = function (f) {
return eval (f.toString ().replace (/\$(\w+)/g,
function (_, name) {return 'this.' + name}));
};
point.def ('init', ruby (function () {$x = $y = 0}));
point.def ('distance', ruby (function () {
return Math.sqrt ($x * $x + $y * $y)}));
:.
And now you can use that class:
::
var p = point.init ();
p.x = 3, p.y = 4;
p.distance () // => 5
:.
The advantage of using metaclasses is that you can do fun stuff with their structure. For example, suppose that we want to insert method tracing into all of our points for debugging
purposes:\footnote{The example here used to contain the expression {\tt arguments.join}, which is invalid -- {\tt arguments} isn't an array. Now it uses the ``pretend this is an array for
the purposes of calling {\tt join} on it'' idiom, which usually works. (Though you'll sometimes get errors about methods not being generalized, as is the case on Chrome if you try to use
{\tt Array.prototype.toString()} this way.)}
::
keys (point.methods).each (function (k) {
var original = point.methods[k];
point.methods[k] = function () {
trace ('Calling method ' + k + ' with arguments ' +
Array.prototype.join.call (arguments, ', '));
return original.apply (this, arguments);
};
});
:.
Now \verb|trace| (which isn't a Javascript built-in, so you'd have to define it) would be called each time any method of a \verb|point| instance was called, and it would have access to
both the arguments and the state.
- Tail calls
Javascript does not do tail-call optimization by default, which is a shame because some browsers have short call stacks (the shortest I'm aware of is 500 frames, which goes by especially
quickly when you have bound functions and iterators). Luckily, encoding tail calls in Javascript is actually really simple:
::
Function.prototype.tail = function () {return [this, arguments]};
Function.prototype.call_with_tco = function () {
var c = [this, arguments];
var escape = arguments[arguments.length - 1];
while (c[0] !== escape)
c = c[0].apply (this, c[1]);
return escape.apply (this, c[1]);
};
:.
We can now use this definition to write a tail-call optimized factorial function:\footnote{This technique is called {\em trampolining} and doesn't constitute implementing delimited
continuations, as I found out later. However, it's still pretty cool.}
::
// Standard recursive definition
var fact1 = function (n) {
return n > 0 ? n * fact1 (n - 1) : 1;
};
// Tail-recursive definition
var fact2 = function (n, acc) {
return n > 0 ? fact2 (n - 1, acc * n) : acc;
};
// With our tail-call mechanism
var fact3 = function (n, acc, k) {
return n > 0 ? fact3.tail (n - 1, acc * n, k) : k.tail (acc);
};
:.
The first two functions can be called normally:
::
fact1 (5) // => 120
fact2 (5, 1) // => 120
:.
\noindent though neither will run in constant stack space. The third one, on the other hand, will if we call it this way:
::
var id = function (x) {return x};
fact3.call_with_tco (5, 1, id) // => 120
:.
The way this tail-call optimization strategy works is that instead of creating new stack frames:
::
fact1(5)
5 * fact1(4)
4 * fact1(3)
...
:.
\noindent or even creating hollow ones:
::
fact2(5, 1)
fact2(4, 5)
fact2(3, 20)
...
:.
\noindent we pop out of the last stack frame before allocating a new one (treating the array of \verb|[function, args]| as a kind of continuation to be returned):
::
fact3(5, 1, k) -> [fact3, [4, 5, k]]
fact3(4, 5, k) -> [fact3, [3, 20, k]]
fact3(3, 20, k) ...
:.
It isn't a bad performance hit, either -- the overhead of allocating a two-element array of pointers is minimal.
- Syntactic macros and operator overloading
Lazy scoping lets us do some cool stuff. Let's say we want to define a new syntax form for variable declaration, so that instead of this:
::
var f = function () {
var y = (function (x) {return x + 1}) (5);
...
};
:.
\noindent we could write this:
::
var f = function () {
var y = (x + 1).where (x = 5);
...
};
:.
This can be implemented in terms of regular expressions if we don't mind being woefully incorrect about half the time:
::
var expand_where = function (f) {
var s = f.toString ();
return eval (s.replace (/\(([^)]+)\)\.where\(([^)])\)/,
function (_, body, value) {
return '(function (' + value.split ('=')[0] + '){return ' +
body + '}) (' + value.split ('=', 2)[1] + ')';
}));
};
:.
Now we can say this:
::
var f = expand_where (function () {
var y = (x + 1).where (x = 5);
...
});
:.
Obviously a proper parser is more appropriate because it wouldn't fail on simple paren boundaries. But the important thing is to realize that a function gives you a way to quote code, just
like in Lisp:
::
(defmacro foo (bar) ...)
(foo some-expression)
:.
\noindent becomes this in Javascript (assuming the existence of \verb|parse| and \verb|deparse|, which are rather complicated):\footnote{Real versions of these are implemented in
\url{http://github.com/spencertipping/caterwaul}, if you're interested to see what they look like. It's also a reasonable reference for syntactic edge cases.}
::
var defmacro = function (transform) {
return function (f) {
return eval (deparse (transform (parse (f.toString ()))));
};
};
var foo = defmacro (function (parse_tree) {
return ...;
});
foo (function () {some-expression});
:.
This principle can be extended to allow for operator overloading if we write a transformation that rewrites operators into method calls:
::
x << y // becomes x['<<'](y)
:.
Remember that property names aren't restricted to identifiers -- so we could overload the \verb|<<| operator for arrays to work like it does in Ruby with:
::
Array.prototype['<<'] = function () {
for (var i = 0, l = arguments.length; i < l; ++i)
this.push (arguments[i]);
return this;
};
:.
The only thing that's unfortunate about implementing this stuff in Javascript rather than Lisp is that Javascript bakes syntactic constructs into the grammar, so trying to introduce new
syntactic forms such as \verb|when| isn't very convenient:
::
expand_when (function () {
when (foo) { // compile error; { unexpected
bar ();
}
});
:.
But anything you can do inside the Javascript parse tree is fair game.\footnote{Keep in mind that {\tt toString} will sometimes rewrite your function to standard form, so leveraging
ambiguities of the syntax isn't helpful. In Firefox, for example, writing expressions with excess parentheses is not useful because those excess parentheses are lost when you call {\tt
toString}.}
- Further reading
I highly recommend reading jQuery (\url{http://jquery.com}) for the quality and conscientiousness of the codebase. It's a brilliant piece of work and I've learned a tremendous amount by
pawing around through it.
Douglas Crockford has written some excellent Javascript references, including the well-known {\it Javascript: The Good Parts} and a less-well-known but free online tour of the language at
\url{http://javascript.crockford.com/survey.html}.\footnote{There are some discrepancies between his view of Javascript and mine. Neither is incorrect, there are just different unstated
assumptions. For example, when he says that there are three primitives he is correct; he counts types by the number of unboxed representations, whereas I count them by the number of literal
constructors.}
As a shameless plug, I also recommend reading through Divergence (\url{http://github.com/spencertipping/divergence}), a library that I wrote. It's very different from jQuery -- much more
terse and algorithmic (and has no DOM involvement). jQuery uses a more traditional approach, whereas Divergence tends to make heavy use of closures and functional metaprogramming.
If you're into Lisp and metaprogramming, you might also enjoy \url{http://github.com/spencertipping/divergence.rebase} and \url{http://github.com/spencertipping/caterwaul}, two projects that
use function serialization and \verb|eval()| to implement some of the syntactic extensions mentioned in the last section.
Also, I recently found a site called \url{http://wtfjs.com} that seems to be dedicated to exposing all of Javascript's edge-case pathologies. It's quite a fun and enlightening read. A more
in-depth look at the good, bad, and ugly parts of Javascript is \url{http://perfectionkills.com}; this site is written by one of the PrototypeJS developers and has convinced me that I really
don't know Javascript that well.
__YGCi/syAMnXXbny13zipjKpx0WLvRajMexm2ilDCLe4
meta::data('header', <<'__bgoNmrNdosmFP/K+Dbg3jFkiPFpZJ3NadO5WCeO7tgY');
\documentclass{article}
\usepackage{amsmath,amssymb,pxfonts,ulem}
\usepackage[utf8]{inputenc}
\usepackage[colorlinks]{hyperref}
__bgoNmrNdosmFP/K+Dbg3jFkiPFpZJ3NadO5WCeO7tgY
meta::data('intro', <<'__uirjwqAxCBH+vu23aaPBO6uNM7a7cUHAYKJo8gIJsGw');
This is a TeX document generator.
If this is your first time using it, then you'll probably want to install
the VIM highlighter for the custom TeX format. To do that, run this:
$ tex-document install-vim-highlighter
If you don't mind an extra line at the end of your .vimrc file, then you
can automatically associate .cltex files:
$ tex-document update-vimrc
Now you're ready to go.
To create a new TeX document, do this:
$ tex-document new newdoc
Next, edit the contents of your new document:
$ ./newdoc e
Once you're done editing, you can build and display the document:
$ ./newdoc make
Alternately, you can use the shell interface (exit with the 'exit' command or
control-D):
$ ./newdoc shell
tex-document$ e
tex-document$ make
...
tex-document$ ^D
$
If you edit and run make a lot, I recommend you save your document just to be
on the safe side. Normally the document is not committed to disk until you exit
the shell, but you can commit at any time by using the 'save' command.
To extract your document in its original form, you can say this:
$ ./newdoc document > file
And to extract the generated TeX:
$ ./newdoc compile-to-tex > file
__uirjwqAxCBH+vu23aaPBO6uNM7a7cUHAYKJo8gIJsGw
meta::data('meta-associations', <<'__Ql36C0s7UTZWfcWP/PM0xaUnVTY2wKIqUxjVsrvsZDU');
^function:: .pl
^internal_function:: .pl
^meta:: .pl
^bootstrap:: .pl
^data::document$ .cltex
^data::vim-highlighter$ .vim
^unlit_converter:: .pl
^line_filter:: .pl
^code_filter:: .pl
__Ql36C0s7UTZWfcWP/PM0xaUnVTY2wKIqUxjVsrvsZDU
meta::data('name', <<'__ZkgTAmxwuI3x4+R+7A69S2WDL5ZYN9C8ZWldHH/i7xM');
js-in-ten-minutes
__ZkgTAmxwuI3x4+R+7A69S2WDL5ZYN9C8ZWldHH/i7xM
meta::data('output-dir', <<'__6WcazSRIScVxZ8ZY+i+Wl1IEj3qxhKPc9cRstNVq4SQ');
/tmp
__6WcazSRIScVxZ8ZY+i+Wl1IEj3qxhKPc9cRstNVq4SQ
meta::data('pdf-output-file', <<'__Fj+Eu1bcia5PjxWNF+/YZ0HliPDR62rUk90q9TJgMIk');
/tmp/js-in-ten-minutes.0CsA-kpJTG34HiFngeJQcS+TR2mcbflcJWdxL+P7BV8/document.pdf
__Fj+Eu1bcia5PjxWNF+/YZ0HliPDR62rUk90q9TJgMIk
meta::data('pdf-reader', <<'__BlFO10Obn0hAHVxLShDpKtlpv0BTeJ7iqm1v7vjCM+A');
evince
__BlFO10Obn0hAHVxLShDpKtlpv0BTeJ7iqm1v7vjCM+A
meta::data('pdftex', <<'__aHolEJEGN4wnHiydZyVzwRrETVuJhJOGo/nKL9tsLRY');
pdflatex -output-directory=__TEMPORARY_DIRECTORY__ __INPUT_FILE__
__aHolEJEGN4wnHiydZyVzwRrETVuJhJOGo/nKL9tsLRY
meta::data('table-of-contents', <<'__a4ayc/80/OGda4BO/1o/V0etpOqiLx1JwB5S3beHW0s');
1
__a4ayc/80/OGda4BO/1o/V0etpOqiLx1JwB5S3beHW0s
meta::data('tex', <<'__36pVFkgFfbTr/DrquMMaLrTY5F5S/VJ5Z3Jc90KC+q4');
latex -output-directory=__TEMPORARY_DIRECTORY__ __INPUT_FILE__
__36pVFkgFfbTr/DrquMMaLrTY5F5S/VJ5Z3Jc90KC+q4
meta::data('vim-highlighter', <<'__9M68+fUDGVDeeJP8SWHRqHPh2Bq1y6WM6Spq8bnK9yo');
" Cleaner TeX
" Maintainer: Spencer Tipping <spencer@spencertipping.com>
" Language: Cleaner TeX (a variant of LaTeX)
if version < 600
syntax clear
elseif exists("b:current_syntax")
finish
endif
syn match cltTitle /^= .*$/
syn match cltAuthor /^a .*$/
syn match cltDate /^d .*$/
syn match cltBegin /^begin$/
syn match cltSection /^\s*- .*$/
syn region cltVerbatim start=/^\s*::$/ end=/^\s*:\.$/
syn match cltEnumeratedThing /^\s*[eid]\[/
syn match cltEnumeratedThing /^\s*\][eid]/
syn match cltItem /^\s*+\s/
syn match cltQuantifiedItem /^\s*+\[[^\]]*\]\s/
runtime! syntax/tex.vim
hi link cltBegin Keyword
hi link cltTitle Identifier
hi link cltAuthor Identifier
hi link cltDate Identifier
hi link cltEnumeratedThing Special
hi link cltItem Special
hi link cltQuantifiedItem Special
hi link cltSection Type
hi link cltVerbatim String
let b:current_syntax = "cltex"
__9M68+fUDGVDeeJP8SWHRqHPh2Bq1y6WM6Spq8bnK9yo
meta::function('add-to', <<'__KBgra0vG1gIsUI8CCVf4ZEdCatZDCdVO6HuUx+jOJ9Q');
my ($filename) = @_;
my @members = grep /^implementation::/, keys %data;
for (@members) {
my $destination_name = basename($_);
open my($handle), "| $filename import $destination_name" or messages::error("Attribute $_ could not be written.");
print $handle retrieve($_);
close $handle;
}
__KBgra0vG1gIsUI8CCVf4ZEdCatZDCdVO6HuUx+jOJ9Q
meta::function('cat', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk');
my ($name) = @_;
$data{$name};
__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk
meta::function('clean', <<'__YiaR22ZfeeFhhPYBUFHWBZqstuOzKtkv2XqmTuXhy1E');
my $output_directory = &{'pdf-output-file'}();
$output_directory =~ s+/.*++g;
unlink <$output_directory/*>;
rmdir $output_directory;
__YiaR22ZfeeFhhPYBUFHWBZqstuOzKtkv2XqmTuXhy1E
meta::function('clone', <<'__qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY');
for (@_) {
if ($_) {
eval {
file::write($_, serialize(), noclobber => 1);
chmod(0700, $_);
print "File $_ cloned successfully.\n";
};
print "$@\n" if $@;
}
}
__qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY
meta::function('compile', <<'__HfmDCotLaO8/ha33D7dUqCAhPmSSrWt1lJc73NXcPz8');
my $tex_command = tex();
my $pdftex_command = pdftex();
my $filename = 'document';
my $contents = &{'compile-to-tex'}();
my $output_directory = &{'output-dir'}();
chomp $output_directory;
my $temporary_directory = state_based_filename();
$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);
if (&{'table-of-contents'}()) {
print "First invocation of TeX:\n";
system($tex_command);
print "Second invocation of TeX:\n";
system($tex_command);
print "PDFTeX:\n";
system($pdftex_command);
} else {
print "PDFTeX:\n";
system($pdftex_command);
}
associate('data::pdf-output-file', my $result = "$temporary_directory/$filename.pdf", execute => 1);
$result;
__HfmDCotLaO8/ha33D7dUqCAhPmSSrWt1lJc73NXcPz8
meta::function('compile-to-tex', <<'__cvWyD1kaLfBVnSppeGw/hSpleoWxLARJQwP1tUKvebc');
my ($document) = document();
$document = &$_($document) for (grep /^unlit_converter::/, sort keys %data);
$document;
__cvWyD1kaLfBVnSppeGw/hSpleoWxLARJQwP1tUKvebc
meta::function('cp', <<'__yn1SQkcEk6o+gnuCy3QGVFtQb2piaCoUdJPGUkLjpD4');
my ($from, $to) = @_;
$data{$to} = $data{$from} if $data{$from};
messages::error("No such attribute $from") unless $data{$from};
$data{$from};
__yn1SQkcEk6o+gnuCy3QGVFtQb2piaCoUdJPGUkLjpD4
meta::function('create', <<'__YDNTuzkJSNUIk4tbdwxep6/rT8uGnceIj7rljM9gusc');
my ($name, $value) = @_;
messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data;
if ($value) {
associate($name, $value);
} else {
associate($name,'');
edit ($name);
}
__YDNTuzkJSNUIk4tbdwxep6/rT8uGnceIj7rljM9gusc
meta::function('e', <<'__VOcQy5WG275NZGlFODdYHYBe3oJ7/CHmTT/L9O1I6t0');
edit('data::document', extension => '.tex');
reload();
__VOcQy5WG275NZGlFODdYHYBe3oJ7/CHmTT/L9O1I6t0
meta::function('edit', <<'__rAkSOSll0evjt/D0qmnz2M++ACqz6cPtN8TLTHdQUJE');
my ($name, %options) = @_;
my $meta_extension = join '', grep {
my $s = $_;
$s =~ s/\s.*$//;
$name =~ /$s/
} split /\n/, &{'meta-associations'}();
$meta_extension =~ s/^.*\s//;
chomp $meta_extension;
messages::error("Attribute $name does not exist.") unless grep {$_ eq $name} keys %data;
associate($name, invoke_editor_on($data{$name} || "# Attribute $name", %options, extension => $meta_extension),
execute => $name !~ /^internal::/ && $name !~ /^bootstrap::/);
delete $data{$name} if length($data{$name}) == 0;
save();
__rAkSOSll0evjt/D0qmnz2M++ACqz6cPtN8TLTHdQUJE
meta::function('exists', <<'__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk');
my $name = shift;
grep {$_ eq $name} keys %data;
__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk
meta::function('export', <<'__I4eRG7HtLDZhFgrlr0QDrQO5MRwJYGFlotQsWQIE/d8');
# Exports data into a text file.
# export attr1 attr2 attr3 ... file.txt
my $name = pop @_;
my @attributes = @_;
if (@attributes) {
my $file = join "\n", map {cat($_)} @attributes;
file::write ($name, $file);
} else {
messages::error ("Not enough arguments");
}
__I4eRG7HtLDZhFgrlr0QDrQO5MRwJYGFlotQsWQIE/d8
meta::function('grab', <<'__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8');
my ($filename, @attribute_names) = @_;
associate("implementation::$_", `$filename cat $_`) for @attribute_names;
__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8
meta::function('import', <<'__7f1Q36AcIJ8/OaaYPGUq10PPFTKnEF0CMxS56DRsjKk');
my $name = pop @_;
my @files = @_;
if (@files) {
my $files = join "", map {file::read ($_)} @files;
associate ($name, $files);
}
else {
associate($name, join('', <STDIN>));
}
__7f1Q36AcIJ8/OaaYPGUq10PPFTKnEF0CMxS56DRsjKk
meta::function('install-vim-highlighter', <<'__3qCLWaQi9Dp8Xjs/XCdFMcPXj4H4+DWldUwYv9/fAGs');
my $home = $ENV{'HOME'};
mkdir "$home/.vim";
mkdir "$home/.vim/syntax";
file::write("$home/.vim/syntax/cltex.vim", retrieve('data::vim-highlighter'));
<<"EOF";
The highlighter was created successfully. To have syntax highlighting activated
automatically, append this line to your .vimrc:
au BufRead,BufNewFile *.cltex set filetype=cltex
Alternately, you can run $0 update-vimrc.
EOF
__3qCLWaQi9Dp8Xjs/XCdFMcPXj4H4+DWldUwYv9/fAGs
meta::function('lock', <<'__pqf/HijyN91BWpnS+uWYip/mFhHhcd+M9/YdlYsvv9Y');
my (undef, undef, $mode) = stat $0;
chmod $mode & 0555, $0;
__pqf/HijyN91BWpnS+uWYip/mFhHhcd+M9/YdlYsvv9Y
meta::function('ls', <<'__OlQOgKhp6VUThx34wxmM3MFkwsHfR/c8dqvu07z059E');
my $criteria = join '|', @_;
my @lines = grep /$criteria/, sort keys %externalized_functions;
my $length = 0;
$length >= length($_) or $length = length($_) for @lines;
my @new_lines = map($_ . ' ' x ($length + 2 - length($_)) . $externalized_functions{$_}, @lines);
join("\n", sort @new_lines);
__OlQOgKhp6VUThx34wxmM3MFkwsHfR/c8dqvu07z059E
meta::function('ls-a', <<'__S5EydJRu+YIfcFmTMz1ZHX0cANJq32bU5JnYCe1tWdk');
my $criteria = join '|', @_;
my @lines = grep(/$criteria/, sort keys %data);
my $length = 0;
my %inverses;
$inverses{$externalized_functions{$_}} = $_ for keys %externalized_functions;
$length >= length($_) or $length = length($_) for @lines;
my @new_lines = map($_ . ' ' x ($length + 2 - length($_)) . ($inverses{$_} || ''), @lines);
join "\n", @new_lines;
__S5EydJRu+YIfcFmTMz1ZHX0cANJq32bU5JnYCe1tWdk
meta::function('make', <<'__8mAUcwqcvcEhgs6tEjHaIx3uF9QOC+9DAI7NmulvJ74');
compile();
view();
clean();
__8mAUcwqcvcEhgs6tEjHaIx3uF9QOC+9DAI7NmulvJ74
meta::function('mv', <<'__ijyNZ8r34FVK0Ki9/Q0Irx5k9U0pZ+/frrdlu+qkEP4');
my ($from, $to) = @_;
messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data;
associate($to, retrieve($from));
rm($from);
__ijyNZ8r34FVK0Ki9/Q0Irx5k9U0pZ+/frrdlu+qkEP4
meta::function('new', <<'__FQjehdFg7T3T2iHPMlGp6nAnrKAsQUIK5CXW02wNnos');
clone(@_);
__FQjehdFg7T3T2iHPMlGp6nAnrKAsQUIK5CXW02wNnos
meta::function('perl', <<'__Ojd593Fa9fx1Yx2XuPzK6WTUyxO70Nbmlbl9YRodUWA');
my $result = eval($_[0]);
$@ ? $@ : $result;
__Ojd593Fa9fx1Yx2XuPzK6WTUyxO70Nbmlbl9YRodUWA
meta::function('pull', <<'__ZU6uOu7dBdjjoNdEL/U7yrjicOQR5OLFQAacjrKqSCg');
my ($class_name) = @_;
my @attributes = grep /^implementation::/, split /\n/, `$class_name ls-a`;
for (@attributes) {
s/^\s+//;
s/\s+$//;
print STDERR "Adding $_\n";
associate(basename($_), `$class_name cat "$_"`);
}
__ZU6uOu7dBdjjoNdEL/U7yrjicOQR5OLFQAacjrKqSCg
meta::function('push-state', <<'__ik0ofu7R8gHAKSmMjek79V+yfgdjdK5Jmtwf7h8SpJk');
push @{$transient{'states'} = $transient{'states'} || []}, {%data};
my $state_count = scalar @{$transient{'states'}};
"There are now $state_count states on the stack.";
__ik0ofu7R8gHAKSmMjek79V+yfgdjdK5Jmtwf7h8SpJk
meta::function('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM');
execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data);
__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM
meta::function('render', <<'__FnElnVt/3KpagnCCpoXWJvm+mW+WFkRxKvTOxQyYOF4');
use File::Copy;
compile();
file::write('./js-in-ten-minutes.tex', &{'compile-to-tex'}());
copy &{'pdf-output-file'}(), './js-in-ten-minutes.pdf';
__FnElnVt/3KpagnCCpoXWJvm+mW+WFkRxKvTOxQyYOF4
meta::function('rm', <<'__7BVECTVo/mcT5+edC70WPc6S1xCbzAeyUCfCjkKWlww');
for my $to_be_deleted (@_) {
messages::warning("$to_be_deleted does not exist") unless grep {$_ eq $to_be_deleted} keys %data;
}
delete @data{@_};
__7BVECTVo/mcT5+edC70WPc6S1xCbzAeyUCfCjkKWlww
meta::function('save', <<'__uWXGnrQr+A7Cl0zcsDuiokbWAw1XdMdjeq9gDcTMJIw');
my $serialized_data = serialize();
my $final_state = state();
my (undef, $temporary_filename) = tempfile("$0." . 'X' x 32, OPEN => 0);
file::write($temporary_filename, $serialized_data);
chmod 0700, $temporary_filename;
my $observed_state = `perl $temporary_filename state`;
chomp $observed_state;
if ($observed_state ne $final_state) {
messages::error("The state of this object ($final_state) is inconsistent with the state of $temporary_filename ($observed_state).\n" .
"$0 has not been updated.");
} else {
eval {file::write($0, $serialized_data)};
warn $@ if $@;
my $observed_self_state = `perl $0 state`;
chomp $observed_self_state;
unlink $temporary_filename if $observed_self_state eq $final_state;
}
__uWXGnrQr+A7Cl0zcsDuiokbWAw1XdMdjeq9gDcTMJIw
meta::function('serialize', <<'__KGiI48MlyG6RAVW5QYRK8y97y8tx+jeAwPlY5eDtMTw');
my @keys_without_internals = grep(!/^internal::/, sort keys %data);
join "\n", $data{'bootstrap::initialization'},
(grep {$_} (map {serialize::single(@_)} grep(/^meta::/, @keys_without_internals),
grep(!/^meta::/, @keys_without_internals),
grep(/^internal::/, sort keys %data))),
"__END__";
__KGiI48MlyG6RAVW5QYRK8y97y8tx+jeAwPlY5eDtMTw
meta::function('shell', <<'__mzNaDzdnJhpI/Va1/nY8LTN9BQtfr77CFKIeK2GdIC0');
use Term::ReadLine;
my $term = new Term::ReadLine "$0 shell";
$term->ornaments(0);
my $prompt = &{'name'}() . '$ ';
my $OUT = $term->OUT || \*STDOUT;
$term->Attribs->{attempted_completion_function} = \&complete;
while (defined ($_ = $term->readline($prompt))) {
my $command_line = $_;
my @args = grep length, split /\s+|("[^"\\]*(?:\\.)?")/o;
my $function_name = shift @args;
return if $function_name eq 'exit';
s/^"(.*)"$/\1/o, s/\\\\"/"/go for @args;
if ($function_name) {
if ($externalized_functions{$function_name}) {
my $result = eval {&{$function_name}(@args)};
messages::warning($@) if $@;
chomp $result;
print $OUT $result, "\n" unless $@;
} else {
messages::warning("Command not found: $function_name");
}
}
for my $watch (@{$transient{'watch_list'}}) {
print $OUT eval($watch), "\n";
print $OUT "Error evaluating watched expression $watch: $@\n" if $@;
}
$prompt = &{'name'}() . '$ ';
}
__mzNaDzdnJhpI/Va1/nY8LTN9BQtfr77CFKIeK2GdIC0
meta::function('size', <<'__lDGr6yVnDwcDWLkJH16MNukltjG2ypBSk/ktYb80h80');
length(serialize());
__lDGr6yVnDwcDWLkJH16MNukltjG2ypBSk/ktYb80h80
meta::function('snapshot', <<'__qjqsCy4CTt88dIi7IWM+Varpb3GcHsYrFTxW7EwpLW0');
my ($name) = @_;
file::write(my $finalname = state_based_filename($name), serialize(), noclobber => 1);
chmod 0700, $finalname;
__qjqsCy4CTt88dIi7IWM+Varpb3GcHsYrFTxW7EwpLW0
meta::function('state', <<'__1S8nzRSMoxJU/VEv2rx/NrAt1iRgXQ9ugxjUP3IFunI');
sha256_base64 serialize();
__1S8nzRSMoxJU/VEv2rx/NrAt1iRgXQ9ugxjUP3IFunI
meta::function('unlock', <<'__08PohCY8fcNe+pWCO6ic6XOOKv48NkrxpNMmTOUIFdA');
my (undef, undef, $mode) = stat $0;
chmod $mode | 0200, $0;
__08PohCY8fcNe+pWCO6ic6XOOKv48NkrxpNMmTOUIFdA
meta::function('update-from', <<'__jJ2tfSk/Quz8/1PiYxIwQjBRt/hYg51iJhAdxcdaHkM');
# Upgrade all attributes that aren't customized. In this case, we want everything except for configuration::, code::, and attribute::.
return "That is a really bad idea." if $0 =~ /\.\/(.*)/ && $_[0] eq $1 || $_[0] eq $0;
my @attributes = map {s/\s+.*//; $_} split(/\n/, qx|$_[0] ls-a|);
terminal::message('info', 'Replicating state...');
&{'push-state'}();
terminal::message('info', 'Updating meta attributes...');
for my $attribute (grep length && /^meta::/, @attributes) {
associate($attribute, join('', qx|$_[0] cat $attribute|));
reload();
print '.';
}
print "\n";
terminal::message('info', 'Updating non-meta attributes...');
for my $attribute (grep length && ! (/^configuration::/ || /^code::/ || /^attribute::/ || /^function::pop-state$/ ||
/^list::/ || /^issue::/ || /^data::/ || /^meta::datatypes/), @attributes) {
associate($attribute, join('', qx|$_[0] cat $attribute|));
reload(); # Necessary to activate new datatypes.
print '.';
}
print "\n";
terminal::message('info', 'Reloading new configuration');
reload();
terminal::message('info', "Imported from $_[0]. Run pop-state to undo this change.");
__jJ2tfSk/Quz8/1PiYxIwQjBRt/hYg51iJhAdxcdaHkM
meta::function('update-vimrc', <<'__QweSkvdteATxlMZfDti3sl2iWNE+LbkHc1E7FkqNuCg');
open my $fh, '>>', "$ENV{'HOME'}/.vimrc";
print $fh "au BufRead,BufNewFile *.cltex set filetype=cltex";
close $fh;
__QweSkvdteATxlMZfDti3sl2iWNE+LbkHc1E7FkqNuCg
meta::function('usage', <<'__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I');
<<"EOD" . join ' ', split /\n/, ls ();
Usage: $0 [options] action [arguments]
Defined actions:
EOD
__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I
meta::function('view', <<'__wEtlK5H0ttR24UvcFUsgg5Es1V/VbjMJlU+SKiO2jKs');
my $pdf_reader = &{'pdf-reader'}();
my $pdf_output_file = &{'pdf-output-file'}();
chomp $pdf_reader;
system("$pdf_reader '$pdf_output_file'");
__wEtlK5H0ttR24UvcFUsgg5Es1V/VbjMJlU+SKiO2jKs
meta::function('vim', <<'__1EcCMR8Tks8HBoOg+zAKJ4LlrRIY8nvLs4M1VTr2Zec');
# Installs VIM highlighters.
file::write("$ENV{'HOME'}/.vim/syntax/$_.vim", retrieve("vim_highlighter::$_")) for map {s/^vim_highlighter:://o; $_} grep /^vim_highlighter::/, sort keys %data;
__1EcCMR8Tks8HBoOg+zAKJ4LlrRIY8nvLs4M1VTr2Zec
meta::internal_function('associate', <<'__D8BKmEFp/adiPPqPnXyMOzlsBMCmuZi62UpJWdoFg/0');
my ($name, $value, %options) = @_;
my $namespace = namespace($name);
messages::error("Namespace $namespace does not exist") unless grep {$_ eq $namespace} @data_types;
$data{$name} = $value;
execute($name) if $options{'execute'};
__D8BKmEFp/adiPPqPnXyMOzlsBMCmuZi62UpJWdoFg/0
meta::internal_function('basename', <<'__T4JEqOUYjMzssdVwV/rdgAhvr0Vz9TQUo0noTdeBLxw');
my ($name) = @_;
$name =~ s/^[^:]*:://;
$name;
__T4JEqOUYjMzssdVwV/rdgAhvr0Vz9TQUo0noTdeBLxw
meta::internal_function('complete', <<'__lhlD80z2kvEUEeHPqLFw6JE8xUdXr6J5Q1gXHg4beHg');
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 '';
}
# This is another way to implement autocompletion.
#
# my $attribs = $term->Attribs;
# $attribs->{completion_entry_function} = $attribs->{list_completion_function};
# $attribs->{completion_word} = [sort keys %data, sort keys %externalized_functions];
my ($text, $line) = @_;
if ($line =~ / /) {
# Start matching attribute names.
match ($text, @attributes);
} else {
# Start of line, so it's a function.
match ($text, @functions);
}
__lhlD80z2kvEUEeHPqLFw6JE8xUdXr6J5Q1gXHg4beHg
meta::internal_function('execute', <<'__Ge94WTpmLuqsMDappj5G/G2BKILAE0GjeCqAeHLW6fQ');
my ($name, %options) = @_;
my $namespace = namespace($name);
eval {&{"meta::$namespace"}(basename($name), retrieve($name))};
warn $@ if $@ && $options{'carp'};
__Ge94WTpmLuqsMDappj5G/G2BKILAE0GjeCqAeHLW6fQ
meta::internal_function('file::read', <<'__ZxBqZsMZZRuLMQp8Sy//ZsoAvriDebjYLGAX7p7AxXg');
my $name = shift;
open my($handle), "<", $name;
my $result = join "", <$handle>;
close $handle;
$result;
__ZxBqZsMZZRuLMQp8Sy//ZsoAvriDebjYLGAX7p7AxXg
meta::internal_function('file::write', <<'__+NhpMabvNL+hHZaTZwBoFx2IFa79cjOZwGxEXX+xG0o');
my ($name, $contents, %options) = @_;
die "Choosing not to overwrite file $name" if $options{'noclobber'} && -f $name;
open my($handle), ">", $name or die "Can't open $name for writing";
print $handle $contents;
close $handle;
__+NhpMabvNL+hHZaTZwBoFx2IFa79cjOZwGxEXX+xG0o
meta::internal_function('invoke_editor_on', <<'__97Lgs5+qfyAu92Vv5GCVVSYgUgFhOKYkVYXlbWoUs6U');
my ($data, %options) = @_;
my $content_hash = sha256_base64($data);
my $editor = $options{'editor'} || $ENV{'VISUAL'} || $ENV{'EDITOR'} ||
messages::error('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 $extension = $options{'extension'} || '';
my (undef, $filename) = tempfile("$0." . ("X" x 32), OPEN => 0);
$filename .= $extension;
file::write($filename, $data);
system("$editor $options \"$filename\"");
my $result = file::read($filename);
unlink $filename;
$result;
__97Lgs5+qfyAu92Vv5GCVVSYgUgFhOKYkVYXlbWoUs6U
meta::internal_function('messages::error', <<'__200qXouilOAQNa4NkmIj6l+Rvb49Jpy8yxvIX29NcK4');
my ($message) = @_;
die "$message\n";
__200qXouilOAQNa4NkmIj6l+Rvb49Jpy8yxvIX29NcK4
meta::internal_function('messages::warning', <<'__DeU/1Klulk/y4fO+wtKt+liOmUKwCEYKM8BvtlXYXBc');
my ($message) = @_;
print "$message\n";
__DeU/1Klulk/y4fO+wtKt+liOmUKwCEYKM8BvtlXYXBc
meta::internal_function('namespace', <<'__D7UfKyyYZ1slZZyaS28hIt8a68jkI3ELBaddROXOHug');
my ($name) = @_;
$name =~ s/::.*$//;
$name;
__D7UfKyyYZ1slZZyaS28hIt8a68jkI3ELBaddROXOHug
meta::internal_function('retrieve', <<'__Erqqkp11FEHKsitr0DEJZ6OCGDYAs+U6BSu4UvLvsFM');
@data{@_};
__Erqqkp11FEHKsitr0DEJZ6OCGDYAs+U6BSu4UvLvsFM
meta::internal_function('serialize::single', <<'__lDBHaXpbrfER2envI2Ipy77IcdjUnlZou+rggaxsAWE');
my $name = shift || $_;
my $contents = $data{$name};
my $delimiter = "__" . sha256_base64 $contents;
my $meta_function_name = "meta::" . namespace($name);
my $invocation_name = basename $name;
"$meta_function_name('$invocation_name', <<'$delimiter');\n$contents\n$delimiter\n";
__lDBHaXpbrfER2envI2Ipy77IcdjUnlZou+rggaxsAWE
meta::internal_function('state_based_filename', <<'__zNSrihAkMKJG5spRYgcFdoNArFKig1u12gIp6gJ8pZw');
my ($name) = @_;
my $noise = $name || state();
$noise =~ s/\//-/g;
"$0.$noise";
__zNSrihAkMKJG5spRYgcFdoNArFKig1u12gIp6gJ8pZw
meta::library('terminal', <<'__bakqo5o+Exi5Kw4onujvi9Pu3zR9lQA8cPKfYj6L74Q');
# Functions for nice-looking terminal output.
package terminal;
use constant black => "0;0";
use constant red => "1;31";
use constant yellow => "1;33";
use constant green => "1;32";
use constant blue => "1;34";
use constant purple => "1;35";
use constant cyan => "1;36";
my %default_colors = (info => green);
my $longest_prefix = 0;
$longest_prefix = $longest_prefix < $_ ? $_ : $longest_prefix for map length, keys %default_colors;
sub message {
my ($prefix, $message) = @_;
my $color = $default_colors{$prefix};
my $padding = ' ' x ($longest_prefix - length $prefix);
print "${padding}[\033[${color}m$prefix\033[0;0m] $message\n";
}
__bakqo5o+Exi5Kw4onujvi9Pu3zR9lQA8cPKfYj6L74Q
meta::line_filter('convert_header_info', <<'__1jqqbjBcdOqh/3nTKZSDbW1AgEo4kg0dQ5HdRAgxyzE');
my ($line) = @_;
$line =~ s/^= (.*)$/\\title{$1}/;
$line =~ s/^a (.*)$/\\author{$1}/;
$line =~ s/^d (.*)$/\\date{$1}/;
my $document_header = '\begin{document}\maketitle';
$document_header .= '\tableofcontents' if &{'table-of-contents'}();
$line =~ s/^begin$/$document_header/;
$line;
__1jqqbjBcdOqh/3nTKZSDbW1AgEo4kg0dQ5HdRAgxyzE
meta::line_filter('convert_itemized_environments', <<'__crKVW6OfRA2nN2SGPWQ/DAD9NPUws+boS8dK4cf3X2I');
my ($line) = @_;
$line =~ s/^\s*\+ /\\item /;
$line =~ s/^\s*\+\[([^\]]*)\] /\\item[\1] /;
$line =~ s/^\s*e\[$/\\begin{enumerate}/;
$line =~ s/^\s*i\[$/\\begin{itemize}/;
$line =~ s/^\s*d\[$/\\begin{description}/;
$line =~ s/^\s*a\[$/\\begin{align*}/;
$line =~ s/^\s*\]e$/\\end{enumerate}/;
$line =~ s/^\s*\]i$/\\end{itemize}/;
$line =~ s/^\s*\]d$/\\end{description}/;
$line =~ s/^\s*\]a$/\\end{align*}/;
$line;
__crKVW6OfRA2nN2SGPWQ/DAD9NPUws+boS8dK4cf3X2I
meta::line_filter('convert_sections', <<'__lmhXPw+0a86ufxG3kCALtSl8Raqrt+7YZomx7zISUbw');
my ($line) = @_;
my %indentation_levels = (
0 => '\section',
2 => '\subsection',
4 => '\subsubsection',
6 => '\paragraph',
8 => '\subparagraph');
if ($line =~ /^(\s*)- (.*)$/) {
my $section = $indentation_levels{length($1)} || die "Invalid indentation level:\n$_";
"${section} {${2}}";
} else {
$line;
}
__lmhXPw+0a86ufxG3kCALtSl8Raqrt+7YZomx7zISUbw
meta::unlit_converter('append_footer', <<'__xZrf+gAwEiK7btRcm+mwC/qtHsXp2FQ/Z8ZCwMvSw4Q');
my ($document) = @_;
"$document\n\\end{document}";
__xZrf+gAwEiK7btRcm+mwC/qtHsXp2FQ/Z8ZCwMvSw4Q
meta::unlit_converter('main', <<'__3D082OWBmT2cWw4D6ktyDGp1MHknawaTDW2B0gvZPyM');
my ($document) = @_;
my $sections_already_encountered = 0;
my $inside_code_block = 0;
my $code_block_indentation = 0;
my $code_section_name = '';
my $result = '';
for (split /\n/, $document) {
# Handle code blocks.
if (/^(\s*):\.$/) {
$inside_code_block = $code_block_indentation = 0;
for my $filter_name (grep /^code_filter::/, sort keys %data) {
$_ = &$filter_name($_, name => $code_section_name, indentation => $code_block_indentation, end => 1);
}
}
if ($inside_code_block) {
my $spaces_to_delete = ' ' x $code_block_indentation;
s/^$spaces_to_delete//;
for my $filter_name (grep /^code_filter::/, sort keys %data) {
$_ = &$filter_name($_, name => $code_section_name, indentation => $code_block_indentation);
}
} else {
for my $filter_name (grep /^line_filter::/, sort keys %data) {
$_ = &$filter_name($_);
}
}
if (/^(\s*)::(\s.*)?$/) {
$inside_code_block = 1;
$code_block_indentation = length($1);
$code_section_name = $2;
for my $filter_name (grep /^code_filter::/, sort keys %data) {
$_ = &$filter_name($_, name => $code_section_name, indentation => $code_block_indentation, begin => 1);
}
}
$result .= "$_\n";
}
$result;
__3D082OWBmT2cWw4D6ktyDGp1MHknawaTDW2B0gvZPyM
meta::unlit_converter('prepend_header', <<'__4YCmTeTBS/MGOkeIFkQRlLujLJ1g/qS0/0iCeanWkkw');
my ($document) = @_;
header() . "\n$document";
__4YCmTeTBS/MGOkeIFkQRlLujLJ1g/qS0/0iCeanWkkw
meta::internal('runtime', <<'__YPmIzwZkTg8URmPfjiwGRG4VDUF2ZCJqTEz+gjETYLQ');
my $initial_state = sha256_base64 serialize();
push @script_args, shift @ARGV while @ARGV && $ARGV[0] =~ /^-/;
my $default_action = retrieve('data::default-action');
chomp $default_action;
my $function_name = shift(@ARGV) || $default_action || 'usage';
$function_name = 'usage' unless $externalized_functions{$function_name};
my $result = &{$function_name}(@ARGV);
chomp $result;
print "$result\n" if $result;
END {
my $serialized_data = serialize();
my $final_state = sha256_base64 $serialized_data;
save() unless $initial_state eq $final_state;
}
__YPmIzwZkTg8URmPfjiwGRG4VDUF2ZCJqTEz+gjETYLQ
__END__
Jump to Line
Something went wrong with that request. Please try again.