#!/usr/bin/perl # 99aeabc9ec7fe80b1b39f5e53dc7e49e <- self-modifying Perl magic # state: 28fef1b8aa03b40b2df41184052311dc # istate: 301b6ad00811db3b3363b2b5b0bbff81 # id: 97d938428ee6c4d2a505f6f35bad0906 # This is a self-modifying Perl file. I'm sorry you're viewing the source (it's # really gnarly). If you're curious what it's made of, I recommend reading # http://github.com/spencertipping/writing-self-modifying-perl. # # If you got one of these from someone and don't know what to do with it, send # it to spencer@spencertipping.com and I'll see if I can figure out what it # does. # For the benefit of HTML viewers (this is a hack): #
This is a paragraph...
#This is another paragraph...
#int main () {return 0;}#
int main () {return 0} // Won't compile#
$_"}; my $quoted = sub {&$escape_all(); &$unindent(); s/^\|(\s?)/ \1/; s/^ //mg; push @markup, &$indent() . "
$_"}; my $paragraph = sub {&$escape_some(); push @markup, &$indent() . "
$_
"}; my $section = sub {my $h = $_[0] > 6 ? 6 : $_[0]; push @markup, &$indent($_[0] - 1) . "elements. A great article about this (and its cross-browser ramifications) at http://www.longren.org/2006/09/27/wrapping-text-inside-pre-tags/. #tutorial-page pre {white-space: pre-wrap; word-wrap: break-word} SDoc page styling. This is optimized for long lines and lots of text. #sdoc-page {padding-bottom: 100px; color: white; position: absolute; display: none} #sdoc-page a.back {font-size: 16pt; color: #999; display: block; text-transform: lowercase; text-decoration: none} #sdoc-page a.back:before {content: '<< '; color: #444} #sdoc-page a.back:hover {color: #ccc} #sdoc-page a.back:hover:before {content: '<< '; color: #fa4} #sdoc-page .file > h1 {color: #999; cursor: pointer; font-weight: normal; font-size: 16pt; white-space: nowrap; word-wrap: none} #sdoc-page .file > h1 .path {color: #444} #sdoc-page .file > h1 .extension {display: none; color: #444} #sdoc-page .file > h1:hover .path {color: #ccc} #sdoc-page .file > h1:hover .extension {display: none; color: #ccc} #sdoc-page .file > h1:hover {color: #ccc} #sdoc-page .file > h1:after {content: ' >>'; color: #444} #sdoc-page .file > h1:hover:after {content: ' >>'; color: #fa4} #sdoc-page .section {margin-top: 50px} #sdoc-page .section h1:before, #sdoc-page .section h2:before, #sdoc-page .section h3:before {content: '< '; color: #fa4} #sdoc-page .section h1:after, #sdoc-page .section h2:after, #sdoc-page .section h3:after {content: ' >'; color: #fa4} #sdoc-page .section h4:before {content: '> '; color: #fa4} #sdoc-page .section h1 {font-size: 16pt} #sdoc-page .section h2 {font-size: 13pt} #sdoc-page .section h3 {font-size: 11pt} #sdoc-page .section h4 {font-size: 10pt} #sdoc-page .section h1 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222} #sdoc-page .section h2 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222} #sdoc-page .section h3 {text-transform: lowercase; color: #999; font-weight: normal} #sdoc-page .section h4 {text-transform: lowercase; color: #999; font-weight: normal} #sdoc-page p {color: #eee; font-family: sans-serif; font-size: 10pt; line-height: 1.8em; max-width: 500px; text-align: justify} #sdoc-page pre.code {border: solid 1px #333; color: white; font-size: 10pt; font-family: 'Droid Sans Mono', monospace; padding: 4px; background: black; white-space: pre; word-wrap: none} #sdoc-page pre.code {line-height: 1.8em} Tutorial page styling. These styles, while they should be more or less consistent across pages, are designed specifically for the tutorial. #tutorial-page {width: 500px; padding-bottom: 100px; padding-right: 200px; padding-left: 50px; position: relative} #tutorial-page p {font-family: sans-serif; font-size: 11pt} #tutorial-page blockquote .signature {float: right; color: #aaa; font-variant: italic} #tutorial-page blockquote {clear: right; margin-top: 10px} #tutorial-page p a {color: #888; text-decoration: none} #tutorial-page p a:before {content: '['; color: #444} #tutorial-page p a:after {content: ']'; color: #444} #tutorial-page p a:hover {color: #eee} #tutorial-page p a:hover:before {content: '['; color: #fa4} #tutorial-page p a:hover:after {content: ']'; color: #fa4} #tutorial-page .toc h1, #tutorial-page .toc h2, #tutorial-page .toc h3, #tutorial-page .toc h4 {color: #999; cursor: pointer; font-weight: normal; white-space: nowrap; word-wrap: none; text-transform: lowercase} #tutorial-page .popdown {position: fixed; top: 0; left: 50px; border-bottom: solid 1px #444; padding-bottom: 2px} #tutorial-page .popdown:hover {border-bottom: solid 1px #fa4; padding-bottom: 10px} #tutorial-page .popdown.open {background: rgba(0, 0, 0, 0.9); left: 40px; border-bottom: solid 4px #fa4; padding-bottom: 10px; padding-left: 10px; padding-right: 10px} #tutorial-page .popdown .label {font-size: 14pt; color: #888; text-transform: lowercase; cursor: pointer} #tutorial-page .popdown .label:hover {color: #eee} #tutorial-page .popdown .label:before {content: '< '; color: #444} #tutorial-page .popdown .label:after {content: ' >'; color: #444} #tutorial-page .popdown:hover .label:before {content: '< '; color: #fa4} #tutorial-page .popdown:hover .label:after {content: ' >'; color: #fa4} #tutorial-page .popdown .contents {display: none} #tutorial-page .popdown.open .contents {display: block; overflow-y: auto; overflow-x: hidden; max-height: 400px} #tutorial-page .toc h1:hover, #tutorial-page .toc h2:hover, #tutorial-page .toc h3:hover, #tutorial-page .toc h4:hover {color: #eee} #tutorial-page .toc h1:after, #tutorial-page .toc h2:after, #tutorial-page .toc h3:after, #tutorial-page .toc h4:after {content: ' >>'; color: #888} #tutorial-page .toc h1:hover:after, #tutorial-page .toc h2:hover:after, #tutorial-page .toc h3:hover:after, #tutorial-page .toc h4:hover:after {content: ' >>'; color: #fa4} #tutorial-page .toc h1 {font-size: 16pt} #tutorial-page .toc h2 {font-size: 13pt; padding-left: 20px} #tutorial-page .toc h3 {font-size: 11pt; padding-left: 40px} #tutorial-page .toc h4 {font-size: 10pt; padding-left: 60px} #tutorial-page .section h1:before, #tutorial-page .section h2:before, #tutorial-page .section h3:before {content: '< '; color: #fa4} #tutorial-page .section h1:after, #tutorial-page .section h2:after, #tutorial-page .section h3:after {content: ' >'; color: #fa4} #tutorial-page .shell {position: fixed; border-radius: 0px; right: 50px; top: 0; bottom: 0; left: 600px; border: solid 2px #222; border-width: 0 1px; overflow-y: auto; overflow-x: hidden} #tutorial-page .shell {font-family: 'Droid Sans Mono', monospace; font-size: 10pt; color: white; background: rgba(0, 0, 0, 0.9)} #tutorial-page .shell .prompt > span {margin: 4px} #tutorial-page .shell .syntax {border-bottom: dotted 1px #468; cursor: default} #tutorial-page .shell .structure {color: #797; display: block; margin: 4px; padding: 4px 0} #tutorial-page .shell .structure:hover:before {color: #888; content: 'parse tree: '; padding-right: 4px} #tutorial-page .shell .shadow {color: #888; display: block; margin: 4px; padding: 4px 0} #tutorial-page .shell .shadow:before {color: #752; content: '>'; padding-right: 4px} #tutorial-page .shell .shadow:hover:before {color: #888; content: 'compiled: '; padding-right: 4px} #tutorial-page .shell .input {font-family: 'Droid Sans Mono', monospace; padding: 0; margin: 0; border: none !important; outline: none !important} #tutorial-page .shell .input {font-size: 10pt; background: transparent; color: white} #tutorial-page .shell .input:focus {border: none !important; outline: none !important} #tutorial-page .shell .history {position: relative} #tutorial-page .shell .history pre {font-family: 'Droid Sans Mono', monospace; font-size: 10pt} #tutorial-page .shell .history .entry, .shell .history .result, .shell .history .error, .shell .history .log {margin: 4px} #tutorial-page .shell .history .entry {color: white} #tutorial-page .shell .history .entry .command {margin-left: 4px} #tutorial-page .shell .history .result {color: #7bf} #tutorial-page .shell .history .log {color: #7fb} #tutorial-page .shell .history .error {color: #f87} #tutorial-page .shell .history .log:hover:before {color: #888; content: 'log: '} #tutorial-page .shell .sandbox {font-family: 'Neuton', 'Garamond', serif; background: #222; color: #eee; padding: 10px; margin-top: 4px} #tutorial-page #seq-decipher {padding: 4px 0; margin: 20px 0} #tutorial-page #seq-decipher input {border: solid 1px #444; background: black; padding: 4px; font-family: 'Droid Sans Mono', monospace; color: white; outline: none !important} #tutorial-page #seq-decipher table.result {font-size: 9pt} #tutorial-page #seq-decipher td.name {text-transform: lowercase; color: #aaa} #tutorial-page #seq-decipher td.fragment {font-family: 'Droid Sans Mono', monospace; color: #fa4} #tutorial-page #seq-decipher tr.variables td.desc {font-family: 'Droid Sans Mono', monospace} #tutorial-page .section h1, #tutorial-page .section h2, #tutorial-page .section h3, #tutorial-page .section h4 {padding-top: 50px} #tutorial-page .section h1 {font-size: 16pt} #tutorial-page .section h2 {font-size: 13pt} #tutorial-page .section h3 {font-size: 11pt} #tutorial-page .section h1 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222} #tutorial-page .section h2 {text-transform: lowercase; color: #999; font-weight: normal; border-bottom: solid 4px #222} #tutorial-page .section h3 {text-transform: lowercase; color: #999; font-weight: normal} #tutorial-page .section h4 {text-transform: lowercase; color: #999; font-weight: normal} #tutorial-page p {color: #eee; font-size: 10pt; line-height: 1.44em; text-align: justify} #tutorial-page code {color: #fff; font-size: 10pt; font-family: 'Droid Sans Mono', monospace; background: black; padding: 4px; border: solid 1px #333} #tutorial-page pre {color: #fff; font-size: 10pt; font-family: 'Droid Sans Mono', monospace; background: black; padding: 4px; border: solid 1px #333} #tutorial-page pre.code {cursor: pointer; padding: 10px 4px} #tutorial-page pre.code:before {content: '> '; color: #fa4} #tutorial-page pre.code:hover {background: #222; color: #fa4} __ meta::sdoc('js::caterwaul.all', <<'__'); This file isn't rendered -- it's just used internally for node testing. - pinclude build/caterwaul.js - pinclude build/caterwaul.std.js - pinclude build/caterwaul.ui.js __ meta::sdoc('js::caterwaul.node', <<'__'); CommonJS-compatible Caterwaul build | Spencer Tipping Licensed under the terms of the MIT source code license - pinclude build/caterwaul.js exports.caterwaul = caterwaul; __ meta::sdoc('js::waul-bootstrap', <<'__'); #!/usr/bin/env node Waul: the offline precompiler for Caterwaul | Spencer Tipping Licensed under the terms of the MIT source code license - include build/caterwaul.min.js - include src/waul/waul.js __ meta::sdoc('js::web/code-snippets', <<'__'); Code snippet initialization. This runs after the page is fully loaded. The idea is to setup clickability for each code snippet. setTimeout(linkify_code_snippets, 0), where [linkify_snippet(s) = s.click(send_code_to_prompt), send_code_to_prompt() = $('.shell .prompt .input').val($(this).text()) -se- $('.shell').click(), linkify_code_snippets() = $('#tutorial-page pre.code') *![linkify_snippet($(x))] /seq]; __ meta::sdoc('js::web/header', <<'__'); Page header. This is basically just a navigation container. var page_header = jquery in div.header(div.title(span.caterwaul('caterwaul'), span.js('the ', span.accent('edge'), ' of javascript'))); __ meta::sdoc('js::web/main', <<'__'); Caterwaul JS web interface | Spencer Tipping Licensed under the terms of the MIT source code license $('#cover .status').text('loading tutorial content'); $(caterwaul('js_all jquery')(function () { var original_body_html = $('body').html(), original_pages = $('#tutorial-page, #sdoc-page'), original_styles = $('style, link[rel="stylesheet"]'); $('#cover .status').text('parsing state'); var construct_page = function (original_html) { original_html = original_html.replace ? original_html : original_body_html; - pinclude pp::js::web/header - pinclude pp::js::web/shell - pinclude pp::js::web/code-snippets - pinclude pp::js::web/state - pinclude pp::js::web/sdoc - pinclude pp::js::web/source - pinclude pp::js::web/render-tutorial $('head').append(jquery in title('caterwaul js')); $('body').empty().append(page_header, original_pages); original_styles.appendTo('head')}; $.get(document.location.href, construct_page).error("construct_page(original_body_html)".qf)})); __ meta::sdoc('js::web/render-tutorial', <<'__'); Building the tutorial. All we have to do here is create a div to contain the tutorial and populate it with the SDoc obtained by parsing the object state. (Since this HTML file is actually a self-modifying Perl object -- long story.) We also build a table of contents. $('#tutorial-page').append(top_wrapper_for(toc, 'Contents'), tutorial, shell()) -where [tutorial_attribute = attributes |[x.namespace === 'sdoc' && x.attribute === 'web/tutorial' && x] |seq, tutorial = jquery [div.tutorial[sdoc_to_dom(tutorial_attribute.value)]] -se- it.find('pre.code') *![$(x).text($(x).text().replace(/^\s*/, ''))] /seq, top_wrapper_for(e, name) = jquery [div.popdown(div.label /text(name), div.contents[e])] -se- setTimeout("it.click(given.e in it.toggleClass('open'))".qf, 10), toc = jquery [div.toc] -se- toc_links *~![x] *![it.append(x)] /seq -where [section_id_count = 0, assign_section_id() = $(this).attr('id', 'section-#{++section_id_count}'), title_of(section) = where [level = Number(/level(\d+)/.exec($(section).attr('class'))[1])] in $('').text($(section).children('h#{level}').text()), sections = tutorial.find('.section').each(assign_section_id), toc_links = [jquery in a.toc_link(h1('source code')) *href('#annotated')] + sections *[jquery in a.toc_link[title_of(x)] *href('##{$(x).attr("id")}')] -seq]]; __ meta::sdoc('js::web/sdoc', <<'__'); SDoc-to-HTML converter. SDoc is a fairly simple format to parse. We just emit stuff based on indentation deltas (basically like the algorithm in the Perl). I'm doing this here rather than up-front to reduce the page size. If we converted every SDoc attribute into HTML up front it would easily double the download size. By the way, I'm using the same heuristic HTML-escaping algorithm that the Perl script uses. This basically tries to do the right thing with <> symbols in SDoc paragraphs by escaping anything that doesn't look like a well-formed tag. Of course, this precludes your saying things like < and expecting that to render verbatim; instead it will be converted to an actual less-than sign in the markup. var sdoc_to_dom = given.text in paragraphs *~!convert -seq -where [known_tags = ('html head body meta script style link title div a span input button textarea option select form label iframe ' + 'blockquote code caption table tbody tr td th thead tfoot img h1 h2 h3 h4 h5 h6 li ol ul noscript p pre samp sub sup ' + 'var canvas audio video').replace(/\s+/g, '|'), paragraphs = text.split(/\n\n+/), indentation_of(p) = (/^(\s*(\|\s)?)/.exec(p)[1].length >> 1) + 1, convert(p) = /^\s*[A-Z]/.test(p) ? documentation(p) : /^\s*\|/.test(p) ? quoted(unindent(p)) : code(p), not_a_valid_tag = new RegExp('<(?!\/|(#{known_tags})[^>]*>(?:.|\n)*\\1>)', 'g'), escape_html_in(s) = s.replace(/&(?!gt;|lt;|amp;)/g, '&').replace(not_a_valid_tag, '<'), escape_all_in(s) = s.replace(/&/g, '&').replace(//g, '>'), quoted(p) = jquery in pre.quoted[escape_all_in(p)], code(p) = jquery in pre.code[p.replace(/^\s*c\n/, '')], starts_section(p) = /^\s*(.*\.)\n\s*(.*)/.exec(p) -re [it && it[1].length + 10 <= it[2].length], unindent(p) = p.replace(indentation, '') -where [spaces = n[indentation_of(p) - 1] *['( |\\|){2}'] -seq -re- it.join(''), indentation = new RegExp('^#{spaces}', 'gm')], documentation(p) = starts_section(p) ? documentation_section(p) : jquery in p[escape_html_in(p)], documentation_section(p) = jquery [div.section[header, paragraph]] -se- it.addClass('level#{indentation_of(p)}') -where [parts = /^\s*(.*)\.\n((?:.|\n)*)/.exec(p), header = $(' ').text(parts[1]), paragraph = jquery in p[escape_html_in(parts[2])]]]; __ meta::sdoc('js::web/shell', <<'__'); var shell = given.nothing in shell.append(history_container, shell_prompt) -se- setTimeout(given.nothing in shell.click(setTimeout(given.nothing in shell.find('.prompt .input').focus(), 10) -given.e), 0) -where [shell = jquery in div.shell, history_container = jquery in div.history, history_entry_for(s) = jquery in pre.entry(span.accent('>'), span.command /text(s)), history_result_for(o) = jquery in pre.result[ui_for(o)], history_log_for(o) = jquery in pre.log /text('' + o), history_error_for(e) = jquery in pre.error /text('' + e), ui_for(x) = ! x ? jquery in span /text('' + x) : x.is_caterwaul_syntax ? jquery [span.syntax %syntax_for(x)] -where [syntax_for(node)(e) = e.text(x.toString()).hover("e.text(x.structure())".qf, "e.text(x.toString())".qf)] : x.constructor === jQuery ? x.parent().length ? jquery in div.sandbox('(A DOM node that is already in document)') /hover(given.e in x.addClass('identify'), given.e in x.removeClass('identify')) : jquery in span /text(jquery in span[x] /html()) + div.sandbox[x.clone(true)] : jquery in span /text('' + x), realign() = setTimeout(input.css({width: input.parent().width() - (input.prev().width() + 10)}) -where [input = shell.find('.prompt .input')] -given.nothing, 10), log(xs = arguments) = xs *![shell.children('.history').append(x) -se- realign()] -seq -re- xs[0], history_n = 0, context = capture [expand() = shell.animate({left: 0, right: 0}, realign), collapse() = shell.animate({left: 600, right: 50}, realign), clear() = shell.children('.history').empty() -re- realign() -re- '', caterwaul = caterwaul.clone(), history = [], console = capture [log() = context.log.apply(this, arguments) -then- undefined], help() = 'available variables:\n' + (pairs %[x[1] && x[1].description] *['#{x[0]}: #{x[1].description}'] /seq -where [keys = context /keys -seq -re- it.sort(), pairs = keys *[[x, context[x]]] -seq]).join("\n"), log(xs = arguments) = xs *![log(history_log_for(x))] -seq -re- xs[0], it = null] -se [it.context = it] -se [it.compiler = it.caterwaul.jquery(it.caterwaul.js_all())] -se [it.context.description = 'variables available to the shell', it.console.description = 'a logging interface; use console.log()', it.expand.description = 'expands the shell', it.collapse.description = 'collapses the shell', it.clear.description = 'clears old output', it.caterwaul.description = 'a copy of the caterwaul global', it.compiler.description = 'the compiler for this shell', it.history.description = 'shell input history', it.log.description = 'logs a value to the shell'], run_command(c) = log(history_entry_for(c)) -re- log(history_result_for(context.it = context.compiler(c, context))) /rescue [context.compiler('(function(){return #{c}})()', context) /rescue [context.compiler('(function(){#{c}})()', context) /rescue [log(history_error_for(context.it = e))]]], shell_prompt = jquery [div.prompt[prompt, input, structure, shadow]] -se- setTimeout(realign, 10) -se- setInterval(update_shadow, 10) -se- it.find('span.prompt').click($(this).siblings('.input').focus() -given.e) -se- setTimeout(given.nothing in it.find('.input').keydown(realign() -re [history_prev() /se [e.preventDefault()] /when [e.which === 38] || history_next() /se [e.preventDefault()] /when [e.which === 40] || run_it() /se [e.preventDefault()] /when [e.which === 13] || true] -given.e), 0) -where [input = jquery in input.input, prompt = jquery in span.accent('>'), structure = jquery in div.structure, shadow = jquery in div.shadow, last_input_text = null, rename_gensyms_in(t) = t.replace(context.caterwaul.gensym_rename_table(t)), update_shadow() = structure.text(context.caterwaul.parse(input.val()).structure() -rescue- 'invalid parse') -se- shadow.text(context.compiler(context.caterwaul.parse(last_input_text = input.val())) -re- rename_gensyms_in(it).toString() -rescue- e.toString()) -unless [input.val() === last_input_text], h_index = 0, history_prev() = (h[h_index] = input.val()) -when [h_index < history_n] -re- input.val(h[--h_index]) -when [h_index > 0] -where [h = context.history], history_next() = (h[h_index] = input.val()) -when [h_index < history_n] -re- input.val(h[++h_index]) -when [h_index < history_n] -where [h = context.history], history_add(s) = history_n = h_index = context.history.push(s), scroll_to_end() = setTimeout(shell.scrollTop(shell.children(':last') -re [shell.scrollTop() + it.position().top + it.height()]) -given.nothing, 0), run_it() = history_add(t) -re- run_command(t) -re- input.val('') -re- scroll_to_end() -when.t -where [t = input.val()]]]; __ meta::sdoc('js::web/source', <<'__'); Building the documentation pages. This is just a matter of finding the right SDoc sources and tying them together. var attributes = perl_attributes(original_html.replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&')); $('#sdoc-page').append(sections) -where[sdocs = attributes %[x.namespace === 'sdoc' && /^js::/.test(x.attribute)] -seq, core = sdocs %[/^js::caterwaul/.test(x.attribute)] -seq, extension = sdocs %[/extensions\//.test(x.attribute)] -seq, web = sdocs %[/web\//.test(x.attribute)] -seq, back_link = jquery in a.back('Back to tutorial') *href('#tutorial'), title(a) = /\//.test(a) ? jquery [span.path[a.replace(/^js::(.*\/).*/, '$1')], span.name[a.replace(/.*\//, '')], span.extension('.js')] : jquery [span.name[a.replace(/^js::/, '')], span.extension('.js')], section(x) = jquery [div.file(h1[title(x.attribute)], div.contents)] -se- setTimeout(given.nothing in it.find('h1').click(given.e in $(this).next('.contents').toggle()), 0) -se- it.find('.contents').hide().append(sdoc_to_dom(x.value)), sections = jquery [div *id('annotated') >= back_link >= core_sections >= extension_sections >= web_sections] -where [core_sections = $([]) -se- core *~!section *![it.push(x)] /seq, extension_sections = $([]) -se- extension *~!section *![it.push(x)] /seq, web_sections = $([]) -se- web *~!section *![it.push(x)] /seq]]; setInterval(check_for_destination, 50) -where [viewing_annotated_source = false, moving = false, check_for_destination() = show_annotated_source() -when [! moving && ! viewing_annotated_source && /^#annotated/.test(document.location.hash)] hide_annotated_source() -when [! moving && viewing_annotated_source && ! /^#annotated/.test(document.location.hash)], show_annotated_source() = moving = $('#sdoc-page').css({display: 'block', top: $(window).scrollTop(), left: $(window).width()}). animate({left: 50}, 'slow') -se- $('#tutorial-page').animate({left: '-=#{distance}'}, 'slow', given.nothing [viewing_annotated_source = true, moving = false]) -se- $('.shell').animate({left: '-=#{distance}', right: '+=#{distance}', opacity: 0}, 'slow') -se- $('.header, .popdown').hide('slow') -where [distance = $(window).width()], hide_annotated_source() = moving = $('#sdoc-page').animate({left: $(window).width()}, 'slow', given.nothing [viewing_annotated_source = moving = false, $(this).css({display: 'none'})]) -se- $('.shell').animate({left: '+=#{distance}', right: '-=#{distance}', opacity: 1}, 'slow') -se- $('#tutorial-page').animate({left: '+=#{distance}'}, 'slow') -se- $('.header, .popdown').show('slow') -where [distance = -$('#tutorial-page').position().left]]; __ meta::sdoc('js::web/state', <<'__'); Self-modifying Perl state parser. This is actually really easy. All of the attributes in self-modifying Perl come in one of two forms. One is the short form, written as meta::\w+('stuff', 'stuff');\n. The other is the long form, written meta::\w+('stuff', <<'eof');\n...\neof\n. We just need to find all occurrences of either one of these things. var perl_attributes = given.text in text.match(long_form) *parse_long + text.match(short_form) *parse_short -seq -where [long_form = /^meta::(\w+)\('([^']+)', (?:<|<){2}'([^']+)'\);[\r\n]{1,2}([\s\S]*?)[\r\n]{1,2}\3$/mg, short_form = /^meta::(\w+)\('([^']+)', '([^']+)'\);$/mg, long_form_parser = new RegExp(long_form .toString().replace(/^\/(.*)\/[mg][mg]$/, '$1')), short_form_parser = new RegExp(short_form.toString().replace(/^\/(.*)\/[mg][mg]$/, '$1')), parse_long(match) = long_form_parser.exec(match) -re- {namespace: it[1], attribute: it[2], value: it[4]}, parse_short(match) = short_form_parser.exec(match) -re- {namespace: it[1], attribute: it[2], value: it[3]}]; __ meta::sdoc('readme', <<'__'); Caterwaul. Caterwaul is two different things. First, and most importantly, it is a powerful low-level Javascript code manipulation library with a Javascript parser, AST, in-process compiler, and replication. Second, it is a programming language implementation that uses this library to transform your code in various arcane ways that I happen to find useful. The whole project is MIT-licensed, and in the unlikely event that you want to use it you are free to [email me](mailto:spencer@spencertipping.com) with any questions/issues. What follows is a ten-minute introduction to caterwaul's core concepts. It covers about 5% of what caterwaul does. Heads up: before you use this. It has a few defects. One relates to stuff like `if (x) switch (y) {} else z();`, which gets rewritten to broken code. This is a parse-level problem and I haven't had time to fix it yet. Second, various ES5/6 features aren't entirely supported; for example functions-in-objects (`let x = {f() {}};`). There's probably more; the ES5 support was hacked on last-minute. Third, Caterwaul parses non-JS. `let @x = 100` is fine, `let x\y\z = @foo[\]` works, etc. Not a defect exactly, but something to be aware of. Caterwaul as a library. Caterwaul is implemented in pure Javascript, so you can use it to live-compile your code in a browser, or you can use the `waul` precompiler to compile your code up-front. You can see the live compiler in action by going to [the caterwaul website](http://caterwauljs.org). This site embeds a caterwaul compiler configured to use the standard macro library; this causes it to compile what I refer to as the caterwaul programming language. The website documents this language in some detail, as does [Caterwaul by Example](http://caterwauljs.org/doc/caterwaul-by-example.pdf) - though some of the examples will fail since Caterwaul 1.2.3, which introduces [a breaking change](https://github.com/spencertipping/caterwaul/commit/05a5e317336e751cbf90a7f574070d3eca4f69a4) to the `seq` library. If you're interested in using caterwaul as a compiler library, I recommend reading the [caterwaul reference manual](http://caterwauljs.org/doc/caterwaul-reference-manual.pdf), which covers its core API in significantly more detail than this readme. You can also read through the caterwaul source code, which contains copious documentation, some of which is up to date. Parsing things. Caterwaul's Javascript parser takes anything with a valid `toString()` method as input, including Javascript functions. | var tree = caterwaul.parse('3 + 4'); tree.toString() // '3 + 4' tree.data // '+' tree.length // 2 tree[0].data // '3' tree[1].data // '4' Detecting patterns. If you're serious about this stuff, I recommend writing this code in the caterwaul programming language. It supports a lot of advanced features that make syntax trees much easier to work with; in particular, preparsed quoted constructs (similar to the ' operator in Lisp). These will give you a significant performance advantage and better notation. | var pattern = caterwaul.parse('_x + _y'); var match = pattern.match(tree); match._x.data // '3' match._y.data // '4' var template = caterwaul.parse('f(_x, _y)'); var new_tree = template.replace(match); new_tree.toString() // 'f(3, 4)' Compiling things. Caterwaul's compiler does a lot, but the basic case works like `eval`: | var f = function (x, y) {return x * y}; caterwaul.compile(new_tree) // 12 You can also bind variables from the compiling environment: | var new_f = function (x, y) {return x + y}; caterwaul.compile(new_tree, {f: new_f}) // 7 You can only compile things that return values (technically, things which are expressions; the litmus test is whether you could legally wrap it in parentheses), so stuff like `var x = 10` won't work. This is different from Javascript's `eval` function. If you want to execute imperative code, you should wrap it in a function: | var function_wrapper = caterwaul.parse('(function() {_body})'); var code = caterwaul.parse('if (x) {console.log(x)}'); var new_function = caterwaul.compile(function_wrapper.replace({_body: code})); new_function(); Caterwaul as a programming language. I wrote a set of macros that use the above API to modify Javascript code; this macro set has been refined over the past year to become a programming language that I find useful. You can learn this language on the caterwaul website, which goes through it by example and provides an interactive shell so you can see what the compilation process looks like. Using caterwaul this way. There are two ways to use caterwaul as a programming language. You can compile code from inside another Javascript process, which works because all caterwaul code is syntactically valid Javascript code as well (note that the following example will work only if you've loaded `caterwaul.std.js` from the `build/` directory): | var compiler = caterwaul(':all'); // :all means 'every macro you know about' var compiled = compiler(function () { console.log(x) -where [x = 10]; }); compiled(); // logs 10 The other way to use the programming language is by using the `waul` precompiler. This compiles your code to straight Javascript, eliminating the runtime overhead imposed by caterwaul's parser, macroexpander, and compiler. Waul files typically end in `.waul` or `.waul.sdoc` (if you're using [SDoc](http://github.com/spencertipping/sdoc), which waul will transparently parse) and contain code like this: | caterwaul(':all')(function () { n[10] *console.log -seq; console.log('done #{message}') -where [message = 'iterating through numbers']; })(); You can compile this by running `waul file.waul`, which will generate `file.js`. `file.js` may contain references to the `caterwaul` global if you use certain macros, but there will be no compilation overhead. Caterwaul as a self-replicating monstrosity. This is the coolest part of caterwaul in my opinion. Both `caterwaul` as a Javascript object and `waul` can give you string expressions that reproduce them. This is very useful for library bundling; for example: | var r = caterwaul.replicator(); var code = r.toString(); If you do this, someone else can `eval` 'code' and they will end up with a global called `caterwaul` that is configured exactly as your `caterwaul` is configured. The only requirement is that configurations be declared as modules, which is done like this: | // caterwaul.module(name, [compiler_configuration], function) caterwaul.module('my-configuration', ':all', function ($) { // $ is the global caterwaul object $.foo = 'bar'; }); The output of `replicator` is a function that recreates all modules by re-running their initializers. Note that `replicator` rewrites the module functions into their post-compilation equivalents; in other words, the body of each function has already been compiled into normal Javascript. This reduces total compilation overhead. Waul replication. Suppose you write a custom caterwaul module and want a new version of `waul` that contains it. You can ask `waul` to replicate itself with an extension like this: | $ ./waul -r -e extension.waul > new-waul $ chmod u+x new-waul $ ./new-waul my-file.waul This is especially useful for setting up shebang lines for scripts that require custom waul extensions: | #!./new-waul caterwaul(':all')(function () { // custom code })(); The `waul` in caterwaul's root directory is preloaded with `build/caterwaul.std.js` and `build/caterwaul.ui.js`. As you might guess, `waul` wasn't written specially to contain these; rather, it was generated by `waul-core` (which doesn't have any libraries built-in) by this process: | $ ./waul-core --replicate -e build/caterwaul.std.min.js -e build/caterwaul.ui.min.js > waul $ chmod 0700 waul __ meta::sdoc('waul::waul', <<'__'); Waul precompiler implementation | Spencer Tipping Licensed under the terms of the MIT source code license Introduction. Caterwaul is useful as a library and an online compiler, but when performance is important you probably want to deliver precompiled code. Waul is a program that does exactly that. You write a regular file that invokes Caterwaul on a function, and Waul emits a Javascript file that contains the Caterwaul-compiled results. For example: | $ cat > test.waul < limit] -then- x, bindings() = n[limit] *[['_#{x + 1}', items[x]]] -object -seq], waul_node_repl() = introduce() -then- require('repl').start('waul> ', undefined, evaluator) -where [evaluator(s, _1, _2, cc) = cc(null, history /~push/ instance(s, {$: $, caterwaul: $, require: require} /-$.merge/ history.bindings())) -rescue- cc(e, undefined), instance = $(options.configuration), history = repl_history()], waul_simple_repl() = introduce() -then- process.stdin.on('data', require('util') /~inspect/ history.push(instance(s, {$: $, caterwaul: $, require: require} /-$.merge/ history.bindings())) -re- process.stderr.write('#{it}\n', 'utf8') -rescue- process.stderr.write('Error: #{e}') -given.s) -then- process.stdin /~setEncoding/ 'utf8' -then- process.stdin.resume() -where [instance = $(options.configuration), history = repl_history()], waul_repl() = options.simple_repl ? waul_simple_repl() : waul_node_repl(), waul_identity(file, m) = file /-waul_output/ m._, waul_transform(file, m, t) = file /-waul_output/ t.replace({_name: m._name, _compiled: transformed_function}).guarded() -where [transformed_function = $(m._transform.as_escaped_string())(m._function) / null /-$.late_bound_tree/ options -re- it.replace(it /!$.gensym_rename_table)], waul_run(m) = $(m._transform.as_escaped_string())(m._function) /-$.compile/ {require: require, caterwaul: $} -re- it()]}); __ meta::sdoc('web/tutorial', <<'__'); Introduction. Caterwaul is a Javascript recompiler that lets you change the semantics of functions. To do this it implements a modular decompiler, macroexpander, and compiler that allow you to manipulate code in a first-class way. It also comes with several macro (Lisp-style, not C-style) libraries to make Javascript more fun (though you can easily disable them and/or write your own). If you see merit in the linguistic features of Haskell, APL, Lisp, and Perl, then you will probably also see merit in the things that Caterwaul tries to achieve and you might even like it. If you're a normal person who wants to use modern tools to write normal, maintainable apps, and you're not a programming language enthusiast who seeks out the pathological and the bizarre, then I recommend using CoffeeScript instead. This page is written in Caterwaul using the libraries covered below. If you're feeling adventurous, you might be interested to see the annotated source (the standard library has similar documentation and is in the same directory). A shell is available to interactively use Caterwaul while reading the tutorial below. I've also started writing documentation in a more traditional form, available from the documentation directory. This is a great place to start if you're interested in using Caterwaul for production projects (which you shouldn't be). Praise for Caterwaul. Lots of people have looked at Caterwaul and great things to say about it. Here are some of them: LaunchCtl? You may as well learn Caterwaul! Jeff Simpson, Senior Software Engineer @ LivingSocialEmbrace the dark side of Javascript. Use Caterwaul. Dan Hopkins, Senior Software Engineer @ LivingSocialNobody in their right mind would use this language. AnonymousUsing this to build applications is like using quantum mechanics to build a car. Hank Racette, Founder of Arden Lake TechnologiesCaterwaul is a terrible language for the vast majority of developers. But I like it well enough. Spencer Tipping, author of CaterwaulGenius bilalhusain@ycombinatorCaterwaul is f**king terrifying @wookiehangoverCaterwaul is for advanced JavaScript developers only, and is in no way recommended to people that don't know what they're doing. SoftpediaUsing Caterwaul. I recommend that you don't use Caterwaul because it's a monstrosity. However, if you're adventurous and self-destructive, here's how to go about it. Caterwaul is pure Javascript, so you can integrate it into any web page without much effort. For example: | <script src='http://caterwauljs.org/build/caterwaul.min.js'></script> <script src='http://caterwauljs.org/build/caterwaul.std.min.js'></script> <script src='http://caterwauljs.org/build/caterwaul.ui.min.js'></script> The next step is to configure a compiler instance. Caterwaul provides a framework that you can use to build custom compilers, so this step tells Caterwaul what kind of macros you want to expand. (This could include your own custom macros or third-party extensions.) For example, here's how to get a compiler that compiles all of the macros discussed on this page: | var compiler = caterwaul('js_all jquery'); What we're saying here is "build a compiler with all Javascript core macros, then add jQuery support." A compiler is a function that recompiles any function you give it: | var compiled = compiler(function () {alert(x) -where [x = 10]}); Generally you create the compiler inline and use it to transform your app's initialization function: | // For jQuery apps: $(caterwaul('js_all jquery')(function () { $('body').append(jquery in div('hi there!')); })); | // For non-jQuery apps, or libraries: caterwaul('js_all')(function () { // app code })(); Check out Caterwaul by example for a more detailed discussion about writing apps with Caterwaul. Libraries. I maintain a motley set of libraries that are in various ways related to Caterwaul. Right now they are:I should mention that they're in various states of disrepair and sometimes break. However, if you'd like to use one in production (which you shouldn't), feel free to e-mail me and I'll set up a stable versioning scheme for it. They're all MIT-licensed, as is Caterwaul. Downloading Caterwaul. Here are some relevant links if you want to hack on or use Caterwaul:
- Bloom filter implementation
- ANSI terminal library
- Headless jQuery constructors (for HTML rendering)
- Regular expression parser (uses Caterwaul syntax trees)
- Nonlinear parser combinators
- Futures implementation
- Value generation combinators (for testing, etc)
- Numerical integration with error function
- Mediocre serialization library
- State transition/invariant library
Caterwaul in node.js. It's really easy to use Caterwaul with node.js. You just need to download
- Caterwaul on Github
- Stable versions of Caterwaul
- Vim highlighter for Javascript/Caterwaul
- Javascript in Ten Minutes
caterwaul.node.js
and whatever extensions you want to use (I'll assumecaterwaul.std.js
for the purposes of this example), and concatenate them into one big file. On Mac or Linux the process looks like this: | $ curl http://caterwauljs.org/build/caterwaul.{node,std}.js > caterwaul-custom.js $ node > var caterwaul = require('./caterwaul-custom.js').caterwaul; > caterwaul.js_all()('[1, 2, 3] *[x + 1] -seq'); [ 2, 3, 4 ] > Because Caterwaul recompiles functions, you'll have to explicitly bindrequire
if you want your app to have access to it. This can be done by specifying a hash of variable bindings after the function you're compiling. For example: | var main_function = function () {...}; caterwaul.js_all()(main_function, {require: require}); Doing this putsrequire
into the local scope of the compiled function. (Unfortunately, Caterwaul has no way of doing this automatically, since all functions that it constructs are created in the global scope and lexical scopes are not first-class in Javascript.) Javascript extensions. Caterwaul's core macro set starts by extending Javascript syntax in some helpful ways. In particular, it enables quick function assignment, infix and postfix function application (experimental), and Ruby-style string interpolation (which works with both single and double quotes): add(x, y) = x + y c String.prototype.say_hi() = 'hi from #{this}!' Caterwaul translates these expressions into this: | add = function (x, y) { return x + y; }; String.prototype.say_hi = function () { return 'hi ' + (this) + '!'; }; Now we can use the new functions in the shell: add(3, 4) 'javascript'.say_hi() Caterwaul also supports some experimental features when defining functions this way. String interpolation, function assignment, and infix and postfix function application are the only irregular syntactic forms provided by Caterwaul. Everything else is implemented as a regular form called a modifier. General modifiers. A modifier is a word that is used with an operator to modify a bit of syntax. For example, Caterwaul provides a modifier calledwhen
to execute things conditionally: log('hi') -when ['foo'.length === 3] There are two parts to a modifier. The first is the operator you use with it (in this case minus), and the second is the modifier and any arguments it takes. The operator is very important; it determines how much stuff you're modifying. For example: log('hi'), log('again') -when [1 === 2] Here thewhen [1 === 2]
only modifieslog('again')
because minus has much higher precedence than the comma operator. However, Caterwaul lets you use several other operators to change this: log('hi'), log('again'), when [1 === 2] In this case thewhen [1 === 2]
modifies bothlog
statements. The reason for this is kind of subtle: comma left-associates, so the first comma was collapsed into a single syntax node that then became the left-hand side of the second comma. Because Caterwaul operates on the structure of your code, it groups bothlog
statements into the conditional. You can inspect Caterwaul's parse tree by using theqs
modifier (for "quote syntax"). For example: qs [log('hi'), log('again'), when [1 === 2]] qs [log('hi'), log('again'), when [1 === 2]].data qs [log('hi'), log('again'), when [1 === 2]].length qs [log('hi'), log('again'), when [1 === 2]][0] qs [log('hi'), log('again'), when [1 === 2]][1] qs [log('hi'), log('again'), when [1 === 2]].structure() Thestructure
method gives you a string containing the syntax tree in S-expression form. I talk more about this in the section about quotation. Modifier operators. There are about six different operators you can use in conjunction with a modifier. From highest to lowest precedence they are:Conditional modifiers.
- The slash. For example,
log('hi') /when [true]
. I use this when I need something tighter than a minus.- The minus. For example,
log('hi') -when [true]
. It also comes in another form:log('hi') -when- true
. I use this most of the time because it seems easier to read.- The
in
operator. For example,given [x] in x + 1
.in
has the same precedence as<
and>
, which is lower than the arithmetic operators. As a result, it's useful when you're binding variables or creating functions around simple expressions.- The
<>
operators. These are used around a modifier:log('hi')
. This has the same precedence asno_logging in
and other relational operators.- The
|
operator. This is the lowest-precedence regular operator; the only things lower are&&
,||
,?:
, assignment, and the comma.- The
,
operator. This is the lowest-precedence operator in Javascript. It can be dangerous to use because it left-associates; for example,f(x, y, z, where [z = 10])
will invokef
on just one parameter, since thewhere
gobbles everything to its left. (Using a|
here would fix the problem.)- The
[]
operator. This starts the precedence hierarchy over by using explicit grouping. For example,where [x = 10] [log(x)]
.when
andunless
are used to make conditionals more flexible. The semantics and return values are: | x -when- y -> y && x x -unless- y -> !y && x Similar areand
andor
, which behave identically to&&
and||
but can have different precedence. For example: | x -or- y + 1 -> (x || y) + 1 x /and.y * 3 -> (x && y) * 3 Binding modifiers. These let you define locally-scoped variables. There are two of these modifiers,where
andusing
.where
is used to bind local variables at runtime, much like it is in Haskell: x -where [x = 10] f(10) -where [f(x) = x + 1] Sometimes, though, you want to have access to all of the properties of an object without qualifying them. Javascript provides thewith
keyword for this purpose, but because it is evaluated at runtime it has significant performance implications. A much faster alternative is to use Caterwaul'susing
modifier, which evaluates its object at compile-time and produces a list of local variable definitions that refer to the object's properties. (Naturally, this means that the object you're using needs to be computable at compile-time.) Also, any variables defined withusing
will shadow surrounding variables with the same name. For example, this refers tocaterwaul.compile
: compile -using.caterwaul The opposite ofusing
iscapture
, which makes an object out of a series of assignments. The assignment structure is just like it is forwhere
: result = capture [f(x) = x + 1, y = 10] result.f(10) result.y Capturing assignments this way has one problem: since it just builds an object, your expressions can't refer to each other. For example, this won't do what you want: capture [f(x) = x + 1, g = f] The way to work around this is to usewcapture
, which, as its name suggests, combineswhere
andcapture
. Likecapture
, it returns an object built out of assignments, but likewhere
, it introduces a local scope so that all of the assignments can see each other: wcapture [f(x) = x + 1, g = f].g(10) Function modifiers. There are two words that create functions. One isgiven
, which creates a regular function. The other isbgiven
, which binds the function to thethis
where it was defined. For example: given [x] in x + 1 x + 1 -given [x] f.call(10) -where [f = this -given- x] f.call(10) -where [f = this -bgiven- x] There's a shorthand you can use if you just have a single operand for a modifier: x + 1 -given.x given.x in x + 1 given.x [x + 1] Side-effecting modifiers. These make it easy to manipulate values and return things without using an explicit variable. We do this in English using pronouns, and Caterwaul binds the variableit
to refer to "the thing that we're working with." There are two ways to create a side-effect. One is to return the side-effecting expression and the other is to return the original value. For example, suppose you want to write a functionhash(k, v)
that returns a hashh
such thath[k] === v
. In plain Javascript you'd write this: | var hash = function (k, v) { var result = {}; result[k] = v; return result; }; However, the amount of typing required is much larger than the complexity of the problem. We want to return an object after applying a side-effect to it; to do this with Caterwaul we would use these
modifier, which stands for "side-effect": hash(k, v) = {} -se [it[k] = v] This style of side-effects returns the original expression. Sometimes, though, you want to return the result of the side-effect rather than the original. For example, here's a zero-division check in plain Javascript: | var x_over_yp1 = function (x, y) { var y_plus_1 = y + 1; return y_plus_1 === 0 ? 0 : x / y_plus_1; }; Here's the same function using a returning side-effect ("re" stands for "returning effect"): x_over_yp1(x, y) = y + 1 -re [it === 0 ? 0 : x / it] Side-effecting won't impact the evaluation order of your code. That is,x -se- y
andx -re- y
will always evaluatex
beforey
. Another modifier is-then-
, which is synonymous to-re-
but doesn't allocate a closure or bind theit
variable. It is semantically equivalent to Javascript's native comma operator, but can take on different precedence levels. Quotation. Most people won't use this, but it's handy if you're doing heavy-duty syntax analysis or writing complex macros. The standard library includes an obscure modifier calledqs
that you can use to quote a piece of code. Quotation is basically grabbing the literal syntax rather than evaluating it normally. For example: qs [foo + bar] qs [foo + bar].data qs [foo + bar].length qs [foo + bar][0] qs [foo + bar].structure() Quotation is an idea that comes from Lisp and is handled similarly by Caterwaul. (The only difference is that Caterwaul returns its own n-ary syntax tree format instead of cons trees.) A variant,qse
, macroexpands the quoted code before returning it as a syntax tree. For example: qse [log(foo) -unless [true]] log(foo) -unless [true], qse You can use this in the shell to see how Caterwaul will macroexpand something. Note that the shell'scaterwaul
function is configured with all extensions enabled. Evaluation modifiers. You can inform Caterwaul that you want to evaluate an expression at compile-time rather than at runtime by using theeval
modifier. For example: given.x in x + Math.sqrt(2) given.x in x + Math.sqrt(2) /eval In the second example,Math.sqrt(2)
is evaluated when the code is macroexpanded and a variable calledeval
is inserted into the code in its place.eval
is bound to the result of the compile-time evaluation. Generally you wouldn't use this modifier, but I've included it for completeness. Macroexpansion modifiers. Thereexpand
andnoexpand
modifiers give Caterwaul instructions about how to handle an expression. For instance, suppose you have a variable calledgiven
, and you want to use it without worrying that Caterwaul will interpret it as a modifier. You can wrap expressions that contain this variable with thenoexpand
modifier to prevent any macroexpansion from happening: qse in noexpand [x -given.x] Similar tonoexpand
isreexpand
, which tells Caterwaul to re-expand the output of the first macroexpansion. Normally you don't need to use this because all of the standard macros re-expand their output automatically and therefore require only the initial expansion. Other modifiers. There are a few more modifiers that I threw in to the standard library to make some edge cases easier: new Error('uh-oh') -raise null.foo -rescue- log('caught #{e}') The exception is always callede
when using therescue
modifier. Sequence library. This is probably the gnarliest part of Caterwaul, but in my opinion it's also the most useful. The sequence library provides a modifier calledseq
that reinterprets some syntax within an APL-like domain-specific language. It generates very efficient code and lets you express maps, folds, unfolds, filters, etc, with very little effort. For instance, suppose we want an array of the first 10 squares. Using the sequence library looks like this: ni[1, 10] *[x * x] /seq Mapping and iterating. The*
operator is responsible for mapping, iterating, and flat-mapping. It's fairly easy to use; you just "multiply" a sequence by a bracketed expression.*
will create a variable calledx
and evaluate your expression for each element in the sequence. It then collects these results and returns a new array. For example: seq in [1, 2, 3] *['x = #{x}'] You don't have to use just arrays. You can use anything with a.length
and[0]
...[n - 1]
attributes. One of the most common non-array collections I use is a jQuery selector (just be sure to wrapx
again so that you're not dealing with a plain DOM node): seq in +$('div') *[$(x).attr('class')] In this case, the+
converts the jQuery collection into an array by invokingArray.prototype.slice
on it. We can also map into another jQuery collection if we want to; for instance: seq in $('div') *[x.parentNode] Alternative forms. Most operators have an alternative form that does something similar to the original. You specify this form by using a!
after the operator. The alternative form of*
is used to iterate without collecting the results; doing this returns the original array. For example: seq in [1, 2, 3] *![log(x)] The third use of*
is flat-mapping, which is denoted by writing*~!
. For example: seq in [1, 2, 3] *~![[x, x + 1]] Like the original form, these alternative forms can be combined with any of the operator features below. Prefixes. Caterwaul 1.0.3 supports prefixes for mapping and iterating over non-array structures. Right now there are two prefixes,%k
and%v
. These can be used with*
and*!
, but not*~!
. These prefixes stand for 'keys' and 'values', respectively, and they're used to limit the scope of a map or iteration to the keys or values of an object. For example: (seq in {foo: 'bar'} %k*[x + 'hi']).foohi (seq in {foo: 'bar'} %v*[x + 'hi']).foo seq in {foo: 'bar'} %v*![log(x)] The reason you can't use%k
and%v
with*~!
is that*~!
isn't a componentwise operator. Because it could return zero, one, or many values for each one that gets visited, it isn't clear what to do with the result. (One example of this isseq in value %v*~![[x, x + 'foo']]
-- the intent is unclear.) Initialized forms. You can provide an initialization block to*~!
to iterate without building up an array. For example, to find the 1024th root of a number: 5 *~![xi < 10][Math.sqrt(x)] -seq This is equivalent to using an unfold (see below) and selecting only the last element of the resulting array. Because no array is built,*~![]
ends up being significantly faster. Hacking iteration. It's not hard to achieve a lot of flexibility with theseq
macro. For example, you can manually incrementxi
to skip elements: [1, 2, 3, 4, 5] *[++xi /unless [x & 1], xs[xi]] -seq I don't exactly recommend that you do this, but under the hood theseq
macro writes more or less normal-lookingfor
loops and so is amenable to this kind of manipulation. Operator features. The sequence library uses operators to describe operations on arrays. Most of them are regular binary infix operators like+
and*
, though a few of them have names (such asni[]
above). Despite the wide array of operators supported, there is a high degree of regularity among them. Each operator that takes a block (like*
does) has several options that can be set to change the way it interprets the block. Variable renaming. The sequence language lets you rename any variable by prefixing the[]
with a new variable name: seq in [1, 2, 3] *y[y + 1] Function promotion. Caterwaul 1.1 adds the ability to implicitly promote functions by using them instead of a[]
block. For example: seq in [1, 2, 3] *![log(x)] seq in [1, 2, 3] *!log Note that the function name is evaluated within the context of the loop, so it is re-evaluated each iteration. This could, in pathological cases, be beneficial; but mostly it's something to watch out for. Generally you should only use local variables, or simple permutations of them, as functions. Function promotion applies to all operators that take blocks. Filtering. The filtering family of operators is denoted by%
. For instance, here's a way to get multiples of three: seq in [1, 2, 3] %[x % 3 === 0] Alternative forms. Negation is so high precedence that it's often difficult to work it into a form without adding parentheses. The alternative form of%
negates the predicate: seq in [1, 2, 3] %![x % 3] The other alternative form of%
is a simultaneous map/filter. The idea is to return the expression value when it's truthy and drop the element otherwise. For example, we can get the squares of all negative elements this way: seq in [1, -2, -3, 4] %~![x < 0 && x * x] Prefixes. Filter operations can all take the%k
and%v
prefixes. These remove and/or transform key-value mapping in objects. For example: seq in {foo: 'bar', bif: 'baz'} %k%[/foo/.test(x)] /pairs seq in {foo: 'bar', bif: 'baz'} %v%[/z/.test(x)] /pairs seq in {foo: 'bar', bif: 'baz'} %k%![/o/.test(x)] /pairs seq in {foo: 'bar', bif: 'baz'} %k%~![/o/.test(x) && x.replace(/o/g, 'a')] /pairs Using/pairs
with prefixes isn't necessary in most cases. I've included it in these examples to better illustrate what's happening. Initialized forms. You can use an init-block with%~!
to separate the selector and the map. For example: [1, 2, 3] %~![x & 1][x + 1] -seq This is faster than its equivalent,%[x & 1] *[x + 1]
. Folding. You can fold stuff through a binary expression by using the/
family of operators./
has two forms: left fold (the default), and right fold (written as/!
). For example, here is how you might sum a bunch of numbers: seq in [1, 2, 3] /[x + x0] Since+
is associative it doesn't matter which direction the fold goes. It becomes obvious, however, if we interpolate the values into a string: seq in [1, 2, 3] /['[#{x0}, #{x}]'] seq in [1, 2, 3] /!['[#{x}, #{x0}]'] Notice that for folding we have a new variablex0
.x0
is always used as the accumulator; that is, the inductive step isx0 = f(x0, x)
. There are actually a few variables you have access to depending on what you're doing. Inside any block you'll havex
,xi
(the current index),xs
(the current source collection),xr
(the result collection), andxl
(the length of the original sequence). Each of these changes uniformly if you rename the variable; so for instance: seq in [1, 2, 3] /bar[bar + bar0 + bari + barl + bars + barr] Prefixes. Sometimes you want to fold into an existing element. For example, suppose you want the sum of the squares of numbers from 1 to 10. This code won't work: seq in n[1, 11] /[x*x + x0*x0] The reason is that you can't square the accumulator. If you can't specify the starting element of the fold, the best you can do is to pre-map the elements and then sum them normally: seq in n[1, 11] *[x * x] /[x + x0] However, the other option is to specify the initial value forx0
by using the fold prefix: seq in n[1, 11] /[0][x0 + x*x] seq in n[1, 11] /![0][x0 + x*x] The fold prefix value is never interpreted in sequence context, even if you modify the body of the fold to do so. Unfolding. Sometimes it's useful to have anamorphic value generators. These are the opposite of folds: unfolds produce multiple values from one. For instance, summing the array[1, 2, 3, 4, 5]
can be done using a fold over the+
operator: [1, 2, 3, 4, 5] /[x + x0] -seq Similarly, generating the array[1, 2, 3, 4, 5]
can be done using an unfold over the increment operator: 1 /~![x < 5 ? x + 1 : null] -seq If we treat the body of the unfold as a functionf(x) = x < 5 ? x + 1 : null
, then an unfold could be seen as[1, f(1), f(f(1)), f(f(f(1))), f(f(f(f(1))))]
. The last element returnsnull
, which tells the sequence library to stop unfolding. I recently received some great feedback from @Phlogistique regarding unfolding. He suggested that I could use an extra body to determine the condition rather than usingnull
as a sentinel. This is a very elegant approach, and it's now implemented in version 1.1.5: 1 /~![x < 5][x + 1] -seq If you use two blocks,null
can be generated from your unfolds and elements will be generated as long as the first block returns truthy values. Also,x0
is set to the value of the first block. So, for example: 1 /~![xi < 5 && 'woot'][x0] -seq Quantification. The sequence library provides existential quantification on arrays. This uses a block that acts as a predicate. So, for instance, to determine whether any element in an array is positive: [-4, -5, 10, 2] |[x > 0] |seq The|
operator returns the first truthy value generated by the expression (not just true or false), so you can use it to detect things too. This block causes the sequence comprehension to return not only whether an element is positive, but if so the first such element will be returned: [-4, -5, 10, 2] |[x > 0 && x] |seq [-4, -5, 10, 2] |[x -when [x > 0]] |seq We can also use this construct to return the index of the first matching element. Because an index of 0 is falsy, we'll have to add one (so 0 is the not-found value rather than -1): [-4, -5, 10, 2] |[xi + 1 -when [x > 0]] |seq You can also ask whether something doesn't exist. This is done with the|!
sequence operator. For example, to ask whether an array contains no negative numbers: [1, 2, 3, 4] |![x < 0] |seq Caterwaul 1.2 adds the|~!
operator, which is identical to|
but matches from the right instead of from the left. For example: [1, 2, 3] |[x & 1 && x] |seq [1, 2, 3] |~![x & 1 && x] |seq Combination. You can combine sequences via concatenation, written+
: seq in [1, 2, 3] + [4, 5, 6] Objects. A really useful and important feature of the sequence library is that it works with objects very easily. It has four operators,/keys
,/values
,/pairs
, and|object
, that can convert between objects and arrays. You can pull an array of the keys or values of an object (not in any particular order of course) by using/keys
and/values
. For example: window /keys -seq jQuery /values -seq More interesting is the/pairs
operator. This pulls out key-value pairs as two-element arrays: {foo: 'bar', bif: 'baz'} /pairs -seq Its inverse is the|object
operator (also can be written as-object
or/object
, depending on what kind of precedence you want), which turns an array of those pairs back into an object: [['foo', 'bar'], ['bif', 'baz']] |object |seq [['foo', 'bar'], ['bif', 'baz']] -object -seq [['foo', 'bar'], ['bif', 'baz']] /object /seq Note the differing precedences of/keys
etc. and|object
. This is intentional. The rationale is that you rarely manipulate objects as objects in sequence comprehensions, since the sequence library has no useful operators for objects other than unpacking. Therefore, objects come from various other values and enter a sequence comprehension, which may at the very end zip an intermediate result into a final object return value. The alternative higher-precedence forms ofobject
didn't exist before, but after some real-world use I've found it useful to be able to maintain a certain precedence level. Also note that when possible you should use%k
and%v
instead of packing and unpacking objects. These prefixes are faster and, in my experience, make the code easier to read. Caterwaul 1.2 introduces a new operator called/mobject
(which can also be written as-mobject
and|mobject
). This is identical to/object
, but groups values by key rather than assigning a single value per key. The result is that each key is mapped to an array of values. For example: m = [[1, 2], [1, 3], [2, 3]] /mobject -seq m[1] This behavior differs from/object
, which just takes the last value: m = [[1, 2], [1, 3], [2, 3]] /object -seq m[1] Numerical iteration. Within a sequence comprehension you have access to then[]
operator, which generates arrays of evenly-spaced numbers. It has three uses. When invoked on one argument it returns integers between 0, inclusive, and the number, exclusive. When invoked with two arguments the first becomes the inclusive lower bound and the second is the exclusive upper bound. Adding a third argument changes the increment from its default value of 1. For example: n[10] -seq n[5, 8] -seq n[0, 1, 0.25] -seq n[0, -1, 0.25] -seq n[0, -1, -0.25] -seq Another similar operator isni[]
, which behaves exactly liken[]
except that it includes its upper bound. For instance: n[10] -seq ni[10] -seq n[1, 4] -seq ni[1, 4] -seq n[0, 1, 0.25] -seq ni[0, 1, 0.25] -seq Note that the usual floating-point caveats apply; for example: n[0, 1, 0.1] -seq ni[0, 1, 0.1] -seq These results are the same because of the inductive loops used inn
andni
. If you need endpoint accuracy in floating-point situations, your best bet is to generate an integer sequence and map across a scaling factor: n[10] *[x * 0.1] -seq ni[10] *[x * 0.1] -seq DOM/jQuery driver. One of the benefits of promoting syntax into a first-class construct is that you can specialize certain syntactic constructs for library interoperation. Caterwaul provides a module that integrates jQuery-based DOM node construction right into the syntax of your program. (You can also write modules to do similar things for other client-side libraries.) For example: jquery in div.foo('hi there') In this example,jquery
is a modifier that interprets its code as HAML-like DOM construction. The code above is translated into this: | jQuery('').addClass('foo').append('' + ('hi there') + '') Nodes and classes. The example above illustrates the node and class syntax. The way Caterwaul sees this is thatdiv
is a node, and each dot-expression after it denotes a class. For example,div.foo.bar.bif
creates adiv
with three classes. You can also create just plain elements;div
creates an emptydiv
element with no CSS classes. This DOM driver uses context to determine when a word should be interpreted as an element name. Importantly, it doesn't have a list of known elements that it knows to promote. So, for example, this is also perfectly valid code: jquery in foo.bar(bif) If you run this you'll get a<foo>
node that contains an empty<bif>
node. Appending children. If you invoke one node on another, you're telling the driver to add the "parameters" of the invocation as children. This is translated into anappend
call to jQuery. So, for example,div.foo(span.bar('hi there'))
creates an anonymous span containinghi there
, adds that to a span with the "bar" class, and adds that to a div with the "foo" class. The div is returned. For reasons that will shortly become apparent there is a lower-precedence way to represent appending. You can use the>
operator to do the same thing as invocation. For example: jquery [div > p] Perhaps counterintuitively, chaining the>
operator does not result in further nesting. This is because>
left-associates, sodiv > p > pre
would be interpreted as(div > p) > pre
. This actually ends up being really convenient -- more so than if it did what it does in CSS, in my opinion. Appending other stuff. Because you can easily use functional abstraction over DOM nodes you'll probably end up factoring the creation of elements into a bunch of different functions. As a result, you'll end up calling those functions and wrapping some of the children in new nodes. The way to do this is to append stuff in a non-DOM context using[]
instead of()
: foo = jquery in div.foo jquery in div.container[foo] The low-precedence counterpart is>=
, and like>
it left-associates. You can also mix the two because its precedence is identical to>
. In the "hi there" example at the top of this section I appended the string "hi there" (which was interpreted as a Javascript value, not as a node constructor) using parentheses rather than square brackets. The DOM driver has an exception for string values, since often you'll want to insert plain text between other nodes: jquery in div('foo', button, 'bar') There's also a much more sinister aspect to it, though. Firefox (and SpiderMonkey-based Javascript engines in general) rewrites your code at compile-time, before Caterwaul can see it. One of the optimizations it performs is constant-folding, which involves rewriting things of the formx['y']
tox.y
whenevery
is a valid identifier. As a result, if you write something like this: jquery in button['hi'] You will get the undesirable outcome<button class='hi'>
in the generated code on Firefox. As a result you are always better off using()
when there is text involved (as long as the text is a literal string, that is). Attributes and jQuery data. These can be setup by using the*
operator. For example: jquery in a('google') *href('http://google.com') This invokes jQuery'sattr()
method on'href', 'http://google.com'
. A similar shorthand is provided for jQuery'sdata()
: jquery in a('got data') *!foo('bar') This results indata('foo', 'bar')
. The expression inside parentheses is evaluated in normal Javascript context. Arbitrary methods and event bindings. These are available by using the/
operator. For instance: jquery in button /text('hi') The slash simply turns into a regular method call:$('<button>').text('hi')
. Similar is the/!
operator, which turns into abind()
call: jquery in button /!click(given.e in alert('hi')) Calling functions. One of the downsides of having a DSL for DOM node construction is that it's hard to call a function on a small piece of the structure. The DOM library addresses this by using the%
operator to represent function invocation. For instance: says_hi(e) = e.text('hi there') jquery in button %says_hi This expands intosay_hi($('<button>'))
. Sometimes you want to pass parameters into the function you're using. This is achieved by currying: says(thing)(e) = e.text(thing) jquery in button %says('click me') Side-effecting. Caterwaul 1.2.1r2 introduces the ability to use-
as a modifier prefix inside a jQuery expression. For example: jquery in button -se [it.text('hi')] jquery in button[contents] -where [contents = 'woot'] Experimental extensions. These are things that I'm experimenting with before committing to the design. As such, they may change or be removed in the next few versions. (So use at your own peril, both due to volatility and because they might be useless.) Side-effects in function definitions. I was writing some parser combinators using curried functions, and realized that it would be useful to be able to do things in between function definitions. For example, consider this definition: f(x)(y) = x + y It would be nice to be able to ensure thatx
was a number as soon as we passed it tof
, rather than waiting for it to also receivey
. To do this, you can use a side-effect, introduced in Caterwaul 1.1.3: must_be_numeric(n) = n.constructor === Number || raise [new Error('#{n} is not a number')] f(x, must_be_numeric(x))(y) = x + y You can also side-effect on the result of the function. This can be useful if you want to attach metadata to intermediate invocations: f(x, result.toString() = 'f(#{x})')(y) = x + y f(5).toString() Infix function application. Haskell gives you a nice way to use a function as an infix operator: you use backticks (sox `f` y
becomesf x y
). This reduces an API designer's pressure to use operator overloading, since now there's another way to get the normal subject-verb-object ordering in expressions. Caterwaul has a couple of similar constructs, though they don't look very nice compared to Haskell. The first is a simple binary application, which looks like this: 1 /-log/ 2 1 |-log| 2 You can stack these up, as they associate leftward: 1 /-log/ 2 /-log/ 3 You can extend the invocation to take arbitrarily many arguments by adding them to the left and prefixing them with/
: 1 / 2 / 3 /-log/ 4 1 | 2 | 3 |-log| 4 Naturally, this syntax is somewhat dangerous since it might collide with certain arithmetic expressions. Hopefully nobody will be dividing twice in a row, but it could happen. That's one of the reasons I consider it to be experimental. Infix method application. You can invoke a method in much the same way that you can invoke a function. To do this, use the~
prefix: caterwaul /~parse/ log Like infix function application, this form can become variadic: foo = 'hi' bar = 'there' console /foo /~log/ bar Postfix function application. This is an alternative way to invoke functions. Rather than putting the function in the second-to-last position, you can put it after all of its arguments. This enables you to invoke functions with only one argument. Expressions of the formx /!f
are converted tof(x)
: 100 /!log Prefix slashes are recognized as before:x /y /... /!f
is converted tof(x, y, ...)
. Literal modifiers. There are two kinds of modifiers. Literal modifiers are used to modify literal values (such as strings and regular expressions). For example, Caterwaul provides a literal modifier called.x
that removes spaces from regular expressions: /foo bar bif/.x All literal modifiers look like this; that is, they appear to be properties and aren't invoked. Caterwaul will only process literal modifiers that it knows about and that match literals of the right type. All of these expansions happen at compile-time, so there is no significant runtime impact of using them. Regular expression modifiers. The simplest regular expression modifier is.x
. This lets you insert whitespace into your regular expressions without actually matching against whitespace; it's very similar to the 'x' flag in Perl or Ruby. /^\w+ @ \w+ \. com$/.x Another is.qf
, short for "quote function". This causes the regular expression to be turned into a function that matches against strings (very similar to Perl's behavior): /foo/.qf String modifiers. Strings can be modified in several different ways: 'foo bar'.qw Here,.qw
causes the string to be split into an array of words. You can put arbitrarily much whitespace between words, though the usual Javascript syntax rules apply. 'foo bar bif baz'.qh Similar to.qw
, but a hash (object) is constructed instead of an array. Every other word is used as a key, and each following word is a value. '^http://'.qr Converts a string into a regular expression, but properly escapes the forward slashes. This is primarily for notational convenience, and has the caveat that some things that you'd do within regular expressions aren't allowed in strings. (For example,'foo\[bar\]'.qr
will fail in Javascript's initial parse because\[
and\]
are invalid escape sequences.) 'x + 1'.qs This creates a reference to the syntax tree generated by parsingx + 1
. It's rare that you'd use this unless you're writing macros. You can also use.qse
as of Caterwaul 1.2.2. '_ + 1'.qf Constructs a function whose sole argument is_
and whose body is_ + 1
. Code within the function is macroexpanded automatically, so you can do things like this: '_ + 1 -when._'.qf __ meta::template('comment', '\'\'; # A mechanism for line or block comments.'); meta::template('def', <<'__'); # Define a new template. Analogous to the C preprocessor's #define directive. # For example: # # - def foo x, y, z << end # hello $x, $y, and $z! # This is an ${x}message. # - end # # You can then use that template immediately: # # - foo 3, 4, 5 # # Interpolation is achieved by evaling a Perl heredoc; the usual caveats apply. # A variable called $body is automatically bound to the body contents if there # are any; for example: # # - def named x << end # name $x { # $body # } # - end # # - named 'foo' << end # woohoo # - end # # You can define a "plural" form like this: # # - def -p say_hi_to << end # Hi $_! # - end # # - say_hi_to Foo, Bar, Baz # # Plural forms still have the $body variable (which doesn't change across # arguments), but they can't take formal parameters. my ($options, $name, @args) = separate_options(split /\s+/, $_[0]); my @formals = map split(/,\s*/), @args; my $body = $_[1]; my $formal_list = join(', ', map "\$$_", @formals); my $parameter_bindings = "my ($formal_list) = split /,\\s*/, \$_[0]"; my $body_binding = "my \$body = \$_[1]"; my $heredoc_delimiter = state(); # This will work great unless you put large hexadecimal barewords on otherwise # blank lines in your code. At that point there's a 2^-128 chance that it will # bomb out horribly. my $plural = $$options{'--plural'} || $$options{'-p'}; die "- def $name: cannot use formal parameters with the -p option" if $plural && @formals; $plural ? meta::externalize "template::$name", "- def -p $name", eval "sub {\n$body_binding;\n" . "join \"\\n\", map <<$heredoc_delimiter, " . "split /,\\s*/, \$_[0];\n" . "$body\n" . "$heredoc_delimiter\n}" : meta::externalize "template::$name", "- def $name @formals", eval "sub {\n$parameter_bindings;\n$body_binding;\n" . "<<$heredoc_delimiter\n$body\n$heredoc_delimiter\n}"; die $@ if $@; # No output from this template. ''; __ meta::template('eval', <<'__'); my $result = eval $_[0]; terminal::warning("Error during template evaluation: $@") if $@; $result; __ meta::template('failing_conditional', <<'__'); my ($commands) = @_; my $should_return = $commands =~ / if (.*)$/ && ! eval $1; terminal::warning("eval of template condition failed: $@") if $@; $should_return; __ meta::template('include', <<'__'); my ($commands) = @_; return '' if template::failing_conditional($commands); join "\n", map retrieve($_), split /\s+/, $commands; __ meta::template('pinclude', <<'__'); # Just like the regular include, but makes sure to insert paragraph boundaries # (this is required for SDoc to function properly). my ($commands) = @_; return '' if template::failing_conditional($commands); my $text = join "\n\n", map retrieve($_), split /\s+/, $commands; "\n\n$text\n\n"; __ meta::template('script-include', <<'__'); my ($name) = @_; my $s = 'script'; my $script = retrieve($name); "<$s>\n$script\n$s>"; __ meta::template('style-include', <<'__'); my ($name) = @_; my $s = 'style'; my $style = retrieve($name); "<$s>\n$style\n$s>"; __ meta::vim_highlighter('caterwaul', <<'__'); " Caterwaul VIM highlighter | Spencer Tipping " Licensed under the terms of the MIT source code license " Language: Javascript with Caterwaul extensions " Maintainer: Spencer Tipping" URL: http://caterwauljs.org/build/caterwaul.vim if !exists("main_syntax") if version < 600 syntax clear elseif exists("b:current_syntax") finish endif let main_syntax = 'caterwaul' endif syn case match setlocal iskeyword=48-57,95,36,A-Z,a-z,@ syn region jsWaulComment start=/#/ end=/$/ syn region jsNodeShebang start=/\%^#!/ end=/$/ syn region jsParenGroup matchgroup=jsParen start=/(/ end=/)/ contains=js.* syn region jsBracketGroup matchgroup=jsBracket start=/\[/ end=/\]/ contains=js.* syn region jsBraceGroup matchgroup=jsBrace start=/{/ end=/}/ contains=js.* syn region jsTernary matchgroup=jsTernaryOperator start=/?/ end=/:/ contains=js\(ColonLHS\)\@!.* syn match jsOperator /[-+*^%&\|!~;=><,]\{1,4\}/ syn match jsDot /\./ syn keyword jsReservedToplevel if else switch while for do break continue return with case default try catch finally throw delete void syn keyword jsOperator in instanceof typeof new syn keyword jsBuiltinType Array Boolean Date Function Number Object String RegExp syn keyword jsBuiltinLiteral true false null undefined syn keyword jsBuiltinValue this arguments syn keyword jsPrototype prototype constructor syn match jsAssignment /\k\+\s*[-+*/^&|%<>]*=[^=]\@=/ contains=jsOperator syn match jsWordPrefix /[-\/|,<]\k\@=/ syn match jsIdentifier /[A-Za-z$_][A-Za-z0-9$@_]*/ contains=jcMetadata syn match jcMetadata /@[A-Za-z0-9$@_]*/ contained syn match jsWildcard /\<_[A-Za-z0-9$_@]\+\>/ syn match jsNumber /-\?0x[0-9A-Fa-f]\+\|-\?\(\d*\.\d\+\|\d\+\.\d*\|\d\+\)\([eE][+-]\?\d\{1,3\}\)\?\|-\?0[0-7]\+/ syn region jsStringD matchgroup=jsQuote start=/"/ skip=/\\\\\|\\"/ end=/"/ oneline keepend contains=jcStringEscape,jcCaterwaulEscape syn region jsStringS matchgroup=jsQuote start=/'/ skip=/\\\\\|\\'/ end=/'/ oneline keepend contains=jcStringEscape,jcCaterwaulEscape syn region jsRegexp matchgroup=jsQuote start=+/[^/ ]+rs=e-1 skip=+\\\\\|\\/+ end=+/[gims]*[^-~\+!\/A-Za-z0-9 #(\[{]\@=+ oneline contains=jcRegexpSpecial syn region jsCodeString matchgroup=jsCodeQuote start=/\z(['"]\)/ end=/\z1\.qf\>/ skip=/\\./ oneline keepend contains=js.* syn match jcCodeStringVariable /\<_\>/ containedin=jsCodeString contained syn match jcRegexpSpecial /\\[sSbBwWdDnr\\\[\]]\|[+*|?]\|\[\([^]\\\/]\|\\.\)\+\]/ contained syn match jcStringEscape /\\\d\{3\}\|\\u[0-9A-Za-z]\{4\}\|\\[a-z"'\\]/ contained syn region jcCaterwaulEscape start=/#{/ end=/}/ contained contains=js\(WaulComment\)\@!.* keepend syn match jsCaterwaulNumericHex /\ / syn match jsCaterwaulNumericBinary /\/ syn match jsColonLHS /\k\+\s*:/ syn region jsVarBinding matchgroup=jsVarBindingConstruct start=/\\|\ / end=/;/ contains=js.* syn match jsVarInBinding /var\s\+\k\+\s\+in/ contains=jcVarBindingKeyword,jsOperator syn region jsParamBinding matchgroup=jsBindingConstruct start=/\(function\|catch\)\s*(/ end=/)/ contains=jsOperator syn keyword jcVarBindingKeyword const var contained syn keyword jcBindingKeyword function catch contained syn match jcBindingAssignment /\k\+\s*=\([^=]\|$\)\@=/ contains=jsOperator contained containedin=jsVarBinding syn match jcExtraBindingAssignment /\k\+\s*\(=\([^=]\|$\)\@=\|(.*=\([^=]\|$\)\)\@=/ contained containedin=jcBindingGroup syn keyword jsBindingMacro where capture wcapture nextgroup=jcBindingGroup syn keyword jsFunctionMacro given bgiven nextgroup=jcFunctionGroup syn keyword jsQuotationMacro qs qse qc qce nextgroup=jcQuotationGroup syn keyword jsOtherMacro raise seq noexpand reexpand eval ahead bitwise syn keyword jsParameterizedMacro se re then and or when unless using rescue eq aeq oeq deq neq acq ocq dcq ncq nextgroup=jsModifierSuffix syn match jcModifierSuffix /[->]/ contained syn cluster jsMacro add=jsBindingMacro,jsFunctionMacro,jsQuotationMacro,jsOtherMacro syn match jsLiteralModifier /\.\(q[frwhs]\|qse\|x\)\>/ syn match jsSeqFilter /\/\(pairs\|keys\|values\)\>/ syn match jsSeqFilter /%[kv][\*%\/~!]/ syn match jsSeqFilter /[-\/|]m\?object\>/ syn region jcBindingGroup matchgroup=jsCaterwaulMacro start='\s*\[' end=']' contained contains=js.* syn region jcFunctionGroup matchgroup=jsCaterwaulMacro start='\s*\[' end=']' contained syn region jcQuotationGroup matchgroup=jsCaterwaulMacro start='\s*\[' end=']' contained contains=js.* syn match jcBindingGroup /\.\k\+/ contained syn match jcFunctionGroup /\.\k\+/ contained syn match jcParens /[()]/ contained syn match jsClosers /[\]})]/ syn match jsCaterwaulInfixFunction /\([|\/]\)[-~][^ \t\/|]\+\1/ syn match jsCaterwaulUnaryFunction +/![^ ,\]\)\}]\++ syn cluster jsCaterwaulHtmlOps contains=jcCaterwaulHtmlClass,jcCaterwaulHtmlSlash,jcCaterwaulHtmlMap,jcCaterwaulHtmlAttr,jcCaterwaulHtmlElement,jcCaterwaulHtmlParens syn cluster jsCaterwaulHtmlOps add=jcCaterwaulHtmlArray,jcCaterwaulHtmlSlashB,jcCaterwaulHtmlAttrB,jcCaterwaulHtmlPlus,jcCaterwaulHtmlContains syn region jsCaterwaulHtmlPrefix1 matchgroup=jsCaterwaulMacro start=/\ \s*/ contained nextgroup=@jsCaterwaulHtmlOps syn region jcCaterwaulHtmlParens matchgroup=jcParens start=/(/ end=/)/ contained nextgroup=@jsCaterwaulHtmlOps containedin=@jsCaterwaulHtmlGroups contains=jsCaterwaulHtmlElement,jsStringS,jsStringD syn region jcCaterwaulHtmlArray matchgroup=jcParens start=/\[/ end=/]/ contained nextgroup=@jsCaterwaulHtmlOps containedin=@jsCaterwaulHtmlGroups contains=js.* syn keyword jcCaterwaulHtmlElement html head body meta script style link title div a span input button textarea option contained containedin=@jsCaterwaulHtmlGroups nextgroup=@jsCaterwaulHtmlOps syn keyword jcCaterwaulHtmlElement table tbody tr td th thead tfoot img h1 h2 h3 h4 h5 h6 li ol ul noscript p pre samp contained containedin=@jsCaterwaulHtmlGroups nextgroup=@jsCaterwaulHtmlOps syn keyword jcCaterwaulHtmlElement blockquote select form label iframe sub sup var code caption canvas audio video contained containedin=@jsCaterwaulHtmlGroups nextgroup=@jsCaterwaulHtmlOps syn region jsBlockComment start=+/\*+ end=+\*/+ contains=@Spell,jsCommentTags syn region jsLineComment start=+//+ end=+$+ contains=@Spell,jsCommentTags syn keyword jcCommentTags TODO FIXME XXX TBD contained syn sync fromstart if main_syntax == "caterwaul" syn sync ccomment javaScriptComment endif hi def link jsNodeShebang Special hi def link jsClosers Error hi def link jsCaterwaulNumericHex Number hi def link jsCaterwaulNumericBinary Number hi def link jcCaterwaulHtmlElement Keyword hi def link jcCaterwaulHtmlClass Special hi def link jcCaterwaulHtmlClassName Type hi def link jcCaterwaulHtmlSlash Special hi def link jcCaterwaulHtmlSlashB Special hi def link jcCaterwaulHtmlMap Special hi def link jcCaterwaulHtmlAttr Special hi def link jcCaterwaulHtmlAttrB Special hi def link jcCaterwaulHtmlPlus Special hi def link jcCaterwaulHtmlContains Special hi def link jsCaterwaulHtmlPrefix2 Special hi def link jsCaterwaulSeqVariable Identifier hi def link jsCaterwaulUnaryLeftOp Special hi def link jsCaterwaulComplexOp Special hi def link jsCaterwaulOperatorFn Special hi def link jsCaterwaulMacro Special hi def link jsCaterwaulInfixFunction Type hi def link jsCaterwaulUnaryFunction Type hi def link jsLiteralModifier Special hi def link jsSeqFilter Special hi def link jsWordPrefix Special hi def link jsParameterizedMacro Special hi def link jsModifierSuffix Special hi def link jsBindingMacro Special hi def link jsFunctionMacro Special hi def link jsOtherMacro Special hi def link jsQuotationMacro Special hi def link jcFunctionGroup Identifier hi def link jcQuotationGroup String hi def link jsWaulComment Comment hi def link jsLineComment Comment hi def link jsBlockComment Comment hi def link jsCommentTags Todo hi def link jsCodeQuote Special hi def link jcCodeStringVariable Identifier hi def link jsQuote Special hi def link jsNumber Number hi def link jsStringS String hi def link jsStringD String hi def link jsRegexp String hi def link jsRegexpEscape Special hi def link jcRegexpSpecial Special hi def link jcStringEscape Special hi def link jcCaterwaulEscape Special hi def link jsColonLHS Type hi def link jsAssignment Type hi def link jsParen Special hi def link jcParens Special hi def link jsBracket Special hi def link jsBrace Special hi def link jsParenCloseError Error hi def link jsBracketCloseError Error hi def link jsBraceCloseError Error hi def link jsTernaryOperator Special hi def link jsVarInBinding Type hi def link jcVarBindingKeyword Keyword hi def link jsVarBindingConstruct Keyword hi def link jsBindingConstruct Special hi def link jcBindingKeyword Keyword hi def link jcBindingAssignment Type hi def link jcExtraBindingAssignment Identifier hi def link jsParamBinding Identifier hi def link jsReservedToplevel Keyword hi def link jsOperator Keyword hi def link jsDot Special hi def link jsBuiltinType Type hi def link jsBuiltinLiteral Special hi def link jsBuiltinValue Special hi def link jsPrototype Special hi def link jsWildcard Identifier hi def link jcMetadata Type let b:current_syntax = "caterwaul" if main_syntax == 'caterwaul' unlet main_syntax endif __ internal::main(); __DATA__