Add hacks to extract and compile tutorial code

Not included in the build by default, since it's fragile and kludgy. Do
something like this to run it:

    cd doc/tutorial
    RUSTC=../../build/stage2/bin/rustc bash test.sh

Closes #1143
This commit is contained in:
Marijn Haverbeke 2011-11-22 16:12:23 +01:00
parent 1b8b0b8584
commit fedb775fbb
14 changed files with 119 additions and 24 deletions

View File

@ -22,6 +22,8 @@ other tasks, and that most data is immutable.
Take the following program: Take the following program:
# fn get_really_big_record() -> int { 1 }
# fn myfunc(a: int) {}
let x = get_really_big_record(); let x = get_really_big_record();
myfunc(x); myfunc(x);
@ -32,6 +34,9 @@ existing value as the argument, without copying.
There are more involved cases. The call could look like this: There are more involved cases. The call could look like this:
# fn myfunc(a: int, b: block()) {}
# fn get_another_record() -> int { 1 }
# let x = 1;
myfunc(x, {|| x = get_another_record(); }); myfunc(x, {|| x = get_another_record(); });
Now, if `myfunc` first calls its second argument and then accesses its Now, if `myfunc` first calls its second argument and then accesses its

View File

@ -4,7 +4,15 @@ require("./lib/codemirror-rust");
md.Markdown.dialects.Maruku.block.code = function code(block, next) { md.Markdown.dialects.Maruku.block.code = function code(block, next) {
if (block.match(/^ /)) { if (block.match(/^ /)) {
var text = block.replace(/(^|\n) /g, "$1"), accum = [], curstr = "", curstyle = null; var text = String(block);
while (next.length && next[0].match(/^ /)) text += "\n" + String(next.shift());
var leaveAlone, accum = [], curstr = "", curstyle = null;
text = text.split("\n").map(function(line) {
line = line.slice(4);
if (line == "## notrust") leaveAlone = true;
return line;
}).filter(function(x) { return !/^##? /.test(x); }).join("\n");
if (leaveAlone) return [["pre", {}, text]];
function add(str, style) { function add(str, style) {
if (style != curstyle) { if (style != curstyle) {
if (curstyle) accum.push(["span", {"class": "cm-" + curstyle}, curstr]); if (curstyle) accum.push(["span", {"class": "cm-" + curstyle}, curstr]);

View File

@ -38,6 +38,7 @@ Rust's `alt` construct is a generalized, cleaned-up version of C's
each labelled with a pattern, and it will execute the arm that matches each labelled with a pattern, and it will execute the arm that matches
the value. the value.
# let my_number = 1;
alt my_number { alt my_number {
0 { std::io::println("zero"); } 0 { std::io::println("zero"); }
1 | 2 { std::io::println("one or two"); } 1 | 2 { std::io::println("one or two"); }
@ -89,6 +90,7 @@ To a limited extent, it is possible to use destructuring patterns when
declaring a variable with `let`. For example, you can say this to declaring a variable with `let`. For example, you can say this to
extract the fields from a tuple: extract the fields from a tuple:
# fn get_tuple_of_two_ints() -> (int, int) { (1, 1) }
let (a, b) = get_tuple_of_two_ints(); let (a, b) = get_tuple_of_two_ints();
This will introduce two new variables, `a` and `b`, bound to the This will introduce two new variables, `a` and `b`, bound to the
@ -118,6 +120,8 @@ it finds one that can be divided by five.
There's also `while`'s ugly cousin, `do`/`while`, which does not check There's also `while`'s ugly cousin, `do`/`while`, which does not check
its condition on the first iteration, using traditional syntax: its condition on the first iteration, using traditional syntax:
# fn eat_cake() {}
# fn any_cake_left() -> bool { false }
do { do {
eat_cake(); eat_cake();
} while any_cake_left(); } while any_cake_left();

View File

@ -56,6 +56,7 @@ Records can be destructured on in `alt` patterns. The basic syntax is
omitted as a shorthand for simply binding the variable with the same omitted as a shorthand for simply binding the variable with the same
name as the field. name as the field.
# let mypoint = {x: 0f, y: 0f};
alt mypoint { alt mypoint {
{x: 0f, y: y_name} { /* Provide sub-patterns for fields */ } {x: 0f, y: y_name} { /* Provide sub-patterns for fields */ }
{x, y} { /* Simply bind the fields */ } {x, y} { /* Simply bind the fields */ }
@ -71,6 +72,7 @@ the fields of a record, a record pattern may end with `, _` (as in
Tags [FIXME terminology] are datatypes that have several different Tags [FIXME terminology] are datatypes that have several different
representations. For example, the type shown earlier: representations. For example, the type shown earlier:
# type point = {x: float, y: float};
tag shape { tag shape {
circle(point, float); circle(point, float);
rectangle(point, point); rectangle(point, point);
@ -96,7 +98,7 @@ equivalent to an `enum` in C:
east; east;
south; south;
west; west;
}; }
This will define `north`, `east`, `south`, and `west` as constants, This will define `north`, `east`, `south`, and `west` as constants,
all of which have type `direction`. all of which have type `direction`.
@ -116,6 +118,7 @@ That is a shorthand for this:
Tag types like this can have their content extracted with the Tag types like this can have their content extracted with the
dereference (`*`) unary operator: dereference (`*`) unary operator:
# tag gizmo_id = int;
let my_gizmo_id = gizmo_id(10); let my_gizmo_id = gizmo_id(10);
let id_int: int = *my_gizmo_id; let id_int: int = *my_gizmo_id;
@ -125,6 +128,8 @@ For tag types with multiple variants, destructuring is the only way to
get at their contents. All variant constructors can be used as get at their contents. All variant constructors can be used as
patterns, as in this definition of `area`: patterns, as in this definition of `area`:
# type point = {x: float, y: float};
# tag shape { circle(point, float); rectangle(point, point); }
fn area(sh: shape) -> float { fn area(sh: shape) -> float {
alt sh { alt sh {
circle(_, size) { std::math::pi * size * size } circle(_, size) { std::math::pi * size * size }
@ -136,6 +141,8 @@ For variants without arguments, you have to write `variantname.` (with
a dot at the end) to match them in a pattern. This to prevent a dot at the end) to match them in a pattern. This to prevent
ambiguity between matching a variant name and binding a new variable. ambiguity between matching a variant name and binding a new variable.
# type point = {x: float, y: float};
# tag direction { north; east; south; west; }
fn point_from_direction(dir: direction) -> point { fn point_from_direction(dir: direction) -> point {
alt dir { alt dir {
north. { {x: 0f, y: 1f} } north. { {x: 0f, y: 1f} }
@ -295,6 +302,7 @@ strings. They are always immutable.
Resources are data types that have a destructor associated with them. Resources are data types that have a destructor associated with them.
# fn close_file_desc(x: int) {}
resource file_desc(fd: int) { resource file_desc(fd: int) {
close_file_desc(fd); close_file_desc(fd);
} }

42
doc/tutorial/extract.js Normal file
View File

@ -0,0 +1,42 @@
var fs = require("fs"), md = require("./lib/markdown");
// Runs markdown.js over the tutorial, to find the code blocks in it.
// Uses the #-markers in those code blocks, along with some vague
// heuristics, to turn them into compilable files. Outputs these files
// to fragments/.
//
// '##ignore' means don't test this block
// '##notrust' means the block isn't rust code
// (used by build.js to not highlight it)
// '# code' means insert the given code to complete the fragment
// (build.js strips out such lines)
var curFile, curFrag;
md.Markdown.dialects.Maruku.block.code = function code(block, next) {
if (block.match(/^ /)) {
var ignore, text = String(block);
while (next.length && next[0].match(/^ /)) text += "\n" + String(next.shift());
text = text.split("\n").map(function(line) {
line = line.slice(4);
if (line == "## ignore" || line == "## notrust") { ignore = true; line = ""; }
if (/^# /.test(line)) line = line.slice(2);
return line;
}).join("\n");
if (ignore) return;
if (!/\bfn main\b/.test(text)) {
if (/(^|\n) *(native|use|mod|import|export)\b/.test(text))
text += "\nfn main() {}\n";
else text = "fn main() {\n" + text + "\n}\n";
}
if (!/\buse std\b/.test(text)) text = "use std;\n" + text;
fs.writeFileSync("fragments/" + curFile + "_" + (++curFrag) + ".rs", text);
}
};
fs.readFileSync("order", "utf8").split("\n").filter(id).forEach(handle);
function id(x) { return x; }
function handle(file) {
curFile = file; curFrag = 0;
md.parse(fs.readFileSync(file + ".md", "utf8"), "Maruku");
}

View File

@ -67,6 +67,7 @@ most notably the Windows API, use other calling conventions, so Rust
provides a way to to hint to the compiler which is expected by using provides a way to to hint to the compiler which is expected by using
the `"abi"` attribute: the `"abi"` attribute:
#[cfg(target_os = "win32")]
#[abi = "stdcall"] #[abi = "stdcall"]
native mod kernel32 { native mod kernel32 {
fn SetEnvironmentVariableA(n: *u8, v: *u8) -> int; fn SetEnvironmentVariableA(n: *u8, v: *u8) -> int;
@ -81,7 +82,9 @@ or `"stdcall"`. Other conventions may be defined in the future.
The native `SHA1` function is declared to take three arguments, and The native `SHA1` function is declared to take three arguments, and
return a pointer. return a pointer.
# native mod crypto {
fn SHA1(src: *u8, sz: uint, out: *u8) -> *u8; fn SHA1(src: *u8, sz: uint, out: *u8) -> *u8;
# }
When declaring the argument types to a foreign function, the Rust When declaring the argument types to a foreign function, the Rust
compiler has no way to check whether your declaration is correct, so compiler has no way to check whether your declaration is correct, so
@ -106,6 +109,9 @@ null pointers.
The `sha1` function is the most obscure part of the program. The `sha1` function is the most obscure part of the program.
# import std::{str, vec};
# mod crypto { fn SHA1(src: *u8, sz: uint, out: *u8) -> *u8 { out } }
# fn as_hex(data: [u8]) -> str { "hi" }
fn sha1(data: str) -> str unsafe { fn sha1(data: str) -> str unsafe {
let bytes = str::bytes(data); let bytes = str::bytes(data);
let hash = crypto::SHA1(vec::unsafe::to_ptr(bytes), let hash = crypto::SHA1(vec::unsafe::to_ptr(bytes),
@ -141,10 +147,15 @@ Rust's safety mechanisms.
Let's look at our `sha1` function again. Let's look at our `sha1` function again.
# import std::{str, vec};
# mod crypto { fn SHA1(src: *u8, sz: uint, out: *u8) -> *u8 { out } }
# fn as_hex(data: [u8]) -> str { "hi" }
# fn x(data: str) -> str unsafe {
let bytes = str::bytes(data); let bytes = str::bytes(data);
let hash = crypto::SHA1(vec::unsafe::to_ptr(bytes), let hash = crypto::SHA1(vec::unsafe::to_ptr(bytes),
vec::len(bytes), std::ptr::null()); vec::len(bytes), std::ptr::null());
ret as_hex(vec::unsafe::from_buf(hash, 20u)); ret as_hex(vec::unsafe::from_buf(hash, 20u));
# }
The `str::bytes` function is perfectly safe, it converts a string to The `str::bytes` function is perfectly safe, it converts a string to
an `[u8]`. This byte array is then fed to `vec::unsafe::to_ptr`, which an `[u8]`. This byte array is then fed to `vec::unsafe::to_ptr`, which

View File

@ -19,6 +19,10 @@ This helps the compiler avoid spurious error messages. For example,
the following code would be a type error if `dead_end` would be the following code would be a type error if `dead_end` would be
expected to return. expected to return.
# fn can_go_left() -> bool { true }
# fn can_go_right() -> bool { true }
# tag dir { left; right; }
# fn dead_end() -> ! { fail; }
let dir = if can_go_left() { left } let dir = if can_go_left() { left }
else if can_go_right() { right } else if can_go_right() { right }
else { dead_end(); }; else { dead_end(); };
@ -96,12 +100,14 @@ of integers backwards:
To run such an iteration, you could do this: To run such an iteration, you could do this:
# fn for_rev(v: [int], act: block(int)) {}
for_rev([1, 2, 3], {|n| log n; }); for_rev([1, 2, 3], {|n| log n; });
But Rust allows a more pleasant syntax for this situation, with the But Rust allows a more pleasant syntax for this situation, with the
loop block moved out of the parenthesis and the final semicolon loop block moved out of the parenthesis and the final semicolon
omitted: omitted:
# fn for_rev(v: [int], act: block(int)) {}
for_rev([1, 2, 3]) {|n| for_rev([1, 2, 3]) {|n|
log n; log n;
} }

View File

@ -55,16 +55,17 @@ dereferences become impossible.
Rust's type inferrer works very well with generics, but there are Rust's type inferrer works very well with generics, but there are
programs that just can't be typed. programs that just can't be typed.
let n = none; let n = std::option::none;
# n = std::option::some(1);
If you never do anything else with `n`, the compiler will not be able If you never do anything else with `n`, the compiler will not be able
to assign a type to it. (The same goes for `[]`, in fact.) If you to assign a type to it. (The same goes for `[]`, in fact.) If you
really want to have such a statement, you'll have to write it like really want to have such a statement, you'll have to write it like
this: this:
let n2: option::t<int> = none; let n2: std::option::t<int> = std::option::none;
// or // or
let n = none::<int>; let n = std::option::none::<int>;
Note that, in a value expression, `<` already has a meaning as a Note that, in a value expression, `<` already has a meaning as a
comparison operator, so you'll have to write `::<T>` to explicitly comparison operator, so you'll have to write `::<T>` to explicitly
@ -120,6 +121,7 @@ take sendable types.
If you try this program: If you try this program:
# fn map(f: block(int) -> int, v: [int]) {}
fn plus1(x: int) -> int { x + 1 } fn plus1(x: int) -> int { x + 1 }
map(plus1, [1, 2, 3]); map(plus1, [1, 2, 3]);
@ -131,6 +133,7 @@ way to pass integers, which is by value. To get around this issue, you
have to explicitly mark the arguments to a function that you want to have to explicitly mark the arguments to a function that you want to
pass to a generic higher-order function as being passed by pointer: pass to a generic higher-order function as being passed by pointer:
# fn map<T, U>(f: block(T) -> U, v: [T]) {}
fn plus1(&&x: int) -> int { x + 1 } fn plus1(&&x: int) -> int { x + 1 }
map(plus1, [1, 2, 3]); map(plus1, [1, 2, 3]);

View File

@ -35,6 +35,7 @@ It is also possible to include multiple files in a crate. For this
purpose, you create a `.rc` crate file, which references any number of purpose, you create a `.rc` crate file, which references any number of
`.rs` code files. A crate file could look like this: `.rs` code files. A crate file could look like this:
## ignore
#[link(name = "farm", vers = "2.5", author = "mjh")]; #[link(name = "farm", vers = "2.5", author = "mjh")];
mod cow; mod cow;
mod chicken; mod chicken;
@ -52,6 +53,7 @@ in a moment.
To have a nested directory structure for your source files, you can To have a nested directory structure for your source files, you can
nest mods in your `.rc` file: nest mods in your `.rc` file:
## ignore
mod poultry { mod poultry {
mod chicken; mod chicken;
mod turkey; mod turkey;
@ -79,6 +81,7 @@ OS X.
It is possible to provide more specific information when using an It is possible to provide more specific information when using an
external crate. external crate.
## ignore
use myfarm (name = "farm", vers = "2.7"); use myfarm (name = "farm", vers = "2.7");
When a comma-separated list of name/value pairs is given after `use`, When a comma-separated list of name/value pairs is given after `use`,
@ -90,6 +93,7 @@ local name `myfarm`.
Our example crate declared this set of `link` attributes: Our example crate declared this set of `link` attributes:
## ignore
#[link(name = "farm", vers = "2.5", author = "mjh")]; #[link(name = "farm", vers = "2.5", author = "mjh")];
The version does not match the one provided in the `use` directive, so The version does not match the one provided in the `use` directive, so
@ -105,12 +109,14 @@ these two files:
#[link(name = "mylib", vers = "1.0")]; #[link(name = "mylib", vers = "1.0")];
fn world() -> str { "world" } fn world() -> str { "world" }
## ignore
// main.rs // main.rs
use mylib; use mylib;
fn main() { log_err "hello " + mylib::world(); } fn main() { log_err "hello " + mylib::world(); }
Now compile and run like this (adjust to your platform if necessary): Now compile and run like this (adjust to your platform if necessary):
## notrust
> rustc --lib mylib.rs > rustc --lib mylib.rs
> rustc main.rs -L . > rustc main.rs -L .
> ./main > ./main
@ -147,8 +153,8 @@ restricted with `export` directives at the top of the module or file.
mod enc { mod enc {
export encrypt, decrypt; export encrypt, decrypt;
const super_secret_number: int = 10; const super_secret_number: int = 10;
fn encrypt(n: int) { n + super_secret_number } fn encrypt(n: int) -> int { n + super_secret_number }
fn decrypt(n: int) { n - super_secret_number } fn decrypt(n: int) -> int { n - super_secret_number }
} }
This defines a rock-solid encryption algorithm. Code outside of the This defines a rock-solid encryption algorithm. Code outside of the

View File

@ -21,6 +21,7 @@ If you modify the program to make it invalid (for example, remove the
`use std` line), and then compile it, you'll see an error message like `use std` line), and then compile it, you'll see an error message like
this: this:
## notrust
hello.rs:2:4: 2:20 error: unresolved modulename: std hello.rs:2:4: 2:20 error: unresolved modulename: std
hello.rs:2 std::io::println("hello world!"); hello.rs:2 std::io::println("hello world!");
^~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~

View File

@ -21,6 +21,7 @@ statements and expressions is C-like. Function calls are written
precedence that they have in C, comments look the same, and constructs precedence that they have in C, comments look the same, and constructs
like `if` and `while` are available: like `if` and `while` are available:
# fn call_a_function(_a: int) {}
fn main() { fn main() {
if 1 < 2 { if 1 < 2 {
while false { call_a_function(10 * 4); } while false { call_a_function(10 * 4); }
@ -39,10 +40,13 @@ of languages. A lot of thing that are statements in C are expressions
in Rust. This allows for useless things like this (which passes in Rust. This allows for useless things like this (which passes
nil—the void type—to a function): nil—the void type—to a function):
# fn a_function(_a: ()) {}
a_function(while false {}); a_function(while false {});
But also useful things like this: But also useful things like this:
# fn the_stars_align() -> bool { false }
# fn something_else() -> bool { true }
let x = if the_stars_align() { 4 } let x = if the_stars_align() { 4 }
else if something_else() { 3 } else if something_else() { 3 }
else { 0 }; else { 0 };
@ -125,6 +129,7 @@ annotation:
// The type of this vector will be inferred based on its use. // The type of this vector will be inferred based on its use.
let x = []; let x = [];
# x = [3];
// Explicitly say this is a vector of integers. // Explicitly say this is a vector of integers.
let y: [int] = []; let y: [int] = [];
@ -272,6 +277,7 @@ The comparison operators are the traditional `==`, `!=`, `<`, `>`,
Rust has a ternary conditional operator `?:`, as in: Rust has a ternary conditional operator `?:`, as in:
let badness = 12;
let message = badness < 10 ? "error" : "FATAL ERROR"; let message = badness < 10 ? "error" : "FATAL ERROR";
For type casting, Rust uses the binary `as` operator, which has a For type casting, Rust uses the binary `as` operator, which has a
@ -311,19 +317,14 @@ followed by a comma-separated list of nested attributes, as in the
`cfg` example above, or in this [crate](mod.html) metadata `cfg` example above, or in this [crate](mod.html) metadata
declaration: declaration:
## ignore
#[link(name = "std", #[link(name = "std",
vers = "0.1", vers = "0.1",
url = "http://rust-lang.org/src/std")]; url = "http://rust-lang.org/src/std")];
An attribute without a semicolon following it applies to the An attribute without a semicolon following it applies to the
definition that follows it. When terminated with a semicolon, it definition that follows it. When terminated with a semicolon, it
applies to the current context. The above example could also be applies to the module or crate.
written like this:
fn register_win_service() {
#[cfg(target_os = "win32")];
/* ... */
}
## Syntax extensions ## Syntax extensions

View File

@ -21,6 +21,7 @@ When you compile the program normally, the `test_twice` function will
not be used. To actually run the tests, compile with the `--test` not be used. To actually run the tests, compile with the `--test`
flag: flag:
## notrust
> rustc --lib twice.rs > rustc --lib twice.rs
> ./twice > ./twice
running 1 tests running 1 tests
@ -30,6 +31,7 @@ flag:
Or, if we change the file to fail, for example by replacing `x + x` Or, if we change the file to fail, for example by replacing `x + x`
with `x + 1`: with `x + 1`:
## notrust
running 1 tests running 1 tests
test test_twice ... FAILED test test_twice ... FAILED
failures: failures:

8
doc/tutorial/test.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
rm -f fragments/*.rs
mkdir -p fragments
node extract.js
for F in `ls fragments/*.rs`; do
$RUSTC $F > /dev/null
if [[ $? != 0 ]] ; then echo $F; fi
done

View File

@ -2485,22 +2485,12 @@ fn parse_crate_directive(p: parser, first_outer_attr: [ast::attribute]) ->
_ { none } _ { none }
}; };
alt p.peek() { alt p.peek() {
// mod x = "foo.rs"; // mod x = "foo.rs";
token::SEMI. { token::SEMI. {
let hi = p.get_hi_pos(); let hi = p.get_hi_pos();
p.bump(); p.bump();
ret spanned(lo, hi, ast::cdir_src_mod(id, file_opt, outer_attrs)); ret spanned(lo, hi, ast::cdir_src_mod(id, file_opt, outer_attrs));
} }
// mod x = "foo_dir" { ...directives... } // mod x = "foo_dir" { ...directives... }
token::LBRACE. { token::LBRACE. {
p.bump(); p.bump();