# spencertipping/js-in-ten-minutes

426eb33 Mar 20, 2013
executable file 2166 lines (1719 sloc) 84.4 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('', )) 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 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 " 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('', )); } __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__