tango: dancing around literate programming

Felix Klock (@pnkfelix), Mozilla

date: 16 June 2016

tango: dancing around literate programming

tango_lp a_md src/a.md (master) b_rs src/b.rs (master) a_rs src/a.rs a_md->a_rs tango b_md src/b.md b_rs->b_md tango a_rs->a_md tango b_md->b_rs tango

What is tango?

Goal of tango:

Simple literate programming for Rust

(meta: written with tango; see http://bit.ly/2618VSS)

http://pnkfelix.github.io/presentations/

Quick Apologies

  • (Lightning) talk re: tool for authoring Rust presentations...
  • ... but talk presents zero interesting Rust snippets. (Sorry.)
  • Implementation = Ugly hacks (some described later) ...
  • ... maybe one day I will revise to use better idioms. (Sorry.)
  • Cool visuals due to reveal.js + pandoc, not tango
  • Using pandoc crate; "just" shells out to pandoc. (Sorry.)

(meta: written with tango; see http://bit.ly/2618VSS)

http://pnkfelix.github.io/presentations/

Concrete Demo

Presenter writes:

```rust
pub fn main() { println!("Hello post `tango`"); }
```

* What is Literate Programming (LP)?

* What is `tango`'s approach to LP?

Computer runs:

% cargo run
   Compiling tango-demo v0.1.0 (file:///Users/fklock/Dev/Rust/tango-demo)
     Running `target/debug/main`
Hello post `tango`

(cargo build script pushes tango onto dance floor.)

Presenter writes:

```rust
pub fn main() { println!("Hello post `tango`"); }
```

* What is Literate Programming (LP)?

* What is `tango`'s approach to LP?

IDE sees (Rust source):

pub fn main() { println!("Hello post `tango`"); }

//@ * What is Literate Programming (LP)?
//@
//@ * What is `tango`'s approach to LP?

Presenter writes:

```rust
pub fn main() { println!("Hello post `tango`"); }
```

* What is Literate Programming (LP)?

* What is `tango`'s approach to LP?

Audience sees:

pub fn main() { println!("Hello post `tango`"); }
  • What is Literate Programming (LP)?

  • What is tango's approach to LP?

Fragments

This is Hello World

pub fn hello_world() {

println! is a macro. It prints things.

    println!("Hello World");

Close curly } ends the scope.

}

That's all folks!

Literate Programming (LP)

Knuth's Literate Programming: WEB

Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.

-- Donald Knuth, 1983

http://www.literateprogramming.com/knuthweb.pdf

(Examples include TeX, Metafont, SGB.)

Usual LP approach

web_lp web foo.web (Master Document) doc foo.tex (LaTeX Documentation Files) web->doc weave prog foo.c (Program Files) web->prog  tangle pdf foo.pdf (Rendered Doc) doc->pdf   pdflatex

Programmer edits .web; utils create intermediate source

(WEB tangle output "write-only"; IMO weave is too)

Architecture motivated (in part) by language limitations

tango is neither tangle nor weave!

"Source": matter of perspective

tango_lp a_md src/a.md (master) b_rs src/b.rs (master) a_rs src/a.rs a_md->a_rs tango b_md src/b.md b_rs->b_md tango a_rs->a_md tango b_md->b_rs tango

Edit either; tango regenerates the other

One should cargo build before switch between .rs/.md

But: editing both first will not destroy work!

Installation

Integrated with cargo

Cargo.toml:

build = "tango-build.rs"

[build-dependencies]
tango = "0.5.0"

tango-build.rs:

extern crate tango;

fn main() { tango::process_root().unwrap() }

Trick(s) to tango'ing

Implementation

  • two line-oriented state machines [+ timestamp (72 loc)]

  • rs2md (219 loc): rust code ↦ ```rust-blocks

  • md2rs (195 loc): markdown ↦ //@-prefixed comments

rs2md_min md prefix ```rust fn foo() { } ``` middle text ```rust fn bar() {} ``` rs //@ prefix fn foo() { } //@ middle //@ text fn bar() {} md->rs md2rs rs->md rs2md

contains bijective submapping

bijective_submapping cluster_0 a_noncanon_rs //@ prefix //@ fn foo() { } fn bar() { } //@ suffix a_canon_rs //@ prefix fn foo() { } fn bar() { } //@ suffix a_canon_md prefix ```rust fn foo() { } fn bar() { } ``` suffix a_noncanon_rs->a_canon_md a_canon_rs->a_canon_md a_canon_md->a_canon_rs a_noncanon_md prefix ```rust fn foo() { } ``` ```rust fn bar() { } ``` suffix a_noncanon_md->a_canon_rs

Range of tango = domain of submap

Edits remain in domain of tango (usually)

Outside submap: content "adjusted" by tango ...

... but (tangotango) idempotent

timestamp games

tango runs in response to cargo build

And tango updates/creates source files

  • Problem: If done naively, such runs would cause subsequent cargo build to reprocess and rebuild (every time).

Goal: no unnecessary cargo rebuilds

timestamp games

The trick

timestamp_games a_rs //@ commentary on `foo` fn foo() {} init->a_rs edit a_md commentary on `foo` ```rust fn foo() {} ``` a_rs->a_md  tango init_file init_file->a_rs initial conversion result init_file->a_md restamp with modification time for .rs file

(also: timestamp separate file to detect if both modified)

Other hacks

Encoding attributes attached to code blocks (//@@)

e.g. //@@ { .attribute1 .attribute2 }

Playpen link integration (//@@@)

e.g. //@@@ playpen link name

Adds up to...

//@ You can follow [stripey] to the *playpen*!

//@@ { .stripes }
#[test]
pub fn blinking_code() {
    println!("This code does not actually blink");
}
//@@@ stripey

will tango into:

You can follow stripey to the playpen!

#[test]
pub fn blinking_code() {
    println!("This code does not actually blink");
}

(assuming appropriate CSS definition for .stripes)

Note that above link works

Thanks

tango_lp a_md src/a.md (master) b_rs src/b.rs (master) a_rs src/a.rs a_md->a_rs tango b_md src/b.md b_rs->b_md tango a_rs->a_md tango b_md->b_rs tango

rs2md_min rs //@ prefix fn foo() { } //@ middle //@ text fn bar() {} md prefix ```rust fn foo() { } ``` middle text ```rust fn bar() {} ``` rs->md md->rs

(meta: written with tango; see http://bit.ly/2618VSS)