#!/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::binary', <<'__qK89WumXv6QZrBrjCYdfxut3czw7FcsZSF5klMYbrT0'); meta::define_form 'binary', sub {}; __qK89WumXv6QZrBrjCYdfxut3czw7FcsZSF5klMYbrT0 meta::meta('datatypes::blogpost', <<'__GlCzpuCaSlxPWOHyKQNFlVbbUrsuFpG2KzwcLFQDqfw'); meta::define_form 'blogpost', sub {}; __GlCzpuCaSlxPWOHyKQNFlVbbUrsuFpG2KzwcLFQDqfw 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::file', <<'__nKC16T08JMQcmbS9wxNVea5ymdev21drePRunS7XWM4'); meta::define_form 'file', sub {}; __nKC16T08JMQcmbS9wxNVea5ymdev21drePRunS7XWM4 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', <<'__3RHc2q2OKjeHL1QRq6jhHHCeSrNLDPWTwSax7MclXRE'); meta::define_form 'library', sub { eval $_[1]; warn $@ if $@; }; __3RHc2q2OKjeHL1QRq6jhHHCeSrNLDPWTwSax7MclXRE 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::minifier', <<'__N7WXMGxfMmyx2PTQJdoD7BWSHMRiDl/16PDoq4x8fqE'); meta::define_form 'minifier', sub { my ($name, $value) = @_; $externalized_functions{$name} = "minifier::$name"; *{$name} = eval "sub {\n$value\n}"; carp $@ if $@; }; __N7WXMGxfMmyx2PTQJdoD7BWSHMRiDl/16PDoq4x8fqE 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::section', <<'__dyHqM5u2qiIza2BZZUe8tCJbUbz1UmJwCpsYozFvBmc'); meta::define_form 'section', sub {}; __dyHqM5u2qiIza2BZZUe8tCJbUbz1UmJwCpsYozFvBmc 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::binary('images/title.png', <<'__k00QQ2+lMUrpK4EobjxsY7tpmRQfpuDulhSVYB/XqtM'); MB5!.1PT*&@H````-24A$4@```>8```"-"`8```!BU]GB`````7-21T(`KLX< MZ0````9B2T=$`/\`_P#_H+VGDP````EP2%ES```+$@``"Q(!TMU^_`````=T M24U%!]D+!Q,G*97UV<\````9=$58=$-O;6UE;G0`0W)E871E9"!W:71H($=) M35!7@0X7```5G$E$051XVNW=^Y<<98'&\[^LE-]GYY+>NZ9F9Y; M=T_?9KIG>@:(@@&4F`T14%"Y",8+48D(,F>[J3IR8;Y_S.2>9Z>KJJGKG?>I]ZZVWWM;4 MU"0``+`\O(V=````P0P```AF```(9@``0#`#`$`P`P``@AD``((9```0S``` M$,P``(!@!@"`8`8```0S```@F`$`()@!``#!#```P0P```AF```(9@``0#`# M`$`P`P``@AD``((9```0S```$,P``(!@!@``!#,```0S```@F`$`()@!``#! M#```P0P```AF```(9@``0#`#`$`P`P``@AD``((9```0S```@&`&`(!@!@`` M!#,```0S```@F`$`()@!``#!#```P0P```AF```(9@``0#`#`$`P`P``@AD` M`!#,```0S```@&`&`(!@!@``!#,```0S```@F`$`()@!G"3W7KC5?KHE4!K7#,7:[[XMU:^/6F7GWA2>WX[\_J M_$B[[%4O5^%UZ-LZO7FAY8_IYQ\;EF.1;6Q.W:)7//ZI4CQPIK^=MK^[7GX3NU]=/KE>AVUK`?:MV>\M=1O;[_5_K)=[;H MDNFN1;>SE'W@2CVE^EZ[+^Y5;^&[O:K;5WE/P/%?:+GR99?Z/HTK%Z[>:5VT MZ>NZ>]?3VO_JF\4UO/&Z#NQ]5/=OOU$;SHNJTT$P`P1SO6SM2FUZ4(>JJJ:/ MZ)YWM5@,I#_IQY]*J-4X3D%VZ#M:O<*HKP)V#VGMS3^ME2MML4#8<]G`G):K(`-;UR?VGFXI+&U7SNW;]%'+SA7JZ8G-9E:I7/6?4B? M^=K=>OSE_)M>T$UQ=]GWJ7=[RO=C0J>MOE`;MSU2>LTP3'].0O9KE%N]Z M+RY_5'_X4^Z?KWU?:SILM0>SO4=K_^>/Q=C;M5EG]C@67K^S1[-7;=?>P_LJ M!'.]V[/0\G;U7?"#]&]SK[V?5=!91[=I_*;T:87Y^NWG@@L&5_5!V.CC7VFY M6KY/G>7"WJOU=_VI4";^O'.SWM7K7/08M@37Z'/WO2C]E6`&"&:+7.'/Z[E\ MJVEK4AX+UZ:K"J3F*6T]D&^)[M*%7?8&!MD+^OI'MNG%W/]^LSDB5TW!;&C% MZF\76H_ZY?6:;#:J^`Z&FD=.UU2W_00%,V'6C+]4!AV05SL5*2]GQB M:.FN4:N!TM2N\WY\-/>^9W7]N+NAP7Q38DCOWY%K6QW^7ZWOMM70,AK0Y;OS M`[Q^IYL3S98&SYV08&YJT[MWY`<>/:\M8=?)$CLM,G*PY?0-/90$XH&@HH,#:FT:%N>9P=BF1^/C5*2!/,!'.] MG,%K]4S^RNB/UJO;=KP"J57G/O"WW/OV:4O$T]A@3E>'&B7FM M_X6"V;[R4A6N2O[J:HU8&E7[]PCF?%"=#,%L]?B7+^>J+9@ME@M[_V5Z/%\F MGLIWH9_JP6Q3>R"5KM1G%.YRRBC]G8-@)I@)YL;Q)/35_?D:Z`T]N>URG>%O MKN&`55DQ.T.Z[MG\>A[7A_OL#0_F)EN7UMV7&[SUQH.ZN,]>53"WKOZ!\D.^ M7K@I+G?3,@YF5TPWY+NRW]JI]W4:)TW$_W;TW)NV0PV^7?4+RA:-=ZW]R6@*5@K7=[%AO\=8]>RP]M>N12 M]=?1DCMQP;S4\:]EN:;:@[GF=*1,GWP0CAAS>;OF#<26GB^4N M-36I:&A8/2WVDK)GR.[MU&`PIF0J]]Y44K'0D+I;'.87Q]\@O:,U,3&A_N M59NS>&)J.+L5R_P^.92NK]SR#0052Z3,]<1["R=7AK-5W4/IWQ4^+Z5$+"A_ M5[/LQB+[U)$+QE0=VU/+^NL*9D/.]I4:BR:5RJTC&1W3RC;G(L&\5#FHM)^= M:NL;532_G]/OCX[VJ6V!7D?+^_[4G&#$I8'S;M"#!Q:Y-_//O]8]6\Y7T&M4 M7VD;#K7TQO3NJV_7,V\5)X[8=85?=@OW_?[^2Q,EK=G*%7"3X=.[[WPUGURZ M8M"^1#"[%-[R?.$>[1_,N17,:C#7NSWS;Y>:S=PN]5^E]U?_6ELFF^L+@>,= MS%4?_UJ6LQ;,M96+ZLN$PS>L<.XXS171:)>KYI.\1@6SK<6O>"[$IB?""@7& M%`B.*Y(N2YD*,;[24_ANSHY1)?+!/1G1>#"DR$2NHIU-*M#ED:N]1P,#?@63 MYOLFQOSI_P^D]:NONT_]_J"2V6":T*A_P/S=8/YSDQIJ*6EA&QZMC.=#>ZJL M]>U63RSS\TD-9NH:FU=#2;.5GHB%T]\MJ.!X.J2G&."V2%?*)6M,,>[*YP< M.#QJ<=O,G[NZ%)XQ6R[CO=Z2BCK=4NT.FI7F3$2];L-"5[8A3_^$&>1]Q1,! MP]FE:+9RGLRVYB8'O<7OF/Z<<.YSVK*?XU2KKTTNFS$O)-L'XV;`C+5G9W8K M!$9VI'A(/,H,T[%.5_&8ID]X6P=BA9Z,.<%<0SF8LY^38^KR%/>SX5BAD43N*@F&QSY>,D"JF@KVJ/[PB[OTQ0NC"TRH MT:!KS(4"V*ZSMN=N?CKVA#X^XCC!7=GU;L]BK\-Z;.OY&G(UH-OTA`7S4L>_ MEN6L!W/UY:+:KNQE&LR&J]!*Z_<8B[:L\D$S&^Z:WUI)AU]':-ILS63'G=1^ MC=G6,F*VI".=A<^WKPAF`RS:UZ6Q3`LJUELX0;6UC65;>C,AW](GGK8VC4Z; M094)\6)@I!2L4,&[>J)F:S#44;%E9FL;R0;03,EWG=\3875[.N2M=?V6@CE] M3%>:QS057#$G`/.M^_YX>3#76`X*^SG3.V"?WUKO-K=S:K08_(W8]P1SI?N= MAR_1O869+?;K*Y/N);M@H]&P@L-]:G<9QSG(RBK@S!_+T$>U.WCVU5IA MG+R#OVRN-JV,OT<;O_>L\C=UO7+'.O7:EULP6SW^M2Q71S#74"XR@[^.6!C\ MY0QLTF_^[BUFFUJ&DV9%&!U63WOS_`HZ5^%V1F%R[3>4B&ML.>_6Z;[D*/_MY877D>E'RU[)C/7-;^H;=G6XY M=ZNO?T"#_D'Y_7[YAT85S;8,8^IQE;3D9L;+YO,W>Q$ZPC/9[NUQGV/QP5:% MUOH"O8J6ML=;^_HM!;-#G9'9['I"/ONB)V.%8*ZU'+AR^SD54'N%[V%K-4-V M-MR1FZ.^0?N>8*Y\P,<^O;?0&GCBLKXJKO>=J"";7P$W-;7H]&TOY>^!TC5! MYR*W2UVFPBVK>SZIX>5ZNY2M0ZMOV5\,LP_TU=6=?6)OEVKD_K(>S-66"WO_ MY26W2VVH^G:IY1',F2[%=OECJ3F##9/Q<8T.=*G5:2NVGB;-EL](:^6):FQ> MOWG=,>F7UV8AF#.5?L3L5AW)7)=-KW,@L\[DL%K2[W/WQ,PNS,SG&6[UQ[O+W!PK7/RN+I4#3F#DJRS6\IKIR876*06EX^=!NW M$LR98SIAWF?N]]H6&!<1+@OFVLI!BSNWGQ,5]G.V5V'([%6(=IK!W*A]3S!7 MUO:>'RL_U<.O-X[DSH:6:S!G'N!PJ7;F;OE][:XU&CQMX0E&KBA,,+)/-TTN MWPE&C/8S=6M^"/W!VW16NT$PUQ3,59:+S`0CCY=,,!)SGU3!;%;2-GG:N]0_ M%%`XGBRI_!(:[7">F&`NZ5J=[/?(EFMM30?-)X89WD%-IO^?]'MEWRNARR&499"$W,"V9OA6`VMW5&\5&_!@<'%];?)8^Q M=%=Q3=MCM[#^Y1[,E?;S`L'Z!;'R0P\K/^7&KZY9 M+%`)9NOEPB;?N73LRGS>C<&=N?9GKQ)D1UA-]\JX(9*_5ID*^PDE_>\`<71WNJ/#];*WF M->9J@CE=?YE=O+/ISW+4M1\M;8]A8?UU=F57[C9N8%=VM<'DK;M/3)_0A%OGYHY/Z*#QK46R_JX6]]7E>^[VR=GIS0Q/0JK5[[07WJRW?J9P<6?^QC?=NSU'ZT MJ_>B'85'(CZS.6)IP-JI',S5EHMLF?AIZ6,?7]+/OGNC/G'Q&KTS-:EX?%)3 MLV?JO(NNTO7;?I@^42N^\^EK`C7W9C1J5+:OMUMM+EM9Z\>0HRW7)9EOV:9; M/TO?)A-=^G8I6XN&I\R!6+WN^15[9V2F6.X3_I(*/3.*-U;R=V&&;'$0VU0N M1.:.YK5YNC26G+O,4L&X^FLQUJ0[YY+;-+U?.EDYU^Y:X_]SB]M2\?JOW M,1=N?U=Y; M+]*PJZG!%7.UKV=U7*_>B)ZX;\K;:'?ZCU%IXR=6H'#I&1BB>G) MB$*AD,+QZ<*$&8&N_'L7">;T[]I&#(2Q MN1-;&,T#V>NRYGVQPW,&%!F>/D7SDU8DTM\M_;G!\(1Y"U)L1*.3U0>S>:VT M5^/Y22YF$HJ%0PH&0QH/QS69^WEBJ&7)J2JM;D]-ZZ]C@A%W5[`P<4=F@I%@ M(*3(I#G!2*CB!"/5EP,KP=S(?7_*7&,V/"LUM?9*7?^-[^NA)Y_3*T=*;UI^ M70?V/JI[OG&-UD]4FM1B^0=S9L*(\6OW5A7,A>Z@P57Z\);;],`3^_3'DIE6 M_O;J[[7GH3NU=>._*][A.$[;4\U^3+]__?W%^9QOFJSYD9VG>C#75B[2%5=O M2N^_=ION?72O#KQ^M-BQ\M?7]-)O'M5]M]Z@#>>%+05R8UO,=GD[^S4<""N> M+'D\8RJI>&A$O6W.^2UI;Y<&0W.G8HR73<6X>#!G;FGRJFN?99?9[2U:B0WDUFM6Y\"8PA-3Q7TSDYE9;%PC`]UJ=2U]DFM]>VI8?T.GY)Q1,C:F_G:G M'(M,R5E-.;`:S(W:]PS^`G!"+?OG,0,@F`&"F?T"$,P`"&8`!#-`,!/,`,$, M@&`&0#`#()@!@AD`P0R`8`9`,`,$,P""&0#!#(!@!@AF``0S`((9`,$,$,P` M_F$YW/^JJX;?KDTC>?^B=[K^C7T#$,P``(!@!@"`8`8```0S```$,P``()@! M`""8`0``P0P``,$,```(9@``"&8``$`P`P``@AD``((9```0S```$,P``(!@ M!@"`8`8```0S```$,P``()@!`""8`0``P0P``,$,```(9@``0#`#`+!\@SG] ;$@``6![^'^JL,,B7@5L0`````$E%3D2N0F"" __k00QQ2+lMUrpK4EobjxsY7tpmRQfpuDulhSVYB/XqtM meta::blogpost('a-gentle-introduction-to-call-cc', <<'__FBWv64aQGpyhfcSQ0sRa55LjD9YP3zyCfmulQWOV0rs'); post:title = a-gentle-introduction-to-call-cc post:real-title = A Gentle Introduction to Call/CC post:status = published post:date = 2008-10-23 13:43:05 post:tags = callcc introduction lisp ruby scheme post:format = html

Introduction

Call/CC stands for Call with Current Continuation. It's sort of like a functional version of the Goto construct, only a lot more confusing and a bit more useful. For simplicity, I'll assume that you know basic Scheme.

Example: Returning a Value the Hard Way

(call/cc (lambda (f) (f 5))) evaluates to 5. Here's why: Call/cc takes a function (which I'll call L) that expects to receive a function (I'll call this F) as its parameter. If you call F with a value X, then the call/cc expression evaluates to X. In this case, X = 5. As you can probably tell, something's fishy about F.

Example: Emulating the Return Command

Call/cc is often used to emulate Return in languages that don't provide the Return construct. Here's how this works:

(define (returns-a-value)
  (call/cc (lambda (return)
    (when some-condition  (return 0))
    (when other-condition (return 1))
    (when error-condition (return -1))
    'nothing-happened)))

Call/cc constructs a special procedure and passes it to the lambda given as the parameter Return. When Return is invoked, the lambda function is terminated immediately and the parameter to Return is used as the return value of the call/cc. So if some-condition is true, then Return gets invoked with the parameter 0, so call/cc returns 0. Otherwise, if other-condition is true, then Return gets invoked with the parameter 1, so call/cc returns 1. The same is true of the last when clause. Finally, if none of those happen, then the lambda returns 'nothing-happened, and call/cc returns 'nothing-happened.

You should be worried by this point about what call/cc is doing. This next example should illustrate why:

Example: Error Handling

A common construct in Scheme is to use call/cc as an escape route in case an error arises. For instance, imagine this scenario:

(define (error-prone-computation error)
  (if (some-condition-not-met)
    (error "The condition wasn't met!"))
  (proceed-with-computation))

(define (small-computation error)
  (+ 1 (error-prone-computation error)))

(define (some-large-computation)
  (let ((result (call/cc small-computation)))
    ;; Result will hold either an error message or the result of the computation.
    ;; ...
    ))

This is a bit involved. Suppose you call some-large-computation. Here's what happens. First, we call/cc into small-computation. Call/cc creates a special function and passes it in as the value error. small-computation then invokes error-prone-computation normally, passing in error as a normal parameter. If error-prone-computation completes normally, then its return value is returned to small-computation, which adds 1 to it and returns normally. Call/cc then picks up that value and returns it, so result gets it. So this case is fairly straightforward.

Now suppose there was an error in computation. Call/cc invokes small-computation, which passes its error parameter to error-prone-computation. But now, error-prone-computation calls error with the string "The condition wasn't met!". Here's where it gets unusual. The stack frames for error-prone-computation and small-computation are never revisited; instead, they are bypassed entirely and the string "The condition wasn't met!" is returned directly from the call/cc. The reason for this is that the "function" produced by call/cc is not a function at all -- rather, it is much like a Goto invocation. It returns its parameter immediately from the call/cc.

Example: Re-Entrant Code

Mutually re-entrant functions, also referred to as coroutines, can be built by saving the call/cc jump functions. Here's an example of two coroutines; the first increments a global counter, and the second prints its values.

(define global-counter 0)

(define (increment-routine)
  ;; Increment the counter and call/cc into the display routine.
  ;; When the call/cc returns, its output will be the new jump target.
  (let loop ((jump-target display-routine))
    (set! global-counter (+ 1 global-counter))
    (loop (call/cc jump-target))))

(define (display-routine return)
  ;; Calling the return will pass a value back. In this case, we call/cc
  ;; the return after displaying the value of global-counter.
  (let loop ((active-return return))
    (display global-counter)
    (newline)
    (loop (call/cc active-return))))

The code starts when increment-routine is invoked. Its initial jump target is display-routine, which is passed a jump back to the call/cc in increment-routine. display-routine runs, displays the global counter, and its call/cc creates a new jump, which it passes to the active-return jump in display-routine. This assigns the jump-target jump in the next iteration of increment-routine, and so on.

What ends up happening is that these two routines pass control back and forth, but neither one ever exits. The call/cc construct in this case is functionally equivalent to an inter-procedural Goto. Nobody in the real world ever uses code like this, but it's worth tracing it through so that you have a very solid understanding for what call/cc does.

Is call/cc a good thing to have in a language? I think so. I also think, however, that programmers shouldn't use it most of the time. It adds orthogonality, which is good, but it also needs to be well-abstracted. In the Lisp family of languages, macros can be used so that instead of this:

(define (catches-errors)
  (let ((result (call/cc (lambda (throw)
      (throwing-computation-1 throw)
      (throwing-computation-2 throw)
      ...))))
    (if (symbol? result)
      (begin
        ;; No error...
        ...)
      (begin
        ;; Error...
        ...))))

You could write this:

(define (catches-errors)
  (try
    (lambda (throw)
      (throwing-computation-1 throw)
      (throwing-computation-2 throw))
    (catch (lambda (error-result)
      ...))
    (otherwise (lambda (result)
      ...))))

Since you don't explicitly mention call/cc in your source code, you avoid making a lot of programmers nervous, and it makes it clear that nothing too sneaky is going on.

__FBWv64aQGpyhfcSQ0sRa55LjD9YP3zyCfmulQWOV0rs meta::blogpost('a-new-perspective-on-the-internet', <<'__zeOMLvsLISezCkxCxWdpvmXE7/Id3Ppo4yDHswdJRoI'); post:title = a-new-perspective-on-the-internet post:real-title = A New Perspective on the Internet post:status = published post:date = 2009-05-20 08:52:12 post:tags = compiler dsl javascript optimization web post:format = html

Before AJAX, a page was just a document that you downloaded from somewhere, and it could refer to other documents and downloadable files. People used JavaScript for simple client-side tasks like animating things or changing an image on mouse-over.

But obviously things have changed now. Since JavaScript is now able to communicate with the server, we have for the most part obviated the need for page reloads at all; it is generally more efficient to download a fragment of data than to download the entire HTML source of a new document. So many sites, such as Facebook and GMail, have adopted the "use-the-anchor-as-the-location" mechanism for navigation, and as a result just one original document must be downloaded to bootstrap the navigation process.

What's really going on here? Browsers provide a safe means of writing distributed applications that are automatically downloaded and then GC'd when no longer in use. This means a few things:

  1. Each time the application is used, it must be downloaded and installed again. Thus a small application footprint is desirable.
  2. It is more expensive to load a new application than it is to use existing code to load new pieces.
  3. If navigation is controlled by one client-side page, then there can be one entry-point URL that serves the entry-point HTML document.
  4. Space leaks on the client-side are a big problem.
  5. (Corollary of #1) JavaScript can be considered a very advanced form of "browser bytecode," but is probably best treated as a compilation target.

I would argue that the GWT has captured this paradigm brilliantly, even if the most appealing features of JavaScript were lost in the process. If the GWT used a more metaprogramming-friendly source language, we might see all of the advantages of coding in JavaScript in a perfect browser-compatible, optimally minified world.

So I'm starting a new project, this time an optimizing compiler library for generating JavaScript from C++ code. I picked C++ as a source language because it's actually quite nice to work with once you start using Boost, and it provides very complete operator-overloading, which is useful for building expression trees.

Once I get something more complete going I'll post a link.

__zeOMLvsLISezCkxCxWdpvmXE7/Id3Ppo4yDHswdJRoI meta::blogpost('a-one-line-bubble-sort', <<'__raKVFQswfZKDNAV70Z3vcBITu/gDU3nTLiE+J2Y/nHM'); post:title = a-one-line-bubble-sort post:real-title = A One-Line Bubble Sort post:status = published post:date = 2008-10-14 16:55:57 post:tags = dsl lisp theoretical universal-translator post:format = html

(orth-rule '(:x :y :(*> :rest) :(! (> ':x ':y))) '(:y :x :@rest))

If you use this rule with the universal translator, it will sort a list of numbers (or anything else comparable with the > operator) by using local swaps. The runtime is something awful, probably quintic or so, but one can't beat this code for brevity.

The idea behind the universal translator is that it has a list of rules that it repeatedly applies to its input, and it finishes when the input stops changing. Each rule takes the form 'pattern 'replacement, where the 'pattern, if it matches, is replaced by 'replacement. 'Pattern and 'replacement may contain keywords, such as :x or :y, to denote variables. So, for instance, the list '(3 + 3) would be matched by the patterns '(:a + :a), '(:a :b :a), '(:a :b 3) and '(3 :a 3), but not '(:a :a 3). The last wouldn't match because :a can take on only one value -- it can't be both 3 and +.

The other construct is a keyworded list, which takes the form :(...). This is a delegate, which can bind variables, cause the pattern to fail to match, and/or alter the remaining input. In the above example, we use two such delegates. The first, *>, grabs the rest of the list and binds it to :rest. The second delegate, !, causes the entire match to fail if (> ':x ':y) is not true -- that is, if :x <= :y. (I'll address the question of why we quote these in a moment.)

A very obvious question is why we bind the rest of the stuff in a list when all we're looking for is two elements to swap. That is, suppose we're sorting '(3 1 6 2 6 2 3) -- why do we bind :x to 3, :y to 1, and then take the trouble to bind :rest to '(6 2 6 2 3)? This has to do with the fact that the pattern must completely match the input. If we had said this: (orth-rule '(:x :y :(! (> ':x ':y))) ...), then the match would have failed for any list longer than two elements.

The other obvious question at this point is why we quote the arguments to >. This is because when the predicate '(> ':x ':y) is evaluated, the values for :x and :y are inserted literally into the s-expression before eval is called. So suppose :x and :y are lists, and we don't quote them. Then we would send an expression such as this to eval: '(> (4 1 2 3) (1 2 3 4)). We want to make sure that the values of :x and :y are treated as data, not code, so we need to quote them.

You've probably noticed the peculiar nomenclature of "orth-rule" by now. There are three different types of rules. The first, whose function name is make-rule, binds to a whole list. This would be the analogue of having a regular expression automatically surrounded by ^ and $. It will fail to bind if the whole list is not consumed by the pattern. The next, semi-rule, will bind to the whole list or any complete sublist. However, no partial lists will be considered. The one we use, orth-rule, will bind to any sublist or cdnr of a sublist. This is why it makes sense to use orth-rule with the sort.

So here's an example of why this rule works:

'(1 5 2 3 1) -> (:x = 1, :y = 5, :rest = '(2 3 1))
                fails because (> ':x ':y) is false.
Push 1, consider CDR:
  '(5 2 3 1) -> (:x = 5, :y = 2, :rest = '(3 1))
                succeeds; input becomes '(2 5 3 1).
  Push 2, consider CDR:
    '(5 3 1) -> (:x = 5, :y = 3, :rest = '(1))
                succeeds; input becomes '(3 5 1).
    Push 3, consider CDR:
      '(5 1) -> (:x = 5, :y = 1, :rest = '())
                succeeds; input becomes '(1 5).
      Push 1, consider CDR:
        '(5) fails.
      '(1 5)
    '(3 1 5)
  '(2 3 1 5)
'(1 2 3 1 5). Input changed, so repeat process.
'(1 2 3 1 5) -> (:x = 1, :y = 2, :rest = '(3 1 5)) fails.
Push 1, consider CDR:
  '(2 3 1 5) -> (:x = 2, :y = 3, :rest = '(1 5)) fails.
  Push 2, consider CDR:
    '(3 1 5) -> (:x = 3, :y = 1, :rest = '(5))
                succeeds; input becomes '(1 3 5).
    Push 1, consider CDR:
      '(3 5) fails.
      Push 3, consider CDR:
        '(5) fails.
      '(3 5)
    '(1 3 5)
  '(2 1 3 5)
'(1 2 1 3 5). Input changed, so repeat process.
'(1 2 1 3 5) fails.
Push 1, consider CDR:
  '(2 1 3 5) -> '(1 2 3 5).
  [steps elided because they all fail]
'(1 1 2 3 5). Input changed, so repeat process.
[steps elided because they all fail]
Return '(1 1 2 3 5), since it did not change.

Also interesting is an infix arithmetic evaluator with operator precedence and parenthesized subexpressions that is coded in about 14 lines -- see the universal translator project page for details.

__raKVFQswfZKDNAV70Z3vcBITu/gDU3nTLiE+J2Y/nHM meta::blogpost('about', <<'__bjj/taWZwLqixf0j7k65dIpyqtrEd4fpbZ+Y2OorIx4'); post:title = about post:real-title = About post:status = published post:date = 2008-09-08 17:20:04 post:tags = post:format = html

This blog is about my personal hobbies of Lisp programming (disclaimer: there may be other genres of programming as well), Linux management, and writing software for my wife Joyce.

__bjj/taWZwLqixf0j7k65dIpyqtrEd4fpbZ+Y2OorIx4 meta::blogpost('an-excellent-knot', <<'__LgdJENVqzdd4QcpYE/aCxHUmAortbqp6iOfI8xlijxo'); post:title = an-excellent-knot post:real-title = An Excellent Knot post:status = published post:date = 2008-10-12 19:48:18 post:tags = knots off-topic post:format = html

Slightly off-topic, I ran across a very useful knot a few weeks ago. Definitely worth looking into if you need to join two ropes securely and then untie the knot easily after a load.

__LgdJENVqzdd4QcpYE/aCxHUmAortbqp6iOfI8xlijxo meta::blogpost('because-a-missing-language-construct', <<'__7MT60ICNRZfJYbwQJ1Xm0FtZNE8qWBVH0xxD54VvYJ4'); post:title = because-a-missing-language-construct post:real-title = "Because": A Missing Language Construct post:status = published post:date = 2009-10-09 18:24:49 post:tags = programming-languages software-engineering post:format = html

Here are two common scenarios:

  1. You inherit a medium-sized codebase, and you want to get a feel for what's going on. "Why does this method call X?", for example. You hope that the author has left a comment, but more often than not they didn't, so you may have to spend five or ten minutes digging around trying to figure it out. When you multiply this over the number of methods that are likely to exist in such a codebase, that amounts to a lot of energy.
  2. You're debugging an application with unknown code, and you reach a point when you really don't know why method X is being called. You'd like to say, "Why am I here?", but the only thing you have is a backtrace into code you didn't write. In order to figure out what's going on, you must again rely on the good documentation practices of other programmers.

I propose a language feature that would solve both of these problems. If each function call took an optional (perhaps required) "because" clause, then both of these situations would be alleviated. So, for instance:

boolean initializeTheBigServerProcess () throws InitializationException {
  checkToMakeSureThatXIsTrue () because [X must be true to initialize the Foo process];
  if (conditionXIsTrue ()) initializeTheFooProcess () because [Condition X was true];
  if (fooProcessGotInitialized ()) initializeBar () because [Foo got initialized];
  // etc.
}

void initializeTheFooProcess () throws InitializationException {
  if (conditionXBecameFalse ()) throw new InitializationException () because [Condition X became false];
  assignTheFooField ();
  if (theFooField == null) throw new InitializationException () because [The Foo Field wasn't expected to be null];
}

boolean conditionXBecameFalse () {
  if (x != null) return false because [X is not null.];
  else return true because [X is null.];
}

And now look at the help that a good debugger could provide:

InitializationException caught because Condition X became false.
initializeTheFooProcess called because Condition X was true.
db$ suppose ! [Condition X became false.]
If [Condition X became false.] hadn't occurred, then execution
would have proceeded to:
  assignTheFooField ();
  if (theFooField == null) ...
db$ why [Condition X became false.]
[Condition X became false.] was activated by:
  if (conditionXBecameFalse ())
db$ why conditionXBecameFalse () == true
conditionXBecameFalse () returned true because X is null.
db$ why [X is null.]
[X is null.] was activated by the else branch of:
  if (x != null)
db$ why x == null
The most recent assignment of x to null was:
  x = null because [The cache didn't initialize properly.];
db$

Some of this could already be done with sufficiently introspective development tools. The thing that I find most interesting is how much more context you can pack into a line -- something as seemingly simple as "The cache didn't initialize properly" speaks volumes to the programmer about where to look for the problem, much more than tracking things down through decisionals. In a way, the because construct lets programmers concisely annotate the state of the program at any given point, which brings code just one bit closer to being easily readable.

__7MT60ICNRZfJYbwQJ1Xm0FtZNE8qWBVH0xxD54VvYJ4 meta::blogpost('cheloniidae-3', <<'__5F8ycek2an7XE2IIZUOExZiC+KnLbXaugFI7wTkJ/tw'); post:title = cheloniidae-3 post:real-title = Cheloniidae 3 post:status = published post:date = 2009-09-20 10:42:33 post:tags = post:format = html

After a long wait (and a lot of refactoring), Cheloniidae 3 is up! Check it out at the Cheloniidae Page.

__5F8ycek2an7XE2IIZUOExZiC+KnLbXaugFI7wTkJ/tw meta::blogpost('cheloniidae-3-1', <<'__aDnYwDSi2J7e1qh14L5SnTp7Izj4e8hs1/Mh4Mb1WPI'); post:title = cheloniidae-3-1 post:real-title = Cheloniidae 3.1 post:status = published post:date = 2009-12-01 23:31:59 post:tags = cheloniidae [ Cheloniidae 3.1 is out! New features include rendering solids, physically-accurate incidence angle adjustments, and, naturally, more examples. ] [ Cheloniidae Project ] __aDnYwDSi2J7e1qh14L5SnTp7Izj4e8hs1/Mh4Mb1WPI meta::blogpost('cheloniidae-in-literate-code', <<'__2W3wmr3Wb/zV7pupbjqbu/QzdqfdQ5l0RCzt9X4T2y4'); post:title = cheloniidae-in-literate-code post:real-title = Cheloniidae in Literate Code post:status = published post:date = 2010-01-11 20:59:14 post:tags = cheloniidae, literate post:format = html [ Cheloniidae is now available in literate format, which should be more approachable than the previous code-only distribution. You can download a PDF here that goes through the code linearly, introducing each piece of it at a conceptual level before diving into the code itself. As usual, it is implemented by a self-modifying Perl program that manages the unlit and PDF compilations. ] [ Cheloniidae Page ] __2W3wmr3Wb/zV7pupbjqbu/QzdqfdQ5l0RCzt9X4T2y4 meta::blogpost('cheloniidae-turtle-graphics', <<'__XGJgi3/oQkcRgIciU81J4t0WEGDCl7q+5cSMlyyV1SA'); post:title = cheloniidae-turtle-graphics post:real-title = Cheloniidae Turtle Graphics post:status = published post:date = 2009-05-25 10:35:35 post:tags = cheloniidae terrapin post:format = html

New name, same great turtles.

Cheloniidae Project

__XGJgi3/oQkcRgIciU81J4t0WEGDCl7q+5cSMlyyV1SA meta::blogpost('cheloniidae-updates', <<'__IQ+GFnnuxXUp63l6Za4w6jhPyB+nnTfSDOgNSD6JJgs'); post:title = cheloniidae-updates post:real-title = Cheloniidae Updates post:status = published post:date = 2009-06-10 10:23:23 post:tags = cheloniidae java terrapin post:format = html

A prerelease of version 3 is now available on the Cheloniidae project page. It's a significant refactoring of version 2.1 including a new turtle object hierarchy that allows definition of non-Euclidean turtle geometry, free-form directional rotation, a streamlined rendering interface, and parallel turtle commands.

Apropos of my earlier post, I decided to keep Cheloniidae written in Java for three reasons. First is that the Scala runtime library is 4MB or so and that's a lot to download, especially in an applet context. Second is that Java has proven to be quite a decent language for expressing these concepts. I get the sense that functional and object-oriented programming haven't quite found orthogonality yet; I was running into a lot of issues determining what was most appropriately expressed as a function vs. a class, and in the end I found it much simpler to stick with Java. Third, it's easy to write high-performance Java code, but not nearly as easy to write fast Scala code. Since performance is a big part of what makes Cheloniidae useful, I decided to use the more performant language.

Version 3.0-pre1 is a transitional release. About half of Cheloniidae has been converted, but there are still some major changes to be made. I would like to represent turtle commands as class instances, thus placing the focus onto these commands rather than operating on turtles directly. So, for instance:

for (int i = 0; i < 4; ++i)
  t.move (100).turn (90);

would become:

TurtleCommand square = move (100).turn (90).repeat (4);
t.run (square);

In order to be able to do this, we need a base class that defines the move(), turn(), etc. methods, and each of these methods needs to return a TurtleCommand data structure.

__IQ+GFnnuxXUp63l6Za4w6jhPyB+nnTfSDOgNSD6JJgs meta::blogpost('cognitive-algebra', <<'__DfG8jLfo/FPZqIULBo7eE6QtaNvK1pRAG18Ea5fkc1w'); post:title = cognitive-algebra post:real-title = Cognitive Algebra post:status = draft post:date = 2009-02-16 01:36:42 post:tags = post:format = html

Every moderately orthogonal programming language is based on the idea that you have an algebra of some kind. In math, algebra is arguably simpler than it is in computer science because most of the time you just have numbers to worry about; however, the concepts are the same. I would argue that the biggest distinguishing factor between languages, semantically, is the set of objects they allow you to manipulate with the algebra that they provide.

One obvious factor that influences this set is whether the language is static or dynamic. In C or Java, for instance, you cannot add fields to a struct at runtime; however in Ruby the equivalent is commonplace. Thus since Ruby's types and functions are first-class values, it can have a more complete algebra than C or Java.

All this being said, however, how much easier is Ruby to use for most things than Java? Well, you do have fewer lines of code. Is it more maintainable code, though? In my experience, it isn't. So here's my question: Given some task, which language features make it easy to write maintainable code?

Since this is a hard question, let's begin by asking it in reverse: Which problems lend themselves to easy-to-maintain, coherent solutions?

__DfG8jLfo/FPZqIULBo7eE6QtaNvK1pRAG18Ea5fkc1w meta::blogpost('coherent-typography', <<'__osg+4cpNiI+2fAlJv+8Nw2h8HbnG7chFhEBg9kamE98'); post:title = coherent-typography post:real-title = Coherent Typography post:status = published post:date = 2008-10-14 18:23:21 post:tags = coherence future-project graphics typography post:format = html

One of the interesting aspects of typographical design is consistency. Each character must have a lot in common with other characters for the font to be readable. For instance, if you took the lower-case A from Garamond and used Bitstream Vera Sans elsewhere, it would look terrible and be difficult to read.

Is it possible to automate the management of the coherence between characters in a typeface? Knuth tried it with MetaFONT, but I haven't seen much done with it. It seems like it would be at least theoretically possible to specify the significant aspects of a font such as serif/sans, the shape of the Q, g, and other highly variable characters, and then tweak using predefined parameters such as weight, serif size/style/angle, vertical/horizontal thickness, slant, etc.

I don't know much about typography, so I welcome any feedback from those with expertise and/or ideas. And if anyone gives me enough detail to do it, I'll write a typeface generator in Lisp and post it here under the GPL.

__osg+4cpNiI+2fAlJv+8Nw2h8HbnG7chFhEBg9kamE98 meta::blogpost('concept-binary-search-implementation', <<'__04irjZ8822s77yCprYmbNz789XoDu13YbOcHcqqQGsY'); post:title = concept-binary-search-implementation post:real-title = Concept Binary Search Implementation post:status = published post:date = 2009-08-24 18:51:41 post:tags = post:format = html

Here's what I've got so far:

#!/bin/bash

typeset -a keys
typeset -a values

function search-for-key () {
  # Usage: search-for-key target-key low-bound high-bound
  # Assumes that all keys are sorted, and if the key is not found then it echoes the index that the key will be in if it is inserted to preserve the sort.

  let "low      = $2"
  let "high     = $3"
  let "midpoint = (low + high) >> 1"

  while (( high - low > 1 )) && [[ "${keys[$midpoint]}" != "$1" ]]; do   
    if   [[ "${keys[$midpoint]}" < "$1" ]]; then let "low  = midpoint"
    else                                         let "high = midpoint"; fi
    let "midpoint = (low + high) >> 1"
  done

  if [[ "${keys[$midpoint]}" == "$1" ]]; then echo $midpoint
  elif [[ "${keys[$low]}" == "$1" ]];    then echo $low
  else                                        echo $high; fi
}

function move-keys-and-values () {
  local i
  for (( i = ${#keys[@]}; i >= $1; i-- )); do
    let "next = i + 1"
    if [[ "${keys[$i]}" ]]; then
      keys[next]="${keys[$i]}"
      values[next]="${values[$i]}"
    fi
  done
}

function associate () {
  local location=$(search-for-key "$1" 0 ${#keys[@]})
  [[ "${keys[$location]}" == "$1" ]] || move-keys-and-values $location
  keys[$location]="$1"
  values[$location]="$2"
}

function lookup () {
  local location=$(search-for-key "$1" 0 ${#keys[@]})
  echo "${values[$location]}"
}

I get 1000 associations in about 10 seconds, which isn't too bad. (Far better than the MD5 implementation.) Lookups are quite fast, at 1000 in just over 4 seconds. Most likely this will replace the old hashtable implementation.

Interestingly, there's also a way to use Perl and Bash together in the same file:

#!/bin/bash
...
perl -x $0 $@  # Pass all params into Perl
exit

#!/usr/bin/perl
# From here down is Perl code, and is
# invoked by the perl -x line above.
print "foo\n";

The -x option tells Perl to start executing the source file when it finds a line starting with #! and containing perl. (I can't take any credit for this idea. I found it while googling bash vs. perl.) Since Perl is nearly as ubiquitous as Bash, this might be a nice alternative route to take -- certainly it would be orders of magnitude faster than just plain Bash.

__04irjZ8822s77yCprYmbNz789XoDu13YbOcHcqqQGsY meta::blogpost('consing-lists-in-reverse', <<'__B4h5gIqbXGqhph4l+aSI7HsjxKsyFrM/fYSO2PJP4QE'); post:title = consing-lists-in-reverse post:real-title = Consing Lists in Reverse post:status = published post:date = 2009-02-25 22:28:46 post:tags = lisp programming-languages post:format = html

This means that (1 2 3 4 5) is consed together as (cons (cons (cons (cons (cons nil 1) 2) 3) 4) 5). But why?

Suppose every function is unary, so you use curried functions when n-arity is required. Further suppose that, somewhat arbitrarily, (cons nil x) is equivalent to x for all x. Then you have a beautiful evaluation rule for programs: To evaluate a cons cell, you simply invoke the evaluation of its first part on the evaluation of its second.

Just a hunch, but I think this could go somewhere.

__B4h5gIqbXGqhph4l+aSI7HsjxKsyFrM/fYSO2PJP4QE meta::blogpost('constant-time-associative-arrays-in-bash', <<'__bJylcT/rt0qXG2kTV+O/tEWwGRrKgMqJaU77/WyFOzc'); post:title = constant-time-associative-arrays-in-bash post:real-title = Constant-Time Associative Arrays in Bash post:status = published post:date = 2009-08-10 22:03:20 post:tags = algorithms bash efficient hashtables post:format = html

There are several articles online describing how to construct associative arrays in Bash. However, many of them rely on a linear search for retrieval, which reduces the average-case lookup time to O(n). In shell-world, this can get to be quite slow for large key/value sets.

Fortunately, there is a way to implement constant-time access. It's not brilliantly fast, but it scales quite well. The key is to use a manual hashing algorithm to convert string keys into numerical indices. Because Bash uses sparse arrays (try setting the 0th and then the 1,000,000,000th elements both to something and notice that Bash uses little memory), the space occupied by hash keys can be in the 63-bit range. (Bash signs its integers.)

So here is an example script that implements a hashtable with linear probing:

#!/bin/bash

typeset -a table_keys
typeset -a table_values

function index_of_key () {
  initial=$(($(echo $1 | md5sum | cut -c 18-32 | awk '{print "0x"$1}')))
  while [[ ${table_keys[$initial]} && ${table_keys[$initial]} != $1 ]]; do
    initial=$((initial + 1))
  done
  echo -n $initial
}

function associate () {
  index=$(index_of_key $1)
  table_keys[$index]=$1
  table_values[$index]=$2
  echo -n $2
}

function lookup () {
  index=$(index_of_key $1)
  echo -n ${table_values[$index]}
}

echo Associating foo with bar and bif with baz
associate foo bar && echo
associate bif baz && echo

echo -n Looking up foo:
lookup foo && echo

echo -n Looking up bif:
lookup bif && echo

echo -n Looking up bar:
lookup bar && echo

The interesting line is where we use MD5 hashing and grab the 60 least-significant bits. The invocation of cut pulls out the requisite digits, and then awk is used to prepend the 0x to trigger hexadecimal parsing.

All of this raises a question regarding how efficient is Bash's sparse array subscripting, of course. At some level I'm sure this is significant, but I'm also confident that Bash's internal handling of such things is far faster than any manual lookup from inside the script.

__bJylcT/rt0qXG2kTV+O/tEWwGRrKgMqJaU77/WyFOzc meta::blogpost('counting-bits', <<'__pdGtK3Ykvc+ucTKgWyH9xRnHRDbVaF8TDRlwoHWLKDs'); post:title = counting-bits post:real-title = Counting Bits post:status = published post:date = 2008-10-12 20:15:03 post:tags = bits c math post:format = html

A few years ago, I ran across an algorithm used to count the number of bits that were set in a 32-bit number. The algorithm looked something like this:

unsigned int bits_on (uint32_t x) {
  return (( x & 0x41041041) +
          ((x & 0x82082082) >> 1) +
          ((x & 0x04104104) >> 2) +
          ((x & 0x08208208) >> 3) +
          ((x & 0x10410410) >> 4) +
          ((x & 0x80280280) >> 5)) % 63;
}

Notice how few machine operations are used: There are six ands, five shifts, five additions, and one modulus. Furthermore, many can be done in parallel. Implemented in loop style, this would have taken over 96 machine operations, and they would most likely not be parallelizable.

Naturally, I didn't understand why the algorithm worked at the time. However, the principle is quite simple, relying only on modular arithmetic and retrieving the correct bits.

The only principle of modular arithmetic that applies here is that (a + b) % n = ((a % n) + (b % n)) % n. That is, when talking about integers modulo a quantity, we may defer the modulus operation until the end and the result will be the same. The algorithm is implicitly dealing with everything modulo 63, but the modulus is computed only once, saving time.

The bitmasks are the other important part of this algorithm. Here's what happens: Consider this binary quantity: 00100001 10001000 00101100 00110011. Each bit has a place-value, starting from the right with 1 and ending with ~2 billion on the left. However, if we take each place-value modulo 63, something interesting happens: From the left, we have 1, 2, 4, 8, 16, 32, 64 = 1, 128 = 2, 256 = 4, ... Notice that the cycle repeats. It continues this way for all of the positions.

Here is where it is possible to leverage some parallelism. Suppose we have the binary quantity 1. This, modulo 63, is 1. Now suppose we have the binary quantity 64 = 1000000. This modulo 63 is also 1. Adding them together yields 1000001, which, by the law of modular arithmetic stated above, must be 2. So we can count two bits by using only one and-operation. In fact, we can count as many bits as we would like this way, as long as each bit's place value is 1 modulo 63. So we construct a bitmask that looks like 01000001 00000100 00010000 01000001, which in hexadecimal is 0x41041041. That is the first term of the algorithm above.

To grab the other bits, we shift so that their place value becomes 1 modulo 63 instead of 2, 4, 8, 16, or 32. Then we treat them as modular quantities as well, adding them to the total. When we perform one final modulus operation, we end up with the number of bits that were set.

A logical question arises: Where does the number 63 come from? I chose 63 because it is one less than a power of two (this is necessary so that many bits' place values are 1 modulo the number), and it is large enough that any number of set bits between 0 and 32 may be represented. If it were larger, then more operations would be necessary to count the bits. If it were smaller, then the algorithm would return 0 for the quantity 0xffffffff, which would be inaccurate. (Actually, this could be special-cased to save two operations.)

__pdGtK3Ykvc+ucTKgWyH9xRnHRDbVaF8TDRlwoHWLKDs meta::blogpost('design-principles-of-obsidian', <<'__Du6JMDXKdFbivuKpQWv6cDpl9UldRuFUaprZlg9WwsA'); post:title = design-principles-of-obsidian post:real-title = Design Principles of Obsidian post:status = draft post:date = 2009-03-28 12:18:54 post:tags = post:format = html __Du6JMDXKdFbivuKpQWv6cDpl9UldRuFUaprZlg9WwsA meta::blogpost('duck-typing', <<'__ls6qbTN5/xX1D1o+UIlsbo6Wn5vHoE3CwStOoVtxbrc'); post:title = duck-typing post:real-title = Duck++ typing post:status = published post:date = 2009-06-01 08:11:28 post:tags = functional morphism scala post:format = html

Of Scala's many convenient features, one of my favorites is the notion that if you have some object of type A and you want to use it as if it were an object of type B, then as long as you provide an implicit conversion function you are free to do so.

So, for instance, if we define a function f mapping ints to Strings, then we can just use any integer as a string transparently.

One of the coolest uses for this is to add methods to classes. Consider, for instance, this code:

:: case class Foo (x: Double, y: Double) :.

If you later wanted to allow instances of Foo to be component-wise added, you could do this:

:: object ArithmeticExtension { case class FooArithmetic (foo: Foo) { def + (that: Foo) = Foo (foo.x + that.x, foo.y + that.y) } implicit def foo_to_foo_arithmetic (f: Foo) = FooArithmetic (f) } :.

The implicit definition allows Scala to automatically convert instances of Foo to FooArithmetic, so now you can say things like this:

:: val f = Foo (10, 20) f + Foo (40, 50) // => Foo (50, 70) :.

A very elegant solution to a classic problem.

__ls6qbTN5/xX1D1o+UIlsbo6Wn5vHoE3CwStOoVtxbrc meta::blogpost('examples-for-terrapin', <<'__hgQPLtpKNmk0tFDmi4oMdnygX0oelBXdclU+/mMQZa8'); post:title = examples-for-terrapin post:real-title = Examples for Terrapin post:status = published post:date = 2008-10-12 12:15:14 post:tags = graphics java terrapin post:format = html

This weekend I've been looking over the example files shipped with Terrapin (now Cheloniidae) and trying to think of more that I could include. Right now, I've got these:

  1. Block city - my first attempt to create a Menger sponge
  2. Circle city - a nice fork from block city
  3. Cube - a bunch of cubes, each rotated at a different angle
  4. Donut - a torus constructed from a series of circles
  5. Donut spiral - Two bent tori, each constructed from several layers of circles
  6. Grass - A circular patch of grass with trees
  7. Koch curve - a Koch snowflake (two-dimensional)
  8. Shading - an experiment involving varied line thickness to simulate lighting
  9. Shooting star - a simple two-dimensional drawing
  10. Sierpinski - a Menger sponge
  11. Spheres - two spheres (one partial and one complete) with grids
  12. Spiral - three spirals
  13. Star - a star spiraling into the distance
  14. Tree - a recursively bifurcating structure resembling a tree

Can anything else be done with a three-dimensional turtle?

__hgQPLtpKNmk0tFDmi4oMdnygX0oelBXdclU+/mMQZa8 meta::blogpost('executable-html', <<'__qrmFyUEP+XkQnIH1IZ9SiNqcOaJzHR/MByFGQmcuzeg'); post:title = executable-html post:real-title = Executable HTML post:status = published post:date = 2009-09-02 07:04:47 post:tags = html object-oriented perl post:format = html

Along the vein of self-modifying files, what about a web page that has state and provides a data: link to its modified representation? And by using this trick:

#!/usr/bin/perl
=header
<html>
  <head>
  ...
  </head>
  <body>
    <script>document.body.removeChild (document.body.firstChild)</script>
    ...
    <div style="display: none">
=cut
# Perl goes here
=footer
    </div>
  </body>
</html>

you can make the file executable on the command-line too. The most difficult aspect of this is unifying the interface between the command-line and the page; particularly, eval() fails to work for adding user-defined functions because Perl and JavaScript are two completely different languages.

__qrmFyUEP+XkQnIH1IZ9SiNqcOaJzHR/MByFGQmcuzeg meta::blogpost('expressive-coding', <<'__ZompX5n/GdGcO2e7eWQOCAH0jjRgvimfsAaIayP187A'); post:title = expressive-coding post:real-title = Expressive Coding post:status = published post:date = 2009-10-20 18:56:21 post:tags = documentation dsl functional programming-languages software-engineering post:format = html

A good programmer is careful to document code, but sometimes documentation is not the best way to convey the purpose of a program. Documentation is great when you need to explain the purpose of a module, some special cases about its behavior, etc. But writing documentation inside a complex function may alleviate some of the "refactor-pressure" that keeps a codebase well-organized.

As languages have gotten more flexible, it has become easier to write self-documenting code. By giving variables complete word names, using terse and elegant constructs, and using a functional style, you end up being able to write code like this:

process_a_record.and (log_it).until (no_more (incoming_records).exist)
print_total_count.when (the_user.wants_totals_to_be_printed)

var enqueue_each_record = add_to (the_debit_queue).when (it (is_a ('debit').or (is_a ('pending_debit'))).
                           otherwise (add_to (the_credit_queue))
enqueue_each_record.distribute_across (all_processed_records)

The supporting code isn't too bad either:

var not = function (f) {return function () {return ! f.apply (this, arguments)}}
Function.prototype.and = function (f) {
  var t = this
  return function () {return t.apply (this, arguments) && f.apply (this, arguments)}
}

Function.prototype.or = Function.prototype.otherwise = function (f) {
  var t = this
  return function () {return t.apply (this, arguments) || f.apply (this, arguments)}
}

Function.prototype.and_then = function (f) {
  var t = this
  return function () {t.apply (this, arguments); return f.apply (this, arguments)}
}

Function.prototype.until = function (condition) {
  var t = this
  return function () {while (! condition.apply (this, arguments)) t.apply (this, arguments)}
}

Function.prototype.when = function (f) {return f.and (this)}
Function.prototype.unless = function (f) {return this.when (not (f))}
Function.prototype.distribute_across = function (xs) {
  var result = []
  for (var i = 0; i < xs.length; ++i) result.push (this (xs[i]))
  return result
}

var it = function (x) {return x}
var is_a = function (type) {return function (record) {return record.type == type}}
var no_more = function (array) {
  return {exist: function () {return ! array.length}}
}

There's a subtle catch lurking here. It would make sense to use eta-reduction and define add_to this way:

var add_to = function (array) {return array.push}

But JavaScript functions aren't persistently bound to their object -- the binding is determined by the invocation context. Since later on array.push becomes t (see how it's used in the first listing and passed to the and combinator), it will be as if we invoked t (some_value), which results in this === window, which probably causes mysterious errors. To fix it, we need to do this:

Function.prototype.bind_to = function (x) {
  var t = this
  return function () {return t.apply (x, arguments)}
}

var add_to = function (array) {return array.push.bind_to (array)}

A nice advantage of writing code this way is that it's instantly understandable to any English speaker. The control flow is easily understood at an intuitive level, encapsulating the details inside constructs that are more straightforward.

Naturally, it does take longer to write English-style code, and not all code can be written like this. The main advantage comes not from being able to write it quickly, however, but from being able to debug it quickly. Since programmers spend a significant amount of time debugging, anything that makes the meaning of the code more transparent is probably a step in the right direction.

__ZompX5n/GdGcO2e7eWQOCAH0jjRgvimfsAaIayP187A meta::blogpost('from-java-to-scala', <<'__4imCev3AKbiZyesEWyV6WpXK/x8EPBG6v3zmicnQztI'); post:title = from-java-to-scala post:real-title = From Java to Scala post:status = published post:date = 2009-05-31 22:10:32 post:tags = cheloniidae java lisp logo scala post:format = html

Over the past few days I've been working on a port of Cheloniidae to Scala from Java. Particularly, I'm using a literate concoction of TeX with Scala source code interspersed, and during the unlit phase the TeX is commented out. Thus the source code can be compiled both into a regular .scala file and into a nicely-formatted PDF (the same system I used to put together the MathBio research project).

Scala poses a lot of interesting challenges. First, it's not quite obvious what the right way is to do something. I imagine this is because it has a first-class representation of both objects and functions, and functions are, for all practical purposes, also objects; thus, the overlap confuses things somewhat. One example of a difficult design choice was how to instruct the turtle to do things. At first I was considering using a string of functions that would be composed and then passed as a single parameter to the turtle object. Thus, for instance, given a turtle t, you would have this:

t << fd(100) << rt(90) << fd(100) << (repeat 3 (fd(100) << rt(120)))

Which is doable enough, except that you lose the ability to have dynamic dispatch parameterized by the type of the turtle. This is especially important in 3D, where you can have a turtle that uses various forms of polar coordinates (thus the meaning of rt would depend on the model being used).

So dynamic dispatch and functional programming don't seem to coincide yet. (Except, perhaps, in CLOS.) Instead, the implementation I have now uses methods of an abstraction called a turtle driver, so you would have this:

val t = new BasicTurtle ()

val d = new SphericalTurtleDriver (t)

d fd 100 rt 90

d rotate_about Vector(0,0,1) by 90

This is perfect for driving a turtle directly, but now it is clumsy to specify a turtle's actions as being potential:

val f = (t: SphericalTurtleDriver) => t fd 100 rt 90 // ...

In Java, of course, there would really be only one right way to do any of this, and that would simply be to stick to the object-only paradigm and define a series of turtle driver classes extending some basic turtle class. And as Java's object-orientedness is about as complete as Scala's (minus default method implementations in traits), it seems just as doable.

So minus all the beating about the bush, I guess the point I'm driving at is the question of what exactly is gained by adding functional formalisms to the object-oriented paradigm? The benefit is quite clear from the standpoint of linguistic purity, but in everyday usage it seems like there is a lot of room for confusion and ambiguity once you have both to work with.

__4imCev3AKbiZyesEWyV6WpXK/x8EPBG6v3zmicnQztI meta::blogpost('improving-stateful-shell-scripts', <<'__n3jnAPi0OEUC05pTnmpPwFsygJfQteXLsrI0mvPn39I'); post:title = improving-stateful-shell-scripts post:real-title = Improving Stateful Shell Scripts post:status = published post:date = 2009-08-17 19:46:12 post:tags = bash metaprogramming object-oriented stateful-shell-script post:format = html

As they stand, stateful shell scripts (see previous post) are cool but have some problems. One of them, hinted at in the comments on the post, is that there isn't a very good way to have data fields. The only thing you can do is associate methods, which, for a stateful script, seems lame. Another problem is that the script has a bunch of redundancy in serialized form. This is a problem for two reasons. First, it's redundancy, which, unless intended, is evil; and second, if there's a syntax error in one of the custom-defined methods, the script will fail to function.

Tackling the first problem is not too difficult. It's OK if everything's a method as long as there is a way to make it appear that some things are values. This is a simple matter given that the association interface is very amenable to metaprogramming design patterns. One implementation might look like this:

:: ./object associate set-value <This data can be used in other methods too:

:: ./object associate edit-with-custom-editor < $filename ${editor:-${VISUAL:-vim}} $filename && associate $1 < $filename rm $filename EOF :.

Solving the redundancy problem is harder, though. It involves using eval to do the evaluation instead of inserting the content directly into the shell-script. Thus there must be two levels of functions -- one to manage the hashtable and set up the associate function, and the other that gets evaled when the script is loaded. Naturally, in order to remain flexible, the hard-coded associate and lookup functions (and their dependencies) will still be editable, so they will have both hard definitions and hashtable entries.

There is some opportunity for bulletproofing stuff here (which needs to happen). As mentioned before, a bad edit renders the script useless. But if the functions get evaled first, and the result of eval is available to the script, then edits that would result in errors can be rejected. We can do even better, too. Editing a function's association inside the script (which you're not supposed to do) to render it invalid could also result in a warning at runtime instead of an error and a disabled script.

The only difficult part is figuring out what the API looks like to separate hard-coded functions from live-evaled ones. Ideally, it's something that can be extended too, so that the user can specify alternate serialized forms. Any ideas?

__n3jnAPi0OEUC05pTnmpPwFsygJfQteXLsrI0mvPn39I meta::blogpost('in-defense-of-the-lisp-2-paradigm', <<'__hBaUSYShHez42PTu6VknEn0KGme15EddhcwqCVdV9Go'); post:title = in-defense-of-the-lisp-2-paradigm post:real-title = In Defense of the Lisp 2 Paradigm post:status = published post:date = 2009-12-13 13:51:05 post:tags = lisp, curried-functions, functional, scheme [ Normally I don't like the distinction made between functions and values in Lisp 2's such as Common Lisp. The Lisp 1 / Scheme way seems much simpler and more orthogonal. For example: ] :: ; The Lisp 1 way: (define (compose f g) (lambda (x) (f (g x)))) (compose car cdr) :. :: ; The Lisp 2 way: (defun compose (f g) ; defun vs. define isn't important for this discussion, ; but included here for correctness. (lambda (x) (funcall f (funcall g x)))) (compose #'car #'cdr) :. [ However, there's an interesting thought process that Lisp 2 seems to suggest. The idea is that a name can have more than one value depending on how you use it, a language feature of Perl that has some interesting ramifications. For example, consider these Perl statements: ] :: $x = 5; # The scalar x $x[0] = 5; # The array x $x{0} = 5; # The hash x x(0); # The function x :. [ The first three values share the scalar sigil, but by context the Perl interpreter can infer whether the scalar, array, or hash value of x is intended. The fourth line reflects the fact that Perl reflects the Lisp-2 spirit of differentiating between functions and values. (Ultimately this distinction is required to support calling a function without using parentheses. Ruby makes a similar distinction for that reason, whereas Python and Scheme do not.) ] [ So why is this at all important? Because I think this code should work: ] :: ; In an imaginary (symbol-value) and (symbol-function) binding Lisp: (let ((x (+ 3))) (print x) ; => 3 (print (x 4)) ; => 7 (print #'x)) ; => (lambda (&rest args) (apply #'+ (cons 3 args))) :. [ This seems like a natural extension of the Lisp 2 idea: Any variadic function application should result in a value that can be called either as a function, completing the application, or as a value, returning any partial result. This opens the door for a couple of nice properties of the language: ] e[ + All function applications are unary. So (f x y) is interpreted as ((f x) y). + A variadic function returns a continuation which takes remaining parameters in the function-slot of its return value. ]e [ So really we'd have an implementation of + that looked something like this: ] :: ; In an imaginary unary-function Lisp with anonymous typeglobs: (defun + x ; Unary functions, so only one arg at a time. (symbol-values (#'symbol-function (lambda y (+ (system::+ x y)))) (#'symbol-value x))) :. [ There are a couple of things going on here that we can't do in Lisp 2 as it stands. One is that the notion of a whole typeglob (to borrow from Perl's vernacular) isn't a first-class value. So if we say (let ((x (f))) ...), then f is assigning straight into the value slot of x. This makes sense because of continuations in general; for example, if we say this: ] :: (* (f) (f)) :. [ we are clearly referring to the value-side of the return value of (f) and not the function-side. So anonymous typeglobs would require some thought to implement. (The fact that Lisp 2 went ahead with the separation without addressing this is, in my opinion, a major weakness.) Naturally there is also a problem of function propagation. That is, suppose we invoke (f) as before: ] :: (let ((g (* (f) (f)))) ...) :. [ and instead of expecting g to contain a value, we use it as a function: ] :: ; In our imaginary Lisp with full-symbol binding (i.e. not only ; symbol-value or symbol-function, but both at once): (let ... (g 5)) :. [ This is a reasonable thing to want to do, especially if the * function, when applied to functions, acts as a combinator: ] :: ; In our imaginary type-selecting Lisp: (defmethod * ((function1 function) (function2 function)) (lambda (x) (* (funcall function1 x) (funcall function2 x)))) :. [ Now wait a minute. If functions and values are somehow distinct, then shouldn't it be the case that we have a distinction between strings and numbers as well? Sounds reasonable enough; you certainly can't multiply two strings (well, in Lisp anyway), nor can you take a subrange of a number. So now we have three slots: ] :: ; In our imaginary anonymous-typeglob Lisp: (symbol-values (#'symbol-function ...) (#'symbol-string ...) (#'symbol-number ...)) :. [ Getting to the point, it turns out that for any two types without a subtype relation (i.e. x is a subtype of y or y is a subtype of x), you could legitimately want two slots for a function's parameters and return values, one for each type. ] [ This sounds like a familiar construct. What we're really doing is implicitly distributing values across generic (in the Common Lisp sense) functions. So you end up with functions acting simultaneously on a bunch of values at once, somewhat nondeterministically since you end up throwing most of the values away. The only really practical way to do this in a dynamically-typed language is to use lazy evaluation. ] [ A language like this is certainly some distance away from what we think of as Lisp 2. However, I don't think it would be a bad idea to consider adopting the notion of anonymous typeglobs into the language, as it might very well clear up some of the problems that the separation of namespaces introduced in the first place. ] __hBaUSYShHez42PTu6VknEn0KGme15EddhcwqCVdV9Go meta::blogpost('instant-ocaml', <<'__Rvtm9GwXDrfbKi/gc4/Y4hcxpVJHrMRSSoEgcAlaX9A'); post:title = instant-ocaml post:real-title = Instant OCaml post:status = published post:date = 2009-10-27 19:49:11 post:tags = build dsl functional literate ocaml self-modifying tex post:format = html

Apropos of being lazy about TeX, I figured I'd continue the trend and find an easy way to set up an OCaml project. Using the universally-applicable (I'm kidding) self-modifying Perl script, it was surprisingly easy to get a nice development environment set up.

So, here's a quick tutorial for creating an OCaml project. First, download the template (OCaml Project), drop it into a directory in your $PATH, and chmod 555 or similar. Now you can easily create a new project:

$ literate-ocaml-project clone my-project
File my-project cloned successfully.
$

As the first order of business I recommend installing the VIM highlighters:

$ literate-ocaml-project install-vim-highlighters
$ literate-ocaml-project update-vimrc
$

Now let's try building the PDF that explains the system.

$ ./my-project make
[TeX output elided]
$

If everything works properly, you should get a nicely-formatted PDF containing a preliminary explanation of what the script does. If it complains about evince not existing, then try this:

$ ./my-project pdf-reader = /path/to/pdf/reader
$

and run ./my-project make again.

The PDF contains two things. One is an attribute called data::document, which you can edit by saying either ./my-project edit data::document, or more concisely ./my-document e. This is just some TeX to introduce your project.

By the way, if the TeX dialect looks weird (which it probably does), I've got a separate post about that: TeX To-Go which describes the conversion process. The only incompatible difference since then is that you need the begin directive to signal the beginning of the document, unlike before when it was inferred.

The second part is a list of sections that make up your code and the rest of the PDF. By default there is only one section, called 00-introduction. Let's edit it:

$ ./my-project edit section::00-introduction
$

You should get something like this:

- Introduction to Literate Coding
  Literate coding allows you to write \TeX{} primarily, but insert ...

  :: ocaml
  open Printf
  let _ = Printf.printf "Hello world!\n"
  :.

With any luck it will also be highlighted nicely so that most of the document renders as comments and the code is colored as OCaml. This is how your project will behave, too. Sections beginning with :: ocaml will be treated as OCaml code, and the rest of it will be commented out. To see this in action:

$ ./my-project unlit
(* ...
  :: ocaml *)
open Printf
let _ = Printf.printf "Hello world!\n"
(*   :. ...
... *)
$

You won't have to run this command manually; your code will be converted automatically during the build process.

Building the system is actually very easy. Your project comes with two build profiles, run and debug, which you can use like this:

$ ./my-project build run
( output elided )
$ ./my-project build debug
( output elided )
$

A build profile is just a Perl function that invokes whatever commands you find appropriate. To edit the run profile:

$ ./my-project edit profile::run
$

Now notice that the run and debug profiles are the same, but the output from running them was different. This is because built into the literate conversion system is a rule for conditional compilation. In section 00-introduction, there's a code section that looks like this:

:: ocaml if (run)
let _ = Printf.printf "You seem to be using the /run/ profile.\n"
:.

You can specify more than one build profile there; just say if (profile1 | profile2 | ... | profileN). This will result in code that is included if any of the mentioned profiles are being built. Otherwise the code will remain commented out.

To get an optimizing build profile, do this:

$ ./my-project create profile::opt
my %options = @_;
print `ocamlopt $options{'in'} -o $options{'out'}`, "\n";
print "Output filename is $options{'out'}\n";
`$options{'out'}`;
^D
$

Now you can run it:

$ ./my-project build opt

Output filename is /tmp/znD3fVltYIIGAkEezvP6rRWXiEHokZcnV9JbTZg4SL0/opt
Hello world!
$

You can create more sections by doing this (you'll want to type Ctrl-D to indicate that you don't want to initialize the section's contents):

$ ./my-project create section::01-something-else
^D
$ ./my-project edit section::01-something-else
$

Sections are compiled in alphabetical order, hence the numbering.

Naturally, there isn't much going on here that can't be extended to other languages. Most of the relevant logic is in the unlit function, which you can edit this way:

$ ./my-project edit function::unlit
$

The commenting logic can be changed to support any other language without too much work. Then to get the syntax highlighting to work, you'll want to edit data::meta-associations to put the right file extension on your section edits:

$ ./my-project edit data::meta-associations
$

...
^section:: .ocamltex    <- Change this to .cpptex, for instance
...

Finally, you'll want to edit the VIM highlighter and its installation logic. I recommend creating a new VIM highlighter like this:

$ ./my-project cp data::ocamltex-vim-highlighter data::cpptex-vim-highlighter
$ ./my-project edit data::cpptex-vim-highlighter  # Hack away on this
$

And editing the install-vim-highlighters function:

$ ./my-project edit function::install-vim-highlighters
$

...
file::write("$home/.vim/syntax/ocamltex.vim", retrieve("data::ocamltex-vim-highlighter"));
# Insert:
file::write("$home/.vim/syntax/cpptex.vim", retrieve("data::cpptex-vim-highlighter"));

Now you can run ./my-project install-vim-highlighters and your new highlighter will be there. Just add a filetype line to your vimrc and you'll be in business.

Once you're done with your modified version, you can set some sensible defaults and then run this:

$ ./my-project lock
$

That removes write access. Then, put it into some directory in your $PATH (maybe with a more descriptive name, like literate-cpp-project), and you can use it as a clone-base for other projects:

$ literate-cpp-project clone my-cpp-project
File my-cpp-project cloned successfully.
$
__Rvtm9GwXDrfbKi/gc4/Y4hcxpVJHrMRSSoEgcAlaX9A meta::blogpost('ja-prerelease-4', <<'__ihgJmgQ/USpGo1KuKbmYbSxfHaxHpxo42hYoXDnt56Y'); post:title = ja-prerelease-4 post:real-title = Ja! prerelease 4 post:status = published post:date = 2008-12-21 12:21:06 post:tags = dsl functional ja javascript object-oriented post:format = html

I'm excited about how well Ja! is coming along. As of this prerelease, I've just begun writing the DOM interaction layer, which is what makes Ja! different from a lot of other JavaScript libraries out there. Here's why.

Many JavaScript libraries rely on the prototype-based inheritance mechanism provided with JavaScript. So, for instance, if you want to create a class, you can say this:

function Point () {}
Point.prototype.x = Point.prototype.y = 0;

And then you can instantiate your class: var p = new Point ();. This approach is very flexible and compact until you want to subclass a DOM element. Normally, subclassing a class involves copying its prototype and then adding stuff to the copy. The problem with using prototypes to subclass DOM elements is that there's more to, say, a submit button, than its class prototype. Since it has native tie-ins, it has to be created by document.createElement, and at that point the prototype has already been set.

This is why I chose to use instance-based object-orientation, which, instead of building a prototype for each class, involves instantiating the class and adding the requisite fields and methods to that instance as it is constructed. So, for instance, the base class for a DIV element looks like this:

var div_base_class = function () {return document.createElement ("div")};

(Note that it is defined differently in Ja!.) Then, to define a child class, you create a hash of things to add to the base class. So, for instance, you might define a child class this way:

var extension = {set_text: function (text) {this.innerHTML = text}};
var new_class = oo.create_class (div_base_class, extension);

Now, instances of the subclass may be created by invoking the new_class function. Upon invocation, the base class is instantiated and the extension class's items are assigned to the instance.

OK, so if that's all there is to it, then how is method overloading achieved? The object-oriented layer provides several wrappers that allow you to specify what happens to extension functions whose names conflict with base class methods. If you're familiar with the CLOS, then some of these wrappers will make sense. They are oo.before, oo.after, oo.after_blind, oo.around, oo.replace, and oo.ctor. Here's what they do (where A.f is the base method and B.f is the extension method):

  • oo.before: B.f is invoked before A.f, and the result of A.f is returned.
  • oo.after: B.f is invoked with the result of A.f as the first parameter and other parameters afterwards. Its result is returned.
  • oo.after_blind: B.f is invoked normally after A.f is, and its result is returned.
  • oo.around: B.f is invoked with A.f as its first parameter and other parameters afterwards. Its result is returned. Note that B.f has the prerogative to invoke A.f any number of times, including not invoking it.
  • oo.replace: B.f replaces A.f. You might think this would happen without a wrapper, as in the above examples of inheritance. Actually, it doesn't -- it's an error to implicitly replace a method. (It's also an error to use oo.replace when no base method is defined.)
  • oo.ctor: This should really be used only for initialize. The idea is that it works whether the base class defines initialize or not -- in other words, like a constructor should. It doesn't rely on you to explicitly invoke the base class constructor; the base constructor is invoked automatically before the child constructor.
  • oo.transform: This is an unusual feature. The function provided to oo.transform is invoked with the base method as its parameter, and the result becomes the new child method of the class. So, for instance, if the base method of a class is defined like this: f: function (x) {return this.x + x} and you want to pluralize it, you can do this: f: oo.transform (fa.pluralize), and the fa.pluralize function will be invoked on the base method (function (x) {return this.x + x}), which will result in a pluralized base function. This new function replaces the old one in the child class.

Constructor parameters are passed by name instead of positionally to allow the same set of parameters to be passed to each constructor down the class hierarchy. Obviously, this would not be as easily-achieved using positional parameters. In practice, this is achieved with a hash. Thus, a common occurrence is to see a class instantiated this way:

var my_instance = my_class ({arg1: val1, arg2: val2, ...});

Next up for the object-oriented layer is to add some RTTI or other reflective capability. This should ease creating dependencies among child classes (the context for this will make more sense once I've defined the interface for it).

__ihgJmgQ/USpGo1KuKbmYbSxfHaxHpxo42hYoXDnt56Y meta::blogpost('ja-works-with-ie-now', <<'__nBLgX3ACdWyeSHZR6rpL+aZ8vxzChLgBHoc7ijRENqY'); post:title = ja-works-with-ie-now post:real-title = Ja! works with IE now post:status = published post:date = 2009-01-01 15:27:35 post:tags = post:format = html

After finding ies4linux, I was able to do some cross-browser diagnostics and find out why Ja! didn't run under IE. As of prerelease 7, all Ja! releases and prereleases will be fully cross-browser and tested in IE, Opera, Safari, Konqueror, Firefox, and Mozilla.

Ja! still has a long way to go, but it can already be used for some cool stuff. Once I get a demo page running, I'll write another post with a link.

__nBLgX3ACdWyeSHZR6rpL+aZ8vxzChLgBHoc7ijRENqY meta::blogpost('java-collections', <<'__hu+YWhqUjn4NX7yUnUA5HWGx0u3uSmtd8Ik49DvM+HM'); post:title = java-collections post:real-title = Java Collections post:status = draft post:date = 2009-06-09 15:32:55 post:tags = post:format = html __hu+YWhqUjn4NX7yUnUA5HWGx0u3uSmtd8Ik49DvM+HM meta::blogpost('javascript-library-prerelease', <<'__ekuBc/7E6kwV0pHu9hBk+X1T1cOYuzxme0GjNEPCYVg'); post:title = javascript-library-prerelease post:real-title = JavaScript Library Prerelease post:status = published post:date = 2008-12-13 00:23:04 post:tags = dsl javascript metaprogramming object-oriented post:format = html

When I was working for Adventure Student Travel, I tried to rewrite their data management system in Ruby on Rails. To save myself some effort (since it's a big system), I wrote a really nifty client-side AJAX library to do bundled data requests and provide a declarative UI development framework.

Naturally, the license for their software would be encumbered, so rather than negotiate redistribution of that software I've decided to start over and write a new library, called "Ja!". So far, I've got a test page, a functional algebra framework, an object-oriented stack, and a very versatile unit testing and live assertion system.

For example, to write a unit test:

using ("com.spencertipping.ja.test", "com.spencertipping.ja.require", function (test, require) {
  var test_data = some_function (some_value);  // Visible only in this using clause.
  require.equal (3 + 4, 7, "3+4 != 7");
  test.add ("Test for something", function () {
    return test.assert.is_true (true, "true != true") ||
           test.assert.equivalent ([1, 2, 3], [1, 2].concat ([3]), "[1, 2, 3] != [1, 2].concat (3)");
  });
});

// ...

using ("com.spencertipping.ja.test", function (test) {
  window.test_results = test.run ();
});

At this point, window.test_results is a hash whose keys are the unit test names and whose values are the results of the tests. So, for instance, we should get this: {"Test for something": "pass"}. However, if the test.assert.equivalent ([1, 2, 3], [1, 2].concat (3)) assertion had failed, then we would have gotten something like this: {"Test for something": {assertion: "Structural equivalence", x: [1, 2, 3], y: [1, 2, 3], message: "[1, 2, 3] != [1, 2].concat (3)"}}. Firebug provides easy browsing of this structure in the DOM explorer tab.

One thing that makes Ja! distinctive is that it allows direct subclassing of HTML elements (though this hasn't quite been finished yet). Another thing is that it was designed for efficiency and no particularly spectacular effects. There are no animations, images, or anything else -- just styled DIV elements and text nodes. Also, there are no prerequisite libraries. Everything is cross-browser-compliant and built from plain JavaScript.

The official project website is http://projects.spencertipping.com/ja. Enjoy, and let me know if you have any ideas to make it better.

__ekuBc/7E6kwV0pHu9hBk+X1T1cOYuzxme0GjNEPCYVg meta::blogpost('jquery-style-java-accessors', <<'__liN7fNvM5jSraiIYMyt/g43t6ETd31nCCZ1uYYpnsws'); post:title = jquery-style-java-accessors post:real-title = jQuery-style Java Accessors post:status = published post:date = 2009-06-08 08:40:49 post:tags = java jquery post:format = html

The dominant paradigm in Java is to code accessors like this:

public int getFoo () {return foo;}
public void setFoo (int _foo) {foo = _foo;}

It is beautifully straightforward, moderately expressive, and very easy to use. But it has some unintuitive aspects, too; consider this expression: foo.getX () + foo.getY () * foo.getZ (). It doesn't read like normal English, as most English sentences don't have five verb clauses. It also is verbose, as the word "get" is used three times.

jQuery solved this problem very well by defining accessors like this (here is its equivalent representation in Java):

public int foo () {return foo;}
public MyClass foo (int _foo) {foo = _foo; return this;}

There are three significant advantages to doing things this way. One is that your expressions read like actual sentences; foo.x () + foo.y () * foo.z () is easily-understood, for instance. Second is that you can chain setters to form a description of your object: foo.x (10).y (20).z (30), and third is that only one identifier is used (foo), and not three (foo, getFoo, and setFoo).

__liN7fNvM5jSraiIYMyt/g43t6ETd31nCCZ1uYYpnsws meta::blogpost('learning-from-web-apps', <<'__p4Mc0si0hoJySh4V5j6Lu+XWSNrg397WQmVjtY4Bpbk'); post:title = learning-from-web-apps post:real-title = Learning from Web Apps post:status = published post:date = 2009-10-14 16:07:12 post:tags = algorithms ui web post:format = html

When web applications first came out they were clunky, slow, ugly, and not very useful. Yahoo! Maps was a great example of this -- before Google Maps came out and blew us all away with their AJAX awesomeness, you couldn't drag the map around; instead, you had to click on the little arrows on the edges and wait for the whole page to reload. So if you had the option of using a desktop application instead, you used that to save yourself the trouble of dealing with the Web.

But things have changed now. Ever since Google Maps, GMail, Facebook, YouTube, and a host of other similarly innovative systems, we're using Web apps all over the place. And for the most part, they're as useful and nice to work with as their equivalent desktop apps.

It turns out, though, that web apps have gotten so good that we take some of their best qualities for granted. I found this out when going back to using Evolution, a local mail client, instead of GMail. After downloading my sum total of 21,000 GMail messages, I typed in a search. And waited...

And waited...

And waited...

About five minutes later it came up with the results. GMail would have done the same thing in under a second, and I'm fairly sure they don't have a dedicated supercomputer for my account yet. So why is Evolution so slow?

Well, it's written in C for one thing. GCC's C compiler is the second highest-performance language implementation on the benchmarks game, but in order to achieve that high performance you have to be willing to put out a lot of effort. Writing a clever indexing strategy in Python, or even Java (GMail probably has components in both), is a much lower time investment than doing the same thing in C.

But even the highest-end desktop mail clients suffer from performance issues despite having no lack of coding effort put into them. Outlook used to require constant archiving to keep it running. Microsoft could have come up with a smart way to handle large volumes of mail, but instead wrote the AutoArchiver tool to periodically move old messages into a separate PST file to work around the problem.

This widespread pathological inefficiency, no doubt, came from reasoning processes like this:

"What happens when the user has too many messages to fit on the screen?"

"Well, we've got a scrollbar, but they can also put them into separate folders."

"OK, so if your folder gets unmanageable then you can just split it."

"Right."

Good enough, right? On the Web, though, the scrollbar strategy doesn't work. Sure the user has one, but if that's the only line of defense against volume then the user is going to end up downloading megabytes of HTML. Since a better solution was needed, web developers came up with pagination (ironically, a strategy that had been mostly lost since the old green-screen days), and it's so intuitive and fast that we use it all over the place.

Another reason efficiency wasn't, and still isn't, an issue for many desktop applications is that they have only one user. Web applications are powered by some central server that has to handle thousands of users at once, so efficiency is key. If you're writing a desktop application, you might be able to use the "find what's slow and rewrite it in assembler" strategy, but this doesn't buy you much for a web application. Scaling from 20,000 users to 80,000 users isn't going to be solved by the offchance that you can beat the C compiler's optimization stage for some bits of the application, because significant performance gains can be made only by using better algorithms or massively parallelizing (which often end up being the same problem).

So interestingly, the humble beginnings of web applications have ended up creating a need for more advanced algorithms, storage strategies, client-server interaction paradigms, and user-interface design. Languages like Haskell, Erlang, and Lisp are now becoming the bleeding edge in high-speed server technology, and rapid client-side development frameworks like jQuery are making it easier than ever to create beautiful, high-performance applications that just work.

I don't think this signals the end of desktop applications, but I do believe that for desktop applications to succeed, they'll need to keep up with their web-based competitors. Fortunately for desktop software authors, this is likely to bring a welcome change of working smarter, not harder.

__p4Mc0si0hoJySh4V5j6Lu+XWSNrg397WQmVjtY4Bpbk meta::blogpost('making-things-final', <<'__wLVNWm7tgrS/ihT1q0/zxdk+fOq1FiciuMQjiasaU/I'); post:title = making-things-final post:real-title = Making Things Final post:status = published post:date = 2009-10-11 13:55:00 post:tags = best-practices functional java post:format = html

Or constant, if you prefer. The idea is to make everything as invariant as possible in a program, so that three things are achieved:

  1. You don't accidentally reassign something
  2. You don't intentionally reassign something when whoever is reading your code isn't expecting it
  3. The compiler can, if it is designed to do so, optimize better because more things will be provably invariant

In most programs, over half of the variables don't need to be variable at all. In fact, the Cheloniidae source includes almost no variables; nearly everything is a constant. For example, the code to run a point-cloud render:

final Graphics2D c          = context ();
final int        pointColor = (~ getBackground ().getRGB ()) & 0xffffff;
c.setColor (getBackground ());
c.fillRect (0, 0, getWidth (), getHeight ());

for (int i = 0; ! shouldCancel && i < intermediatePointCloud.length; ++i)
  if (intermediatePointCloud[i] != null) {
    final Vector tp = transformPoint (intermediatePointCloud[i]);
    if (tp.z > 0.0) {
      final Vector pp = projectPoint (tp);
      if (pp.x >= 0.0 && pp.x < getWidth () && pp.y >= 0.0 && pp.y < getHeight ())
        offscreen.setRGB ((int) pp.x, (int) pp.y, pointColor);
    }
  }

repaint ();

Here the only variable is i. The variables inside the for loop can be marked final because their scope ends with the closing-brace -- so each time around they are recreated.

This practice also works well with objects. Instead of having private instance variables, you can make them public final. This way, you know that once you have an object its immediate state is immutable. It also makes you do the right thing and provide a constructor that fully initializes the object's state, instead of using setters after the fact. (Well, OK, I'm not sure this is the right thing, but I've found it to be very convenient for my own projects.)

__wLVNWm7tgrS/ihT1q0/zxdk+fOq1FiciuMQjiasaU/I meta::blogpost('minifying-javascript', <<'__YWW3rWYCk6pGpDMo+1SbKrITXJ2b14RNyV6RE/sb+yI'); post:title = minifying-javascript post:real-title = Minifying JavaScript post:status = draft post:date = 2009-05-03 11:24:45 post:tags = dsl javascript post:format = html

JavaScript minification is an interesting problem on multiple levels. One one hand, syntactic minification has been well-studied; however semantic minification is a very open-ended problem because the dynamic nature of the language makes static analysis very difficult (and, in fact, not possible in the general case). But let's suppose that every byte counts and we want to squeeze as much out of JavaScript as we can and still preserve the structural attributes of the code. For now, let's assume that eval is off-limits and, to make our lives easier, let's also assume that we can look at any given identifier and know whether it's safe to change its name.

First, here are some transformations that are functionally isomorphic:

x.y                  <=>   x["y"]

if (x) return y;
else   return z;     <=>   return (x) ? y : z;

var x = y;
var z = a;           <=>   var x = y, z = a;

if (x) return x();
else   return false; <=>   return (!! x) && x ();

These are all syntactic transformations because they are semantically equivalent. However, these constructs depend on the

__YWW3rWYCk6pGpDMo+1SbKrITXJ2b14RNyV6RE/sb+yI meta::blogpost('models-of-computation-graphics-analogy', <<'__4+N/WzPgjhw4CjFTDO8oa8tI8h+KYbsk0rz+TDjtJu0'); post:title = models-of-computation-graphics-analogy post:real-title = Models of Computation : Graphics Analogy post:status = published post:date = 2008-10-20 11:00:10 post:tags = abstract open-ended terrapin theoretical turtle-graphics post:format = html

I was just glancing back at Terrapin (now Cheloniidae), which prompted me to consider how difficult it was to draw some types of shapes with a turtle. For instance, drawing the letters J A V A with Terrapin is not a trivial task, especially if the J is curved. On the other hand, making something like a spiral is very easy.

What is the difference between things that can be drawn easily with a turtle and things that can't? One factor is how much the previous state has to do with the next operation. Shapes that are logically contiguous, such as spirals, are ideally suited to the preservation of state. Shapes with many irregularities are not ideally suited, as each irregularity must be rendered twice (once in reverse to undo the state changes).

So if a model of computation is made from turtle graphics, then it could be represented as this: I = En o ... o E3 o E2 o E1 o T0, where each Ei is an elementary transformation of the turtle, T0 is the turtle's initial state, and I is the final image (expressed here as the final state of the turtle). If the turtle were to need to make a tangential drawing and then return to its former location, the path would look like this: I = En o ... o Ek-2-1 o Ek-1-1 o Ek-1 o Ek o Ek-1 o Ek-2 o ... o E1. Note that for any Ei, Ei-1 o Ei = Id(Ei) -- that is, while the turtle returns to its former location and heading, its state is still different because it may have drawn something on the canvas. Each Ei has a side-effect that must be considered, unlike in mathematics.

Generic vector graphics, on the other hand, can be represented by sets. That is, an image constructed from vector elements can be written as I = I0 U E1 U E2 U E3 U ... U En, where I0 is the initial state of the canvas and each Ei is an elementary vector element. There is no full ordering on the elements, so the image may be constructed in parallel.

Given that there are some scenarios that favor each model, it makes sense to logically combine the models so that any vector element can be generated by a series of turtle elements. This would make sense, for instance, if two spirals needed to be constructed:

Spiral(n, T0) = (n > 0) ? (Spiral (n - 1) o Right (n) o Forward (n)) : T0
I = I0 U Spiral (100, Forward (100) o T0) U Spiral (100, T0)

Because of the reuse of state, a simple recursive definition suffices to build two independently-placed spirals.

This is excellent for basic vector graphics, but what about situations where the two-dimensional representation of something has an underlying form? For instance, suppose a perspective representation of a three-dimensional scene is going to be drawn. The underlying three-dimensional scene has only right angles, so it has structure. That scene gets compiled down to a visually coherent but otherwise unstructured two-dimensional form. So in a sense the three-dimensional form serves as a model for the two-dimensional form.

In such a situation, there are two approaches that can be taken. Since perspective rendering involves projecting each line segment onto the plane, each elementary three-dimensional operation could simply be converted by a function; that is, for a three-dimensional image I = En o ... o E1, we could apply the projection function P to make I = P(En) o P(En-1) o ... o P(E1) o T0. The other approach is a whole-image conversion; that is, we could let I = P(En o En-1 o ... o E1 o T0).

I cheated a little bit with the first solution. If P maps three-dimensional turtle operations to two-dimensional ones, then there is no preservation of any three-dimensional turtle attributes across composition. Thus information is lost, and the model is not complete. So the only viable solution is the whole-image conversion.

This reflects a phenomenon that is well-known in software circles: When constructing a model, make sure that the model's state is finalized before reducing it to a form that loses information. Then, perform a complete conversion and do not modify the generated form directly.

Here is my big question: There are parallels between the two forms of graphics construction and the ways we program computers. Specifically, turtle graphics represents an imperative process with nondestructive assignment (the "nondestructive" part is important), and unioned vector graphics represents a functional paradigm. Are there any ways that we construct or encode graphics that don't have a simple analog in computer science, and if so, can anything be done with those?

__4+N/WzPgjhw4CjFTDO8oa8tI8h+KYbsk0rz+TDjtJu0 meta::blogpost('monads-for-form-management', <<'__1OnrBHLeRfT6xirQOkVQY61A7/RXqSdws1gAcyTIgKw'); post:title = monads-for-form-management post:real-title = Monads for Form Management post:status = published post:date = 2008-10-19 21:06:36 post:tags = follow-up gwt java monads post:format = html

Form management often involves a lot of information redundancy, especially when you consider the schema as the significant piece of information. For instance, here's a GWT application with two text fields and a validator:

// Client-side
public class Person {
  private String name;
  private int    age;
  // ...
}
public class PersonForm extends Composite {
  private TextField nameBox           = new TextField ();
  private Label     nameBoxValidation = new Label ();
  private TextField ageBox            = new TextField ();
  private Label     ageBoxValidation  = new Label ();
  private Grid      layout            = new Grid ();

  // Validation is performed on the server-side.
  public void validate () {
    PersonServiceAsync s = PersonServiceHelper.create ();
    s.validate (nameBox.getText (), ageBox.getText (), new AsyncCallback () {
      public void onSuccess (ValidationResults r) {
        if (r.nameInvalid ()) nameBoxValidation.setText (r.nameMessage ());
        if (r.ageInvalid ())  ageBoxValidation.setText  (r.ageMessage ());
      }
      // ...
    });
  }
  // ...
}

This is only the client side. The server side validation function would have stages of validation that looked like this:

public class PersonServiceImpl implements PersonService {
  public ValidationResults validate (String name, String age) {
    ValidationResults result = new ValidationResults ();  // Defaults to everything being valid.
    if (name == null) {
      result.nameProblem ("Name cannot be null.");
    } else {
      if (name.equals (""))       result.nameProblem ("Name cannot be empty.");
      if (name.indexOf (' ') < 0) result.nameProblem ("Name must be a full name.");
      // ...
    }
    // Age validation...
  }
}

A lot of work could be saved if the code were refactored. One such refactoring involves building monads to encapsulate form values. There are two levels of abstraction. First, by taking each value of a form and making it into a monad, the validation steps can be abstracted into bind-functions. Form values can then contain their own validation status. Second, an entire form can be a monad. This is useful for managing a bunch of fields; it may be that in order for the form to do anything at all, each field must be valid. In this case, the form's binder would ensure that all of the fields are valid or produce an invalid form object. Here is an example of how this might be done:

public class ValidatedField implements Monad {
  private T data;
  private String validationMessage = null;

  public ValidatedField (T data, String validationMessage) {
    this.data              = data;
    this.validationMessage = validationMessage;
  }

  public T       get ()                  {return data;}
  public String  getValidationMessage () {return validationMessage;}
  public boolean valid ()                {return validationMessage == null;}

  public Monad bind (BindFunction f) {
    if (valid ()) return f.apply (data);
    else          return this;
  }
}
public interface Validator extends BindFunction {}
public class NullValidator implements Validator {
  public Monad apply (T data) {
    if (data == null) return new ValidatedField (data, "Cannot be null.");
    else              return new ValidatedField (data, null);
  }
}
public class FullNameValidator implements Validator {
  public Monad apply (String data) {
    if (data.indexOf (' ') < 0) return new ValidatedField (data, "Must be a full name.");
    else                        return new ValidatedField (data, null);
  }
}

// To validate the name:
nameResult = nameField.bind (new NullValidator ()).bind (new FullNameValidator ());

This last part could be reduced to a composition:

nameResult = nameField.bind (new Composition (
    new NullValidator (), new FullNameValidator ()));

That composition could be reused across many different fields. By extending the validation system, it is possible to add security checks, database conflict checks, etc., without changing any of the design. I'll have to think about whether this actually saves any work when it's all put together (especially considering that most of this could be done just as well with exceptions), but it's a different way of doing form validation and if nothing else it should make web coding a little more interesting.

__1OnrBHLeRfT6xirQOkVQY61A7/RXqSdws1gAcyTIgKw meta::blogpost('monads-in-java', <<'__2q3N1Sn3zIMKo0FwY8oFOCnm/HTVg83ctj6AYDnSDZk'); post:title = monads-in-java post:real-title = Monads in Java post:status = published post:date = 2008-10-19 19:26:56 post:tags = functional gwt java monads post:format = html

Haskell's monads are a beautiful thing, and I would like to use a similar concept to develop Java-GWT applications. To do this, I defined a simple monad library for Java and some basic test monads to get started.

It might seem like this would require some clunky code, but Java's generics make halfway type-safe monads surprisingly simple. Here are the core components:

public interface Monad {
  public Monad bind (BindFunction f);
}
public interface BindFunction {
  public Monad apply (T instance);
}

And here is an example of the Maybe monad defined using these:

public class Maybe implements Monad {
  private boolean hasValue = false;
  private T       instance = null;
  // Constructor, etc. elided
  public Monad bind (BindFunction f) {
    if (hasValue) return f.apply (instance);
    else          return this;
  }
  public T get (T defaultValue) {
    if (hasValue) return instance;
    else          return defaultValue;
  }
}

There's only one significant problem. Notice the typing on the Monad interface. In Haskell, the Monad typeclass includes this contract:

  >>= :: (Monad m) => m a -> (a -> m b) -> m b

The current Java definition, however, would render into Haskell as something like this:

  >>= :: (Monad m, Monad n) => m a -> (a -> n b) -> n b

The problem is that the type of the monad is lost, minus Java's RTTI. So a lot of explicit casting has to be done, or the monad must simply be treated as opaque. For instance, when working with numbers like this:

Maybe m       = new Maybe (10);
Maybe mPlus10 = (Maybe) m.bind (new BindFunction () {/*...*/});

The (Maybe<Integer>) typecast is necessary. However, as long as typecasts are made locally, it shouldn't cause too many problems.

Once I get a little bit more code in, I'll post a link to the monad implementation and a composition class for bind functions.

__2q3N1Sn3zIMKo0FwY8oFOCnm/HTVg83ctj6AYDnSDZk meta::blogpost('new-projects', <<'__uDwmcO2yk6e13CPjmGpBD+IRrJms6xUQxviTOvFbBrI'); post:title = new-projects post:real-title = New Projects post:status = published post:date = 2010-05-09 17:37 post:tags = projects [ I've been working on some new stuff lately, most of it on my Github profile. In particular, I've been really diving into JavaScript and trying to push the limits. Here are the new projects: ] i[ + Divergence JavaScript library: functional JavaScript library + JavaScript in Ten Minutes: a quick but thorough guide to serious functional JavaScript programming. This is a work in progress, but would probably make a good read for anyone interested in writing some heavy client-side code or getting into something like node.js. + Gnarly: a very slow Lisp derivative written in self-modifying Perl. Gnarly takes the concept of Lisp macros to a new level by making them properly first-class, but the downside to this is that programs are nearly impossible to optimize. ]i [ As usual, feedback is welcome. ] [ Update: There are now even more projects! Check it out: ] i[ + Divergence Rebase: Operator overloading and syntactic macros for JavaScript + Divergence Guide: A user guide for Divergence core and Rebase + Divergence Debug module: Expression-level debugging for JavaScript + Cheloniidae Live: A port of Cheloniidae to JavaScript/Canvas using Divergence and Rebase (source available here) ]i __uDwmcO2yk6e13CPjmGpBD+IRrJms6xUQxviTOvFbBrI meta::blogpost('ocaml-isnt-bad', <<'__ABmYIrQW3MXw8pgcQYLK3moJyvlAYTFFErffkj/YsFY'); post:title = ocaml-isnt-bad post:real-title = OCaml isn't bad post:status = published post:date = 2008-11-06 21:04:40 post:tags = post:format = html

When I first looked at OCaml, one thing that drove me away from it was the awful ;; syntax. Why would anyone put that into a language? I'm still not sure what the motivation is, but I've since seen some excellent OCaml code written by Jon D. Harrop demonstrating that you almost never need to use ;; even in medium-sized programs.

So why learn OCaml? Well, for one thing it's quite fast. It won't beat out C, but it's competitive with Java for a lot of applications, and quite a bit more expressive. It also has a lot of useful bindings. OpenGL, regular expressions, and popular databases are all provided as compiled OCaml modules.

I don't know enough of the language to comment on it extensively, but I'm learning more by solving the Facebook challenge problems in it, and as I find stuff I'll write more blog posts.

So far, the major drawbacks I've found are:

  1. No call/cc. (SML/NJ provides this)
  2. Non-SMP garbage collection. Though Jane Street is working on a multicore GC.
  3. No S-expression syntax.

Also worth mentioning is CamlP4, a mechanism by which metaprogramming can be performed. It sounds almost as comprehensive as Common Lisp macros, though perhaps not as straightforward.

Update: Nemerle, a CLR language, also has explicit metaprogrammability. Worth a look if you are doing .NET functional programming.

__ABmYIrQW3MXw8pgcQYLK3moJyvlAYTFFErffkj/YsFY meta::blogpost('on-lisp', <<'__TrfXXASG709tpayC7dsLrsyxvS61UuV71S0aZhBTJHM'); post:title = on-lisp post:real-title = On Lisp post:status = published post:date = 2009-11-28 22:06:08 post:tags = lisp, metaprogramming, performance [ A few days ago I downloaded Paul Graham's excellent book On Lisp to get back into Lisp as a potential language for high-performance numerical applications. This happened after seeing some surprising examples on the Programming Language Shootout (http://shootout.alioth.debian.org) where some SBCL programs outperformed OCaml ones. There are two remarkable things about this: ] i[ + OCaml is statically typed, whereas Lisp is dynamically typed (though there's a caveat; more below). + OCaml is an offline compiler that produces a machine-code image, whereas SBCL uses incremental compilation. ]i [ Particularly of interest to me is the second point. OCaml has lots of optimization opportunities that include interprocedural analysis, type-specific inlined functions, lots of strength-reduction, etc. It should be a clear win over a dynamic, halfway-interpreted language like Lisp (the Stalin Scheme compiler, for instance, ends up winning out over both using static compilation). ] [ The caveat to Lisp's dynamic typing is that in order to get reasonable performance, you have to use type declarations for your functions. The downside to this is twofold. First, SBCL's type inference isn't very good, so you often have to annotate local variables as well as parameters and return values. Second, there are no type parameters, so once you insert declarations your method types are fixed with no covariance (a downside if you're used to OCaml's approach). ] [ OK, so why mention On Lisp and then digress to talk about Lisp vs. OCaml performance? Because all of the shortcomings of Lisp's elementary type-inferencing, and thus performance as well, can be overcome by a good set of macros that implement a type solver. Paul Graham doesn't go quite this far in his book, but the framework is well-established for writing sublanguages that exhibit non-Lisp-like properties. The key to it all is, naturally, macros. ] [ Before reading Graham's book, I had mostly dismissed macros as a surface-level solution to handle language-aesthetic problems (well, after I had let the original enlightenment fade by learning languages other than Lisp). This is a real shame, because while macros can give you little bits of functionality like with-open-file and what-not, they can also make the following legal Lisp code: ] :: (curried-form let f x = x + y where y = 5 in let g x = x + 1 in f o g) :. [ To do this, you need to write the curried-form macro, which transforms the code in some non-trivial ways (probably facilitated by the universal translator). A good pattern matcher makes this sort of thing somewhat simpler. But the code powering that transformation can use type inference and ultimately produce something like this: ] :: (let* ((y (the fixnum 5)) (f (lambda (x) (declare (type fixnum x)) (the fixnum (+ x y)))) (g (lambda (x) (declare (type fixnum x)) (the fixnum (1+ x))))) (lambda (x) (declare (type fixnum x)) (the fixnum (funcall f (the fixnum (funcall g x)))))) :. [ It's a little unfortunate to lose the elegance of the original implementation, but it hardly matters (except for debugging) since the compiler is the only recipient of the generated code. This property also lets us optimize that code further by static analysis and constant propagation: ] :: (lambda (x) (declare (type fixnum x) (the integer (+ 6 x)))) :. [ So with that in mind, I think I'm going to have to start writing up some macros to capture my favorite idioms from other languages. Who knows -- there might even be a nice speed-boost there as well. ] __TrfXXASG709tpayC7dsLrsyxvS61UuV71S0aZhBTJHM meta::blogpost('orthogonal-object-oriented-programming', <<'__8JQ7nwBGpnROiCrRk2f8kwqiKJms+BCnmUrTozuSk0c'); post:title = orthogonal-object-oriented-programming post:real-title = Orthogonal Object-Oriented Programming post:status = published post:date = 2009-06-21 13:30:52 post:tags = cheloniidae javascript object-oriented orthogonality typeclass post:format = html

Ever since coding up Cheloniidae in Java rather than Scala, I've been wondering about OOP and where functional methodologies fit in. Certainly functional programming is a nice extension to object-oriented; if we're going to talk about objects and methods, then it would be nice to be able to talk about functions too.

And it turns out the OOP systems, if done right, give you a very complete introspection that functional non-OO systems are unlikely to provide. For instance, it's possible, with minimal alteration, to change the JavaScript typeclass system that I defined earlier to define classes as instances of the metaclass Typeclass that are aware of whether or not they have unit tests associated with them. With just a little more work, it's also possible to tell them to automatically wrap their methods to perform code coverage analysis. And this all happens inside the system; there is no systematic transformation of source code required.

JavaScript happens to have the constructs necessary to pull this sort of thing off gracefully. What happens if we distill those constructs down to the bare minimum, though? Would we get niceties such as indirected method invocation?

A philosophical question: Is it the case that a language should provide just one binding construct? So, for instance, since you can (setf (symbol-value 'some-symbol) some-value) in Lisp, does that obviate the need for a lambda construct? If so, then should a language provide a mutable binding object and require that functions are declared point-free?

__8JQ7nwBGpnROiCrRk2f8kwqiKJms+BCnmUrTozuSk0c meta::blogpost('polymorphism-and-functional-programming', <<'__a9G/Lj2O0FfNbD011EkBU4nyR8I+kxrsRnZVQtQ8hvU'); post:title = polymorphism-and-functional-programming post:real-title = Polymorphism and Functional Programming post:status = published post:date = 2009-12-24 12:00:45 post:tags = lisp, functional, object-oriented [ One of the problems that arises when functions are addressed as first-class values is that of polymorphism. Some languages, like OCaml, simply fail to implement overloaded polymorphic functions (though some degree of polymorphism can be achieved using type parameters). Others, like Haskell, have a construct built into the language to address this very problem. ] [ Fortunately, there is an encoding for overloadable polymorphic functions in a unary pure-functional language. The only things that need to be changed are these: ] e[ + Values need to be callable. I'll get to what this means in a moment. + Generic functions (in the Common Lisp sense) must map to placeholders. ]e [ So what is a callable value? It's ultimately a way to encapsulate state inside of a function, thus providing a form of object-orientation. So, for example, to add 3 and 5 you would say this (in Lisp syntax): ] :: (3 + 5) :. [ The number 3 is actually a function that looks something like this: ] :: ; In our imaginary unary Lisp-1: (lambda operator (cond ((equal operator +) (lambda x (system::integer-+ 3 x))) ((equal operator -) (lambda x (system::integer-- 3 x))) ... ; Allow new operators to be defined by calling them back (t (operator 3)))) :. [ Then the symbol + is simply bound to itself, just like t in Common Lisp. ] [ There are two interesting things that happen when you use this model. First, you have left-associative infix syntax for free, though no precedence is considered. Second, you have the ability to write new functions based on the old ones, and do so within whichever abstraction you happen to be using. So, for instance, I can write this fully generic average function: ] :: ; In our imaginary unary Lisp-1, where functions are all curried: (define (average x y) ((x + y) / 2)) (3 average 5) ; => ((average 3) 5) because of the (t) condition above ; => ((3 + 5) / 2) ; => (8 / 2) ; => 4 :. [ The nice thing about this operator is that it works across types as long as they provide the abstraction (which is what you normally get for free with object-oriented programming). So you end up with a dynamically-typed version of Haskell typeclasses, but without any explicit language support. ] __a9G/Lj2O0FfNbD011EkBU4nyR8I+kxrsRnZVQtQ8hvU meta::blogpost('purity-vs-practicality', <<'__t/j4WAPxESg+JUIAZvmDhDD5z/MNmHgQgPcewiC/iSY'); post:title = purity-vs-practicality post:real-title = Purity vs. Practicality post:status = published post:date = 2009-05-22 20:36:43 post:tags = lambda-calculus language-theory mathematics paradigms post:format = html

A few posts ago I described the last programming language I would ever need. In theory I'm sure it was just that; for expressing computable notions it is very close to being mathematically minimal and fully orthogonal. However as a language for everyday use it leaves a lot to be desired.

So in general, why is it that elegant, mathematical languages tend to be less well-suited to getting software jobs done while less elegant, less orthogonal languages are superior?

Two thoughts. One: Mathematical languages don't reflect what's going on in the hardware, and two: Programs, for the most part, aren't about computing things.

Mathematical languages such as the SK combinator calculus, the lambda calculus, or even the Turing machine don't really have any basis in the hardware we use. The C programming language provides a much better abstraction of what the hardware does than any of the more formal calculi that are commonly used.

Perhaps a more interesting point is that programming tasks aren't about computing things anymore. It used to be that computers did math; we had humans to move stuff around while computers' relative ability was crunching numbers quickly. Anymore, storage is cheap and we have networks to move data around at very high speeds. The situation we find ourselves in is that computers have crept into business, marketing, and other fields that don't do math; they keep records and move data around.

Well, it certainly doesn't take a Turing-complete system to push bits around, nor does it even matter how well such a system expresses computability of arbitrary functions. Most of what these systems are doing is format-conversion, which largely involves copying data and inserting or deleting surrounding content. (For which Perl is an excellent choice.)

It seems to me that there are two completely different needs in software. First, we need to be able to express algorithms very well given easily-accessible data. Second, we need to be able to transform poorly-accessible data into algorithm-friendly data and back into human-readable form.

Viewed differently, back when computers were used for math people converted the data to and from machine-readable form; thus programs just did the numbers and computational models made sense. But now the programs are doing all the work; not only do they do the math, but they're doing more and more of the conversion for us. Computing paradigms haven't changed much since the lambda calculus and the Turing machine were introduced, but now it's probably time for a new way of thinking.

__t/j4WAPxESg+JUIAZvmDhDD5z/MNmHgQgPcewiC/iSY meta::blogpost('re-thinking-javascript-oop', <<'__6laUu42NFe08l/xMAGi2VjPbnX8LWQEY3WUcBtT+98w'); post:title = re-thinking-javascript-oop post:real-title = Re-Thinking JavaScript OOP post:status = published post:date = 2009-01-07 22:41:09 post:tags = post:format = html

Since I'm defining my own object-oriented framework in Ja!, I've been spending a lot of time thinking about how OOP would work in an ideal world: What are some things that it would be great to be able to do, and how can they be done?

Haskell's typeclasses have been a great inspiration here. I've just finished a standalone implementation of dynamic typeclasses, which are similar to Haskell's typeclasses but can be applied and removed to individual objects at runtime. So, for instance, you have some object O that has methods {a, b, c}, and you apply a typeclass T that provides methods {d, e}; afterwards, O has methods {a, b, c, d, e}. You can later remove T or add other typeclasses. If you do this when O is constructed, then you have the equivalent of a class.

Here is the JavaScript code I've got so far (later I'll add a class facility, as I've mentioned in the comments):

// JavaScript typeclass implementation
// Created by Spencer Tipping, licensed under the terms of the MIT source code license.

// Overview
//
// A typeclass is a collection of methods and attributes that apply to an object of a given type. So, for instance, if we have some object X, it is unclear
// whether a given operation is supported because we have no information about its type. However, given that X is an integer, the expression "X + 3" becomes
// meaningful. Asserting that an object belongs to a given type makes available methods and attributes reflecting that assertion. Since JavaScript has no
// variable-level type information (i.e. it is dynamically typed), these assertions apply to individual objects and bypass the prototype system.

   var tc = {};

// The Attachable typeclass
//
// In the spirit of reflection, I'm defining a typeclass that represents the operations that can be performed on typeclasses. First, typeclasses can be
// installed and removed from objects -- this entails doing some typeclass-specific stuff if the typeclass requires it and then automatically adding or removing
// methods. Next, typeclasses can be checked for compatibility. This is important because some typeclasses have prerequisites; one example of a real-world
// typeclass with prerequisites is a bijection to the integers; this requires that the type be ordinal.
//
// One rule of typeclasses is that methods and properties specified statically cannot be replaced; that is, if you have an object that is joining a typeclass
// and the object and typeclass both define some value, then an error will be produced indicating that the typeclass causes a collision. The purpose of a
// typeclass is to /extend/ an object's functionality, but never to change it. As such, there is no method overloading and no value clobbering.
//
// That said, we need to build up to the point where checking makes sense. Each typeclass belongs to the 'typeclass' typeclass. This will make more sense in
// code than in English.

   tc.bind = function (f, t) {
     return function () {return f.apply (t, arguments);};
   };

   tc.attachable = {
     members: {
       attach: function (obj) {
         // Naively assume that we're not causing problems. The /this/ reference will be bound to the object directly, not to one of the objects here.
         for (var k in this.members)
           if (this.members[k].constructor === Function) obj[k] = tc.bind (this.members[k], obj);
       },

       detach: function (obj) {
         // Assume that the members did not overwrite anything. Later on we will implement checking.
         for (var k in this.members) delete obj[k];
       }
     }
   };

   // The attachable typeclass is itself attachable. This is the only bootstrapped component; everything else is legitimately within the framework.
   tc.bind (tc.attachable.members.attach, tc.attachable) (tc.attachable);

// The AddableWithHooks typeclass
//
// This typeclass allows hooks to be set when it is attached or detached from an object. The hooks have the option of throwing an error or triggering other
// actions, but their return values are discarded.

   tc.addable_with_hooks = {
     members: {
       add: function () {
         for (var i = 0, l = arguments.length; i < l; ++i) {
           for (var j = 0, lh = this.add_hooks.length; j < lh; ++j)
             this.add_hooks[j].apply (this, [arguments[i]]);
           this.attach (arguments[i]);
         }
       },

       remove: function () {
         for (var i = 0, l = arguments.length; i < l; ++i) {
           for (var j = 0, lh = this.remove_hooks.length; j < lh; ++j)
             this.remove_hooks[j].apply (this, [arguments[i]]);
           this.detach (arguments[i]);
         }
       }
     }
   };

   tc.attachable.attach (tc.addable_with_hooks);
   tc.addable_with_hooks.attach (tc.addable_with_hooks);

   // The arrays are initialized by this constructor.
   tc.addable_with_hooks.add_hooks = [function (obj) {
     obj.add_hooks = obj.add_hooks || [];
     obj.remove_hooks = obj.add_hooks || [];
   }];

   tc.addable_with_hooks.add (tc.attachable, tc.addable_with_hooks);

// Introspection
//
// A typeclass needs to be able to determine (1) whether it has already been installed on an object, and (2) whether it collides with an object. These
// operations are called "introspection."

   tc.is_introspective = {
     members: {
       collides_with: function (obj) {
         for (var k in this.members) if (obj[k] !== undefined) return true;
         return false;
       },

       implemented_on: function (obj) {
         // Note that if another equivalent typeclass provides these methods or members, that's OK. All we care about is whether the members
         // exist. Realistically, we have no good way of determining whether the typeclass has actually been installed without installing some form of explicit
         // RTTI because functions are opaquely bound and values may have been altered.
         for (var k in this.members) if (obj[k] === undefined) return false;
         return true;
       }
     }
   };

   // Something of a kludge here because we need to keep updating all of the typeclass parts. Later on, all of these elements will be unified into a proper
   // Typeclass type.
   tc.attachable.add (tc.is_introspective);
   tc.addable_with_hooks.add (tc.is_introspective);
   tc.is_introspective.add (tc.attachable, tc.addable_with_hooks, tc.is_introspective);

// Hooks to provide useful behavior
//
// Some examples of useful behavior are collision-detection, construction and destruction, and prerequisite inclusion or failure. Collision detection will
// determine whether there are any shadowing members being added by a typeclass and will raise an error to prevent overloading. This is important because a
// typeclass is never supposed to replace anything.
//
// Constructors, so to speak, and destructors, are just add_hooks and remove_hooks that are reverse-bound; that is, /this/ is the object and the typeclass is
// passed in as the parameter. How are constructor arguments passed? Later on, in the section about generator functions, I'll define the conventions used to
// store per-object constructor data. The short answer is that each object receives a hash when it is created, and this hash is stored for the object's
// lifetime. This is all handled in a standardized way, so accessing constructor arguments involves saying something like "this.constructor_args". Naturally,
// these parameters are passed by-name and not by-position.
//
// There are two ways prerequisites can be handled. Suppose typeclass B requires that an object have the methods specified by typeclass A. There can be an
// add_hook on typeclass B that adds A to the object first if necessary, which is quite a fine way to handle the situation. However, suppose the typeclass (here
// my usage diverges from Haskell's idea of a typeclass) is actually the implementation for something such as, for instance, ordering of a set, and a different
// typeclass is used depending on the type of the object. In this case, it is not necessarily obvious which typeclass to add to the object to satisfy the
// prerequisite, so the best choice may simply be to raise an error.
//
// The bottom line is that in general, you need to be aware of the prerequisites of a typeclass before using it and you should be prepared to manually extend
// the object beforehand using a separate typeclass.

   tc.detect_collisions = function (obj) {
     for (var k in this.members)
       if (obj[k] !== undefined) throw {error:     "tc.detect_collisions: Colliding attribute: " + k,
                                        obj:       obj,
                                        typeclass: this};
   };

   tc.requires = function () {
     var external_args = arguments;

     return function (obj) {
       // Takes any number of typeclasses and ensures that each one exists.
       for (var i = 0, l = external_args.length; i < l; ++i)
         if (! external_args[i].implemented_on (obj)) throw {error:     "tc.requires: Object did not implement required typeclass.",
                                                             object:    obj,
                                                             typeclass: external_args[i]};
     };
   };

   tc.brings = function () {
     var external_args = arguments;

     return function (obj) {
       for (var i = 0, l = external_args.length; i < l; ++i)
         if (! external_args[i].implemented_on (obj)) external_args[i].add (obj);
     };
   };

   tc.constructor = tc.destructor = function (f) {
     // Wraps f so that it can be used as an add_hook or remove_hook but it behaves as a constructor or destructor.
     return function (obj) {
       f.apply (obj, [this]);
     };
   };

// The Typeclass typeclass
//
// Finally we can combine all of this stuff to produce the Typeclass typeclass. This typeclass is not particularly different from other typeclasses, but it does
// provide some nice features such as integrated requisition processing, constructor and destructor support, and collision detection.

   tc.typeclass = {
     members: {
       brings:          function () {this.add_hooks.push (tc.brings.apply (this, arguments));},
       requires:        function () {this.add_hooks.push (tc.requires.apply (this, arguments));},
       add_constructor: function (f) {this.add_hooks.push (tc.constructor (f));},
       add_destructor:  function (f) {this.remove_hooks.push (tc.destructor (f));},
       add_member:      function (name, value) {this.members[name] = value},

       remove_member:   function (name) {
         var member = this.members[name];
         delete this.members[name];
         return member;
       },

       create:          function (obj) {
         // A convenient way to create an instance of a typeclass. The object is optional; if not provided, then a regular old Object will be used.
         if (! obj) obj = new Object ();
         this.add (obj);
         return obj;
       }
     }
   };

   // Some weird bootstrapping logic. First, we need to make sure that we can add the Typeclass typeclass to objects that should be typeclasses. Next, we need
   // to make sure it has add/remove hooks. Then, we need to add it to itself so that its constructor brings it along.
   tc.attachable.attach (tc.typeclass);
   tc.addable_with_hooks.add (tc.typeclass);
   tc.typeclass.attach (tc.typeclass);

   // OK, so build the typeclass from the ground up, and then make sure that it is a member of its own typeclass.
   tc.typeclass.brings (tc.attachable, tc.addable_with_hooks, tc.is_introspective);
   tc.typeclass.add_constructor (function () {
     if (! this.members) this.members = {};
   });
   tc.typeclass.add (tc.typeclass);

// Making things consistent
//
// Each one of the original attachable entities is in fact a real typeclass, or it should be. So we need to make that true now.

   tc.typeclass.add (tc.attachable, tc.addable_with_hooks, tc.is_introspective);
__6laUu42NFe08l/xMAGi2VjPbnX8LWQEY3WUcBtT+98w meta::blogpost('renaming-terrapin', <<'__2mx1OeHu7SxKahDc54v+ttR9rwHgncc6D+hIk8ZcQw4'); post:title = renaming-terrapin post:real-title = Renaming Terrapin post:status = published post:date = 2009-05-25 09:37:14 post:tags = terrapin post:format = html

It turns out that the name Terrapin has been trademarked by the folks at www.terrapinlogo.com, who have been designing turtle graphics software for quite some time. So I'm renaming Terrapin to something else (will post again once I find a name). In the meantime, the existing software can still be accessed through the link and its original page, and when I do the replacement it will automatically redirect to the new site.

As usual, ideas are welcome.

__2mx1OeHu7SxKahDc54v+ttR9rwHgncc6D+hIk8ZcQw4 meta::blogpost('requirements-for-joyces-project', <<'__hupqgpcD3l2avU9rCztg02zIUZ48xR/C3Mwt5cymwdk'); post:title = requirements-for-joyces-project post:real-title = Requirements for Joyce's Project post:status = published post:date = 2008-10-12 12:03:33 post:tags = dsl graphics joyce knitting lisp postscript post:format = html

Joyce wants to create a site that hosts openly-licensed knitting patterns, so she presented me with the challenge of being able to publish patterns with highly-repetitive content in a simple way. I, like most programmers, wasn't very familiar with knitting before she and I got married, so here's a rundown of the basic ideas:

A knitting pattern is a mostly-rectangular grid of stitches. Stitches are created serially per row, and then the knitter advances to the next row. The main function of a pattern is to indicate the types of stitches that need to be produced at a given location. Stitches include knits (k), purls (p), yarn-overs (yo), slip-stitches (sl), cables of various sorts, and other more esoteric things. For now, I'm going to assume that most stitches behave like knits, purls, yarn-overs, etc. That is, they occupy a 1x1-stitch cell.

There are two ways knitting patterns are normally represented. One is in a textual format, which ends up behaving much like a programming script. This format is ubiquitous in knitting circles and looks something like this:

Row 1. k 20, p 10, k 20.
Row 2. p 20, k 10, p 20.
Row 3 - 10. Repeat.

The other is a visual grid where each cell contains a symbol representing the stitch type. There is a standardized set of symbols, and most of them are simple vector-graphics such as X, -, ., etc.

Since text-only patterns are often longer than equivalent graphical patterns, Joyce decided that for web publication graphical patterns made sense as long as the format was widely readable (e.g. HTML, PDF, PNG). No GUI is necessary as long as there's a convenient way to create patterns.

The easiest/coolest way I can think of to do this is to use Lisp to write a simple DSL that produces PostScript, then convert that to PDF. The rationale is that Lisp will provide the programmability necessary to make the system extensible (in case I don't anticipate a particular use case), while PostScript is very simple to generate and compiles directly to PDF.

__hupqgpcD3l2avU9rCztg02zIUZ48xR/C3Mwt5cymwdk meta::blogpost('search-database', <<'__lFS0CHnFTHpP281t5xtVskO8xgoidri5KJOqigdH5QA'); post:title = search-database post:real-title = Search = Database? post:status = published post:date = 2009-05-20 22:31:27 post:tags = data searching post:format = html

There's an unspoken and not well-respected principle in computer science that says that data entered directly by the user is somehow special. That is, the user's original words mean more than the conclusions the program can draw from them, simply because the originals can't be replaced whereas the conclusions can.

Unfortunately, this is not well-implemented at all these days, and with good reason. We have database schemas, which say that regardless of what the user enters, if it doesn't fit into the predefined format we've established for our data, then it either gets shoehorned somewhere else or gets ignored entirely.

Besides posing usability issues, the paradigm that causes this problem also makes programmers' lives more difficult. We must explicitly anticipate a sufficiently flexible and fast schema, tell the computer how to optimize it, and then hope that nothing changes. There's a good reason for this, too: Programs just can't read or sort through large amounts of unstructured data quickly.

Or can they? Google does brilliantly. You type something in and it gives you relevant search results almost instantly. Nobody on the website told Google what people would look for to end up at their site; Google just figured it out. I attribute Google's success to an automatically-optimizing dynamic index construction layer that can be incrementally updated as new content is found.

So imagine what would happen if we did this to business applications. First, each record could be just a regular old file whose name is its ID. (For usability, it would also keep symlinks around for reference by name.) Inside the file would be XML or JSON or some such to store the object data; ideally something human-readable and writable, and you have a crawler that goes through and indexes all of these plain-text files, constructing fast lookups for frequently-issued queries.

Already there are several benefits. First, backups are super-easy: Just copy a directory somewhere. Regular old ZIP compression would do just fine. Second, indexes are dynamically generated based on queries that get issued, which means that the schema can be nonexistent or poorly-enforced. Third, importing and exporting data is as simple as dropping files into the directory structure. Also, since you have file-level access, you can easily store your data on a network share -- thus delegating the abstraction to the OS kernel instead of leaving it in your application.

The downside? File access is much slower than database lookups. But with good indexing, you don't need to care about this. An indexer just needs to check the modification time on each file and read through ones that have changed.

One other major benefit of such a setup: Your application has search for free. This isn't trivial; most business applications don't even provide search to begin with, or if they do, it's so cumbersome to use that nobody wants to. But with a small domain-specific language for specifying search queries (and decoupling the preposterous linkage between queries that retrieve data and those that change it), you have a user-friendly uniform interface to quickly retrieve data objects.

And best of all, perhaps? You don't need server-side data models because your data can be served as static web files.

__lFS0CHnFTHpP281t5xtVskO8xgoidri5KJOqigdH5QA meta::blogpost('self-modifying-perl-script-draft-1', <<'__jG6+kA/yD6awDvn6PH/oB39iwu72iVCTUleA8l7m5zQ'); post:title = self-modifying-perl-script-draft-1 post:real-title = Self-Modifying Perl Script, Draft 1 post:status = published post:date = 2009-09-18 17:30:40 post:tags = functional metaprogramming oop perl self-modifying post:format = html

Update! If you downloaded the first revision, you'll probably notice it complaining about inconsistent state all the time and leaving temporary files around as a consequence, which is a major bummer. It did this because I tacitly assumed that the current directory was in your $PATH (as it was mine), but this is generally not the case. The latest version does not have this problem. Now the only assumption that is made is that perl is in your $PATH.

After reading the excellent book Higher Order Perl by Mark Jason Dominus (and thus discovering that Perl is far from being an evil language -- more on this later), I decided to have a go at writing a self-modifying script in Perl.

It turned out to be much easier than I had figured, considering my experience doing the same thing in Bash. First (and perhaps most importantly), Perl provides built-in associative arrays, which make things like having a centralized data table very simple and fast. Second, it makes it very easy to write new functions by using typeglob assignment. For example, these two definitions are (almost) equivalent:

sub foo {}
*{"foo"} = sub {}

The only difference is that in the second case you will need to invoke foo with parentheses; in the first case this is generally unnecessary.

This is a welcome feature. It means that you have the same level of control over function definitions as you would in a language with a single namespace, but you still have namespace separation between variables and functions. (To convert from a function to a scalar isn't bad either: \&foo.)

Anyway, here's the script after a number of self-modifications:

#!/usr/bin/perl

use File::Temp  'tempfile';
use Carp        'carp';
use Digest::MD5 'md5_hex';

my %data;

sub meta::define_form {
  my ($namespace, $delegate) = @_;
  *{"meta::${namespace}::implementation"} = $delegate;
  *{"meta::$namespace"} = sub {
    my ($name, $value) = @_;
    $data{"${namespace}::${name}"} = $value;
    $delegate->($name, $value);
  };
}

meta::define_form 'meta', sub {
  my ($name, $value) = @_;
  eval $value;
  carp $@ if $@;
};

meta::meta('datatypes::bootstrap', <<'__b4109527493c00f6f20793eedd08ceb9');
meta::define_form 'bootstrap', sub {};
__b4109527493c00f6f20793eedd08ceb9

meta::meta('datatypes::data', <<'__428cabdae6955123a0e16328b2bbcfea');
meta::define_form 'data', sub {
  my ($name, $value) = @_;
  *{$name} = sub {print $value};
};
__428cabdae6955123a0e16328b2bbcfea

meta::meta('datatypes::function', <<'__7f5dd8d7cd5b973823c50e677bc78216');
meta::define_form 'function', sub {
  my ($name, $value) = @_;
  *{$name} = eval "sub {$value}";
  carp $@ if $@;
};
__7f5dd8d7cd5b973823c50e677bc78216

meta::meta('internal::runtime', <<'__78031ac14943dc121f0471eda34370a6');
meta::define_form 'internal', \&meta::meta::implementation;
__78031ac14943dc121f0471eda34370a6

meta::bootstrap('initialization', <<'__77ccbc556a1b8cc2bda5514aaf273676');
#!/usr/bin/perl

use File::Temp  'tempfile';
use Carp        'carp';
use Digest::MD5 'md5_hex';

my %data;

sub meta::define_form {
  my ($namespace, $delegate) = @_;
  *{"meta::${namespace}::implementation"} = $delegate;
  *{"meta::$namespace"} = sub {
    my ($name, $value) = @_;
    $data{"${namespace}::${name}"} = $value;
    $delegate->($name, $value);
  };
}

meta::define_form 'meta', sub {
  my ($name, $value) = @_;
  eval $value;
  carp $@ if $@;
};
__77ccbc556a1b8cc2bda5514aaf273676

meta::bootstrap('pod', <<'__89ed580add0efb12ac02b68703b31683');

=head1 NAME

object - Stateful file-based object

=head1 SYNOPSYS

object [options] action [arguments...]

object help::usage

=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.

=cut

__89ed580add0efb12ac02b68703b31683

meta::function('file::read', <<'__c0308fd18332c7a715672d799529c0c2');
my $name = shift;
open my($handle), "<", $name;
my $result = join "", <$handle>;
close $handle;
$result;
__c0308fd18332c7a715672d799529c0c2

meta::function('file::write', <<'__71b4a7aa5e93241ea07e8af462c77659');
my ($name, $contents) = @_;
open my($handle), ">", $name or die "Can't open $name for writing";
print $handle $contents;
close $handle;
__71b4a7aa5e93241ea07e8af462c77659

meta::function('functions::edit', <<'__ea5d29bd19f7de210f38bd7da80d930c');
my $name         = shift;
my $content_hash = md5_hex($name . $data{$name});
my $editor       = $ENV{'VISUAL'} || $ENV{'EDITOR'} || messages::error('Either the $VISUAL or $EDITOR environment variable should be set to a valid editor.');
my $namespace    = ((split /::/, $name)[0]);

my (undef, $filename) = tempfile("X" x 32, OPEN => 0);

$data{$name} or $data{$name} = "# New attribute '$name'\n";
file::write($filename, $data{$name});
system("$editor \"$filename\"");

if (-f $filename) {
  $data{$name} = file::read($filename);
  $data{$name} .= "\n" unless $data{$name} =~ /\n$/;
  unlink $filename;
  delete $data{$name} if length($data{$name}) == 0;
} else {
  messages::warning("The temporary file used for editing no longer exists; $name has not been updated.");
}
__ea5d29bd19f7de210f38bd7da80d930c

meta::function('functions::exists', <<'__929f454827ad3588fc1efa95f768cb12');
my $name = shift;
if (grep /$name/, keys %data) {
  print "$name is a defined attribute.\n";
} else {
  print "$name is not defined.\n";
}
__929f454827ad3588fc1efa95f768cb12

meta::function('functions::md5', <<'__0e1d4cafb9d662a98b1ec4a865c0ebed');
print md5_hex serialize();
__0e1d4cafb9d662a98b1ec4a865c0ebed

meta::function('functions::mv', <<'__32d41be6d86846aba9fb57e92a9f69c1');
my ($from, $to) = @_;
messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data;
$data{$to} = $data{$from};
delete $data{$from};
__32d41be6d86846aba9fb57e92a9f69c1

meta::function('functions::rm', <<'__e016d657842380be2387f361a90a24df');
my $name = shift;
delete $data{$name};
__e016d657842380be2387f361a90a24df

meta::function('help::ls', <<'__d13d1c1d60925eddf9c6a536b0f5a3b1');
print join("\n", map {s/^function:://; "  $_"} grep /^function::/, sort keys %data) . "\n";
__d13d1c1d60925eddf9c6a536b0f5a3b1

meta::function('help::ls-a', <<'__fad7c89642d52f4e3bbbe016724d7aad');
print join "\n", map {"  $_"} sort keys %data;
print "\n";
__fad7c89642d52f4e3bbbe016724d7aad

meta::function('help::usage', <<'__094bce4839687b3de6f2d1f259ebb602');
print <<"EOD";
Usage: $0 action [arguments]
Defined actions:
EOD

help::ls();
__094bce4839687b3de6f2d1f259ebb602

meta::function('messages::error', <<'__e25c6104ad29725892e0540803adb254');
my $message = shift;
print STDERR $message, "\n";
exit 1;
__e25c6104ad29725892e0540803adb254

meta::function('messages::warning', <<'__bca9e541c182a26a59a3c9ab8d52cc38');
my $message = shift;
print STDERR $message, "\n";
__bca9e541c182a26a59a3c9ab8d52cc38

meta::function('serialize', <<'__83ca9dbe0417fcd0d6df1ac876e3bcc2');
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__";
__83ca9dbe0417fcd0d6df1ac876e3bcc2

meta::function('serialize::single', <<'__db61061746e4132e9995ba98f087bf5c');
my $name               = shift || $_;
my $contents           = $data{$name};
my $delimiter          = "__" . md5_hex $contents;
my @function_name_bits = split /::/, $name;
my $meta_function_name = "meta::" . shift @function_name_bits;
my $invocation_name    = join "::", @function_name_bits;
"$meta_function_name('$invocation_name', <<'$delimiter');\n$contents$delimiter\n";
__db61061746e4132e9995ba98f087bf5c

meta::internal('runtime', <<'__970776fc8a8186b7f4c7901eab4630b5');
my $initial_state = md5_hex serialize();
my @script_args   = ();

push @script_args, shift @ARGV while @ARGV && $ARGV[0] =~ /^-/;
&{shift(@ARGV) || 'help::usage'}(@ARGV[1 .. @ARGV]);

END {
  my $serialized_data = serialize();
  my $final_state     = md5_hex $serialized_data;

  if ($initial_state ne $final_state) {
    my (undef, $temporary_filename) = tempfile('X' x 32, OPEN => 0);
    file::write($temporary_filename, $serialized_data);
    chmod 0700, $temporary_filename;

    my $observed_state = `perl $temporary_filename functions::md5`;
    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 {
      file::write($0, $serialized_data);
      unlink $temporary_filename;
    }
  }
}
__970776fc8a8186b7f4c7901eab4630b5

__END__

(Download here)

It's better than the earlier Bash version (see a few posts back) for several reasons:

  1. It uses noncollidable delimiters for data. This means that you'll never have the end-of-data signal in your data (unless you happen to be unfortunate enough to discover a string that contains its own MD5 hash).
  2. It's much faster.
  3. It supports separate namespaces to allow for different types of data.
  4. Much less duplicated code -- most of it is evaled at runtime.
  5. Functionality checking -- it ensures that the modified script functions and that your data is intact before overwriting the original.
  6. It works with perldoc.

Obviously it's still a work in progress. I'll be posting updates as it gets more usable. It should run on any platform with a Perl install (that's my goal -- let me know if it doesn't) and require nothing but the standard libraries.

__jG6+kA/yD6awDvn6PH/oB39iwu72iVCTUleA8l7m5zQ meta::blogpost('self-modifying-perl-script-draft-2', <<'__kx4hAv4Uuhb29ZnP7vVMHSnWsVB+VixVPXyWrOHwY3s'); post:title = self-modifying-perl-script-draft-2 post:real-title = Self-Modifying Perl Script, Draft 2 post:status = published post:date = 2009-09-29 22:08:10 post:tags = oop perl self-modifying post:format = html

After some hacking, I've got the beginnings of an object-oriented system centered around self-modifying Perl scripts. You can use the canonical object for just about any purpose, and here's a quick tutorial on classes (you'll need to download canonical-object and class and chmod u+x them to make this work -- links below):

The first thing to do is to set your $PATH to contain the current directory for convenience -- in Bash, that is export PATH="$PATH:./". Alternately, remember to put ./ in front of all of the script invocations.

Next is to get a class by cloning the class class:

$ class clone my-class
File my-class cloned successfully.
$

Now we can get inside our new class by running its shell. That's what happens when you invoke a Perl object without arguments (it can be changed by resetting the value of data::default-action), and you can also say my-class shell.

$ my-class
class$ name = my-class
my-class
my-class$

Let's get a quick list of actions supported by my-class and then define a data member:

my-class$ ls
...
my-class$ create implementation::data::foo
bar
^D
my-class$ exit
$

At this point, if you add my-class to another object, that object will have a member called data::foo, accessible as other-object foo (and, correspondingly, other-code foo = whatever -- you get both access and mutability). Let's grab an instance and do just that:

$ canonical-object clone instance
File instance cloned successfully.
$ my-class add-to instance
$ instance foo
bar
$

And as expected, the new instance supports the foo method.

There's a lot more to it than just this, and I hope to write some more complete documentation later. But in the meantime, here are the download links:

canonical-object

class

__kx4hAv4Uuhb29ZnP7vVMHSnWsVB+VixVPXyWrOHwY3s meta::blogpost('specifications-for-joyces-project', <<'__1Z/Tx6+iQaTgiHBTnF6357oDuo2cuYw4tgAgMLr8/fM'); post:title = specifications-for-joyces-project post:real-title = Specifications for Joyce's Project post:status = draft post:date = 2008-10-17 15:53:18 post:tags = dsl graphics joyce knitting lisp program-specifications post:format = html

This is a follow-up to the requirements for Joyce's project.

The system will probably have to handle nonperiodic patterns comprised of periodic components. This arises in some knitting patterns if, for instance, one column of stitches repeats every 7 rows and another repeats every 13 rows. The LCM of these two numbers is 7x13=91, which is essentially nonperiodic. (Admittedly, I chose two primes to make a point; more realistically, we would have numbers such as 6 and 8.) Rather than having to precompile these large segments based on LCMs, it seems more elegant to have a system by which individual vertical strips may be assembled separately and then "glued" together. This would also afford greater reusability of pattern components.

Sometimes a pattern is nonrectangular. It will always have a linear top and bottom, but its width may change. Joyce mentioned that joining a rectangular border to an uneven edge should result in the border molding to the edge.

The difficulty comes in when defining the pattern description language. To expedite this process, I'll make the stipulation that the language is based on S-expressions so that I can use the universal translator to compile it down to objects.

The primitive data structure is the cell, which is an S-expression that defines what type of stitch is present at a location. Most likely, this will just be a symbol. The next layer of abstraction is the group, which is a list of lists of cells. This should not be an array because it is not rectangular.

__1Z/Tx6+iQaTgiHBTnF6357oDuo2cuYw4tgAgMLr8/fM meta::blogpost('stateful-shell-scripts', <<'__xByPgpLTgKioyXn3vxwBNcFU/DjGoMdlUILZbg6eOdY'); post:title = stateful-shell-scripts post:real-title = Stateful Shell Scripts post:status = published post:date = 2009-08-12 22:09:45 post:tags = bash hashtable metaprogramming object-oriented post:format = html

After a lot of tweaking, here's a Bash script that modifies itself in-place:

:: #!/bin/bash typeset -a table_keys typeset -a table_values typeset -a ordered_keys key_count=0 function index-of-key () { local initial=$(($(echo $1 | md5sum | cut -c 18-32 | awk '{print "0x"$1}'))) while [[ ${table_keys[$initial]} && ${table_keys[$initial]} != $1 ]]; do initial=$((initial + 1)) done echo -n $initial } function associate () { local index=$(index-of-key $1) [[ ${table_keys[$index]} ]] || ordered_keys[$((key_count++))]=$index table_keys[$index]=$1 table_values[$index]="$(cat)" } function lookup () { local index=$(index-of-key $1) echo -n "${table_values[$index]}" } function serialize () { echo "#!/bin/bash" echo typeset -a table_keys echo typeset -a table_values echo typeset -a ordered_keys echo key_count=0 echo for key in ${ordered_keys[@]}; do echo "function ${table_keys[$key]} () {" echo "${table_values[$key]}" echo "}" echo done for key in ${ordered_keys[@]}; do echo associate ${table_keys[$key]} "< \$0.temporary" echo "mv \$0.temporary \$0 && chmod u+x \$0" } function edit () { local filename=/tmp/$(md5sum <<<"$0-$1" | awk '{print $1}') lookup $1 > $filename ${VISUAL:-vim} $filename && associate $1 < $filename rm $filename } associate index-of-key < \\\$0.temporary" echo "mv \\\$0.temporary \\\$0 && chmod u+x \\\$0" end associate edit < \$filename \${VISUAL:-vim} \$filename && associate \$1 < \$filename rm \$filename end [[ $(lookup $1) ]] && $* serialize > $0.temporary mv $0.temporary $0 && chmod u+x $0 :.

(Download here)

To invoke a method with parameters, you can do this (assuming you've called it "object"):

$ ./object method param1 param2 ... paramN

Arguably the most useful method is edit, which lets you edit the contents of a given method. To edit the associate method, for instance, you can do this:

$ ./object edit associate

I'm sure there are bugs; this is only a preliminary implementation. Let me know if you find any.

__xByPgpLTgKioyXn3vxwBNcFU/DjGoMdlUILZbg6eOdY meta::blogpost('stating-the-problem', <<'__G6iKqOfCpt+8P7K9hVzhbOv/IjCsNcNEPHGlAJsAuOI'); post:title = stating-the-problem post:real-title = Stating the Problem post:status = published post:date = 2009-10-19 21:26:10 post:tags = communication problem-statement software-engineering post:format = html

Every program was created to solve a problem. Microsoft created Word because not participating in the word-processing market had a high opportunity cost. Linus Torvalds created Linux because there wasn't a free UNIX available. Bram Moolenar wrote VIM because (1) he didn't like some things about the old VI, and (2) he didn't have a widely-used program to use as a vehicle for charity work in Uganda. (I'm sure there are other reasons too.)

Often the problem statement shapes the project. If your problem statement is to build a calculator that is useful to chemists, for instance, then you'll know immediately that you need reaction equations and other chemistry-related stuff. That, in turn, influences the design (in this example you'll want symbolic computation), and ultimately the whole development process (we could use Lisp or Python to make symbolic computation easy).

If you're just one developer, usually you will have a problem statement of some sort even if it isn't verbally stated. And often, in familiar problem domains, that intuition-level statement is enough to get by. The real challenge begins when you have a team of developers. If each developer has their own vision for a project, then it will end up with small fragments of functionality that are poorly tied together. Making a central problem statement ensures that all of the developers are focused on solving one fundamental problem, even if they end up solving other problems along the way.

What does a problem statement look like? I like to have one sentence that answers the question, "Why do I care about this software." So, for example, decentralized version control systems such as Git exist because, "As a developer, you want to be able to make commits on your local code without influencing the main branch in production." Cheloniidae exists because, "The Joseph Baldwin Academy needed a free turtle graphics library for Java, and Spencer wanted it to be 3D." What's cool about this is you often get an elevator-pitch and a direction for marketing presentations for free. This is important because marketing isn't just for people outside the company; it needs to be there to give developers a goal to work towards too.

I mentioned that solo developers can get by without a problem statement for familiar problem domains. Today I realized that writing Perl file-objects (covered a few posts ago) is not a familiar domain, and, well, I'm a bit stuck. The original problem statement, as I recall, was "wouldn't it be awesome to have a program that rewrote itself ....". Unfortunately, being awesome doesn't quite set the direction for a project. Awesomeness is a good feature, but the system needs to be useful too.

So, since I'm having trouble coming up with a problem statement, I'd like to hear some thoughts, use-cases, etc. The idea is that you've got a script that runs anywhere (Perl is a good choice for this, being almost ubiquitous) and saves its state between executions by rewriting itself. It could do anything, from summing a list of numbers to storing a document and its revisions to being a self-contained configurable backup script.

__G6iKqOfCpt+8P7K9hVzhbOv/IjCsNcNEPHGlAJsAuOI meta::blogpost('tex-to-go', <<'__fiD4jXM2Nw395SEQ05TuOfLvpo4DzyMibZ512GeBl80'); post:title = tex-to-go post:real-title = TeX To-Go post:status = published post:date = 2009-10-05 21:02:01 post:tags = perl-objects post:format = html

I love TeX as a typesetting system, but creating new documents and setting up the build process (which, if you use a TOC, you will probably want) is a few steps too many. So I wrote a self-modifying Perl script that acts as your TeX document. Here's how to use it:

First, download here and chmod u+x. Now stash that in a bin directory and remove all write permissions so that it can't be changed. (It will be used as a template for future TeX documents.)

The file comes bundled with instructions; if you run it without arguments it will give you a basic tutorial. I'll give a brief overview of the usage here.

If this is your first time using tex-document and you're a VIM user, then I recommend doing this:

$ tex-document install-vim-highlighter
$ tex-document update-vimrc

That will register the VIM highlighter for my custom TeX format. Now you can create a new document:

$ tex-document new mydoc
File mydoc cloned successfully.
$ ./mydoc shell
tex-document$

Now you're inside the document's shell. Here are some of the commands you can use (you can find all of them by running ls):

  • e -- edits the source of your document.
  • make -- compiles into TeX, invokes LaTeX twice and pdfLaTeX once, and displays the PDF.
  • save -- commits changes to disk. This happens when you exit, but I recommend saving more frequently.
  • snapshot my-snapshot-name -- saves a copy of the file. This can be useful for basic version control, though I would recommend using Git or similar.

There are also options that you can set. Since the shell interface still needs some work, I recommend doing this from Bash:

  • ./mydoc table-of-contents = x -- if x is a true value in Perl, then the table of contents will be generated. Otherwise, if x is the empty string, then there is no TOC and TeX will be invoked only once.
  • ./mydoc edit data::header -- allows you to define more packages, new commands, etc.
  • ./mydoc pdf-reader = /usr/bin/some-reader -- changes the PDF reader that gets invoked when you run view (which is run from inside make).

Finally, the TeX syntax. Hopefully it's not too mysterious, but here's basically what's going on:

                       % data::header gets inserted at the beginning.
                       %
= something            % --> \title{something}
a someone              % --> \author{someone} -- optional, as in TeX
d somedate             % --> \date{somedate}  -- optional, as in TeX
                       %
                       % \begin{document}\maketitle\tableofcontents
                       % gets inserted here, before the first
                       % section.
                       %
- sectiontitle         % --> \section{sectiontitle}
  text                 %
  ::                   % --> \begin{verbatim}
  code                 %
  code                 % These lines are all outdented to
  code                 % the level of the ::
  code                 %
  :.                   % --> \end{verbatim}
                       %
  - sectiontitle       % --> \subsection{sectiontitle}
                       % levels are determined by indentation.
                       %
                       % \end{document} gets inserted at the end.

Everything else is straight TeX. To see what it's generating, you can also say this:

$ ./mydoc compile-to-tex

And to retrieve your original document:

$ ./mydoc document

Enjoy!

__fiD4jXM2Nw395SEQ05TuOfLvpo4DzyMibZ512GeBl80 meta::blogpost('the-last-programming-language-ill-ever-need', <<'__V3FUQ2aFx/UpjzNKxawTuhJr4+nYUa/x8odhLUAdzFA'); post:title = the-last-programming-language-ill-ever-need post:real-title = The Last Programming Language I'll Ever Need post:status = published post:date = 2009-03-26 09:12:02 post:tags = post:format = html

Well, if I'm lucky. I've got the first draft of the level-0 specification up at http://projects.spencertipping.com/obsidian/level-0.pdf.

Once I get an interpreter written I'll put it up too.

__V3FUQ2aFx/UpjzNKxawTuhJr4+nYUa/x8odhLUAdzFA meta::blogpost('the-list-processing-chainsaw', <<'__lJ2DfzI7GGF+FmCKOv7FFOEyc9HxUUzb+uVFM+tXNQg'); post:title = the-list-processing-chainsaw post:real-title = The List Processing Chainsaw post:status = published post:date = 2010-02-20 post:tags = gnarly lisp functional [ After revisiting some of the mistakes I made trying to design the perfect programming language, I decided to give it another go and see what I could come up with. The result so far is a system that is incredibly slow (implemented as an interpreter written in self-modifying-perl), but also incredibly expressive. Implementing the cond macro from Lisp is a simple map/filter operation: ] :: (def cond (qn options (:: (o * _ (_ tail) (o % _ (:: (_ head tail)) (reverse options)) tail)))) :. [ Here, o is function composition, * is map, % is filter, :: is evaluate, and _ is a macro that constructs a unary function for an expression over _. qn is a macro that constructs a non-evaluating function. None of these constructs are language primitives or syntax; they were built from the initial bound functions beta, :, and o'. ] __lJ2DfzI7GGF+FmCKOv7FFOEyc9HxUUzb+uVFM+tXNQg meta::blogpost('things-to-know-about-javascript', <<'__N8DpvOZwpoAEPJFUotcrQt43zrWCD40SH9daJvVa3rA'); post:title = things-to-know-about-javascript post:real-title = Things to Know about JavaScript post:status = published post:date = 2008-12-16 19:13:02 post:tags = cross-browser javascript problems questions post:format = html

While working on my JavaScript library (both the first incarnation and this one), I ran across some aspects of JavaScript that threw some monkey-wrenches into the development process. Here are a few:

  1. The "really-equals" operator. You may have noticed strange facts about JavaScript's world, such as 1 == true, null == undefined, false == 0, and the highly disturbing "" == 0. That's because the == operator doesn't mean "really equals" -- rather, it means "sort of equals." If you want to determine whether two things are really equal, you need the === operator, which behaves as one would expect. Its complement, the "really doesn't equal" operator (written !==) is also included.
  2. typeof doesn't give you very useful information. In Firefox 3, for instance, typeof ([1, 2, 3]) returns "object". While this is technically true, it isn't the most specific type. To get the specific type of an object, use its constructor attribute. So, to determine whether something is an array, you say x.constructor == Array. This should also work for user-defined classes (though not classes defined with Ja!).
  3. Shallow destructuring binds (e.g. var [x, y] = [1, 2]) don't work in Konqueror, so you can't use them in cross-browser code.

Some things that I'd like to find out but haven't taken the time to:

  1. What is the time complexity of a DOM property lookup? (Where n is the number of assigned properties)
  2. Is Array.unshift as fast as Array.push?
  3. Is there a cross-browser way to augment the prototype of an HTMLElement subclass? (I don't believe there is.)

Once I find the answers to these, I'll post them.

__N8DpvOZwpoAEPJFUotcrQt43zrWCD40SH9daJvVa3rA meta::blogpost('thoughts-about-the-incompleteness-theorem', <<'__Ijdu7bbTpxWKwggiWaY33WZZklmxBsum431LmXjtUws'); post:title = thoughts-about-the-incompleteness-theorem post:real-title = Thoughts about the Incompleteness Theorem post:status = unworthy-of-publication post:date = 2009-12-08 16:49:09 post:tags = math, proofs, incompleteness [ Suppose there's an infinite one-way highway, and I make the claim that you will never know whether there's a Wal-Mart on that highway. That would suggest that no matter how far you drive, you'll never see one; otherwise you'll know that it's there. However, then you can reasonably conclude that there cannot be a Wal-Mart, since if there were you would find it. (Keep in mind that while infinite highways are possible in this scenario, moving Wal-Marts are not.) Armed with that knowledge, you thus conclude that (1) there cannot possibly be a Wal-Mart on the highway, and (2) my claim was false, since you know whether there's a Wal-Mart on this highway. ] [ In mathematics, then, the existence of a Wal-Mart represents a simple proposition, and the highway represents an enumerable set. What we now know is that every proposition that can be expressed in terms of a single existence quantifier over an enumerable set has a definite value. (Naturally, this applies to for-all quantifiers as well.) Put differently, determining the value of any such proposition is an undecidable, but not unrecognizable, problem. ] [ Incidentally, it may appear that I am misusing Gödel's theorem here. The theorem makes statements about provability, but not about truth. However, consider that for an existence quantifier an example constitutes a proof. Thus the space of possible proofs simply requires exhaustive enumeration. ] [ OK, now let's rephrase the initial problem like this. Everything is as before, except that the Wal-Mart is now invisible. At any point you can ask whether your front bumper is exactly aligned with the perhaps-existent invisible Wal-Mart. My previous claim would now be true, since between any two nonidentical locations there are uncountably many more locations in which the alignment could occur. Since there is no way to go through them all, the uncertainty is plausible. ] [ But here is an interesting detail. Let us suppose that there might or might not be a Wal-Mart on the highway, but that when buildings are built their locations must be exactly specified and completely communicated to a builder before construction begins. It would not initially seem like this is much of a help, but it is. The main problem with the invisible scenario is that there are uncountably many locations that the Wal-Mart could have. But if the builder received a complete specification and then began construction, then we know something about how many possibilities there are. Given that each one has a finite size, we may begin enumerating all possible location specifications that could be given to the builder, trying each one in turn, and we will ultimately know once more that there is no Wal-Mart after all. ] [ Even though the quantification domain is uncountable, the proof domain is countable and thus enumerable. ] __Ijdu7bbTpxWKwggiWaY33WZZklmxBsum431LmXjtUws meta::blogpost('thumbs-up-for-mongoose', <<'__UowIS0vdNuHiJxcCiH5voosCmdolOPNGNE9Xahk8oRs'); post:title = thumbs-up-for-mongoose post:real-title = Thumbs Up for Mongoose post:status = published post:date = 2009-10-05 14:50:23 post:tags = bicycles non-cs product-review post:format = html

Warning: Non-Computational Content

However, if you are into mountain biking, then you might enjoy this. A couple of months ago I bought a Mongoose XR-75 bike. I've found it to be a good, inexpensive, reasonably lightweight and tough bike for both city and off-road riding, and the suspension bottoms out only on the worst rides once you adjust the rear spring.

This afternoon I had the misfortune of ending up with the rear shifter caught in the spokes going downhill. (Not much idea of how this happened, either.) I came out of it all right, but the shifter was pretty well mangled, the gear-plate was shattered, and the spokes were bent out of shape.

I didn't imagine I would get a great reception from customer support ("How the heck did you manage that?!", for instance), but in fact they were extremely understanding and immediately volunteered to send out a new shifter. When I asked about a new rear wheel too, they told me that they were backordered but that they would send one out as soon as it was in stock. All of this with no questions asked.

Definitely made my day.

__UowIS0vdNuHiJxcCiH5voosCmdolOPNGNE9Xahk8oRs meta::blogpost('type-parameterization-in-lisp', <<'__HLvYPIu1pyf930eSndueY4eGPfW0roGQFmCeQ+0o498'); post:title = type-parameterization-in-lisp post:real-title = Type Parameterization in Lisp post:status = published post:date = 2009-12-03 22:28:14 post:tags = lisp, metaprogramming, performance, typing [ As mentioned in an earlier post, one of the shortcomings of Lisp's static typing model is that there isn't a clear way to establish parameterized types for a method. It's easy enough to write a method with constant types: ] :: (defun has-constant-types (x) (declare (type integer x)) (the integer (1+ x))) :. [ But imagine that we wanted to generalize this function over any group of integers. Rewriting for each case definitely wouldn't be the Lisp way. Better is to use a macro to generate the parameterized body: ] :: (defmacro parameterized (type) `(progn (declare (type ,type x)) (the ,type (1+ x)))) (defun integer-function (x) (parameterized integer)) (defun mod-5-function (x) (parameterized (mod 5))) :. [ Not beautiful, but it reduces repetition. ] [ Ideally, though, we can use macros to not only save typing but also to improve Lisp's type inference in general. Most Lisp functions don't provide type signatures in the formal sense, but we can make some (here I'm imagining a bunch of library code that as yet doesn't exist): ] :: (sig 1+ 'a 'a) (infer-type (1+ 5)) ; => (type-of 5) :. [ Once a type-solver is implemented, a logical next step is to allow per-invocation function instantiation. Ultimately, for example, 1+ for integers and 1+ for double-floats are two different functions. They should be coded as such too for maximum speed: ] :: ; Generated code: (defun 1+-integer (x) (declare (type integer x)) (the integer (1+ x))) (defun 1+-double-float (x) (declare (type double-float x)) (the double-float (1+ x))) :. [ What we really have at this point is a typeclass in the Haskell sense: ] :: (deftypeclass (has-1+ 'a) 'b (1+ 'a 'a)) (defimplementation (has-1+ double-float) double-float-1+ (1+ x = (the double-float (1+ x)))) (defimplementation (has-1+ integer) integer-1+ (1+ x = (the integer (1+ x)))) :. [ However, since this is all implemented in libraries, we can extend it to include functional contracts: ] :: (deftypeclass (has-1+ (ordinal choosable 'a)) 'b (1+ 'a 'a) (such-that (forall 'a x (< x (1+ x))))) :. [ That contract could be compiled into the resulting code this way: ] :: (defun 1+-integer (x) (declare (type integer x)) (let ((result (the integer (1+ x)))) (assert (< x result)) result)) ; Test cases run at defimplementation-time as a quick check. ; These failing will result in a compile-time error. (dotimes (i 100) (let ((choice (choose integer))) (assert (< choice (1+-integer choice))))) :. [ The nice part of this approach is that test cases, or contracts, are implemented in semantically appropriate places. It provides a constructive rigidity to the type system that prevents people from defining invalid types. Clearly it isn't bulletproof because it relies on choosing elements for test cases. (However, a forall quantifier does not have to be used — there could be a constant instead of the variable x.) ] [ Once these constructs exist you have a language that is at least as rigorously statically checked as a Haskell program, and you can implement any subset of it in dynamically-typed Lisp. ] [ Incidentally, it looks like the Qi folks have achieved a lot of this in their Lisp-based language. I'll have to read up on this if I end up implementing a type system. ] __HLvYPIu1pyf930eSndueY4eGPfW0roGQFmCeQ+0o498 meta::blogpost('typeclass-prototype', <<'__ivjg/HBWXJNe6OA27NqG6YXIMpJLCjpdodumwlbhpuY'); post:title = typeclass-prototype post:real-title = Typeclass -> Prototype post:status = published post:date = 2009-03-06 01:11:58 post:tags = dsl javascript oop typeclasses post:format = html

Typeclasses are great for GUI stuff where the prototype is inaccessible, but what if speed is an issue and you don't care about dynamic extensibility of your objects? There's actually a very nice mapping from typeclasses to normal JavaScript prototype functions.

As a prerequisite, you'll need to be able to add a typeclass to an object without the typeclass binding the functions as it adds them. This is important because traditional prototype-based classes share their functions across instances, so the binding is the only way to tie the function invocation to the object. Currently, the typeclass library doesn't support this; I may, however, release a fix that provides this flexibility.

Once you have this, all you need to do is write a constructor function in normal prototype style, such as this:

function f (x, y) {
  this.x = x;
  this.y = y;
}

And then add a typeclass to its prototype, without binding:

my_typeclass.add_without_rebinding (f.prototype);

After this, instances may be created as new f (x, y) and will have the same members as their originating typeclass.

Naturally, by extension, any function prototype may also become a typeclass. This conversion is simpler, since the members hash of a typeclass does not expect its functions to be bound, and indeed in a prototype context they are not; thus no extra conversion needs to be done to make this happen.

__ivjg/HBWXJNe6OA27NqG6YXIMpJLCjpdodumwlbhpuY meta::blogpost('types-of-abstractions', <<'__s2Ia32gi0wrsmaWPPfXTQWOC1E9K1QbEhcDcC62XLCM'); post:title = types-of-abstractions post:real-title = Types of Abstractions post:status = draft post:date = 2008-10-16 15:51:26 post:tags = abstractions ideal-computation post:format = html

Lately I've been thinking about aspects of an idealized computer interface -- that is, one that overcomes the inconveniences that most computer users face by allowing us to do common things more easily. There are several common tasks that jump to mind:

  1. Undo after reopening a file.
  2. Copy a file and everything it depends on.
  3. Find a file that contains a particular piece of information.
  4. Sort files by some content-related attribute.
  5. Contact the author of a file.
  6. Check for updates to the file.

There are a couple of different aspects to all of this. First, files should be represented not just as a series of bytes, but rather in some structured way. I think we all agree about that, at least at some level. But this raises the issue of user-mode vs. primitive abstraction, and that issue is not easily resolved.

So what is the difference between user-mode abstraction and primitive abstraction? User-mode abstraction is extensible; given one abstraction, you can implement another on top of it, you can replace the first, etc., all without changing the underlying platform. Primitive abstraction is provided by the underlying platform and cannot be changed. One example of user-mode abstraction is the HTML format. The only applications that know what HTML is were written to know; the OS doesn't need to be changed if you want to implement HTML emphasis using the <em> tag instead of <i>. In contrast, file metadata is a primitive abstraction, at least on many filesystems currently in use. You get the modification date, permissions, owner, group, and so forth, but outside of those there is little to no extensibility.

The same patterns of abstraction occur all throughout computation. Some programming languages such as PHP and Ruby provide a lot of primitive abstractions (and many user-mode ones as well), while others such as C and Lisp provide more user-mode abstractions. HTML provided so many primitive abstractions that the W3C decided to create XHTML to consolidate the base and establish CSS, an orthogonal framework, to provide much-needed isolation of functionality. A common theme is visible: Languages with many primitive abstractions are used ubiquitously but are later phased out, while languages with few primitive abstractions are used infrequently but survive for a very long time.

I have a lot of respect for the way John McCarthy designed Lisp (which, incidentally, was much more similar to Scheme than it was to the modern Common Lisp -- this is important). He had five primitive functions, and that was sufficient to make a Turing-complete language. One of the consequences of designing the language with so little predefined behavior was that when object-oriented programming was invented, Lisp hackers wrote the CLOS in Lisp itself, and the language and compiler didn't have to be changed. CLOS could be used as a library instead of a built-in system, at least in theory. When exception processing and stack unwinding were popularized, Scheme could build that abstraction on top of call/cc, also without changing the language.

OK, so why this tangent? Because it's very important to choose your primitive abstractions carefully. If you introduce even one primitive abstraction that is unnecessary and later must be changed, then you'll be fighting against legacy and lagging behind the cutting edge. That's where file systems are today, which is why it's impossible to annotate some arbitrary file to say things like, "I got this file from www.whereever.com/download.html on Tuesday." It's also why we have special programs such as CVS and Git to manage revisions, Java packages to manage source code locations, HTML hrefs and src tags, SQL servers, E-mail clients with their own data formats, and a general inability to convert from one file format to another.

Pragmatism dictates that most abstractions should be primitive, since they are easier to code and easier to use. I agree. However, this philosophy should be carefully considered when dealing with software that is spread over a large user base and is unlikely to be replaced with anything else. At that point, the type of pragmatism that is valued so highly for internal application development should be put aside in favor of a well-thought-out, elegant solution. It may not pay off in the immediate future, but 50 years later users will still be happy with the elegant solution.

__s2Ia32gi0wrsmaWPPfXTQWOC1E9K1QbEhcDcC62XLCM meta::blogpost('upgrades-for-ocaml-projects', <<'__NkMI9quN4DzWbUYGipb3qPX8UDW7epDTx+tiFj43sTQ'); post:title = upgrades-for-ocaml-projects post:real-title = Upgrades for OCaml Projects post:status = published post:date = 2009-11-01 16:40:31 post:tags = literate ocaml perl self-modifying tex update post:format = html

After using the literate OCaml project (see previous post) for a Sudoku application, I've seen the need to make a few upgrades.

The first, rather important, upgrade is that the edit function automatically saves to disk after each edit. This prevents the data loss that otherwise could occur if you made an edit, didn't save, and then killed the shell. (This could also happen if you issued Ctrl-C during a running OCaml build and it bypassed the automatic exiting-from-shell save.)

The other more superficial upgrade is that there's now a list command that will show you a listing of the generated source code at a particular line. Currently it gives you nine lines of context in each direction. So, suppose you get this output from OCaml:

File "/tmp/bOETwNQRZYUPpL-wN1PljSx5xGQOr4bcIFWuZA---GM/input.ml", line 377, characters 42-51:
Error: This expression has type
         [a long type description] list
       but an expression was expected of type
         [another long type description] list
       Types for method remove_value are incompatible

Now you can type list 377 (if you're inside the shell; otherwise you'll need to type ./my-project list 377) to get this:

 368:       Buffer.contents s
 369: 
 370:     method setup =
 371:       List.iter (fun x ->
 372:         List.iter (fun c -> if x # contains c then x # invoke c) cs)
 373:       xs
 374: 
 375:     method run s = self # read_from_string s;
 376:                    self # setup;
 377:                    b # set_contents cs;
 378:                    b # run;
 379:                    self # print_to_string
 380:   end
 381: (*     :.
 382: 
 383: - Tests
 384:   Hopefully these cover the full range of conditions that we can expect to happen.
 385: 
 386:   - Simple cases

list also takes an optional second parameter to specify the build profile. By default, 'run' is used, but if you want to make sure your conditional-compilation rules are working correctly you can say something like list 377 debug.

Incidentally, after some Googling I was able to build a profile that gives you backtraces for top-level errors (I'm using OUnit for unit testing as well):

my %options = @_;
my $libs = "-I +oUnit unix.cma oUnit.cma";
`ocamlc -g $libs $options{'in'} -o $options{'out'}`;
`ocamlrun -b $options{'out'}`;

The key is to compile debugging information into your bytecode (-g) and to specify the backtrace option (-b) for ocamlrun. Unfortunately, I don't think there's a way to get backtraces with the normal OCaml interpreter.

The new version is up under the same download link, since it's fully backwards-compatible: Literate OCaml Project. To upgrade an existing project to use the new definitions, you'll have to download the new template, replacing the old one, and then run this:

$ literate-ocaml-project cat function::edit | ./my-project import function::edit
$ literate-ocaml-project cat function::list | ./my-project import function::list
$
__NkMI9quN4DzWbUYGipb3qPX8UDW7epDTx+tiFj43sTQ meta::blogpost('using-the-universal-translator-for-arithmetic-parsing', <<'__WqUrn9kjHABXbDhU7R0kxlzmq/et6XzLS8N/BSgMtv4'); post:title = using-the-universal-translator-for-arithmetic-parsing post:real-title = Using the Universal Translator for Arithmetic Parsing post:status = published post:date = 2008-10-15 13:43:55 post:tags = lisp translator post:format = html

I mentioned a day or two ago that it is possible to parse infix arithmetic expressions using the universal translator. Here's a snippet of such a configuration and how it works (all included with the universal translator source code, by the way):

(defun precedence (x)
  (case x (+ 0) (- 0) (* 1) (/ 1) (^ 2)))

(defun precedence-collapse (p)
  (orth-rule `(:x :op :y :(*> :rest) :(! (eql (precedence ':op) ,p)))
             '((:x :op :y) :@rest)))

(defun left-associativity (p)
  (orth-rule `(:x :op :y :op :z :(*> :rest)
                                :(! (eql (precedence ':op) ,p)))
             '((:x :op :y) :op :z :@rest)))

(defconstant *precedence-rules* (list
  (left-associativity 2) (precedence-collapse 2)
  (left-associativity 1) (precedence-collapse 1)
  (left-associativity 0) (precedence-collapse 0)
  (orth-rule '((:x) :(*> :rest)) '(:x :@rest))))

(defconstant *arithmetic-rules* (append *precedence-rules*
  (list
    (make-rule '(:x) :x)
    (orth-rule '((:x) :op :y :(! (and (atom ':x) (precedence ':op))))
               '(:x :op :y))
    (orth-rule '(:x :op (:y) :(! (and (atom ':y) (precedence ':op))))
               '(:x :op :y))
    (orth-rule '(:x :op :y :(*> :rest) :([] ':op '(+ - * /))
                           :(! (and (numberp ':x) (numberp ':y))))
               '(:(:op :x :y) :@rest))
    (orth-rule '(:x ^ :y :(*> :rest)
                         :(! (and (numberp ':x) (numberp ':y))))
               '(:(expt :x :y) :@rest)))))

With these definitions in place, one can now evaluate expressions:

(translate '(3 + 4 + 5) *arithmetic-rules*)              ; => 12
(translate '(2 ^ 3 + (5 * 6)) *arithmetic-rules*)        ; => 38
(translate '(2 ^ 2 ^ 2 - 3 ^ 2 * 1) *arithmetic-rules*)  ; => 7

Obviously, the steps going into this are not that straightforward. I'm cheating just a little bit by doing a direct mapping from the symbols +, -, *, and / to their Lisp-function equivalents. That's done with the construct :(:op :x :y), which literally means, "evaluate the list (:op :x :y) after :op, :x, and :y have been replaced by their bound values." Notice, however, that an extra rule is required for exponentiation since ^ isn't defined as a Lisp exponentiation function.

An important note: This code is unlikely to work outside of CLisp (though the universal translator does in general). The reason is that most Lisps don't allow empty keywords unless they are made explicit by typing :||.

There are many more cool things that can be done in remarkably little code by using the translator system. Once I have some more examples, I'll post them and try to explain why the rules work the way they do.

__WqUrn9kjHABXbDhU7R0kxlzmq/et6XzLS8N/BSgMtv4 meta::blogpost('we-may-not-need-rich-people-after-all', <<'__9fhRzhwBgaxbfhU28bx0Wqm9l/WaFNNqolnwkGyMPVw'); post:title = we-may-not-need-rich-people-after-all post:real-title = We may not need rich people after all post:status = published post:date = 2008-10-19 11:40:21 post:tags = economics financial follow-up off-topic post:format = html

After looking over the points made in Hank's comment, I'll have to declare agnosticism for now about the "greed-is-good" philosophy. Kudos to Hank for pointing out those fallacies!

__9fhRzhwBgaxbfhU28bx0Wqm9l/WaFNNqolnwkGyMPVw meta::blogpost('when-complexity-theory-isnt-enough', <<'__u6/ofBAH8P6SQJl6zbm3aOvdB0RJNaUpk8EgCBoSuA8'); post:title = when-complexity-theory-isnt-enough post:real-title = When Complexity Theory Isn't Enough post:status = published post:date = 2009-08-23 17:08:57 post:tags = bash complexity-theory post:format = html

When writing stateful shell scripts, I was very careful to have an average-case insertion/lookup time of O(1). This involved running md5sum externally to produce a good, presumably uniform hash value for the input string. This makes collisions extremely unlikely.

That O(1) figure is deceptive, though. It's true that for all practical purposes the computation is constant in the number of elements in the table, but the constant is huge. Since md5sum isn't a Bash builtin, it's got to create a whole new process, connect the input pipe to a string, grab the output, etc.

After noting the obvious inefficiency of such a strategy (it becomes obvious if you time the execution of a stateful shell script), I started thinking about more efficient ways to do the same thing. Some deliberation on this point led to the conclusion that I should use a binary searching algorithm with a complexity of O(log n). While having a higher asymptotic complexity, the point at which it becomes slower than the constant-time algorithm is far beyond any practical use-case. (Something on the order of 2100, I believe.)

So given this analysis, I'll write up an associative-array setup in Bash using binary instead of hash-based searching. Hopefully it will result in noticeably faster lookup and associate operations.

__u6/ofBAH8P6SQJl6zbm3aOvdB0RJNaUpk8EgCBoSuA8 meta::blogpost('why-state-is-evil', <<'__Jrnh2cBAvm0V7hoyIqTxIzGx83qAJbH3/xasx/qh6Es'); post:title = why-state-is-evil post:real-title = Why State is Evil post:status = published post:date = 2009-10-13 19:24:50 post:tags = best-practices functional state post:format = html

I was on one of my (frequent) rants about how more people need to make their variables constant when my wife stopped me. "How is functional programming fundamentally different from stateful, though, considering that both are Turing-complete?"

I had to think about that for a minute. Apart from the obvious multithreading scenario where shared-nothing is useful, it took me a while to come up with a reason that the traditional way of maintaining state (whether in the form of objects, local variables in long methods, etc.) was counterintuitive. After some thought, I was able to come up with this:

If your program is purely functional, then you do of course have "state" of a sort -- where you are in the program and which values you received as parameters. But any state you introduce into your program in the form of local variables and parameters all gets erased with the activation frame. So your state management shouldn't be any more complicated than the call stack can be.

However, in a stateful program you end up with state persisting beyond the end of a function call. If you combine this with impure functions, then you have the spaghetti-code equivalent of state -- control flow being determined very nonlocally in a way that's difficult to trace. The problem is compounded with multiple threads.

The problem becomes obvious when you look at it in a real-world context. Years ago there was a video game called Marble Drop in which you were supposed to put marbles into a series of tracks and into the properly colored bins. The challenge was to predict where the marbles would go, which wouldn't have been difficult except that the track had elements that would change each time a marble passed over them. And you could have multiple marbles running through the system at once, which meant that you had to be able to predict exactly when certain changes would happen. Designed as a challenging puzzle game, I was somewhat dismayed to notice the similarity to deciphering stateful code: http://www.youtube.com/watch?v=K3oe2dlTQxQ.

__Jrnh2cBAvm0V7hoyIqTxIzGx83qAJbH3/xasx/qh6Es meta::blogpost('why-we-need-rich-people', <<'__8+r8FCkB6u7FM15vsMd2AMG1jixJpzHMdVXgRkrI4Y4'); post:title = why-we-need-rich-people post:real-title = Why we need rich people post:status = published post:date = 2008-10-17 13:11:27 post:tags = economics financial off-topic post:format = html

I've often heard that rich people are greedy. This can certainly be true. After all, greed will prompt one to accumulate wealth. But many people who believe that rich people are greedy also believe that this is a problem.

I'm convinced that rich people need to be greedy to help everyone else.

Here's why. Suppose you have two non-greedy people, Bob and Joe. Bob and Joe are two college graduates each working for the same company and making the same salary. However, Bob works three times as fast as Joe does. So anytime the supervisor needs something done, he gives it to Bob. Since Bob can get three times as much stuff done as Joe can, the company saves a lot of money. Unfortunately, the company also doesn't need Joe anymore, since Bob is effectively taking the place of two other programmers. So the company lays off Joe and Stan, one of Bob and Joe's friends. The end result is that Bob is producing $150,000 of value for the company (since the company would be paying three programmers), but only making $50,000. Two other employees are out of work and not producing anything or making anything.

It gets worse. Suppose people like Bob all around the world just stay where they are and put other programmers out of work. Then employers will have a surplus of ordinary jobs being filled by exceptional people, and the market for ordinary jobs will get much more competitive. As a result, ordinary programmers will either have to apply to very difficult positions (those where people like Bob could be working if they wanted to), or they will have to apply to positions where they are paid even less than they would make working where they were before they got laid off. Also, organizations who do high-end stuff will have to close down because there are just not enough high-end programmers out there who want to work for them. So high-end and middle-level jobs are both lost, and more people are left to compete for the low-end jobs for which they are overqualified and underpaid.

Now let's rewind this scenario and make Bob greedy. He starts out working for the same salary as everyone else, but quickly becomes aware that he's more productive than his coworkers. After being passed up for promotion, he decides to look for a better job. He finds an opening for someone of his skills and is now being paid $120,000 per year. So what happens here? Bob still produces about $150,000 of value for his company, and Joe, Stan, and Dave (the guy who replaced Bob) are each still making $50,000 per year and producing at least that much for their company. The only difference is that Bob replaced one person in his high-end corporation instead of three people in the ordinary corporation. That's OK for ordinary people, because high-end programmers accustomed to making $120,000 per year are not likely to start looking for $50K jobs.

OK, so why should corporations pay high salaries to capable people? It makes sense from a supply-and-demand perspective, but it also helps less-capable workers keep their jobs. Here's why. At the beginning of the greedy-Bob scenario, he's working for the ordinary company and doing three people's worth of work. He probably enjoys the amount of attention he's getting from everyone because he's doing an exceptional job. So that provides him with some incentive to stay. However, he's also got a family to take care of, and the attention he's getting isn't worth $70,000 in income he could be making if he found a better job. But if the high-end company he found the $120K job with had been offering only $60,000 per year, Bob might have simply been able to negotiate a pay raise with his current employer and stay where he was.

We need to pay capable people more because it attracts them away from jobs that less-capable people could be working. This helps everyone; more capable people make more money, which isn't a bad thing, and less-capable people have higher job security.

Now, is it really a good thing for rich people to be greedy and hoard their money? Probably not. But regardless of how little they give back to the economy from their paycheck, they're doing the world a tremendous favor by producing whatever value they provide for their employers, and by increasing the job security of everyone who makes less money than they do.

__8+r8FCkB6u7FM15vsMd2AMG1jixJpzHMdVXgRkrI4Y4 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', <<'__jeDgVN1Gw3jpsylOJypAx27p+G2x41Ef3qUdWMoS3sQ'); my ($line, %settings) = @_; unless ($settings{'name'}) { return '
'  if $settings{'begin'};
  return '
' if $settings{'end'}; } $line =~ s/&/&/g; $line =~ s//>/g; return $line; __jeDgVN1Gw3jpsylOJypAx27p+G2x41Ef3qUdWMoS3sQ meta::data('default-action', <<'__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc'); shell __zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc meta::data('document', <<'__ipjOTC2TP1jEbhQf/Tx7esp4HA3+ZsQkz/NjLrkjftA'); script(jquery) script(jquery.anchor) script(jquery.toc) script(main) stylesheet(main) Spencer Tipping | Software Well Done include(header-div)
section(blog) section(meta) section(projects) section(cheloniidae) section(universal-translator) section(self-modifying-perl) section(jquery-plugins) section(obsidian) section(mathbio-simulation) section(mcm-entry) section(about-me)
__ipjOTC2TP1jEbhQf/Tx7esp4HA3+ZsQkz/NjLrkjftA meta::data('meta-associations', <<'__d9VdhMh4meb73mOywwym2+kdo9qu2LA+G2uSrGa4LyY'); ^function:: .pl ^internal_function:: .pl ^meta:: .pl ^bootstrap:: .pl ^data::vim-highlighter .vim ^data::document$ .html ^data::header$ .html ^section:: .clhtml ^unlit_converter:: .pl ^line_filter:: .pl ^code_filter:: .pl ^file::.*\.js$ .js ^file::.*\.css$ .css ^file::.*\.html$ .html ^blogpost:: .clhtml ^minifier:: .pl __d9VdhMh4meb73mOywwym2+kdo9qu2LA+G2uSrGa4LyY meta::data('name', <<'__+64EGwLEHtD9ik77A5vHgN1q9KHwxCD0JWGucF3aQ/4'); site __+64EGwLEHtD9ik77A5vHgN1q9KHwxCD0JWGucF3aQ/4 meta::data('post-content', <<'__qsMmUbEPVnxGG5tPJV1vsfpoWbU2jYvZpRr5IKshzyM'); __qsMmUbEPVnxGG5tPJV1vsfpoWbU2jYvZpRr5IKshzyM meta::data('pre-content', <<'__08DryJv6GHa3WO54tEhY+oplvHvtkP4IdfAtbUiFZtE');
__08DryJv6GHa3WO54tEhY+oplvHvtkP4IdfAtbUiFZtE meta::data('table-of-contents', <<'__a4ayc/80/OGda4BO/1o/V0etpOqiLx1JwB5S3beHW0s'); 1 __a4ayc/80/OGda4BO/1o/V0etpOqiLx1JwB5S3beHW0s meta::data('top-level-sections', <<'__/VTh01YAqvlcRb8Yguljj4Yh6bfNFqBlrGzITqXzQC0'); blog meta cheloniidae simulation operations-research __/VTh01YAqvlcRb8Yguljj4Yh6bfNFqBlrGzITqXzQC0 meta::data('vim-highlighter', <<'__mFCbKlCky8km0a6t5He/FDNQht+N8oWVIsdTX4oWQh4'); " Cleaner HTML " Maintainer: Spencer Tipping " Language: Cleaner HTML if version < 600 syntax clear elseif exists("b:current_syntax") finish endif syn match clhSection /^\s*- .*$/ syn match clhBegin /^begin$/ syn region clhVerbatim start=/^\s*::$/ end=/^\s*:\.$/ syn region clhBlogPost start=/^\s*post:/ end=/$/ contains=clhPostKeyword,clhPostValue syn match clhPostKeyword /[A-Za-z0-9_-]\+\s*=/ contained syn match clhPostValue /[^=]*$/ contained syn match clhEnumeratedThing /^\s*[ei]\[/ syn match clhEnumeratedThing /^\s*\][ei]/ syn match clhItem /^\s*+\s/ syn match clhQuantifiedItem /^\s*+\[[^\]]*\]\s/ runtime! syntax/html.vim hi link clhBegin Keyword hi link clhTitle Identifier hi link clhAuthor Identifier hi link clhDate Identifier hi link clhEnumeratedThing Special hi link clhItem Special hi link clhQuantifiedItem Special hi link clhSection Type hi link clhVerbatim String hi link clhBlogPost Special hi link clhPostKeyword Type hi link clhPostValue String let b:current_syntax = "clhtml" __mFCbKlCky8km0a6t5He/FDNQht+N8oWVIsdTX4oWQh4 meta::file('mcm/mcm-2007.py', <<'__dZ8HzfFvi1P+hIlJV//7AmLoPGL6jrfIPKBteHTuzwY'); # vim: set ts=4 sw=4 : # # Simulation code written by Spencer Tipping # Licensed under the LGPL, latest version. # from random import Random from sys import stderr from sys import stdout # # Generic simulation code # def main (): # # These are keys used to index connections between nodes. # class directions: north = "n" south = "s" east = "e" west = "w" def opposite (s): if s == directions.north: return directions.south if s == directions.south: return directions.north if s == directions.east: return directions.west if s == directions.west: return directions.east return None def ordinal (s): # # Right and down are positive; other directions # are negative. # if s == directions.north or s == directions.west: return -1 if s == directions.east or s == directions.south: return 1 return None class debugging: very_verbose = 5 quite_verbose = 4 moderately_verbose = 3 not_looped = 2 status = 1 output = 0 error = -1 tracing = True current_debug = very_verbose def debug (level, message_function): # # I'm using a message function for optimization purposes. If we don't end up # using this level of debugging, then there's no reason to compute the string # required. # if level <= debugging.current_debug: if level >= debugging.error: stdout.write (message_function () + "\n") else: stderr.write (message_function () + "\n") def shuffle (l): r = Random () new_list = [[r.randint (0, len (l)), x] for x in l] new_list.sort () return [y[1] for y in new_list] class node: # # Note that these row and file are mainly for reference # purposes so that we can have each node print out its # contents in a meaningful way. # # # Arranged like this: # # SSSS A SSS A SSSS # A A # SSSS A SSS A SSSS # ... # where vertical is row and horizontal is file. # def __init__ (self, row, file, nearest_luggage_bin): self.current_occupant = None self.row = row self.file = file self.nearest_luggage_bin = nearest_luggage_bin self.connectors = {directions.north: None, directions.south: None, directions.east: None, directions.west: None} def __str__ (self): if self.nearest_luggage_bin: return " B(%3d, %3d)B " % (self.row, self.file) else: return " (%3d, %3d) " % (self.row, self.file) def available (self): return self.current_occupant == None def enter (self, someone): self.current_occupant = someone someone.location = self return self def leave (self, someone): self.current_occupant = None return self def connect (self, direction, destination): # # Connects this to another node (and vice versa) and returns the destination. # self.connectors [direction] = destination destination.connectors [opposite(direction)] = self return destination def is_seat (self): return not (self.connectors [directions.north] or self.connectors [directions.south]) def is_aisle (self): return self.connectors [directions.north] or self.connectors [directions.south] def shoot_off (self, direction): n = self while n.connectors [direction]: n = n.connectors [direction] return n def travel (self, direction, distance): n = self for i in range (distance): n = n.connectors [direction] return n def travel_until_empty (self, direction): n = self count = 0 # # This function stops on borrowed nodes. # while n.connectors [direction] and n.connectors [direction].current_occupant and \ n.connectors [direction].current_occupant.location == n.connectors [direction]: n = n.connectors [direction] count += 1 return (count, n) def nearest_aisle (self): # # Returns a 2-tuple with (distance, aisle_cell). # if self.is_aisle (): return (0, self) else: west_offshoot = self west_counter = 0 east_offshoot = self east_counter = 0 while west_offshoot.connectors [directions.west] and not west_offshoot.connectors [directions.west].is_aisle (): west_offshoot = west_offshoot.connectors [directions.west] west_counter += 1 while east_offshoot.connectors [directions.east] and not east_offshoot.connectors [directions.east].is_aisle (): east_offshoot = east_offshoot.connectors [directions.east] east_counter += 1 if not west_offshoot.connectors [directions.west]: return (east_counter, east_offshoot) if not east_offshoot.connectors [directions.east]: return (west_counter, west_offshoot) if east_counter < west_counter: return (east_counter, east_offshoot) else: return (west_counter, west_offshoot) def trail (self, direction): n = self t = [n] while n.connectors [direction]: n = n.connectors [direction] t += [n] return t def compact_representation (self): if self.current_occupant: if self.current_occupant.location != self: # # The occupant is borrowing a cell. # return " -%03d-%01d- " % (self.current_occupant.personal_delay_counter, self.current_occupant.number_of_bags) else: if self.is_aisle (): return " #%03d-%01d# " % (self.current_occupant.personal_delay_counter, self.current_occupant.number_of_bags) else: return " ++%03d++ " % (self.current_occupant.needed_to_wait) else: if self.is_aisle (): return " | " else: return " - " class boarder: pre_boarding = None find_aisle = 0 find_row = 1 find_seat = 2 # # I'm using self.location to determine whether or not the boarder is on the plane. # If they are not, then the location is None, whereas if they are, it will be set # to someplace where they can find their way to their seats. # def __init__ (self, location, target, number_of_bags, aisles_on_plane, SS, AS, SA, AA): self.target = target self.number_of_bags = number_of_bags self.personal_delay_counter = 0 self.aisles_on_plane = aisles_on_plane self.location = boarder.pre_boarding self.seek_phase = boarder.find_aisle self.closest_aisle = None self.SS = SS self.SA = SA self.AS = AS self.AA = AA self.sequence_identifier = 0 self.needed_to_wait = 0 self.borrowed_cells = [] # # Create a back-reference to the passenger. # target.passenger = self def __str__ (self): return "#%4d: %s --> %s; delay %4d; carrying %2d bags" % \ (self.sequence_identifier, str (self.location), str (self.target), self.personal_delay_counter, self.number_of_bags) def number_of_bags_factor (self): return self.number_of_bags_factor_function (self.number_of_bags) def finished (self): return self.location == self.target and self.personal_delay_counter == 0 def step (self): if self.personal_delay_counter == 0: for cell in self.borrowed_cells: if cell.current_occupant == self: cell.current_occupant = None else: debug (debugging.error, lambda: "%s: Inconsistency in cell ownership of %s" % (str (self.location), str (cell))) self.borrowed_cells = [] if self.location != self.target and self.location: if self.seek_phase == boarder.find_aisle: # # We find the aisle closest to our seat. # if not self.closest_aisle: self.closest_aisle = min ([[abs (a.file - self.target.file), a.file] for a in self.aisles_on_plane]) [1] # # Now, navigate towards that aisle. # if self.location.file < self.closest_aisle: self.next_direction = directions.east elif self.location.file > self.closest_aisle: self.next_direction = directions.west else: self.seek_phase = boarder.find_row self.next_direction = None if self.next_direction: if self.location.connectors [self.next_direction].available (): self.personal_delay_counter += self.AA () self.location.leave (self).connectors [self.next_direction].enter (self) else: self.needed_to_wait += 1 # # We're going to go ahead and fall through to the next if statement. # if self.seek_phase == boarder.find_row: # # Now, we embark upon the simple task of locating our row. # if self.location.row < self.target.row: self.next_direction = directions.south elif self.location.row > self.target.row: self.next_direction = directions.north else: self.seek_phase = boarder.find_seat self.next_direction = None if self.next_direction: if self.location.connectors [self.next_direction]: if self.location.connectors [self.next_direction].available (): self.location.leave (self).connectors [self.next_direction].enter (self) self.personal_delay_counter += self.AA () else: self.needed_to_wait += 1 #debug (debugging.very_verbose, lambda: " > Waiting") else: debug (debugging.error, lambda: " > %s is trying to go along nonexistent path %s" % (str (self), self.next_direction)) if self.seek_phase == boarder.find_seat: # # Finally, we find the seat in question. # First, we make sure that we don't have any bags. # if self.number_of_bags > 0 and self.location.nearest_luggage_bin: self.personal_delay_counter += self.location.nearest_luggage_bin.load_delay (self.number_of_bags) self.number_of_bags = 0 if self.location.file > self.target.file: self.next_direction = directions.west elif self.location.file < self.target.file: self.next_direction = directions.east else: debug (debugging.quite_verbose, lambda: "Found my seat!") self.next_direction = None if self.next_direction and self.location != self.target and self.location.connectors [self.next_direction]: if self.location.connectors [self.next_direction].available (): # # This is the simplest case. The cell that we want to move into is available. # if self.location.is_aisle (): self.personal_delay_counter += self.AS () else: self.personal_delay_counter += self.SS () self.borrowed_cells += [self.location] self.location.connectors [self.next_direction].enter (self) else: # # We need to figure out how many people we have to cross. # number_of_people_to_cross = self.location.travel_until_empty (self.next_direction) [0] if number_of_people_to_cross == 0: # # The adjacent cell is borrowed, so we must wait for it to clear. # self.needed_to_wait += 1 else: # # If we can borrow a spot in the aisle, then do that. # To pull off the borrowing part, we'll be in both places simultaneously. :) # next_cell = self.location.connectors [self.next_direction] if self.location.is_aisle (): # # Try to claim adjacency for the aisle so that we can get more space # and avoid shuffling (not explcitly simulated). # south_aisle_cell = self.location.connectors [directions.south] if south_aisle_cell and south_aisle_cell.available (): # # We have more space to work with, so we can save some time. # mandatory_delay = 0 else: # # Simulate some aisle-shuffling that would take extra time. # mandatory_delay = self.AA () + self.AA () if number_of_people_to_cross == 1: # # The cell may be borrowed. This is possible because travel_until_empty stops # when it hits a borrowed or empty cell. # if next_cell.connectors [self.next_direction].available (): self.borrowed_cells += [self.location] if south_aisle_cell and south_aisle_cell.available (): self.borrowed_cells += [south_aisle_cell] south_aisle_cell.current_occupant = self mandatory_delay += max (self.AA (), self.SS ()) + self.AS () + max (self.SS (), self.AS ()) self.personal_delay_counter += mandatory_delay next_cell.current_occupant.personal_delay_counter += mandatory_delay next_cell.connectors [self.next_direction].enter (self) else: self.needed_to_wait += 1 elif number_of_people_to_cross == 2: # # If we have to cross two people, then we are going after our target because # no seat is farther than three away from the aisle. # if self.target.available (): self.borrowed_cells += [self.location] if south_aisle_cell and south_aisle_cell.available (): self.borrowed_cells += [south_aisle_cell] south_aisle_cell.current_occupant = self mandatory_delay += max (self.AA (), self.SA (), self.SS ()) + \ self.SA () + self.AS () + max (self.SA (), self.SS ()) + \ max (self.SS (), self.SS (), self.AS ()) self.personal_delay_counter += mandatory_delay next_cell.current_occupant.personal_delay_counter += mandatory_delay next_cell.connectors [self.next_direction].current_occupant.personal_delay_counter += mandatory_delay self.target.enter (self) else: self.needed_to_wait += 1 else: debug (debugging.error, lambda: "%s: Too many people to handle crossing process" % str (self.location)) else: # # Use the simple function for the number of spots to cross mid-seat. # First, though, we need the aisle to be available so that we can swap. # aisle_cell = self.location.connectors [opposite (self.next_direction)] if aisle_cell.available () and self.target.available () and \ next_cell.current_occupant.personal_delay_counter == 0: aisle_cell.current_occupant = self self.borrowed_cells += [aisle_cell, self.location] mandatory_delay = max (self.SA (), self.SS ()) + self.SA () + self.AS () + max (self.SS (), self.AS ()) self.personal_delay_counter += mandatory_delay + self.SS () next_cell.current_occupant.personal_delay_counter += mandatory_delay + self.SS () self.target.enter (self) else: self.needed_to_wait += 1 class luggage_bin: def __init__ (self, bag_capacity, load_delay_function): self.bag_capacity = bag_capacity self.current_load = 0 self.delay = load_delay_function def load_one_bag (self): self.current_load += 1 return self.delay (self.current_load, self.bag_capacity) def load_delay (self, additional_load): return sum ([self.load_one_bag () for i in range (additional_load)]) class aisle: # # An aisle is just the collection of nodes that makes up the # aisle portion of an aircraft. It does not include any seats. # Seats are added by using add_window_row and add_bridge_row. # def __init__ (self, rows, file, bin_capacity, bin_load_delay_function, bin_row_span): last_bin = luggage_bin (bin_capacity, bin_load_delay_function) self.head = node (0, file, last_bin) self.file = file self.nodes = self.head, self.rows = rows p = self.head last_bin_row = 1 # # The rows will go from 0 to rows - 1, inclusive. # I'm using 1..rows because this translates to 1..rows - 1, and we # added the zero row above. # for i in range (1, rows): if i > last_bin_row + bin_row_span - 1: last_bin = luggage_bin (bin_capacity, bin_load_delay_function) last_bin_row = i n = node (i, file, last_bin) p.connect (directions.south, n) self.nodes += n, p = n self.tail = n def add_window_row (self, row, direction, files, major_file): # # Returns the window seat. # base = self.nodes [row] for i in range (files): base = base.connect (direction, node (row, self.file + (i+1) * ordinal (direction), None)) base.major_file = major_file return base def add_bridge_row (self, bridged_aisle, row, direction, files, major_file): # # Returns the seat in the bridged aisle to which this aisle connects. # return self.add_window_row (row, direction, files, major_file).connect ( \ direction, bridged_aisle.nodes [row]) class grid_plane_geometry: def __init__ (self, rows, file_count_list, \ row_select_function, number_of_bags_function, SS, AS, SA, AA, bin_capacity, \ bin_load_delay_function, bin_row_span): # # Build the aisles at the appropriate files. The reason I'm subtracting one # is because there are three file widths here, and only two aisles: # # 3 3 4 # SSS|SSS|SSSS # SSS|SSS|SSSS # # ... # self.aisles = [aisle (rows + 1, sum (file_count_list [0:i+1]) + i, \ bin_capacity, bin_load_delay_function, bin_row_span) \ for i in range (len (file_count_list) - 1)] self.file_count_list = file_count_list self.start_location = self.aisles [0].head self.passengers = [] self.rows = rows + 1 # # Create the bridge for the first row. There won't be passengers here, just some nodes # to allow them to cross. # for aisle_index in range (1, len (self.aisles)): self.aisles [aisle_index - 1].add_bridge_row ( \ self.aisles [aisle_index], 0, directions.east, file_count_list [aisle_index], aisle_index) for row in range (1, rows + 1): # # First, connect all of the aisles together east-west. # self.aisles [0].add_window_row (row, directions.west, file_count_list [0], 0) for aisle_index in range (1, len (self.aisles)): self.aisles [aisle_index - 1].add_bridge_row ( \ self.aisles [aisle_index], row, directions.east, file_count_list [aisle_index], aisle_index) self.aisles [len (self.aisles) - 1].add_window_row (row, directions.east, file_count_list [len (file_count_list) - 1], len (self.aisles)) # # Next, put a passenger in each seat. # However, we're using the filter-lambda combination to eliminate aisle seats # from that list (those being ones with north or south connections). # self.passengers += [boarder (boarder.pre_boarding, x, number_of_bags_function (), \ self.aisles, SS, AS, SA, AA) \ for x in filter (lambda cell: not (cell.connectors [directions.north] or cell.connectors [directions.south]), \ self.row (row))] # # Now, re-index all of the passengers. # for i in range (len (self.passengers)): self.passengers [i].sequence_identifier = i def row (self, index): return self.aisles [0].nodes [index].shoot_off (directions.west).trail (directions.east) def __str__ (self): s = "" for i in range (self.rows): for cell in self.row (i): s += str (cell) s += "\n" return s def compact_representation (self): s = "" for i in range (self.rows): for cell in self.row (i): s += cell.compact_representation () s += "\n" return s class two_floor_plane_geometry: upper_floor = "upper" lower_floor = "lower" def __init__ (self, lower_geometry, upper_geometry, floor_change_time_function): self.lower_geometry = lower_geometry self.upper_geometry = upper_geometry self.floor_change_time_function = floor_change_time_function self.passengers = lower_geometry.passengers + upper_geometry.passengers self.start_location = lower_geometry.start_location self.rows = lower_geometry.rows # # Change the floors of each geometry. # for row in range (lower_geometry.rows): for cell in lower_geometry.row (row): cell.floor = two_floor_plane_geometry.lower_floor for row in range (upper_geometry.rows): for cell in upper_geometry.row (row): cell.floor = two_floor_plane_geometry.upper_floor def board (self, passenger): if passenger.target.floor == two_floor_plane_geometry.upper_floor: self.upper_geometry.start_location.enter (passenger) else: self.lower_geometry.start_location.enter (passenger) def available (self): return self.upper_geometry.start_location.available () and \ self.lower_geometry.start_location.available () def compact_representation (self): return "Upper floor:\n" + self.upper_geometry.compact_representation () + \ "\nLower floor:\n" + self.lower_geometry.compact_representation () class combined_plane_geometry: def __init__ (self, north_geometry, south_geometry, binding_function = \ lambda north, south: [north.aisles [x].tail.connect (directions.south, south.aisles [x].head) \ for x in range (len (north.aisles))]): binding_function (north_geometry, south_geometry) self.passengers = [p for p in north_geometry.passengers + south_geometry.passengers] self.rows = north_geometry.rows + south_geometry.rows self.aisles = north_geometry.aisles self.north_geometry = north_geometry self.south_geometry = south_geometry # # Add the new sequencing index to the south group of passengers. # Also, properly set their aisle knowledge. # max_in_north = max ([p.sequence_identifier for p in north_geometry.passengers]) for p in south_geometry.passengers: p.sequence_identifier += max_in_north p.aisles_on_plane = north_geometry.aisles # # Also, update the row indices appropriately. # To do this, we'll just bump them up by the number of rows in the northern part. # for row_index in range (south_geometry.rows): for n in south_geometry.row (row_index): n.row += north_geometry.rows binding_function (north_geometry, south_geometry) def row (self, index): if index >= self.north_geometry.rows: return self.north_geometry.row (index) else: return self.south_geometry.row (index) def __str__ (self): return str (self.north_geometry) + "\n\n" + str (self.south_geometry) def compact_representation (self): return self.north_geometry.compact_representation () + "\n\n" + self.south_geometry.compact_representation () class single_entrance_manager: def __init__ (self, entrance): self.entrance = entrance def board (self, person): self.entrance.enter (person) def available (self): return self.entrance.current_occupant == None class simulation: def __init__ (self, plane, boarding_function): self.plane = plane self.boarding_function = boarding_function def run (self, passenger_selector_function = lambda p: True, boarding_delay_function = lambda: 8, time_step = 1): time = 0 iterations = 0 next_boarding = 0 debug (debugging.status, lambda: "Beginning simulation...") debug (debugging.not_looped, lambda: str (self.plane) + "\n") currently_unboarded = self.plane.passengers currently_unfinished = [] queue = [] while len (currently_unfinished) or len (currently_unboarded) > 0 or len (queue) > 0: iterations += 1 if debugging.tracing and iterations % 1 == 0: debug (debugging.quite_verbose, lambda: self.plane.compact_representation () + "\n" + str (int (time)) + "\n") if len (queue) == 0 and len (currently_unboarded) > 0: # # The boarding function is used so that we can choose to board people in # stages; for example, we may want to board first-class first and then let # everyone else file in randomly. # queue += self.boarding_function (time, currently_unboarded) debug (debugging.quite_verbose, lambda: "Enqueued %d person(s)" % len (queue)) for passenger in queue: if passenger in currently_unboarded: currently_unboarded.remove (passenger) else: debug (debugging.quite_verbose, lambda: "") # # The plane will decide how to board queued passengers. # if len (queue) > 0 and self.plane.available () and time > next_boarding: debug (debugging.quite_verbose, lambda: "Plane: Boarding one person") passenger = queue.pop (0) # # Add this passenger to the "unfinished" list and board them onto the plane. # Also, set the clock for that person to be synchronized with the "since-the-beginning- # of-the-boarding-process" clock, and set the appropriate delay before adding the # next person. # currently_unfinished += [passenger] self.plane.board (passenger) next_boarding = time + boarding_delay_function () else: debug (debugging.quite_verbose, lambda: "") time += time_step delete_necessary = False for p in currently_unfinished: p.personal_delay_counter -= time_step if p.personal_delay_counter < 0: p.personal_delay_counter = 0 p.step () if p.finished (): delete_necessary = True if delete_necessary: copy = currently_unfinished for p in copy: if p.finished (): currently_unfinished.remove (p) return time # # Planes # class airbus_320 (single_entrance_manager, grid_plane_geometry): name = "airbus-320" def __init__ (self, number_of_bags_function, bin_load_delay_function, SS, AS, SA, AA): grid_plane_geometry.__init__ (self, 23, (3, 3), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 4, bin_load_delay_function, 2) single_entrance_manager.__init__ (self, self.aisles [0].head) S1 = airbus_320 class boeing_767_200 (single_entrance_manager, combined_plane_geometry): name = "boeing-767-200" def __init__ (self, number_of_bags_function, bin_load_delay_function, SS, AS, SA, AA): combined_plane_geometry.__init__ (self, \ grid_plane_geometry (8, (2, 2, 2), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 8, bin_load_delay_function, 3), \ grid_plane_geometry (25, (2, 3, 2), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 8, bin_load_delay_function, 3)) single_entrance_manager.__init__ (self, self.aisles [0].head) S2 = boeing_767_200 class boeing_767_400 (single_entrance_manager, combined_plane_geometry): name = "boeing-767-400" def __init__ (self, number_of_bags_function, bin_load_delay_function, SS, AS, SA, AA): combined_plane_geometry.__init__ (self, \ grid_plane_geometry (8, (2, 2, 2), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 8, bin_load_delay_function, 3), \ grid_plane_geometry (25, (2, 3, 2), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 8, bin_load_delay_function, 3)) single_entrance_manager.__init__ (self, self.aisles [0].head) M1 = boeing_767_400 class airbus_a300_600 (single_entrance_manager, grid_plane_geometry): name = "airbus-a300-600" def __init__ (self, number_of_bags_function, bin_load_delay_function, SS, AS, SA, AA): grid_plane_geometry.__init__ (self, 50, (2, 4, 2), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 4, bin_load_delay_function, 2) single_entrance_manager.__init__ (self, self.aisles [0].head) M2 = airbus_a300_600 class boeing_747 (single_entrance_manager, grid_plane_geometry): name = "boeing-747" def __init__ (self, number_of_bags_function, bin_load_delay_function, SS, AS, SA, AA): grid_plane_geometry.__init__ (self, 40, (3, 4, 3), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 4, bin_load_delay_function, 2) single_entrance_manager.__init__ (self, self.aisles [0].head) L1 = boeing_747 class airbus_380 (single_entrance_manager, two_floor_plane_geometry): name = "airbus-380" def __init__ (self, number_of_bags_function, bin_load_delay_function, SS, AS, SA, AA): two_floor_plane_geometry.__init__ (self, \ grid_plane_geometry (40, (3, 4, 3), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 4, bin_load_delay_function, 2), \ grid_plane_geometry (30, (2, 4, 2), \ lambda row: True, number_of_bags_function, SS, AS, SA, AA, 4, bin_load_delay_function, 2), \ SS) def board (self, passenger): two_floor_plane_geometry.board (self, passenger) def available (self): return two_floor_plane_geometry.available (self) L2 = airbus_380 # # Test code # # # Passenger queues # def blocks (unboarded_passengers, number_of_blocks): # # This isn't a queue function; rather, it just breaks the plane # into roughly equal blocks (actually, the blocks are adaptively # sized to be roughly equal for the number of passengers remaining). # farthest_back_row = max ([p.target.row for p in unboarded_passengers]) # # Choose even sections. # zone = range (number_of_blocks) for i in range (len (zone)): zone [i] = filter (lambda p: p.target.row * number_of_blocks / (farthest_back_row + 1) == i, unboarded_passengers) return zone def random_loader (time, unboarded_passengers): return shuffle (unboarded_passengers) random_loader.name = "pre_assigned_random" def sequential_loader (time, unboarded_passengers): return unboarded_passengers sequential_loader.name = "sequential" def sequential_block_loader (time, unboarded_passengers): b = blocks (shuffle (unboarded_passengers), 5) return b[0] + b[1] + b[2] + b[3] + b[4] sequential_block_loader.name = "sequential_block" def reverse_block_loader (time, unboarded_passengers): b = blocks (shuffle (unboarded_passengers), 5) return b[4] + b[3] + b[2] + b[1] + b[0] reverse_block_loader.name = "reverse_block" def reverse_loader (time, unboarded_passengers): unboarded_passengers.reverse () return unboarded_passengers reverse_loader.name = "reverse_sequential" def outside_in_loader (time, unboarded_passengers): maximum_distance_to_aisle = max ([p.target.nearest_aisle ()[0] for p in unboarded_passengers]) return filter (lambda p: p.target.nearest_aisle ()[0] == maximum_distance_to_aisle, shuffle (unboarded_passengers)) outside_in_loader.name = "outside_in" def reverse_pyramid_loader (time, unboarded_passengers): maximum_distance_to_aisle = max ([p.target.nearest_aisle ()[0] for p in unboarded_passengers]) farthest_back_row = max ([p.target.row for p in unboarded_passengers]) return filter (lambda p: \ (maximum_distance_to_aisle - p.target.nearest_aisle ()[0]) * (farthest_back_row / 2) < \ p.target.row, shuffle (unboarded_passengers)) reverse_pyramid_loader.name = "reverse_pyramid" def rotating_block_loader (time, unboarded_passengers): b = blocks (shuffle (unboarded_passengers), 5) return b[0] + b[4] + b[1] + b[3] + b[2] rotating_block_loader.name = "rotating_block" # # Variations on the queue functions # def staggered_adapter (previous_method): return lambda time, unboarded_passengers: \ filter (lambda p: p.target.row % 2 == p.target.major_file % 2, previous_method (time, unboarded_passengers)) + \ filter (lambda p: p.target.row % 2 != p.target.major_file % 2, previous_method (time, unboarded_passengers)) staggered_adapter.name = "staggered" def even_odd_adapter (previous_method): return lambda time, unboarded_passengers: \ filter (lambda p: p.target.row % 2 == 0, previous_method (time, unboarded_passengers)) + \ filter (lambda p: p.target.row % 2 == 1, previous_method (time, unboarded_passengers)) even_odd_adapter.name = "even_odd" def identity_adapter (previous_method): return lambda time, unboarded_passengers: previous_method (time, unboarded_passengers) identity_adapter.name = "original" # # Running routines # def plane_generator (plane, r, SS, AS, SA, AA, bin_load_delay): return plane ( number_of_bags_function = lambda: r.randint (0, 2), \ bin_load_delay_function = lambda t, c: t**0.5 * r.gauss (bin_load_delay, bin_load_delay / 6.0), \ SS = SS, AS = AS, SA = SA, AA = AA) def run_single_simulation (): r = Random () debugging.current_debug = debugging.very_verbose debugging.tracing = True debug (debugging.status, lambda: "Building aircraft model and passenger list...") debug (debugging.output, lambda: "Simulation: boarding took %s units of time." % \ simulation (plane_generator (S2, r, \ lambda: r.gauss (7.0, 2.0), \ lambda: r.gauss (3.0, 0.8), \ lambda: r.gauss (3.5, 0.4), \ lambda: r.gauss (2.0, 0.3), \ 3.0), \ boarding_function = staggered_adapter (reverse_block_loader)).run ( \ passenger_selector_function = lambda passenger: True, \ boarding_delay_function = lambda: r.gauss (7.0, 1.0), \ time_step = 0.5)) def run_statistical_batch_simulation (planes, sensitivity_test_levels, how_many_adapters = 1, trial_count = 200): r = Random () debugging.current_debug = debugging.error debugging.tracing = False boarding_functions = (reverse_block_loader, rotating_block_loader, random_loader, reverse_pyramid_loader, outside_in_loader) adapters = [identity_adapter, even_odd_adapter, staggered_adapter][:how_many_adapters] adjustable_parameters = (7.0, 3.0, 3.5, 2.0, 2.0, 0.8, 0.4, 0.3, 2.0, 7.0) possibilities = [[1.0, 1.0, 1.0, 1.0, 1.0, d] for d in sensitivity_test_levels.keys ()] + \ [[1.0, 1.0, 1.0, 1.0, c, 1.0] for c in sensitivity_test_levels.keys ()] + \ [[1.0, 1.0, 1.0, b, 1.0, 1.0] for b in sensitivity_test_levels.keys ()] + \ [[1.0, 1.0, a, 1.0, 1.0, 1.0] for a in sensitivity_test_levels.keys ()] + \ [[1.0, x, 1.0, 1.0, 1.0, 1.0] for x in sensitivity_test_levels.keys ()] + \ [[y, 1.0, 1.0, 1.0, 1.0, 1.0] for y in sensitivity_test_levels.keys ()] if len (possibilities) == 0: possibilities = [[1.0] * 6] sensitivity_test_levels = {1.0: 'n'} else: sensitivity_test_levels[1.0] = 'n' possibility_description = lambda p: sensitivity_test_levels[p[0]] + sensitivity_test_levels[p[1]] + \ sensitivity_test_levels[p[2]] + sensitivity_test_levels[p[3]] + \ sensitivity_test_levels[p[4]] + sensitivity_test_levels[p[5]] trials_per_configuration = trial_count time_step = 1 for plane in planes: for possibility in possibilities: if len (possibilities) > 1: current_file = file (plane.name + possibility_description (possibility), 'w') else: current_file = file (plane.name, 'w') for a in adapters: for b in boarding_functions: current_file.write ("%s_%s\t" % (a.name, b.name)) debug (debugging.status, lambda: "%s_%s\n" % (a.name, b.name)) current_file.write ("\n") for trial in range (trials_per_configuration): for a in adapters: for b in boarding_functions: immediate_result = str (simulation ( \ plane = plane_generator (plane, r, \ lambda: r.gauss (adjustable_parameters [0] * possibility [0], adjustable_parameters [4] * possibility [0]), \ lambda: r.gauss (adjustable_parameters [1] * possibility [1], adjustable_parameters [5] * possibility [1]), \ lambda: r.gauss (adjustable_parameters [2] * possibility [2], adjustable_parameters [6] * possibility [2]), \ lambda: r.gauss (adjustable_parameters [3] * possibility [3], adjustable_parameters [7] * possibility [3]), \ adjustable_parameters [8] * possibility [4]), \ boarding_function = a (b)).run ( \ passenger_selector_function = lambda passenger: True, \ boarding_delay_function = lambda: \ r.gauss (adjustable_parameters [9] * possibility [5], possibility [5]), \ time_step = time_step)) current_file.write (immediate_result + "\t") debug (debugging.status, lambda: immediate_result + "\n") current_file.write ("\n") current_file.flush () debug (debugging.status, lambda: "\n") current_file.close () run_single_simulation () # run_statistical_batch_simulation ([L2], {}, 3, 200) # run_statistical_batch_simulation ([S1, S2, M1, M2, L1, L2], {0.5: 'l', 1.75: 'h'}, 1, 25) main () __dZ8HzfFvi1P+hIlJV//7AmLoPGL6jrfIPKBteHTuzwY meta::file('objects/class', <<'__P98jjKXN1Mx1BiXmtRN+nNWN+VIcZokvYtirR5d+5jY'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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::data', <<'__96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek'); 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"); }; }; __96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek 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::implementation', <<'__HlPiT4rdiKooLTNwpSFca93RrAG3djkduI7cDiOVkmY'); meta::define_form 'implementation', sub {}; __HlPiT4rdiKooLTNwpSFca93RrAG3djkduI7cDiOVkmY 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('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA'); meta::define_form 'internal', \&meta::meta::implementation; __Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA meta::bootstrap('initialization', <<'__bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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 $@; }; __bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs 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::data('default-action', <<'__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc'); shell __zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc meta::data('name', <<'__CIkRPgTTID8MQBwXwP2LNSt0DcYHQzd50+3KoTMgsAE'); class __CIkRPgTTID8MQBwXwP2LNSt0DcYHQzd50+3KoTMgsAE 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('clone', <<'__qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY'); for (@_) { if ($_) { eval { file::write($_, serialize(), noclobber => 1); chmod(0700, $_); print "File $_ cloned successfully.\n"; }; print "$@\n" if $@; } } __qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY 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', <<'__4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY'); my ($name, $value) = @_; messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data; associate($name, $value || join('', ) || "\n"); __4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY meta::function('edit', <<'__OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ'); 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(); __OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ meta::function('exists', <<'__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk'); my $name = shift; grep {$_ eq $name} keys %data; __bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk meta::function('grab', <<'__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8'); my ($filename, @attribute_names) = @_; associate("implementation::$_", `$filename cat $_`) for @attribute_names; __sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8 meta::function('import', <<'__oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA'); my ($name) = @_; associate($name, join('', )); __oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA meta::function('lock', <<'__VTyQSQauC4LFHxYTJhus499/qkzVt6stbxsovwA5kGs'); my (undef, undef, $mode) = stat $0; chmod $mode & 0555, $0; __VTyQSQauC4LFHxYTJhus499/qkzVt6stbxsovwA5kGs meta::function('ls', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18'); join("\n", map {" $_"} sort keys %data) . "\n"; __6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18 meta::function('mv', <<'__PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y meta::function('pull', <<'__IIDq/dsWHEYECpYpzXqTvkcve38taGb7d0m+Vsq+TQk'); 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 "$_"`); } __IIDq/dsWHEYECpYpzXqTvkcve38taGb7d0m+Vsq+TQk meta::function('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM'); execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data); __GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM 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', <<'__HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto'); 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 { file::write($0, $serialized_data); unlink $temporary_filename; } __HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto 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', <<'__1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $prompt = name() . '$ '; my $OUT = $term->OUT || \*STDOUT; while (defined ($_ = $term->readline($prompt))) { my $command_line = $_; my @args = split /\s+/; my $function_name = shift @args; return if $function_name eq 'exit'; 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"); } $term->addhistory($command_line) if $command_line; $prompt = name() . '$ '; } __1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4 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', <<'__2f3NoSvgge16m8wBuDokogr54ssy8D8fKdZ7+RtZitE'); my (undef, undef, $mode) = stat $0; chmod $mode | 0200, $0; __2f3NoSvgge16m8wBuDokogr54ssy8D8fKdZ7+RtZitE meta::function('usage', <<'__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I'); <<"EOD" . join ' ', split /\n/, ls (); Usage: $0 [options] action [arguments] Defined actions: EOD __oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I meta::implementation('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::implementation('meta::datatypes::implementation', <<'__HlPiT4rdiKooLTNwpSFca93RrAG3djkduI7cDiOVkmY'); meta::define_form 'implementation', sub {}; __HlPiT4rdiKooLTNwpSFca93RrAG3djkduI7cDiOVkmY 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('execute', <<'__FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(basename($name), retrieve($name))}; carp $@ if $@ && $options{'carp'}; __FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE 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', <<'__4Qkh4HQjoCLvAQ83W1ClaVM5dkrhv2YNMxIHO18k1cA'); 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) . $extension, OPEN => 0); file::write($filename, $data); system("$editor $options \"$filename\""); my $result = file::read($filename); unlink $filename; $result; __4Qkh4HQjoCLvAQ83W1ClaVM5dkrhv2YNMxIHO18k1cA 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', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk 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::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__ __P98jjKXN1Mx1BiXmtRN+nNWN+VIcZokvYtirR5d+5jY meta::file('objects/literate-ocaml-project', <<'__mCenmYz3wjgcf77IzQUJSMKnjn131e5HtYBp9F0mAdw'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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', <<'__96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek'); 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"); }; }; __96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek 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::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::profile', <<'__ooDHth9M5OueUrUkgnPd0FXBXu16i2n8xg0w+pmXkVE'); meta::define_form 'profile', sub { my ($name, $value) = @_; *{"profile::$name"} = eval "sub {\n$value\n}"; carp $@ if $@; }; __ooDHth9M5OueUrUkgnPd0FXBXu16i2n8xg0w+pmXkVE meta::meta('datatypes::section', <<'__dyHqM5u2qiIza2BZZUe8tCJbUbz1UmJwCpsYozFvBmc'); meta::define_form 'section', sub {}; __dyHqM5u2qiIza2BZZUe8tCJbUbz1UmJwCpsYozFvBmc 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('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA'); meta::define_form 'internal', \&meta::meta::implementation; __Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA meta::bootstrap('initialization', <<'__bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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 $@; }; __bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs 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('ocaml', <<'__QFbEjTsynjJMakDMUcNnuimkfFrOIIFYhlOcz0dVOwI'); my ($line, %settings) = @_; if ($settings{'name'} =~ /\socaml(\s|$)/) { return '\begin{ocamlcode}' if $settings{'begin'}; return '\end{ocamlcode}' if $settings{'end'}; } return $line; __QFbEjTsynjJMakDMUcNnuimkfFrOIIFYhlOcz0dVOwI 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('cltex-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::data('default-action', <<'__xDKzcuDjAmfmXiahKkLHlXq1LiXf48bUuSkhPYiWXkU'); intro __xDKzcuDjAmfmXiahKkLHlXq1LiXf48bUuSkhPYiWXkU meta::data('default-profile', <<'__rLolUSEA+AtW/DzNFMZb5V2UgAzad1hcX0Goh+OY+b4'); run __rLolUSEA+AtW/DzNFMZb5V2UgAzad1hcX0Goh+OY+b4 meta::data('document', <<'__38zrG0j5TGSHp0RwIEolgs0s7tPFRMJUty0obo+Z+7s'); = Literate OCaml Project a Spencer Tipping begin - Introduction The \TeX{} document generator is a simple utility for encapsulating the generation and compilation of \TeX{} documents. Combined with literate OCaml processing, it provides a way to simultaneously write good documentation and simplify the build process for your program. It simplifies: e[ + Writing \TeX{} documents quickly + Writing literate code ]e - Usage The easiest way to use the system is to clone a new project, like this: :: $ literate-ocaml-project clone my-project :. Then you can edit your new project by entering its shell and issuing the ``e'' command: :: $ my-document shell my-document$ e :. __38zrG0j5TGSHp0RwIEolgs0s7tPFRMJUty0obo+Z+7s meta::data('header', <<'__SonLChWgyOqhK+2WcupCsI3GoJLNWo9cobO0PIXVAHI'); \documentclass{article} \usepackage{palatino,mathpazo,amsmath,amssymb,listings} \lstnewenvironment{ocamlcode} {\lstset{language=[Objective]caml,frame=tlb,basicstyle={\scriptsize\tt}}} {} __SonLChWgyOqhK+2WcupCsI3GoJLNWo9cobO0PIXVAHI 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', <<'__N3KxYcBOzsS3OT1/dhxdDQoOlLHlHzvjQUHQVbvR1XM'); ^function:: .pl ^internal_function:: .pl ^meta:: .pl ^bootstrap:: .pl ^data::document$ .cltex -vim-highlighter$ .vim ^section:: .ocamltex ^unlit_converter:: .pl ^line_filter:: .pl ^code_filter:: .pl ^profile:: .pl __N3KxYcBOzsS3OT1/dhxdDQoOlLHlHzvjQUHQVbvR1XM meta::data('name', <<'__qUJIiBdWgxwK2BNKAYW1jW2bfWWB3gersY5X5lBXcO8'); literate-ocaml-project __qUJIiBdWgxwK2BNKAYW1jW2bfWWB3gersY5X5lBXcO8 meta::data('ocamltex-vim-highlighter', <<'__t466fjjfb63cNJow0A3UqQQB7IIO1JX9bvEyjonZT4k'); " TeX with OCaml " Maintainer: Spencer Tipping " Language: TeX with OCaml in it if version < 600 syntax clear elseif exists("b:current_syntax") finish endif syn region ocamltexComment start=/\%^\|^\s*:.$/ end=/\%$\|^\s*::.*\.*$/ contains=ocamltexSection syn match ocamltexSection /^\s*- .*$/ contained runtime! syntax/ocaml.vim hi link ocamltexSection Special hi link ocamltexComment Comment let b:current_syntax = "ocamltex" __t466fjjfb63cNJow0A3UqQQB7IIO1JX9bvEyjonZT4k meta::data('output-dir', <<'__6WcazSRIScVxZ8ZY+i+Wl1IEj3qxhKPc9cRstNVq4SQ'); /tmp __6WcazSRIScVxZ8ZY+i+Wl1IEj3qxhKPc9cRstNVq4SQ meta::data('pdf-output-file', <<'__oZdf0jyS/kLpZfGwwuMI2OK/tt4p1OwCoY0Z0N9MWmQ'); /tmp/literate-ocaml-project.Q7j-MmSr74MuQaObvyBbBaivonO5fF7xQvHMHCi205g/document.pdf __oZdf0jyS/kLpZfGwwuMI2OK/tt4p1OwCoY0Z0N9MWmQ 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::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('build', <<'__4Lp2CNnntEJAojZM3II/pFzJ1LU2cfLvxlMsJgsJSsg'); my ($profile) = @_; $profile ||= &{'default-profile'}(); my $unlit_source_code = unlit($profile); my $state = state(); $state =~ s/\//-/g; my $build_directory = "/tmp/$state"; mkdir $build_directory; file::write("$build_directory/input.ml", $unlit_source_code); &{"profile::$profile"}(in => "$build_directory/input.ml", out => "$build_directory/$profile", profile => $profile, build_directory => $build_directory); __4Lp2CNnntEJAojZM3II/pFzJ1LU2cfLvxlMsJgsJSsg 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', <<'__4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY'); my ($name, $value) = @_; messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data; associate($name, $value || join('', ) || "\n"); __4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY meta::function('e', <<'__VOcQy5WG275NZGlFODdYHYBe3oJ7/CHmTT/L9O1I6t0'); edit('data::document', extension => '.tex'); reload(); __VOcQy5WG275NZGlFODdYHYBe3oJ7/CHmTT/L9O1I6t0 meta::function('edit', <<'__OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ'); 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(); __OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ meta::function('exists', <<'__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk'); my $name = shift; grep {$_ eq $name} keys %data; __bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk meta::function('grab', <<'__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8'); my ($filename, @attribute_names) = @_; associate("implementation::$_", `$filename cat $_`) for @attribute_names; __sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8 meta::function('import', <<'__oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA'); my ($name) = @_; associate($name, join('', )); __oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA meta::function('install-vim-highlighters', <<'__S9G7+5oBYqFAmTiBsEi0BzDB6UeM75H09wm4tG6uHGA'); my $home = $ENV{'HOME'}; mkdir "$home/.vim"; mkdir "$home/.vim/syntax"; file::write("$home/.vim/syntax/cltex.vim", retrieve('data::cltex-vim-highlighter')); file::write("$home/.vim/syntax/ocamltex.vim", retrieve('data::ocamltex-vim-highlighter')); <<"EOF"; The highlighters were created successfully. To have syntax highlighting activated automatically, append this to your .vimrc: au BufRead,BufNewFile *.cltex set filetype=cltex au BufRead,BufNewFile *.ocamltex set filetype=ocamltex Alternately, you can run $0 update-vimrc. EOF __S9G7+5oBYqFAmTiBsEi0BzDB6UeM75H09wm4tG6uHGA meta::function('list', <<'__+bxJbCjbt91ELEYggMKi3XgsZ3Nf9oa0lPiCTWTW384'); my ($line_number, $profile) = @_; $profile ||= 'run'; my @lines = split /\n/, unlit($profile); my $result = ''; for (0 .. scalar(@lines)) { $result .= sprintf "% 4d: %s\n", $_ + 1, $lines[$_] if abs ($_ + 1 - $line_number) < 10; } $result; __+bxJbCjbt91ELEYggMKi3XgsZ3Nf9oa0lPiCTWTW384 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', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18'); join("\n", map {" $_"} sort keys %data) . "\n"; __6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18 meta::function('make', <<'__8mAUcwqcvcEhgs6tEjHaIx3uF9QOC+9DAI7NmulvJ74'); compile(); view(); clean(); __8mAUcwqcvcEhgs6tEjHaIx3uF9QOC+9DAI7NmulvJ74 meta::function('mv', <<'__PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y meta::function('new', <<'__FQjehdFg7T3T2iHPMlGp6nAnrKAsQUIK5CXW02wNnos'); clone(@_); __FQjehdFg7T3T2iHPMlGp6nAnrKAsQUIK5CXW02wNnos 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('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM'); execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data); __GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM 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', <<'__HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto'); 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 { file::write($0, $serialized_data); unlink $temporary_filename; } __HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto 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', <<'__1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $prompt = name() . '$ '; my $OUT = $term->OUT || \*STDOUT; while (defined ($_ = $term->readline($prompt))) { my $command_line = $_; my @args = split /\s+/; my $function_name = shift @args; return if $function_name eq 'exit'; 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"); } $term->addhistory($command_line) if $command_line; $prompt = name() . '$ '; } __1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4 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('unlit', <<'__qJxQQTnIdxxWBaOiySqDtTNC9V8xmwwiJXmRFACA8kA'); my ($profile, %options) = @_; $profile ||= &{'default-profile'}(); my $literate_code = join "\n", map retrieve($_), grep /^section::/, sort keys %data; my $inside_code = 0; my $code_indentation = 0; my $resulting_code = '(* '; for my $line (split /\n/, $literate_code) { if ($inside_code && $line =~ /^\s*:\.$/) { $inside_code = $code_indentation = 0; $line =~ s/^/(* /; } if ($inside_code) { my $spaces = ' ' x $code_indentation; $line =~ s/^$spaces//; } if ($line =~ /^(\s*)::(.*)$/) { my $spaces = $1; my $code_section_rules = $2; my $indentation = length $spaces; if ($code_section_rules =~ /\socaml(\s|$)/) { # See whether we should include this code piece. Its inclusion depends on the active # profile and whatever guards are present. if ($code_section_rules =~ /if\s*\(([^)]*)\)/) { my $guards = $1; my @profiles = split /\|/, $guards; if (grep {s/^\s*//; s/\s*$//; $_ eq $profile} @profiles) { $inside_code = 1; $code_indentation = $indentation; $line =~ s/$/ *)/; } } else { $inside_code = 1; $code_indentation = $indentation; $line =~ s/$/ *)/; } } } $resulting_code .= "$line\n"; } $resulting_code . ' *)'; __qJxQQTnIdxxWBaOiySqDtTNC9V8xmwwiJXmRFACA8kA meta::function('unlock', <<'__08PohCY8fcNe+pWCO6ic6XOOKv48NkrxpNMmTOUIFdA'); my (undef, undef, $mode) = stat $0; chmod $mode | 0200, $0; __08PohCY8fcNe+pWCO6ic6XOOKv48NkrxpNMmTOUIFdA meta::function('update-vimrc', <<'__sRyErNBmLBe0jRSE6VYb8QPNC/6/rBd+nf5X7r3XBtM'); open my $fh, '>>', "$ENV{'HOME'}/.vimrc"; print $fh "au BufRead,BufNewFile *.cltex set filetype=cltex\n"; print $fh "au BufRead,BufNewFile *.ocamltex set filetype=ocamltex"; close $fh; __sRyErNBmLBe0jRSE6VYb8QPNC/6/rBd+nf5X7r3XBtM 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::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('execute', <<'__FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(basename($name), retrieve($name))}; carp $@ if $@ && $options{'carp'}; __FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE 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', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk 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::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', <<'__gAE2Xtr56GtOlw89sIcBSU7zkQUP/eBlx479kht7O2s'); 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; __gAE2Xtr56GtOlw89sIcBSU7zkQUP/eBlx479kht7O2s meta::line_filter('convert_sections', <<'__r7g/dpG55JIga/lA77C7KlRs+wzunUtHKGgnLpn34CY'); 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$_"; my $section_name = $2; my $label_name = lc $section_name; $label_name =~ s/[^A-Za-z0-9.]/-/g; "${section} {$section_name} \\label{sec:$label_name}"; } else { $line; } __r7g/dpG55JIga/lA77C7KlRs+wzunUtHKGgnLpn34CY meta::profile('debug', <<'__RGdbCxc2zlwCZFKEQS8VGmaouCqzdyUqPk+E3KjrEw0'); my %options = @_; `ocaml $options{'in'}`; __RGdbCxc2zlwCZFKEQS8VGmaouCqzdyUqPk+E3KjrEw0 meta::profile('run', <<'__RGdbCxc2zlwCZFKEQS8VGmaouCqzdyUqPk+E3KjrEw0'); my %options = @_; `ocaml $options{'in'}`; __RGdbCxc2zlwCZFKEQS8VGmaouCqzdyUqPk+E3KjrEw0 meta::section('00-introduction', <<'__F4d/G+dyCjJNgMvAqDBcQhYBPlR3xyw4JMajrzOb1tE'); - Introduction to Literate Coding Literate coding allows you to write \TeX{} primarily, but insert code snippets where appropriate. For example: :: ocaml open Printf let _ = Printf.printf "Hello world!\n" :. Compiling the system is also simple: :: $ ./my-project compile [optional-output-filename] :. - Build Profiles Each build profile provides a set of compiler options. In addition, sections of code can be conditionally included using this syntax at the beginning of your \verb|::|-\verb|:.| blocks: :: ocaml if(profile1 | profile2 | ... | profileN) :. :: ocaml if (run) let _ = Printf.printf "You seem to be using the /run/ profile.\n" :. Each build profile can be configured independently using a \verb|profile::| variable. Profiles are simply Perl functions that will be passed a hash containing these parameters: e[ +[\tt in] Input filename after literate-to-source (unlit) conversion. +[\tt out] Expected output filename. +[\tt profile] Name of the active build profile. +[\tt build\_directory] Absolute pathname of the build directory. ]e There may also be other parameters later. An example of a simple build profile is: :: my %options = @_; `ocamlopt $options{'in'} -o $options{'out'}`; :. __F4d/G+dyCjJNgMvAqDBcQhYBPlR3xyw4JMajrzOb1tE meta::unlit_converter('create_sections', <<'__sUEcZkQbKEhRUbj5hKceGP4VevPCE9+rz0yea91XZVc'); my ($document) = @_; $document .= "\n" . retrieve($_) . "\n" for (grep /^section::/, sort keys %data); $document; __sUEcZkQbKEhRUbj5hKceGP4VevPCE9+rz0yea91XZVc 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('zz_append_footer', <<'__xZrf+gAwEiK7btRcm+mwC/qtHsXp2FQ/Z8ZCwMvSw4Q'); my ($document) = @_; "$document\n\\end{document}"; __xZrf+gAwEiK7btRcm+mwC/qtHsXp2FQ/Z8ZCwMvSw4Q meta::unlit_converter('zz_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__ __mCenmYz3wjgcf77IzQUJSMKnjn131e5HtYBp9F0mAdw meta::file('objects/object', <<'__53Hrxn/tcx0GV8vdSdJICs0/pXgkpjq+Z7svl2oLZ3I'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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::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('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA'); meta::define_form 'internal', \&meta::meta::implementation; __Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA meta::bootstrap('initialization', <<'__bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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 $@; }; __bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs 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::data('default-action', <<'__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc'); shell __zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc meta::data('meta-associations', <<'__GGcLMIdIpQPyuKPkIVDso+D8DmmbfkaLqgG4z5YhJeA'); ^function:: .pl ^internal_function:: .pl ^meta:: .pl ^bootstrap:: .pl __GGcLMIdIpQPyuKPkIVDso+D8DmmbfkaLqgG4z5YhJeA meta::data('name', <<'__KVjUFtCKpaRy17UJA2y36v1UKt2EUn5moUXqZMtM3HU'); object __KVjUFtCKpaRy17UJA2y36v1UKt2EUn5moUXqZMtM3HU 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('clone', <<'__qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY'); for (@_) { if ($_) { eval { file::write($_, serialize(), noclobber => 1); chmod(0700, $_); print "File $_ cloned successfully.\n"; }; print "$@\n" if $@; } } __qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY 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', <<'__4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY'); my ($name, $value) = @_; messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data; associate($name, $value || join('', ) || "\n"); __4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY 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('grab', <<'__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8'); my ($filename, @attribute_names) = @_; associate("implementation::$_", `$filename cat $_`) for @attribute_names; __sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8 meta::function('import', <<'__oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA'); my ($name) = @_; associate($name, join('', )); __oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA meta::function('lock', <<'__VTyQSQauC4LFHxYTJhus499/qkzVt6stbxsovwA5kGs'); my (undef, undef, $mode) = stat $0; chmod $mode & 0555, $0; __VTyQSQauC4LFHxYTJhus499/qkzVt6stbxsovwA5kGs meta::function('ls', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18'); join("\n", map {" $_"} sort keys %data) . "\n"; __6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18 meta::function('mv', <<'__PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y meta::function('pull', <<'__IIDq/dsWHEYECpYpzXqTvkcve38taGb7d0m+Vsq+TQk'); 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 "$_"`); } __IIDq/dsWHEYECpYpzXqTvkcve38taGb7d0m+Vsq+TQk meta::function('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM'); execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data); __GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM 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', <<'__vhEbj5ff7CB7CfXgtAmJ/BCNllK5d8QgJfHWaHlBu70'); 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`; unlink $temporary_filename if $observed_self_state eq $final_state; } __vhEbj5ff7CB7CfXgtAmJ/BCNllK5d8QgJfHWaHlBu70 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', <<'__g0FKviVlh5VYzuV+REx/lpPg3EmMtycTaZTK85Zq/SU'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $prompt = name() . '$ '; my $OUT = $term->OUT || \*STDOUT; while (defined ($_ = $term->readline($prompt))) { my $command_line = $_; my @args = split /\s+/; my $function_name = shift @args; return if $function_name eq 'exit'; 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"); } $term->addhistory($command_line) if $command_line; $prompt = name() . '$ '; save(); } __g0FKviVlh5VYzuV+REx/lpPg3EmMtycTaZTK85Zq/SU 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', <<'__2f3NoSvgge16m8wBuDokogr54ssy8D8fKdZ7+RtZitE'); my (undef, undef, $mode) = stat $0; chmod $mode | 0200, $0; __2f3NoSvgge16m8wBuDokogr54ssy8D8fKdZ7+RtZitE meta::function('usage', <<'__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I'); <<"EOD" . join ' ', split /\n/, ls (); Usage: $0 [options] action [arguments] Defined actions: EOD __oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I 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('execute', <<'__FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(basename($name), retrieve($name))}; carp $@ if $@ && $options{'carp'}; __FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE 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', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk 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::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__ __53Hrxn/tcx0GV8vdSdJICs0/pXgkpjq+Z7svl2oLZ3I meta::file('objects/tex-document', <<'__OxT3ZMZk0rDYS3fxoGkTW0EQMj+XeZU5MU+hSc8awpM'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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', <<'__96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek'); 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"); }; }; __96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek 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::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::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('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA'); meta::define_form 'internal', \&meta::meta::implementation; __Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA meta::bootstrap('initialization', <<'__bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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 $@; }; __bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs 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', <<'__xDKzcuDjAmfmXiahKkLHlXq1LiXf48bUuSkhPYiWXkU'); intro __xDKzcuDjAmfmXiahKkLHlXq1LiXf48bUuSkhPYiWXkU meta::data('document', <<'__XTJAsZ2LWKGEYCet0rXSP8DwQBh2w15+p7Q6mmOdQ6A'); = TeX Document Generator a Spencer Tipping begin - Introduction The \TeX{} document generator is a simple utility for encapsulating the generation and compilation of \TeX{} documents. It simplifies: e[ + Writing \TeX{} documents quickly + Writing literate code ]e - Usage The easiest way to use the system is to clone a new document, like this: :: $ tex-document clone my-document :. Then you can edit your new document by entering its shell and issuing the ``e'' command: :: $ my-document shell my-document$ e :. __XTJAsZ2LWKGEYCet0rXSP8DwQBh2w15+p7Q6mmOdQ6A meta::data('header', <<'__eDtyvHFZSQrphEHm92Rjuvx5KOB2Cm+mxAPuG0JPt2g'); \documentclass{article} \usepackage{palatino,mathpazo,amsmath,amssymb} __eDtyvHFZSQrphEHm92Rjuvx5KOB2Cm+mxAPuG0JPt2g 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', <<'__MyX0rdO1O8lBOay1b19w6THTKQKU8ah1XPoRH26kXPU'); tex-document __MyX0rdO1O8lBOay1b19w6THTKQKU8ah1XPoRH26kXPU meta::data('output-dir', <<'__6WcazSRIScVxZ8ZY+i+Wl1IEj3qxhKPc9cRstNVq4SQ'); /tmp __6WcazSRIScVxZ8ZY+i+Wl1IEj3qxhKPc9cRstNVq4SQ meta::data('pdf-output-file', <<'__tFA84XAs7yM9v+osGnVIsQMu6yJffnoNslnzwDZA8Ys'); /tmp/tex-document.xk2VWDh4ZgOvAtdIIPbrIuXgugV6JFWL6Rg1HL4pOLw/document.pdf __tFA84XAs7yM9v+osGnVIsQMu6yJffnoNslnzwDZA8Ys 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', <<'__4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY'); my ($name, $value) = @_; messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data; associate($name, $value || join('', ) || "\n"); __4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY meta::function('e', <<'__VOcQy5WG275NZGlFODdYHYBe3oJ7/CHmTT/L9O1I6t0'); edit('data::document', extension => '.tex'); reload(); __VOcQy5WG275NZGlFODdYHYBe3oJ7/CHmTT/L9O1I6t0 meta::function('edit', <<'__OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ'); 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(); __OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ meta::function('exists', <<'__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk'); my $name = shift; grep {$_ eq $name} keys %data; __bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk meta::function('grab', <<'__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8'); my ($filename, @attribute_names) = @_; associate("implementation::$_", `$filename cat $_`) for @attribute_names; __sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8 meta::function('import', <<'__oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA'); my ($name) = @_; associate($name, join('', )); __oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA 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', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18'); join("\n", map {" $_"} sort keys %data) . "\n"; __6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18 meta::function('make', <<'__8mAUcwqcvcEhgs6tEjHaIx3uF9QOC+9DAI7NmulvJ74'); compile(); view(); clean(); __8mAUcwqcvcEhgs6tEjHaIx3uF9QOC+9DAI7NmulvJ74 meta::function('mv', <<'__PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y meta::function('new', <<'__FQjehdFg7T3T2iHPMlGp6nAnrKAsQUIK5CXW02wNnos'); clone(@_); __FQjehdFg7T3T2iHPMlGp6nAnrKAsQUIK5CXW02wNnos 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('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM'); execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data); __GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM 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', <<'__HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto'); 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 { file::write($0, $serialized_data); unlink $temporary_filename; } __HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto 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', <<'__1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $prompt = name() . '$ '; my $OUT = $term->OUT || \*STDOUT; while (defined ($_ = $term->readline($prompt))) { my $command_line = $_; my @args = split /\s+/; my $function_name = shift @args; return if $function_name eq 'exit'; 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"); } $term->addhistory($command_line) if $command_line; $prompt = name() . '$ '; } __1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4 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-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::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('execute', <<'__FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(basename($name), retrieve($name))}; carp $@ if $@ && $options{'carp'}; __FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE 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', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk 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::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', <<'__ALAptDKGAFawh7jgFZo/RoW5oHCjUVEUEn68mWYAARw'); 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*\]e$/\\end{enumerate}/; $line =~ s/^\s*\]i$/\\end{itemize}/; $line =~ s/^\s*\]d$/\\end{description}/; $line; __ALAptDKGAFawh7jgFZo/RoW5oHCjUVEUEn68mWYAARw 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__ __OxT3ZMZk0rDYS3fxoGkTW0EQMj+XeZU5MU+hSc8awpM meta::file('objects/timesheet', <<'__O/0VT8xF6teBa3u5uSAndyXoPzQ/6gNDBY7luZigVRQ'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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::data', <<'__96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek'); 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"); }; }; __96H42jUO3qe63MqMco7Ih6e1BtrIcB+e18KFqPpfIek 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('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA'); meta::define_form 'internal', \&meta::meta::implementation; __Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA meta::bootstrap('initialization', <<'__bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; 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 $@; }; __bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs 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::data('default-action', <<'__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc'); shell __zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc meta::data('meta-associations', <<'__GGcLMIdIpQPyuKPkIVDso+D8DmmbfkaLqgG4z5YhJeA'); ^function:: .pl ^internal_function:: .pl ^meta:: .pl ^bootstrap:: .pl __GGcLMIdIpQPyuKPkIVDso+D8DmmbfkaLqgG4z5YhJeA meta::data('name', <<'__lt2Y2LOs08pjJC6uQYzodV6ZkX6V98YHz3BTeak4jc4'); timesheet __lt2Y2LOs08pjJC6uQYzodV6ZkX6V98YHz3BTeak4jc4 meta::data('times', <<'__AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs'); __AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs 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('clone', <<'__qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY'); for (@_) { if ($_) { eval { file::write($_, serialize(), noclobber => 1); chmod(0700, $_); print "File $_ cloned successfully.\n"; }; print "$@\n" if $@; } } __qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY meta::function('convert-to-csv', <<'__N4mlu4KLvs35DgiU887qqEExft/pQLqlWzZirc9N/1U'); my $times = retrieve('data::times'); my $minute_total = 0; my @new_lines; for my $line (split /\n+/, $times) { my @line_bits = split /\s{2,}/, $line; my $new_line = '"' . (join '","', @line_bits) . '"'; my @start_time = split /:/, $line_bits[1]; my @end_time = split /:/, $line_bits[2]; my $current_duration = ($end_time[0] - $start_time[0]) * 60 + $end_time[1] - $start_time[1]; $minute_total += $current_duration; push @new_lines, $new_line; } my $hour_total = 0; ++$hour_total, $minute_total -= 60 while $minute_total >= 60; my $formatted_minutes = sprintf '%02d', $minute_total; (join "\n", @new_lines) . "\n\"Total\",\"\",\"\",\"$hour_total:$formatted_minutes\""; __N4mlu4KLvs35DgiU887qqEExft/pQLqlWzZirc9N/1U 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', <<'__4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY'); my ($name, $value) = @_; messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data; associate($name, $value || join('', ) || "\n"); __4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY meta::function('edit', <<'__OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ'); 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(); __OtbuwBGfWZZef+v2WP0gCvvxbtpyxi1FbVD11BTjZzQ meta::function('edit-times', <<'__5hy38e1mjTxFPn/pFfgcF9ftiZ1f/O1He8Z4mHJLXVA'); associate('data::times', invoke_editor_on(retrieve('data::times'), extension => 'times')); __5hy38e1mjTxFPn/pFfgcF9ftiZ1f/O1He8Z4mHJLXVA meta::function('exists', <<'__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk'); my $name = shift; grep {$_ eq $name} keys %data; __bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk meta::function('grab', <<'__sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8'); my ($filename, @attribute_names) = @_; associate("implementation::$_", `$filename cat $_`) for @attribute_names; __sXs1aeJVBERH6nWE7ZpWiIO5Cg7fSBWcoscDg1DHzD8 meta::function('import', <<'__oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA'); my ($name) = @_; associate($name, join('', )); __oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA meta::function('lock', <<'__VTyQSQauC4LFHxYTJhus499/qkzVt6stbxsovwA5kGs'); my (undef, undef, $mode) = stat $0; chmod $mode & 0555, $0; __VTyQSQauC4LFHxYTJhus499/qkzVt6stbxsovwA5kGs meta::function('ls', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18'); join("\n", map {" $_"} sort keys %data) . "\n"; __6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18 meta::function('mv', <<'__PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y meta::function('pull', <<'__IIDq/dsWHEYECpYpzXqTvkcve38taGb7d0m+Vsq+TQk'); 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 "$_"`); } __IIDq/dsWHEYECpYpzXqTvkcve38taGb7d0m+Vsq+TQk meta::function('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM'); execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data); __GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM 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', <<'__HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto'); 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 { file::write($0, $serialized_data); unlink $temporary_filename; } __HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto 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', <<'__1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $prompt = name() . '$ '; my $OUT = $term->OUT || \*STDOUT; while (defined ($_ = $term->readline($prompt))) { my $command_line = $_; my @args = split /\s+/; my $function_name = shift @args; return if $function_name eq 'exit'; 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"); } $term->addhistory($command_line) if $command_line; $prompt = name() . '$ '; } __1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4 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', <<'__2f3NoSvgge16m8wBuDokogr54ssy8D8fKdZ7+RtZitE'); my (undef, undef, $mode) = stat $0; chmod $mode | 0200, $0; __2f3NoSvgge16m8wBuDokogr54ssy8D8fKdZ7+RtZitE meta::function('usage', <<'__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I'); <<"EOD" . join ' ', split /\n/, ls (); Usage: $0 [options] action [arguments] Defined actions: EOD __oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I 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('execute', <<'__FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(basename($name), retrieve($name))}; carp $@ if $@ && $options{'carp'}; __FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE 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', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk 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::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__ __O/0VT8xF6teBa3u5uSAndyXoPzQ/6gNDBY7luZigVRQ meta::file('scripts/jquery.anchor.js', <<'__zvoIEvDYttNq/I50NKov2Mdde8EDaIU/7mwRMSi74Yc'); // jQuery anchor-based navigation plugin // Created by Spencer Tipping, licensed under the MIT source code license (function ($) { var state_changed_listeners = [] var the_current_state = document.location.hash $.anchor = { encodeURL: function (state) { var result = '' $.each (state, function (k) {result += k + '=' + this + '&'}) return '#' + result.replace (/&$/, '') }, decodeURL: function (anchor_part_of_url) { var result = {} $.each (anchor_part_of_url.replace (/^#/, '').split (/&/), function () { var pair = this.split (/=/) result[pair[0]] = pair[1] }) return result }, state: function (the_new_state) { if (the_new_state) return document.location.hash = $.anchor.encodeURL (the_new_state) else return $.anchor.decodeURL (document.location.hash) }, listen: function () { $.each (arguments, function () {state_changed_listeners.push (this)}) }, notify: function () { if (document.location.hash != the_current_state) { var the_new_state = $.anchor.decodeURL (the_current_state = document.location.hash) $.each (state_changed_listeners, function () { this.apply (the_new_state, []) }) } } } $(document).ready (function () { window.setInterval ($.anchor.notify, 100) }) }) (jQuery) __zvoIEvDYttNq/I50NKov2Mdde8EDaIU/7mwRMSi74Yc meta::file('scripts/jquery.js', <<'__yDcKLQUDWenVBazEEeb0V6SbITYKIebLySKbrTp2eJk'); /* * jQuery JavaScript Library v1.3.2 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) * Revision: 6246 */ (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); /* * Sizzle CSS Selector Engine - v0.9.3 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); __yDcKLQUDWenVBazEEeb0V6SbITYKIebLySKbrTp2eJk meta::file('scripts/jquery.toc.js', <<'__gHxSwl5QfVDCZ7jDzinFclLYybkcK3An6wGsn7GYjEI'); // jQuery table of contents plugin // Created by Spencer Tipping and licensed under the MIT source code license (function ($) { $.toc = { encodeText: function (text) {return text.toLowerCase ().replace (/["']/g, '').replace (/[^a-zA-Z0-9]/g, '-'). replace (/-+/g, '-').replace (/-+$/, '').replace (/^-+/, '')} } $.fn.setupTableOfContents = function (options) { var options_prime = $.extend ({ pathToContent: '#content > div', pathToTitle: 'h1', variant: 'section', defaultOptions: {}, defaultItemPath: ':first', itemActivator: function () {$(this).addClass ('selected')}, itemDeactivator: function () {$(this).removeClass ('selected')}, contentActivator: function () {$(this).show ()}, contentDeactivator: function () {$(this).hide ()}, firstRunItemActivator: null, firstRunItemDeactivator: null, firstRunContentActivator: null, firstRunContentDeactivator: null }, options) var id_unique_stuff = $.toc.encodeText ($.anchor.encodeURL (options_prime.defaultOptions)) var id_for = function (item_name) {return id_unique_stuff + '-' + options_prime.variant + '-' + item_name} $(options_prime.pathToContent).each (function () { $(this).attr ('id', 'content-' + id_for ($.toc.encodeText ($(this).children (options_prime.pathToTitle).text ()))); (options_prime.firstRunContentDeactivator || options_prime.contentDeactivator).apply (this, []); }) var $i = function (encoded_item_name) {return $('#item-' + id_for (encoded_item_name))} var $c = function (encoded_item_name) {return $('#content-' + id_for (encoded_item_name))} this.each (function () { var state_delta = {} var encoded_item_name = $.toc.encodeText ($(this).text ()) state_delta[options_prime.variant] = encoded_item_name if ($c (encoded_item_name).size ()) $(this).wrap (''). attr ('id', 'item-' + id_for (encoded_item_name)); this != window && (options_prime.firstRunItemDeactivator || options_prime.itemDeactivator).apply (this, []); }) var current_item_name = null var select_item = function (new_item_encoded_name, first_run) { if (current_item_name != new_item_encoded_name) { if (current_item_name) { var current_item = $i(current_item_name).get (0) var current_content = $c(current_item_name).get (0) current_item && (first_run && options_prime.firstRunItemDeactivator || options_prime.itemDeactivator).apply (current_item, []); current_content && (first_run && options_prime.firstRunContentDeactivator || options_prime.contentDeactivator).apply (current_content, []); } var new_item = $i(new_item_encoded_name).get (0) var new_content = $c(new_item_encoded_name).get (0) new_item && (first_run && options_prime.firstRunItemActivator || options_prime.itemActivator).apply (new_item, []); new_content && (first_run && options_prime.firstRunContentActivator || options_prime.contentActivator).apply (new_content, []); current_item_name = new_item_encoded_name } } var default_item = function () { return $.toc.encodeText ($(options_prime.pathToContent).filter (options_prime.defaultItemPath).children (options_prime.pathToTitle).text ()) } select_item ($.anchor.state ()[options_prime.variant] || default_item (), true) $.anchor.listen (function () { select_item (this[options_prime.variant] || default_item ()) }) return this } }) (jQuery) __gHxSwl5QfVDCZ7jDzinFclLYybkcK3An6wGsn7GYjEI meta::file('scripts/main.js', <<'__vLAk9YHUaZFew9Qy7huvonRc811MOiRw2YkyS/OW4iU'); $(document).ready (function () { var slide_out = function () { $(this).stop (false, true).css ({position: 'absolute'}). animate ({left: '-10px', opacity: 'hide'}, {complete: function () { $(this).css ({position: 'relative', left: '-10px'}) }}) } var slide_in = function () { $(this).stop (false, true).css ({left: '10px', position: 'absolute'}). animate ({left: '0px', opacity: 'show'}, {complete: function () { $(this).css ({position: 'relative', left: '0px'}) }}) } var fade_out = function () {$(this).fadeOut ()} var fade_in = function () {$(this).fadeIn ()} var show = function () {$(this).show ().css ({position: 'relative'})} var hide = function () {$(this).hide ().css ({position: 'relative'})} $('.projects-link').replaceWith ("

<< All Projects

") $('#sidebar > div'). hover (function () {$(this).addClass ('hover')}, function () {$(this).removeClass ('hover')}). setupTableOfContents ({ contentActivator: slide_in, contentDeactivator: slide_out, firstRunContentActivator: show, firstRunContentDeactivator: hide }) var setup_section = function (section_name) { $('#' + section_name + '-navigation > div'). hover (function () {$(this).addClass ('hover')}, function () {$(this).removeClass ('hover')}). setupTableOfContents ({ pathToContent: '#' + section_name + '-content > div', pathToTitle: 'h2', variant: 'page', defaultOptions: {section: section_name}, contentActivator: slide_in, contentDeactivator: slide_out, firstRunContentActivator: show, firstRunContentDeactivator: hide }) } setup_section ('cheloniidae') setup_section ('universal-translator') setup_section ('jquery-plugins') setup_section ('self-modifying-perl') // Blog loading var blog_index = null var blog_posts = [] var blocks_to_load = 0 $('#blog-navigation').hide () $.getJSON ('blog-data/index', function (data) { blog_index = data load_individual_entries () }) var load_individual_entries = function () { $.each (blog_index.blocks, function () { ++blocks_to_load $('#blog-loading-indicator').prepend ('.') $.getJSON ('blog-data/' + this, function (block_data) { blog_posts = blog_posts.concat (block_data) $('#blog-loading-progress-' + blocks_to_load).removeClass ('inactive').addClass ('active') --blocks_to_load || window.setTimeout (setup_blog_menu, 100) }) }) } var post_link = function (post_data) { return $('
' + post_data['real-title'] + '
') } var post_content = function (post_data, last_post) { var header = '

' + post_data['real-title'] + '

'; var pre_content = '
All Posts ' + (last_post ? ' | ' + last_post['real-title'] + ' >>' : '') + '
'; return $('
' + header + pre_content + post_data['compiled-content'] + '
').hide () } var setup_blog_menu = function () { // First, sort blog posts by date. blog_posts = blog_posts.sort (function (p1, p2) {return p1.date < p2.date ? -1 : 1}) var last_post = null var post_nodes = [] var post_links = [] $.each (blog_posts, function () { post_links.push (post_link (this)) post_nodes.push (post_content (this, last_post)) last_post = this }) $('#blog-loading-indicator').remove () $.each (post_links.reverse (), function () {this.appendTo ('#blog-navigation')}) $.each (post_nodes.reverse (), function () {this.appendTo ('#blog-entries')}) $('#blog-navigation > div'). hover (function () {$(this).addClass ('hover')}, function () {$(this).removeClass ('hover')}). setupTableOfContents ({ pathToContent: '#blog-entries > div', pathToTitle: 'h1', variant: 'post', defaultOptions: {section: 'blog'}, defaultItemPath: '#content-section-blog-post-' + blog_posts[blog_posts.length - 1].title, contentActivator: slide_in, contentDeactivator: slide_out, firstRunContentActivator: fade_in, firstRunContentDeactivator: hide }) // Now enqueue images to start loading. $('.delayed-image > span').each (function () { var image_source = $(this).attr ('image-src') var alternate_text = $(this).attr ('alt') var class_name = $(this).attr ('class') $(this).replaceWith ('' + alternate_text + '') }) } }) __vLAk9YHUaZFew9Qy7huvonRc811MOiRw2YkyS/OW4iU meta::file('style/main.css', <<'__fEyBmSE7J5wVTEyhflYaSEllNKoXs9zmTil1p78mPow'); body { font-family: sans-serif; font-size: 9pt; margin: 0; background: #eee; min-width: 700px; padding-bottom: 40px; } #sidebar { float: right; width: 200px; background: #111; padding-top: 140px; padding-bottom: 300px; border-left: solid 1px black; } #sidebar div { font-size: 8pt; color: #666; padding: 10px 0px 10px 30px; cursor: pointer; margin-right: 20px; font-weight: bold; border-style: solid; border-width: 1px 1px 1px 0; border-color: transparent; } #sidebar div.sub { padding: 6px 0px 6px 40px; font-weight: normal; border-width: 0 1px 0 0; } #sidebar span.separator { display: block; height: 4px; } #sidebar div.hover { background: #222; border-color: #0c0c0c; } #sidebar div.selected { color: #f62; background: #222; border-color: #0c0c0c; } #header { background: #222; height: 140px; border-bottom: solid 1px black; } #content { left: 20px; margin-right: 201px; position: relative; } #content > div { padding: 0 40px 0 0; position: absolute; top: 0; left: 0; } #content h1, #content h2, #content h3 { display: block; font-weight: normal; margin-top: 0; text-transform: uppercase; margin-bottom: 16px; margin-top: 30px; } #content h1 { font-size: 16pt; color: #333; border-bottom: solid 1px #aaa; } #content h2 { font-size: 12pt; color: black; margin-bottom: 0; border-bottom: solid 1px #ccc; } #content h3 { font-size: 10pt; color: #c51; margin-bottom: 0; } .information { color: #444; display: block; text-align: right; } #content pre { overflow: auto; background: #ddd; color: #222; padding: 10px; font-size: 8pt; border-left: solid 2px #222; } .quotation { float: right; background: #aaa; margin-left: 20px; padding: 20px; } #content ul { color: #aca; } #content p, #content li { font-size: 9pt; color: #111; } .footer { clear: both; } a { color: #151; font-size: 9pt; text-decoration: none; outline: 0; } a.strong { font-weight: bold; } a:hover { color: #797; } img { border: none; } img.screenshot { border: solid 4px #222; margin: 10px; } .subsection-navigation div { display: inline-block; color: #222; padding: 5px; } .subsection-navigation div.hover { padding: 4px; border: solid 1px #888; } .subsection-navigation div.selected { color: #ccc; background: #444; border: solid 1px #222; padding: 4px; } .subsection-content { position: relative; } .indicator { color: #888; font-size: 9pt; } .field-label { color: #444; font-size: 9pt; } input { background: transparent; border: solid 1px #888; padding: 1px; color: #444; font-size: 9pt; } #blog-search { margin-left: 10px; float: right; } #blog-loading-indicator { padding-top: 60px; font-size: 40pt; font-weight: bold; text-align: center; } #blog-loading-indicator .inactive { color: #ccc; } #blog-loading-indicator .active { color: #888; } __fEyBmSE7J5wVTEyhflYaSEllNKoXs9zmTil1p78mPow 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', <<'__qF19UvmjPvPOLR44SC4faaa4DCELcB0xlzfnV98Dy2I'); use File::Path 'mkpath'; use File::Basename 'dirname'; my $full = grep /^full$/, @_; my $contents = &{'compile-to-html'}(); my $name = (dirname $0) . '/' . name(); my $output_directory = "$name.output"; my $delta_directory = "$name.delta"; if ($full) { `rm -rf $output_directory`; `rm -rf $delta_directory`; overlay(); } mkpath($delta_directory); file::write("$delta_directory/index.html", $contents); file::write("$delta_directory/site-image", serialize()); generate_blog_posts(); for my $file (grep /^file::/ || /^binary::/, sort keys %data) { my $is_binary = $file =~ /^binary::/; $file =~ s/^file:://; $file =~ s/^binary:://; mkpath "$delta_directory/" . dirname $file; mkpath "$delta_directory/unpacked/" . dirname $file unless $is_binary; file::write("$delta_directory/$file", $is_binary ? unpack('u*', retrieve("binary::$file")) : &{'pack-file'}($file, retrieve("file::$file"))); file::write("$delta_directory/unpacked/$file", retrieve("file::$file")) unless $is_binary; } &{'overlay-delta'}(); __qF19UvmjPvPOLR44SC4faaa4DCELcB0xlzfnV98Dy2I meta::function('compile-sidebar', <<'__9YzWhZVTj7QkTEz9Yyxd29s4T4YwBNljba5u+NmYSVk'); my $sidebar = '"; __9YzWhZVTj7QkTEz9Yyxd29s4T4YwBNljba5u+NmYSVk meta::function('compile-to-html', <<'__h29x4X+U/q5mIgG/kTGUZf2OGJa3wNp1cPYFBVNhdso'); my ($document) = @_; $document ||= document(); $document = &$_($document) for (grep /^unlit_converter::/, sort keys %data); $document; __h29x4X+U/q5mIgG/kTGUZf2OGJa3wNp1cPYFBVNhdso 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('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('generate_blog_post_bundles', <<'__YQkLMHqWnw6QOP95vRQIVdQUHVF+OG5xqAH3IUJNNKw'); # This deserves some explanation. # # What's going on here is that we want to optimize page loading by bundling all of the blog entries into roughly ideally-sized pieces. I'm assuming that the # content will be compressed in this calculation (as all modern browsers support this), so the content should get about 50% compression on average. # # The optimum size for a transfer is subjective, but I've set it in the variable $optimum_size. This should be large enough that it cuts down on a lot of # overhead, but small enough that a retry isn't expensive. Because of GZip compression, we should plan on things losing 50% of their volume over the wire. # # For now the algorithm is really simple. We just keep adding posts to the list until we get to the right size, and then bundle those up. Here's the thing, # though: We always start with the oldest and bundle in reverse. This way users' caches of old posts remain valid. If we started with the new ones, then we'd # have a rolling-effect whenever we added a new post. # # The serialization format, incidentally, is JSON. # # To navigate the blog effectively, an index file is also generated. This is another JSON document whose elements are 'blocks' and a hash 'posts' -- the # 'blocks' is an array that lists all files that should be requested, and the 'posts' is a hash mapping post titles to block names. The latter exists to handle # the case of a user jumping to the site without waiting for the full load time. # # Each block's filename is the hash of its contents (naturally with problematic characters replaced). The index, however, has a constant name of 'index'. use JSON; use File::Path 'mkpath'; my %posts = @_; my $optimum_size = 1024 * 20; sub encode_list { to_json([map {title => $_->{'title'}, 'real-title' => $_->{'real-title'}, date => $_->{'date'}, author => $_->{'author'}, 'compiled-content' => $_->{'compiled-content'}, tags => $_->{'tags'}}, @_]); } sub compact_hashsum { my $hash = sha256_base64($_[0]); $hash =~ tr[/+][-_]; $hash; } my @batches; my @queue; my @date_sorted_posts = sort {$posts{$a}{'date'} cmp $posts{$b}{'date'}} keys %posts; for my $post_name (@date_sorted_posts) { if ($posts{$post_name}{'status'} eq 'published') { push @queue, $posts{$post_name}; my $current_queue_encoding = encode_list @queue; if (length($current_queue_encoding) > $optimum_size) { push @batches, [$current_queue_encoding, map(${$_}{'title'}, @queue)]; @queue = (); } } } push @batches, [(encode_list @queue), map(${$_}{'title'}, @queue)] if scalar(@queue); my %index; my %blocks; my @ordered_blocks; mkpath('site.delta/blog-data'); for my $encoding (@batches) { my @encoding = @$encoding; my $contents = shift @encoding; my $block_name = compact_hashsum $contents; $index{$_} = $block_name for @encoding; $blocks{$block_name} = $contents; push @ordered_blocks, $block_name; file::write("site.delta/blog-data/$block_name", $contents); } file::write('site.delta/blog-data/index', to_json({'index' => \%index, 'blocks' => [reverse @ordered_blocks]})); __YQkLMHqWnw6QOP95vRQIVdQUHVF+OG5xqAH3IUJNNKw meta::function('generate_blog_posts', <<'__jP5UERNuTgmbpP7KkEmnDRWLI51KxYkAMwBwNP5Tct0'); my %blog_data; my %keyword_handlers; for my $post (grep /^blogpost::/, sort keys %data) { my $content = retrieve($post); my @keyword_lines = grep /^\s*post:/, split /\n/, $content; my %post_data; for my $line (@keyword_lines) { my ($keyword, $equals, @value_pieces) = split /(\s*=\s*)/, $line; my $value = join '', @value_pieces; $keyword =~ s/^\s*post://; $value =~ s/\s*$//; $post_data{$keyword} = $value; } $post_data{'content'} = $content; $post_data{'compiled-content'} = &{'compile-to-html'}($content); $blog_data{$post} = \%post_data; } generate_blog_post_bundles(%blog_data); __jP5UERNuTgmbpP7KkEmnDRWLI51KxYkAMwBwNP5Tct0 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('import-binary', <<'__YQWKWmUREtXl75Ef+Mys3p6ia1M+m/ShhN7bK9qfZfc'); my ($name) = @_; associate($name, pack('u*', join('', ))); __YQWKWmUREtXl75Ef+Mys3p6ia1M+m/ShhN7bK9qfZfc meta::function('install-vim-highlighter', <<'__C7NdY3A4/O1xabnov56kIPazB2UViRtF/Lc7gF1e5Tg'); my $home = $ENV{'HOME'}; mkdir "$home/.vim"; mkdir "$home/.vim/syntax"; file::write("$home/.vim/syntax/clhtml.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 *.clhtml set filetype=clhtml Alternately, you can run $0 update-vimrc. EOF __C7NdY3A4/O1xabnov56kIPazB2UViRtF/Lc7gF1e5Tg 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', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__ZF0mLdFoa0pys6NZ+Yz5lsmaE2GeJMG2Aw1C3j88moQ'); my $criteria = join '|', @_; join("\n", grep(/$criteria/, sort keys %data)); __ZF0mLdFoa0pys6NZ+Yz5lsmaE2GeJMG2Aw1C3j88moQ 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('overlay', <<'__gFJlsNufi1tzGTd1QoXHosOpBWd3n0BGArArZS7nVLM'); `cp -r site.downloads site.output`; __gFJlsNufi1tzGTd1QoXHosOpBWd3n0BGArArZS7nVLM meta::function('overlay-delta', <<'__tdC5UQoRJs+qGkOpb+0CB1FyykO6VOHV6vyFlzXMHa0'); `cp -r site.delta/* site.output/`; __tdC5UQoRJs+qGkOpb+0CB1FyykO6VOHV6vyFlzXMHa0 meta::function('pack-file', <<'__H7hJfYvh2rzpgbamYm7gagDPaHL6p1J+EGhxH4SWAn4'); my ($filename, $data) = @_; return &{'compress-javascript'}($data) if $filename =~ /\.js$/; return &{'compress-css'}($data) if $filename =~ /\.css$/; return $data; __H7hJfYvh2rzpgbamYm7gagDPaHL6p1J+EGhxH4SWAn4 meta::function('perl', <<'__Ojd593Fa9fx1Yx2XuPzK6WTUyxO70Nbmlbl9YRodUWA'); my $result = eval($_[0]); $@ ? $@ : $result; __Ojd593Fa9fx1Yx2XuPzK6WTUyxO70Nbmlbl9YRodUWA meta::function('pop-state', <<'__eqnCLsMapvq2sYSx82KCxh25zmff+JIXFMYUan2kGKM'); %data = %{pop @{$transient{'states'}}} if @{$transient{'states'}}; reload(); __eqnCLsMapvq2sYSx82KCxh25zmff+JIXFMYUan2kGKM 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('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', <<'__8cUncWUFuzX5fV+tgmVU+RvuWGPysHg3WqI6pYTcrQI'); # 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; terminal::message('info', 'Replicating state...'); &{'push-state'}(); terminal::message('info', 'Updating meta attributes...'); for my $attribute (grep length && /^meta::/, split(/\n/, `$_[0] ls-a`)) { associate($attribute, join('', `$_[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/), split(/\n/, `$_[0] ls-a`)) { associate($attribute, join('', `$_[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."); __8cUncWUFuzX5fV+tgmVU+RvuWGPysHg3WqI6pYTcrQI meta::function('update-vimrc', <<'__vXYJyuiswAu5gUEffomx4F4qQKXZVppkzX+0XOyxRVU'); open my $fh, '>>', "$ENV{'HOME'}/.vimrc"; print $fh "au BufRead,BufNewFile *.clhtml set filetype=clhtml"; close $fh; __vXYJyuiswAu5gUEffomx4F4qQKXZVppkzX+0XOyxRVU meta::function('usage', <<'__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I'); <<"EOD" . join ' ', split /\n/, ls (); Usage: $0 [options] action [arguments] Defined actions: EOD __oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I 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_stuff', <<'__hlSIsuhEdQW46sxXbd5WyYgT0RVROsZX8q0UXKGOimg'); my ($line) = @_; $line =~ s/^\s*script\((.*)\)$/