diff options
| author | Marijn Haverbeke <marijnh@gmail.com> | 2011-10-31 16:18:59 +0100 |
|---|---|---|
| committer | Marijn Haverbeke <marijnh@gmail.com> | 2011-10-31 16:21:39 +0100 |
| commit | 0b4f0a4caa6e765226c5f9d5609289dccb1072ce (patch) | |
| tree | 05bc8ec0e832959fa0b0fbd7c1f2a1a1e45dffb5 /doc/tutorial | |
| parent | 80c926c5e253d6db299698cfd4932dbcb61cd1bd (diff) | |
Add a first stab at a tutorial
You build it with `cd doc/tutorial; node build.js`, and then point your browser at doc/tutorial/web/index.html. Not remotely ready for publicity yet.
Diffstat (limited to 'doc/tutorial')
| -rw-r--r-- | doc/tutorial/args.md | 126 | ||||
| -rw-r--r-- | doc/tutorial/build.js | 82 | ||||
| -rw-r--r-- | doc/tutorial/control.md | 169 | ||||
| -rw-r--r-- | doc/tutorial/data.md | 291 | ||||
| -rw-r--r-- | doc/tutorial/func.md | 88 | ||||
| -rw-r--r-- | doc/tutorial/generic.md | 104 | ||||
| -rw-r--r-- | doc/tutorial/index.md | 1 | ||||
| -rw-r--r-- | doc/tutorial/intro.md | 48 | ||||
| -rw-r--r-- | doc/tutorial/lib/markdown.js | 1466 | ||||
| -rw-r--r-- | doc/tutorial/mod.md | 3 | ||||
| -rw-r--r-- | doc/tutorial/order | 9 | ||||
| -rw-r--r-- | doc/tutorial/setup.md | 42 | ||||
| -rw-r--r-- | doc/tutorial/syntax.md | 207 | ||||
| -rw-r--r-- | doc/tutorial/web/style.css | 36 |
14 files changed, 2672 insertions, 0 deletions
diff --git a/doc/tutorial/args.md b/doc/tutorial/args.md new file mode 100644 index 00000000000..77a221cdcf4 --- /dev/null +++ b/doc/tutorial/args.md @@ -0,0 +1,126 @@ +# Argument passing + +Rust datatypes are not trivial to copy (the way, for example, +JavaScript values can be copied by simply taking one or two machine +words and plunking them somewhere else). Shared boxes require +reference count updates, big records or tags require an arbitrary +amount of data to be copied (plus updating the reference counts of +shared boxes hanging off them), unique pointers require their origin +to be de-initialized. + +For this reason, the way Rust passes arguments to functions is a bit +more involved than it is in most languages. It performs some +compile-time cleverness to get rid of most of the cost of copying +arguments, and forces you to put in explicit copy operators in the +places where it can not. + +## Safe references + +The foundation of Rust's argument-passing optimization is the fact +that Rust tasks for single-threaded worlds, which share no data with +other tasks, and that most data is immutable. + +Take the following program: + + let x = get_really_big_record(); + myfunc(x); + +We want to pass `x` to `myfunc` by pointer (which is easy), *and* we +want to ensure that `x` stays intact for the duration of the call +(which, in this example, is also easy). So we can just use the +existing value as the argument, without copying. + +There are more involved cases. The call could look like this: + + myfunc(x, {|| x = get_another_record(); }); + +Now, if `myfunc` first calls its second argument and then accesses its +first argument, it will see a different value from the one that was +passed to it. + +The compiler will insert an implicit copy of `x` in such a case, +*except* if `x` contains something mutable, in which case a copy would +result in code that behaves differently (if you mutate the copy, `x` +stays unchanged). That would be bad, so the compiler will disallow +such code. + +When inserting an implicit copy for something big, the compiler will +warn, so that you know that the code is not as efficient as it looks. + +There are even more tricky cases, in which the Rust compiler is forced +to pessimistically assume a value will get mutated, even though it is +not sure. + + fn for_each(v: [mutable @int], iter: block(@int)) { + for elt in v { iter(elt); } + } + +For all this function knows, calling `iter` (which is a closure that +might have access to the vector that's passed as `v`) could cause the +elements in the vector to be mutated, with the effect that it can not +guarantee that the boxes will live for the duration of the call. So it +has to copy them. In this case, this will happen implicitly (bumping a +reference count is considered cheap enough to not warn about it). + +## The copy operator + +If the `for_each` function given above were to take a vector of +`{mutable a: int}` instead of `@int`, it would not be able to +implicitly copy, since if the `iter` function changes a copy of a +mutable record, the changes won't be visible in the record itself. If +we *do* want to allow copies there, we have to explicitly allow it +with the `copy` operator: + + type mutrec = {mutable x: int}; + fn for_each(v: [mutable mutrec], iter: block(mutrec)) { + for elt in v { iter(copy elt); } + } + +## Argument passing styles + +The fact that arguments are conceptually passed by safe reference does +not mean all arguments are passed by pointer. Composite types like +records and tags *are* passed by pointer, but others, like integers +and pointers, are simply passed by value. + +It is possible, when defining a function, to specify a passing style +for a parameter by prefixing the parameter name with a symbol. The +most common special style is by-mutable-reference, written `&`: + + fn vec_push(&v: [int], elt: int) { + v += [elt]; + } + +This will make it possible for the function to mutate the parameter. +Clearly, you are only allowed to pass things that can actually be +mutated to such a function. + +Another style is by-move, which will cause the argument to become +de-initialized on the caller side, and give ownership of it to the +called function. This is written `-`. + +Finally, the default passing styles (by-value for non-structural +types, by-reference for structural ones) are written `+` for by-value +and `&&` for by(-immutable)-reference. It is sometimes necessary to +override the defaults. We'll talk more about this when discussing +[generics][gens]. + +[gens]: FIXME + +## Other uses of safe references + +Safe references are not only used for argument passing. When you +destructure on a value in an `alt` expression, or loop over a vector +with `for`, variables bound to the inside of the given data structure +will use safe references, not copies. This means such references have +little overhead, but you'll occasionally have to copy them to ensure +safety. + + let my_rec = {a: 4, b: [1, 2, 3]}; + alt my_rec { + {a, b} { + log b; // This is okay + my_rec = {a: a + 1, b: b + [a]}; + log b; // Here reference b has become invalid + } + } diff --git a/doc/tutorial/build.js b/doc/tutorial/build.js new file mode 100644 index 00000000000..7318b2a0400 --- /dev/null +++ b/doc/tutorial/build.js @@ -0,0 +1,82 @@ +var fs = require("fs"), md = require("./lib/markdown"); + +function markdown(str) { return md.toHTML(str, "Maruku"); } + +function fileDates(file, c) { + function takeTime(str) { + return Number(str.match(/^(\S+)\s/)[1]) * 1000; + } + require("child_process").exec("git rev-list --timestamp HEAD -- " + file, function(err, stdout) { + if (err != null) { console.log("Failed to run git rev-list"); return; } + var history = stdout.split("\n"); + if (history.length && history[history.length-1] == "") history.pop(); + var created = history.length ? takeTime(history[0]) : Date.now(); + var modified = created; + if (history.length > 1) modified = takeTime(history[history.length-1]); + c(created, modified); + }); +} + +function head(title) { + return "<html><head><link rel='stylesheet' href='style.css' type='text/css'>" + + "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'><title>" + + title + "</title></head><body>\n"; +} + +function foot(created, modified) { + var r = "<p class='head'>" + var crStr = formatTime(created), modStr = formatTime(modified); + if (created) r += "Created " + crStr; + if (crStr != modStr) + r += (created ? ", l" : "L") + "ast modified on " + modStr; + return r + "</p>"; +} + +function formatTime(tm) { + var d = new Date(tm); + var months = ["", "January", "February", "March", "April", "May", "June", "July", "August", + "September", "October", "November", "December"]; + return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear(); +} + +var files = fs.readFileSync("order", "utf8").split("\n").filter(function(x) { return x; }); +var max_modified = 0; +var sections = []; + +// Querying git for modified dates has to be done async in node it seems... +var queried = 0; +for (var i = 0; i < files.length; ++i) + (function(i) { // Make lexical i stable + fileDates(files[i], function(ctime, mtime) { + sections[i] = { + text: fs.readFileSync(files[i] + ".md", "utf8"), + ctime: ctime, mtime: mtime, + name: files[i], + }; + max_modified = Math.max(mtime, max_modified); + if (++queried == files.length) buildTutorial(); + }); + })(i); + +function htmlName(i) { return sections[i].name + ".html"; } + +function buildTutorial() { + var index = head("Rust language tutorial") + "<div id='content'>" + + markdown(fs.readFileSync("index.md", "utf8")) + "<ol>"; + for (var i = 0; i < sections.length; ++i) { + var s = sections[i]; + var html = htmlName(i); + var title = s.text.match(/^# (.*)\n/)[1]; + index += '<li><a href="' + html + '">' + title + "</a></li>"; + + var nav = '<p class="head">Section ' + (i + 1) + ' of the Rust language tutorial.<br>'; + if (i > 0) nav += '<a href="' + htmlName(i-1) + '">« Section ' + i + "</a> | "; + nav += '<a href="index.html">Index</a>'; + if (i + 1 < sections.length) nav += ' | <a href="' + htmlName(i+1) + '">Section ' + (i + 2) + " »</a>"; + nav += "</p>"; + fs.writeFileSync("web/" + html, head(title) + nav + '<div id="content">' + markdown(s.text) + "</div>" + + nav + foot(s.ctime, s.mtime) + "</body></html>"); + } + index += "</ol></div>" + foot(null, max_modified) + "</body></html>"; + fs.writeFileSync("web/index.html", index); +} diff --git a/doc/tutorial/control.md b/doc/tutorial/control.md new file mode 100644 index 00000000000..9172cb7303f --- /dev/null +++ b/doc/tutorial/control.md @@ -0,0 +1,169 @@ +# Control structures + +## Conditionals + +We've seen `if` pass by a few times already. To recap, braces are +compulsory, an optional `else` clause can be appended, and multiple +`if`/`else` constructs can be chained together: + + if false { + std::io::println("that's odd"); + } else if true { + std::io::println("right"); + } else { + std::io::println("neither true nor false"); + } + +The condition given to an `if` construct *must* be of type boolean (no +implicit conversion happens). If the arms return a value, this value +must be of the same type for every arm in which control reaches the +end of the block: + + fn signum(x: int) -> int { + if x < 0 { -1 } + else if x > 0 { 1 } + else { ret 0; } + } + +The `ret` (return) and its semicolon could have been left out without +changing the meaning of this function, but it illustrates that you +will not get a type error in this case, although the last arm doesn't +have type `int`, because control doesn't reach the end of that arm +(`ret` is jumping out of the function). + +## Pattern matching + +Rust's `alt` construct is a generalized, cleaned-up version of C's +`switch` construct. You provide it with a value and a number of arms, +each labelled with a pattern, and it will execute the arm that matches +the value. + + alt my_number { + 0 { std::io::println("zero"); } + 1 | 2 { std::io::println("one or two"); } + 3 to 10 { std::io::println("three to ten"); } + _ { std::io::println("something else"); } + } + +There is no 'falling through' between arms, as in C—only one arm is +executed, and it doesn't have to explicitly `break` out of the +construct when it is finished. + +The part to the left of each arm is called the pattern. Literals are +valid patterns, and will match only their own value. The pipe operator +(`|`) can be used to assign multiple patterns to a single arm. Ranges +of numeric literal patterns can be expressed with `to`. The underscore +(`_`) is a wildcard pattern that matches everything. + +If the arm with the wildcard pattern was left off in the above +example, running it on a number greater than ten (or negative) would +cause a run-time failure. When no arm matches, `alt` constructs do not +silently fall through—they blow up instead. + +A powerful application of pattern matching is *destructuring*, where +you use the matching to get at the contents of data types. Remember +that `(float, float)` is a tuple of two floats: + + fn angle(vec: (float, float)) -> float { + alt vec { + (0f, y) when y < 0f { 1.5 * std::math::pi } + (0f, y) { 0.5 * std::math::pi } + (x, y) { std::math::atan(y / x) } + } + } + +A variable name in a pattern matches everything, *and* binds that name +to the value of the matched thing inside of the arm block. Thus, `(0f, +y)` matches any tuple whose first element is zero, and binds `y` to +the second element. `(x, y)` matches any tuple, and binds both +elements to a variable. + +Any `alt` arm can have a guard clause (written `when EXPR`), which is +an expression of type `bool` that determines, after the pattern is +found to match, whether the arm is taken or not. The variables bound +by the pattern are available in this guard expression. + +## Destructuring let + +To a limited extent, it is possible to use destructuring patterns when +declaring a variable with `let`. For example, you can say this to +extract the fields from a tuple: + + let (a, b) = get_tuple_of_two_ints(); + +This will introduce two new variables, `a` and `b`, bound to the +content of the tuple. + +You may only use irrevocable patterns in let bindings, though. Things +like literals, which only match a specific value, are not allowed. + +## Loops + +`while` produces a loop that runs as long as its given condition +(which must have type `bool`) evaluates to true. Inside a loop, the +keyword `break` can be used to abort the loop, and `cont` can be used +to abort the current iteration and continue with the next. + + let x = 5; + while true { + x += x - 3; + if x % 5 == 0 { break; } + std::io::println(std::int::str(x)); + } + +This code prints out a weird sequence of numbers and stops as soon as +it finds one that can be divided by five. + +When iterating over a vector, use `for` instead. + + for elt in ["red", "green", "blue"] { + std::io::println(elt); + } + +This will go over each element in the given vector (a three-element +vector of strings, in this case), and repeatedly execute the body with +`elt` bound to the current element. You may add an optional type +declaration (`elt: str`) for the iteration variable if you want. + +For more involved iteration, such as going over the elements of a hash +table, Rust uses higher-order functions. We'll come back to those in a +moment. + +## Failure + +The `fail` keyword causes the current [task][tasks] to fail. You use +it to indicate unexpected failure, much like you'd use `exit(1)` in a +C program, except that in Rust, it is possible for other tasks to +handle the failure, allowing the program to continue running. + +`fail` takes an optional argument, which must have type `str`. Trying +to access a vector out of bounds, or running a pattern match with no +matching clauses, both result in the equivalent of a `fail`. + +[tasks]: FIXME + +## Logging + +Rust has a built-in logging mechanism, using the `log` statement. +Logging is polymorphic—any type of value can be logged, and the +runtime will do its best to output a textual representation of the +value. + + log "hi"; + log (1, [2.5, -1.8]); + +By default, you *will not* see the output of your log statements. The +environment variable `RUST_LOG` controls which log statements actually +get output. It can contain a comma-separated list of paths for modules +that should be logged. For example, running `rustc` with +`RUST_LOG=rustc::front::attr` will turn on logging in its attribute +parser. If you compile a program `foo.rs`, you can set `RUST_LOG` to +`foo` to enable its logging. + +Turned-off `log` statements impose minimal overhead on the code that +contains them, so except in code that needs to be really, really fast, +you should feel free to scatter around debug logging statements, and +leave them in. + +For interactive debugging, you often want unconditional logging. For +this, use `log_err` instead of `log` [FIXME better name]. diff --git a/doc/tutorial/data.md b/doc/tutorial/data.md new file mode 100644 index 00000000000..25135787f4d --- /dev/null +++ b/doc/tutorial/data.md @@ -0,0 +1,291 @@ +# Datatypes + +Rust datatypes are, by default, immutable. The core datatypes of Rust +are structural records and 'tags' (tagged unions, algebraic data +types). + + type point = {x: float, y: float}; + tag shape { + circle(point, float); + rectangle(point, point); + } + let my_shape = circle({x: 0.0, y: 0.0}, 10.0); + +## Records + +Rust record types are written `{field1: TYPE, field2: TYPE [, +...]}`, and record literals are written in the same way, but with +expressions instead of types. They are quite similar to C structs, and +even laid out the same way in memory (so you can read from a Rust +struct in C, and vice-versa). + +The dot operator is used to access record fields (`mypoint.x`). + +Fields that you want to mutate must be explicitly marked as such. For +example... + + type stack = {content: [int], mutable head: uint}; + +With such a type, you can do `mystack.head += 1u`. When the `mutable` +is omitted from the type, such an assignment would result in a type +error. + +To 'update' an immutable record, you use functional record update +syntax, by ending a record literal with the keyword `with`: + + let newpoint = {x: 0f with oldpoint}; + +This will create a new struct, copying all the fields from `oldpoint` +into it, except for the ones that are explicitly set in the literal. + +Rust record types are *structural*. This means that `{x: float, y: +float}` is not just a way to define a new type, but is the actual name +of the type. Record types can be used without first defining them. If +module A defines `type point = {x: float, y: float}`, and module B, +without knowing anything about A, defines a function that returns an +`{x: float, y: float}`, you can use that return value as a `point` in +module A. (Remember that `type` defines an additional name for a type, +not an actual new type.) + +## Record patterns + +Records can be destructured on in `alt` patterns. The basic syntax is +`{fieldname: pattern, ...}`, but the pattern for a field can be +omitted as a shorthand for simply binding the variable with the same +name as the field. + + alt mypoint { + {x: 0f, y: y_name} { /* Provide sub-patterns for fields */ } + {x, y} { /* Simply bind the fields */ } + } + +When you are not interested in all the fields of a record, a record +pattern may end with `, _` (as in `{field1, _}`) to indicate that +you're ignoring all other fields. + +## Tags + +Tags [FIXME terminology] are datatypes that have several different +representations. For example, the type shown earlier: + + tag shape { + circle(point, float); + rectangle(point, point); + } + +A value of this type is either a circle¸ in which case it contains a +point record and a float, or a rectangle, in which case it contains +two point records. The run-time representation of such a value +includes an identifier of the actual form that it holds, much like the +'tagged union' pattern in C, but with better ergonomics. + +The above declaration will define a type `shape` that can be used to +refer to such shapes, and two functions, `circle` and `rectangle`, +which can be used to construct values of the type (taking arguments of +the specified types). So `circle({x: 0f, y: 0f}, 10f)` is the way to +create a new circle. + +Tag variants do not have to have parameters. This, for example, is +equivalent to an `enum` in C: + + tag direction { + north; + east; + south; + west; + }; + +This will define `north`, `east`, `south`, and `west` as constants, +all of which have type `direction`. + +There is a special case for tags with a single variant. These are used +to define new types in such a way that the new name is not just a +synonym for an existing type, but its own distinct type. If you say: + + tag gizmo_id = int; + +That is a shorthand for this: + + tag gizmo_id { gizmo_id(int); } + +Tag types like this can have their content extracted with the +dereference (`*`) unary operator: + + let my_gizmo_id = gizmo_id(10); + let id_int: int = *my_gizmo_id; + +## Tag patterns + +For tag types with multiple variants, destructuring is the only way to +get at their contents. All variant constructors can be used as +patterns, as in this definition of `area`: + + fn area(sh: shape) -> float { + alt sh { + circle(_, size) { std::math::pi * size * size } + rectangle({x, y}, {x: x2, y: y2}) { (x2 - x) * (y2 - y) } + } + } + +For variants without arguments, you have to write `variantname.` (with +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. + + fn point_from_direction(dir: direction) -> point { + alt dir { + north. { {x: 0f, y: 1f} } + east. { {x: 1f, y: 0f} } + south. { {x: 0f, y: -1f} } + west. { {x: -1f, y: 0f} } + } + } + +## Tuples + +Tuples in Rust behave exactly like records, except that their fields +do not have names (and can thus not be accessed with dot notation). +Tuples can have any arity except for 0 or 1 (though you may see nil, +`()`, as the empty tuple if you like). + + let mytup: (int, int, float) = (10, 20, 30.0); + alt mytup { + (a, b, c) { log a + b + (c as int); } + } + +## Pointers + +In contrast to a lot of modern languages, record and tag types in Rust +are not represented as pointers to allocated memory. They are, like in +C and C++, represented directly. This means that if you `let x = {x: +1f, y: 1f};`, you are creating a record on the stack. If you then copy +it into a data structure, the whole record is copied, not just a +pointer. + +For small records like `point`, this is usually still more efficient +than allocating memory and going through a pointer. But for big +records, or records with mutable fields, it can be useful to have a +single copy on the heap, and refer to that through a pointer. + +Rust supports several types of pointers. The simplest is the unsafe +pointer, written `*TYPE`, which is a completely unchecked pointer +type only used in unsafe code (and thus, in typical Rust code, very +rarely). The safe pointer types are `@TYPE` for shared, +reference-counted boxes, and `~TYPE`, for uniquely-owned pointers. + +All pointer types can be dereferenced with the `*` unary operator. + +### Shared boxes + +Shared boxes are pointers to heap-allocated, reference counted memory. +A cycle collector ensures that circular references do not result in +memory leaks. + +Creating a shared box is done by simply applying the binary `@` +operator to an expression. The result of the expression will be boxed, +resulting in a box of the right type. For example: + + let x = @10; // New box, refcount of 1 + let y = x; // Copy the pointer, increase refcount + // When x and y go out of scope, refcount goes to 0, box is freed + +NOTE: We may in the future switch to garbage collection, rather than +reference counting, for shared boxes. + +Shared boxes never cross task boundaries. + +### Unique boxes + +In contrast to shared boxes, unique boxes are not reference counted. +Instead, it is statically guaranteed that only a single owner of the +box exists at any time. + + let x = ~10; + let y <- x; + +This is where the 'move' (`<-`) operator comes in. It is similar to +`=`, but it de-initializes its source. Thus, the unique box can move +from `x` to `y`, without violating the constraint that it only has a +single owner. + +NOTE: If you do `y = x` instead, the box will be copied. We should +emit warning for this, or disallow it entirely, but do not currently +do so. + +Unique boxes, when they do not contain any shared boxes, can be sent +to other tasks. The sending task will give up ownership of the box, +and won't be able to access it afterwards. The receiving task will +become the sole owner of the box. + +### Mutability + +All pointer types have a mutable variant, written `@mutable TYPE` or +`~mutable TYPE`. Given such a pointer, you can write to its contents +by combining the dereference operator with a mutating action. + + fn increase_contents(pt: @mutable int) { + *pt += 1; + } + +## Vectors + +Rust vectors are always heap-allocated and unique. A value of type +`[TYPE]` is represented by a pointer to a section of heap memory +containing any number of `TYPE` values. + +NOTE: This uniqueness is turning out to be quite awkward in practice, +and might change. + +Vector literals are enclosed in square brackets. Dereferencing is done +with square brackets (and zero-based): + + let myvec = [true, false, true, false]; + if myvec[1] { std::io::println("boom"); } + +By default, vectors are immutable—you can not replace their elements. +The type written as `[mutable TYPE]` is a vector with mutable +elements. Mutable vector literals are written `[mutable]` (empty) or +`[mutable 1, 2, 3]` (with elements). + +Growing a vector in Rust is not as inefficient as it looks (the `+` +operator means concatenation when applied to vector types): + + let myvec = [], i = 0; + while i < 100 { + myvec += [i]; + i += 1; + } + +Because a vector is unique, replacing it with a longer one (which is +what `+= [i]` does) is indistinguishable from appending to it +in-place. Vector representations are optimized to grow +logarithmically, so the above code generates about the same amount of +copying and reallocation as `push` implementations in most other +languages. + +## Strings + +The `str` type in Rust is represented exactly the same way as a vector +of bytes (`[u8]`), except that it is guaranteed to have a trailing +null byte (for interoperability with C APIs). + +This sequence of bytes is interpreted as an UTF-8 encoded sequence of +characters. This has the advantage that UTF-8 encoded I/O (which +should really be the goal for modern systems) is very fast, and that +strings have, for most intents and purposes, a nicely compact +representation. It has the disadvantage that you only get +constant-time access by byte, not by character. + +A lot of algorithms don't need constant-time indexed access (they +iterate over all characters, which `std::str::chars` helps with), and +for those that do, many don't need actual characters, and can operate +on bytes. For algorithms that do really need to index by character, +there's the option to convert your string to a character vector (using +`std::str::to_chars`). + +Like vectors, strings are always unique. You can wrap them in a shared +box to share them. Unlike vectors, there is no mutable variant of +strings. They are always immutable. + +## Resources + +FIXME fill this in diff --git a/doc/tutorial/func.md b/doc/tutorial/func.md new file mode 100644 index 00000000000..a8bfacedb0f --- /dev/null +++ b/doc/tutorial/func.md @@ -0,0 +1,88 @@ +# Functions + +Functions (like all other static declarations, such as `type`) can be +declared both at the top level and inside other functions (or modules, +which we'll come back to in moment). + +The `ret` keyword immediately returns from a function. It is +optionally followed by an expression to return. In functions that +return `()`, the returned expression can be left off. A function can +also return a value by having its top level block produce an +expression (by omitting the final semicolon). + +Some functions (such as the C function `exit`) never return normally. +In Rust, these are annotated with return type `!`: + + fn dead_end() -> ! { fail; } + +This helps the compiler avoid spurious error messages. For example, +the following code would be a type error if `dead_end` would be +expected to return. + + let dir = if can_go_left() { left } + else if can_go_right() { right } + else { dead_end(); }; + +## Closures + +FIXME Either move entirely to fn~/fn@ nomenclature, or fix compiler to +accept lambda as a type + +Normal Rust functions (declared with `fn`) do not close over their +environment. A `lambda` expression can be used to create a closure. + + fn make_plus_function(x: int) -> fn@(int) -> int { + lambda(y: int) -> int { x + y } + } + let plus_two = make_plus_function(2); + assert plus_two(3) == 5; + +A `lambda` function *copies* its environment (in this case, the +binding for `x`). It can not mutate the closed-over bindings, and will +not see changes made to these variables after the `lambda` was +evaluated. `lambda`s can be put in data structures and passed around +without limitation. + +A different form of closure is the block. Blocks are written like they +are in Ruby: `{|x| x + y}`, the formal parameters between pipes, +followed by the function body. They are stack-allocated and properly +close over their environment (they see updates to closed over +variables, for example). But blocks can only be used in a limited set +of circumstances. They can be passed to other functions, but not +stored in data structures or returned. + + fn map_int(f: block(int) -> int, vec: [int]) -> [int] { + let result = []; + for i in vec { result += [f(i)]; } + ret result; + } + map_int({|x| x + 1 }, [1, 2, 3]); + +A block with no arguments is written `{|| body(); }`—you can not leave +off the pipes. + +## Iteration + +Functions taking blocks provide a good way to define non-trivial +iteration constructs. For example, this one iterates over a vector +of integers backwards: + + fn for_rev(v: [int], act: block(int)) { + let i = std::vec::len(v); + while (i > 0u) { + i -= 1u; + act(v[i]); + } + } + +To run such an iteration, you could do this: + + for_rev([1, 2, 3], {|n| log n; }); + +But Rust allows a more pleasant syntax for this situation, with the +loop block moved out of the parenthesis and the final semicolon +omitted: + + for_rev([1, 2, 3]) {|n| + log n; + } diff --git a/doc/tutorial/generic.md b/doc/tutorial/generic.md new file mode 100644 index 00000000000..4ab121bd7b3 --- /dev/null +++ b/doc/tutorial/generic.md @@ -0,0 +1,104 @@ +# Generics + +## Generic functions + +Throughout this tutorial, I've been defining functions like `map` and +`for_rev` to take vectors of integers. It is 2011, and we no longer +expect to be defining such functions again and again for every type +they apply to. Thus, Rust allows functions and datatypes to have type +parameters. + + fn for_rev<T>(v: [T], act: block(T)) { + let i = std::vec::len(v); + while i > 0u { + i -= 1u; + act(v[i]); + } + } + + fn map<T, U>(f: block(T) -> U, v: [T]) -> [U] { + let acc = []; + for elt in v { acc += [f(elt)]; } + ret acc; + } + +When defined in this way, these functions can be applied to any type +of vector, as long as the type of the block's argument and the type of +the vector's content agree with each other. + +Inside a parameterized (generic) function, the names of the type +parameters (capitalized by convention) stand for opaque types. You +can't look inside them, but you can pass them around. + +## Generic datatypes + +Generic `type` and `tag` declarations follow the same pattern: + + type circular_buf<T> = {start: uint, + end: uint, + buf: [mutable T]}; + + tag option<T> { some(T); none; } + +You can then declare a function to take a `circular_buf<u8>` or return +an `option<str>`, or even an `option<T>` if the function itself is +generic. + +The `option` type given above exists in the standard library as +`std::option::t`, and is the way Rust programs express the thing that +in C would be a nullable pointer. The nice part is that you have to +explicitly unpack an `option` type, so accidental null pointer +dereferences become impossible. + +## Type-inference and generics + +Rust's type inferrer works very well with generics, but there are +programs that just can't be typed. + + let n = none; + +If you never do anything else with none, the compiler will not be able +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 +this: + + let n = none::<int>; + +Note that, in a value expression, `<` already has a meaning as a +comparison operator, so you'll have to write `::<T>` to explicitly +give a type to a name that denotes a generic value. Fortunately, this +is rarely necessary. + +## Polymorphic built-ins + +There are two built-in operations that, perhaps surprisingly, act on +values of any type. It was already mentioned earlier that `log` can +take any type of value and output it as a string. + +More interesting is that Rust also defines an ordering for all +datatypes, and allows you to meaningfully apply comparison operators +(`<`, `>`, `<=`, `>=`, `==`, `!=`) to them. For structural types, the +comparison happens left to right, so `"abc" < "bac"` (but note that +`"bac" < "ác"`, because the ordering acts on UTF-8 sequences without +any sophistication). + +## Generic functions and argument-passing + +If you try this program: + + fn plus1(x: int) -> int { x + 1 } + map(plus1, [1, 2, 3]); + +You will get an error message about argument passing styles +disagreeing. The reason is that generic types are always passed by +pointer, so `map` expects a function that takes its argument by +pointer. The `plus1` you defined, however, uses the default, efficient +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 +pass to a generic higher-order function as being passed by pointer: + + fn plus1(&&x: int) -> int { x + 1 } + map(plus1, [1, 2, 3]); + +NOTE: This is inconvenient, and we are hoping to get rid of this +restriction in the future. diff --git a/doc/tutorial/index.md b/doc/tutorial/index.md new file mode 100644 index 00000000000..d8c9be56568 --- /dev/null +++ b/doc/tutorial/index.md @@ -0,0 +1 @@ +# Rust language tutorial diff --git a/doc/tutorial/intro.md b/doc/tutorial/intro.md new file mode 100644 index 00000000000..9976d7f14e6 --- /dev/null +++ b/doc/tutorial/intro.md @@ -0,0 +1,48 @@ +# Introduction + +## Scope + +This is a tutorial for the Rust programming language. It assumes the +reader is familiar with the basic concepts of programming, and has +programmed in one or more other languages before. The tutorial covers +the whole language, though not with the depth and precision of the +[language reference][1]. + +FIXME: maybe also the stdlib? + +[1]: http://www.rust-lang.org/doc/rust.html + +## Disclaimer + +Rust is a language under development. The general flavor of the +language has settled, but details will continue to change as it is +further refined. Nothing in this tutorial is final, and though we try +to keep it updated, it is possible that the text occasionally does not +reflect the actual state of the language. + +## First Impressions + +Though syntax is something you get used to, an initial encounter with +a language can be made easier if the notation looks familiar. Rust is +a curly-brace language in the tradition of C, C++, and JavaScript. + + fn fac(n: int) -> int { + let result = 1; + while n > 0 { + result *= n; + n -= 1; + } + ret result; + } + +Several differences from C stand out. Types do not come before, but +after variable names (preceded by a colon). In local variables +(introduced with `let`), they are optional, and will be inferred when +left off. Constructs like `while` and `if` do not require parenthesis +around the condition (though they allow them). Also, there's a +tendency towards aggressive abbreviation in the keywords—`fn` for +function, `ret` for return. + +You should, however, not conclude that Rust is simply an evolution of +C. As will become clear in the rest of this tutorial, it goes into +quite a different direction. diff --git a/doc/tutorial/lib/markdown.js b/doc/tutorial/lib/markdown.js new file mode 100644 index 00000000000..f19d0529953 --- /dev/null +++ b/doc/tutorial/lib/markdown.js @@ -0,0 +1,1466 @@ +// Released under MIT license +// Copyright (c) 2009-2010 Dominic Baggott +// Copyright (c) 2009-2010 Ash Berlin +// Copyright (c) 2011 Christoph Dorn <christoph@christophdorn.com> (http://www.christophdorn.com) + +(function( expose ) { + +/** + * class Markdown + * + * Markdown processing in Javascript done right. We have very particular views + * on what constitutes 'right' which include: + * + * - produces well-formed HTML (this means that em and strong nesting is + * important) + * + * - has an intermediate representation to allow processing of parsed data (We + * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). + * + * - is easily extensible to add new dialects without having to rewrite the + * entire parsing mechanics + * + * - has a good test suite + * + * This implementation fulfills all of these (except that the test suite could + * do with expanding to automatically run all the fixtures from other Markdown + * implementations.) + * + * ##### Intermediate Representation + * + * *TODO* Talk about this :) Its JsonML, but document the node names we use. + * + * [JsonML]: http://jsonml.org/ "JSON Markup Language" + **/ +var Markdown = expose.Markdown = function Markdown(dialect) { + switch (typeof dialect) { + case "undefined": + this.dialect = Markdown.dialects.Gruber; + break; + case "object": + this.dialect = dialect; + break; + default: + if (dialect in Markdown.dialects) { + this.dialect = Markdown.dialects[dialect]; + } + else { + throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); + } + break; + } + this.em_state = []; + this.strong_state = []; + this.debug_indent = ""; +} + +/** + * parse( markdown, [dialect] ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * + * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. + **/ +expose.parse = function( source, dialect ) { + // dialect will default if undefined + var md = new Markdown( dialect ); + return md.toTree( source ); +} + +/** + * toHTML( markdown, [dialect] ) -> String + * toHTML( md_tree ) -> String + * - markdown (String): markdown string to parse + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Take markdown (either as a string or as a JsonML tree) and run it through + * [[toHTMLTree]] then turn it into a well-formated HTML fragment. + **/ +expose.toHTML = function toHTML( source , dialect , options ) { + var input = expose.toHTMLTree( source , dialect , options ); + + return expose.renderJsonML( input ); +} + +/** + * toHTMLTree( markdown, [dialect] ) -> JsonML + * toHTMLTree( md_tree ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Turn markdown into HTML, represented as a JsonML tree. If a string is given + * to this function, it is first parsed into a markdown tree by calling + * [[parse]]. + **/ +expose.toHTMLTree = function toHTMLTree( input, dialect , options ) { + // convert string input to an MD tree + if ( typeof input ==="string" ) input = this.parse( input, dialect ); + + // Now convert the MD tree to an HTML tree + + // remove references from the tree + var attrs = extract_attr( input ), + refs = {}; + + if ( attrs && attrs.references ) { + refs = attrs.references; + } + + var html = convert_tree_to_html( input, refs , options ); + merge_text_nodes( html ); + return html; +} + +var mk_block = Markdown.mk_block = function(block, trail, line) { + // Be helpful for default case in tests. + if ( arguments.length == 1 ) trail = "\n\n"; + + var s = new String(block); + s.trailing = trail; + // To make it clear its not just a string + s.toSource = function() { + return "Markdown.mk_block( " + + uneval(block) + + ", " + + uneval(trail) + + ", " + + uneval(line) + + " )" + } + + if (line != undefined) + s.lineNumber = line; + + return s; +} + +function count_lines( str ) { + var n = 0, i = -1;; + while ( ( i = str.indexOf('\n', i+1) ) != -1) n++; + return n; +} + +// Internal - split source into rough blocks +Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) { + // [\s\S] matches _anything_ (newline or space) + var re = /([\s\S]+?)($|\n(?:\s*\n|$)+)/g, + blocks = [], + m; + + var line_no = 1; + + if ( ( m = /^(\s*\n)/.exec(input) ) != null ) { + // skip (but count) leading blank lines + line_no += count_lines( m[0] ); + re.lastIndex = m[0].length; + } + + while ( ( m = re.exec(input) ) != null ) { + blocks.push( mk_block( m[1], m[2], line_no ) ); + line_no += count_lines( m[0] ); + } + + return blocks; +} + +/** + * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] + * - block (String): the block to process + * - next (Array): the following blocks + * + * Process `block` and return an array of JsonML nodes representing `block`. + * + * It does this by asking each block level function in the dialect to process + * the block until one can. Succesful handling is indicated by returning an + * array (with zero or more JsonML nodes), failure by a false value. + * + * Blocks handlers are responsible for calling [[Markdown#processInline]] + * themselves as appropriate. + * + * If the blocks were split incorrectly or adjacent blocks need collapsing you + * can adjust `next` in place using shift/splice etc. + * + * If any of this default behaviour is not right for the dialect, you can + * define a `__call__` method on the dialect that will get invoked to handle + * the block processing. + */ +Markdown.prototype.processBlock = function processBlock( block, next ) { + var cbs = this.dialect.block, + ord = cbs.__order__; + + if ( "__call__" in cbs ) { + return cbs.__call__.call(this, block, next); + } + + for ( var i = 0; i < ord.length; i++ ) { + //D:this.debug( "Testing", ord[i] ); + var res = cbs[ ord[i] ].call( this, block, next ); + if ( res ) { + //D:this.debug(" matched"); + if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) + this.debug(ord[i], "didn't return a proper array"); + //D:this.debug( "" ); + return res; + } + } + + // Uhoh! no match! Should we throw an error? + return []; +} + +Markdown.prototype.processInline = function processInline( block ) { + return this.dialect.inline.__call__.call( this, String( block ) ); +} + +/** + * Markdown#toTree( source ) -> JsonML + * - source (String): markdown source to parse + * + * Parse `source` into a JsonML tree representing the markdown document. + **/ +// custom_tree means set this.tree to `custom_tree` and restore old value on return +Markdown.prototype.toTree = function toTree( source, custom_root ) { + var blocks = source instanceof Array + ? source + : this.split_blocks( source ); + + // Make tree a member variable so its easier to mess with in extensions + var old_tree = this.tree; + try { + this.tree = custom_root || this.tree || [ "markdown" ]; + + blocks: + while ( blocks.length ) { + var b = this.processBlock( blocks.shift(), blocks ); + + // Reference blocks and the like won't return any content + if ( !b.length ) continue blocks; + + this.tree.push.apply( this.tree, b ); + } + return this.tree; + } + finally { + if ( custom_root ) + this.tree = old_tree; + } + +} + +// Noop by default +Markdown.prototype.debug = function () { + var args = Array.prototype.slice.call( arguments); + args.unshift(this.debug_indent); + if (typeof print !== "undefined") + print.apply( print, args ); + if (typeof console !== "undefined" && typeof console.log !== "undefined") + console.log.apply( null, args ); +} + +Markdown.prototype.loop_re_over_block = function( re, block, cb ) { + // Dont use /g regexps with this + var m, + b = block.valueOf(); + + while ( b.length && (m = re.exec(b) ) != null) { + b = b.substr( m[0].length ); + cb.call(this, m); + } + return b; +} + +/** + * Markdown.dialects + * + * Namespace of built-in dialects. + **/ +Markdown.dialects = {}; + +/** + * Markdown.dialects.Gruber + * + * The default dialect that follows the rules set out by John Gruber's + * markdown.pl as closely as possible. Well actually we follow the behaviour of + * that script which in some places is not exactly what the syntax web page + * says. + **/ +Markdown.dialects.Gruber = { + block: { + atxHeader: function atxHeader( block, next ) { + var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); + + if ( !m ) return undefined; + + var header = [ "header", { level: m[ 1 ].length } ]; + Array.prototype.push.apply(header, this.processInline(m[ 2 ])); + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + setextHeader: function setextHeader( block, next ) { + var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); + + if ( !m ) return undefined; + + var level = ( m[ 2 ] === "=" ) ? 1 : 2; + var header = [ "header", { level : level }, m[ 1 ] ]; + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + code: function code( block, next ) { + // | Foo + // |bar + // should be a code block followed by a paragraph. Fun + // + // There might also be adjacent code block to merge. + + var ret = [], + re = /^(?: {0,3}\t| {4})(.*)\n?/, + lines; + + // 4 spaces + content + var m = block.match( re ); + + if ( !m ) return undefined; + + block_search: + do { + // Now pull out the rest of the lines + var b = this.loop_re_over_block( + re, block.valueOf(), function( m ) { ret.push( m[1] ) } ); + + if (b.length) { + // Case alluded to in first comment. push it back on as a new block + next.unshift( mk_block(b, block.trailing) ); + break block_search; + } + else if (next.length) { + // Check the next block - it might be code too + var m = next[0].match( re ); + + if ( !m ) break block_search; + + // Pull how how many blanks lines follow - minus two to account for .join + ret.push ( block.trailing.replace(/[^\n]/g, '').substring(2) ); + + block = next.shift(); + } + else + break block_search; + } while (true); + + return [ [ "code_block", ret.join("\n") ] ]; + }, + + horizRule: function horizRule( block, next ) { + // this needs to find any hr in the block to handle abutting blocks + var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); + + if ( !m ) { + return undefined; + } + + var jsonml = [ [ "hr" ] ]; + + // if there's a leading abutting block, process it + if ( m[ 1 ] ) { + jsonml.unshift.apply( jsonml, this.processBlock( m[ 1 ], [] ) ); + } + + // if there's a trailing abutting block, stick it into next + if ( m[ 3 ] ) { + next.unshift( mk_block( m[ 3 ] ) ); + } + + return jsonml; + }, + + // There are two types of lists. Tight and loose. Tight lists have no whitespace + // between the items (and result in text just in the <li>) and loose lists, + // which have an empty line between list items, resulting in (one or more) + // paragraphs inside the <li>. + // + // There are all sorts weird edge cases about the original markdown.pl's + // handling of lists: + // + // * Nested lists are supposed to be indented by four chars per level. But + // if they aren't, you can get a nested list by indenting by less than + // four so long as the indent doesn't match an indent of an existing list + // item in the 'nest stack'. + // + // * The type of the list (bullet or number) is controlled just by the + // first item at the indent. Subsequent changes are ignored unless they + // are for nested lists + // + lists: (function( ) { + // Use a closure to hide a few variables. + var any_list = "[*+-]|\\d\\.", + bullet_list = /[*+-]/, + number_list = /\d+\./, + // Capture leading indent as it matters for determining nested lists. + is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), + indent_re = "(?: {0,3}\\t| {4})"; + + // TODO: Cache this regexp for certain depths. + // Create a regexp suitable for matching an li for a given stack depth + function regex_for_depth( depth ) { + + return new RegExp( + // m[1] = indent, m[2] = list_type + "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + + // m[3] = cont + "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" + ); + } + function expand_tab( input ) { + return input.replace( / {0,3}\t/g, " " ); + } + + // Add inline content `inline` to `li`. inline comes from processInline + // so is an array of content + function add(li, loose, inline, nl) { + if (loose) { + li.push( [ "para" ].concat(inline) ); + return; + } + // Hmmm, should this be any block level element or just paras? + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para" + ? li[li.length -1] + : li; + + // If there is already some content in this list, add the new line in + if (nl && li.length > 1) inline.unshift(nl); + + for (var i=0; i < inline.length; i++) { + var what = inline[i], + is_str = typeof what == "string"; + if (is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) + { + add_to[ add_to.length-1 ] += what; + } + else { + add_to.push( what ); + } + } + } + + // contained means have an indent greater than the current one. On + // *every* line in the block + function get_contained_blocks( depth, blocks ) { + + var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), + replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), + ret = []; + + while ( blocks.length > 0 ) { + if ( re.exec( blocks[0] ) ) { + var b = blocks.shift(), + // Now remove that indent + x = b.replace( replace, ""); + + ret.push( mk_block( x, b.trailing, b.lineNumber ) ); + } + break; + } + return ret; + } + + // passed to stack.forEach to turn list items up the stack into paras + function paragraphify(s, i, stack) { + var list = s.list; + var last_li = list[list.length-1]; + + if (last_li[1] instanceof Array && last_li[1][0] == "para") { + return; + } + if (i+1 == stack.length) { + // Last stack frame + // Keep the same array, but replace the contents + last_li.push( ["para"].concat( last_li.splice(1) ) ); + } + else { + var sublist = last_li.pop(); + last_li.push( ["para"].concat( last_li.splice(1) ), sublist ); + } + } + + // The matcher function + return function( block, next ) { + var m = block.match( is_list_re ); + if ( !m ) return undefined; + + function make_list( m ) { + var list = bullet_list.exec( m[2] ) + ? ["bulletlist"] + : ["numberlist"]; + + stack.push( { list: list, indent: m[1] } ); + return list; + } + + + var stack = [], // Stack of lists for nesting. + list = make_list( m ), + last_li, + loose = false, + ret = [ stack[0].list ]; + + // Loop to search over block looking for inner block elements and loose lists + loose_search: + while( true ) { + // Split into lines preserving new lines at end of line + var lines = block.split( /(?=\n)/ ); + + // We have to grab all lines for a li and call processInline on them + // once as there are some inline things that can span lines. + var li_accumulate = ""; + + // Loop over the lines in this block looking for tight lists. + tight_search: + for (var line_no=0; line_no < lines.length; line_no++) { + var nl = "", + l = lines[line_no].replace(/^\n/, function(n) { nl = n; return "" }); + + // TODO: really should cache this + var line_re = regex_for_depth( stack.length ); + + m = l.match( line_re ); + //print( "line:", uneval(l), "\nline match:", uneval(m) ); + + // We have a list item + if ( m[1] !== undefined ) { + // Process the previous list item, if any + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + m[1] = expand_tab( m[1] ); + var wanted_depth = Math.floor(m[1].length/4)+1; + //print( "want:", wanted_depth, "stack:", stack.length); + if ( wanted_depth > stack.length ) { + // Deep enough for a nested list outright + //print ( "new nested list" ); + list = make_list( m ); + last_li.push( list ); + last_li = list[1] = [ "listitem" ]; + } + else { + // We aren't deep enough to be strictly a new level. This is + // where Md.pl goes nuts. If the indent matches a level in the + // stack, put it there, else put it one deeper then the + // wanted_depth deserves. + var found = stack.some(function(s, i) { + if ( s.indent != m[1] ) return false; + list = s.list; // Found the level we want + stack.splice(i+1); // Remove the others + //print("found"); + return true; // And stop looping + }); + + if (!found) { + //print("not found. l:", uneval(l)); + wanted_depth++; + if (wanted_depth <= stack.length) { + stack.splice(wanted_depth); + //print("Desired depth now", wanted_depth, "stack:", stack.length); + list = stack[wanted_depth-1].list; + //print("list:", uneval(list) ); + } + else { + //print ("made new stack for messy indent"); + list = make_list(m); + last_li.push(list); + } + } + + //print( uneval(list), "last", list === stack[stack.length-1].list ); + last_li = [ "listitem" ]; + list.push(last_li); + } // end depth of shenegains + nl = ""; + } + + // Add content + if (l.length > m[0].length) { + li_accumulate += nl + l.substr( m[0].length ); + } + } // tight_search + + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + // Look at the next block - we might have a loose list. Or an extra + // paragraph for the current li + var contained = get_contained_blocks( stack.length, next ); + + // Deal with code blocks or properly nested lists + if (contained.length > 0) { + // Make sure all listitems up the stack are paragraphs + stack.forEach( paragraphify, this ); + + last_li.push.apply( last_li, this.toTree( contained, [] ) ); + } + + var next_block = next[0] && next[0].valueOf() || ""; + + if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { + block = next.shift(); + + // Check for an HR following a list: features/lists/hr_abutting + var hr = this.dialect.block.horizRule( block, next ); + + if (hr) { + ret.push.apply(ret, hr); + break; + } + + // Make sure all listitems up the stack are paragraphs + stack.forEach( paragraphify , this ); + + loose = true; + continue loose_search; + } + break; + } // loose_search + + return ret; + } + })(), + + blockquote: function blockquote( block, next ) { + if ( !block.match( /^>/m ) ) + return undefined; + + var jsonml = []; + + // separate out the leading abutting block, if any + if ( block[ 0 ] != ">" ) { + var lines = block.split( /\n/ ), + prev = []; + + // keep shifting lines until you find a crotchet + while ( lines.length && lines[ 0 ][ 0 ] != ">" ) { + prev.push( lines.shift() ); + } + + // reassemble! + block = lines.join( "\n" ); + jsonml.push.apply( jsonml, this.processBlock( prev.join( "\n" ), [] ) ); + } + + // if the next block is also a blockquote merge it in + while ( next.length && next[ 0 ][ 0 ] == ">" ) { + var b = next.shift(); + block += block.trailing + b; + block.trailing = b.trailing; + } + + // Strip off the leading "> " and re-process as a block. + var input = block.replace( /^> ?/gm, '' ), + old_tree = this.tree; + jsonml.push( this.toTree( input, [ "blockquote" ] ) ); + + return jsonml; + }, + + referenceDefn: function referenceDefn( block, next) { + var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; + // interesting matches are [ , ref_id, url, , title, title ] + + if ( !block.match(re) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) { + this.tree.splice( 1, 0, {} ); + } + + var attrs = extract_attr( this.tree ); + + // make a references hash if it doesn't exist + if ( attrs.references === undefined ) { + attrs.references = {}; + } + + var b = this.loop_re_over_block(re, block, function( m ) { + + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + var ref = attrs.references[ m[1].toLowerCase() ] = { + href: m[2] + }; + + if (m[4] !== undefined) + ref.title = m[4]; + else if (m[5] !== undefined) + ref.title = m[5]; + + } ); + + if (b.length) + next.unshift( mk_block( b, block.trailing ) ); + + return []; + }, + + para: function para( block, next ) { + // everything's a para! + return [ ["para"].concat( this.processInline( block ) ) ]; + } + } +} + +Markdown.dialects.Gruber.inline = { + __call__: function inline( text, patterns ) { + // Hmmm - should this function be directly in Md#processInline, or + // conversely, should Md#processBlock be moved into block.__call__ too + var out = [ ], + m, + // Look for the next occurange of a special character/pattern + re = new RegExp( "([\\s\\S]*?)(" + (patterns.source || patterns) + ")", "g" ), + lastIndex = 0; + + //D:var self = this; + //D:self.debug("processInline:", uneval(text) ); + function add(x) { + //D:self.debug(" adding output", uneval(x)); + if (typeof x == "string" && typeof out[out.length-1] == "string") + out[ out.length-1 ] += x; + else + out.push(x); + } + + while ( ( m = re.exec(text) ) != null) { + if ( m[1] ) add( m[1] ); // Some un-interesting text matched + else m[1] = { length: 0 }; // Or there was none, but make m[1].length == 0 + + var res; + if ( m[2] in this.dialect.inline ) { + res = this.dialect.inline[ m[2] ].call( + this, + text.substr( m.index + m[1].length ), m, out ); + } + // Default for now to make dev easier. just slurp special and output it. + res = res || [ m[2].length, m[2] ]; + + var len = res.shift(); + // Update how much input was consumed + re.lastIndex += ( len - m[2].length ); + + // Add children + res.forEach(add); + + lastIndex = re.lastIndex; + } + + // Add last 'boring' chunk + if ( text.length > lastIndex ) + add( text.substr( lastIndex ) ); + + return out; + }, + + "\\": function escaped( text ) { + // [ length of input processed, node/children to add... ] + // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! + if ( text.match( /^\\[\\`\*_{}\[\]()#\+.!\-]/ ) ) + return [ 2, text[1] ]; + else + // Not an esacpe + return [ 1, "\\" ]; + }, + + " + // 1 2 3 4 <--- captures + var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*(\S*)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + m[2] == this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { alt: m[1], href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + return [ m[0].length, [ "img", attrs ] ]; + } + + // ![Alt text][id] + m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); + + if ( m ) { + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), text: m[0] } ] ]; + } + + // Just consume the '![' + return [ 2, "![" ]; + }, + + "[": function link( text ) { + // [link text](/path/to/img.jpg "Optional title") + // 1 2 3 4 <--- captures + var m = text.match( /^\[([\s\S]*?)\][ \t]*\([ \t]*(\S+)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + // Process escapes only + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + var link = [ "link", attrs ]; + Array.prototype.push.apply( link, this.processInline( m[1] ) ); + return [ m[0].length, link ]; + } + + // [Alt text][id] + // [Alt text] [id] + // [id] + m = text.match( /^\[([\s\S]*?)\](?: ?\[(.*?)\])?/ ); + + if ( m ) { + // [id] case, text == id + if ( m[2] === undefined || m[2] === "" ) m[2] = m[1]; + + attrs = { ref: m[ 2 ].toLowerCase(), original: m[ 0 ] }; + link = [ "link_ref", attrs ]; + Array.prototype.push.apply( link, this.processInline( m[1] ) ); + + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion. + // Store the original so that conversion can revert if the ref isn't found. + return [ + m[ 0 ].length, + link + ]; + } + + // Just consume the '[' + return [ 1, "[" ]; + }, + + + "<": function autoLink( text ) { + var m; + + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) { + if ( m[3] ) { + return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; + + } + else if ( m[2] == "mailto" ) { + return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; + } + else + return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; + } + + return [ 1, "<" ]; + }, + + "`": function inlineCode( text ) { + // Inline code block. as many backticks as you like to start it + // Always skip over the opening ticks. + var m = text.match( /(`+)(([\s\S]*?)\1)/ ); + + if ( m && m[2] ) + return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; + else { + // TODO: No matching end code found - warn! + return [ 1, "`" ]; + } + }, + + " \n": function lineBreak( text ) { + return [ 3, [ "linebreak" ] ]; + } + +} + +// Meta Helper/generator method for em and strong handling +function strong_em( tag, md ) { + + var state_slot = tag + "_state", + other_slot = tag == "strong" ? "em_state" : "strong_state"; + + function CloseTag(len) { + this.len_after = len; + this.name = "close_" + md; + } + + return function ( text, orig_match ) { + + if (this[state_slot][0] == md) { + // Most recent em is of this type + //D:this.debug("closing", md); + this[state_slot].shift(); + + // "Consume" everything to go back to the recrusion in the else-block below + return[ text.length, new CloseTag(text.length-md.length) ]; + } + else { + // Store a clone of the em/strong states + var other = this[other_slot].slice(), + state = this[state_slot].slice(); + + this[state_slot].unshift(md); + + //D:this.debug_indent += " "; + + // Recurse + var res = this.processInline( text.substr( md.length ) ); + //D:this.debug_indent = this.debug_indent.substr(2); + + var last = res[res.length - 1]; + + //D:this.debug("processInline from", tag + ": ", uneval( res ) ); + + var check = this[state_slot].shift(); + if (last instanceof CloseTag) { + res.pop(); + // We matched! Huzzah. + var consumed = text.length - last.len_after; + return [ consumed, [ tag ].concat(res) ]; + } + else { + // Restore the state of the other kind. We might have mistakenly closed it. + this[other_slot] = other; + this[state_slot] = state; + + // We can't reuse the processed result as it could have wrong parsing contexts in it. + return [ md.length, md ]; + } + } + } // End returned function +} + +Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**"); +Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__"); +Markdown.dialects.Gruber.inline["*"] = strong_em("em", "*"); +Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_"); + + +// Build default order from insertion order. +Markdown.buildBlockOrder = function(d) { + var ord = []; + for ( var i in d ) { + if ( i == "__order__" || i == "__call__" ) continue; + ord.push( i ); + } + d.__order__ = ord; +} + +// Build patterns for inline matcher +Markdown.buildInlinePatterns = function(d) { + var patterns = []; + + for ( var i in d ) { + if (i == "__call__") continue; + var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) + .replace( /\n/, "\\n" ); + patterns.push( i.length == 1 ? l : "(?:" + l + ")" ); + } + + patterns = patterns.join("|"); + //print("patterns:", uneval( patterns ) ); + + var fn = d.__call__; + d.__call__ = function(text, pattern) { + if (pattern != undefined) + return fn.call(this, text, pattern); + else + return fn.call(this, text, patterns); + } +} + +// Helper function to make sub-classing a dialect easier +Markdown.subclassDialect = function( d ) { + function Block() {}; + Block.prototype = d.block; + function Inline() {}; + Inline.prototype = d.inline; + + return { block: new Block(), inline: new Inline() }; +} + +Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); +Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); + +Markdown.dialects.Maruku = Markdown.subclassDialect( Markdown.dialects.Gruber ); + +Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) { + // we're only interested in the first block + if ( block.lineNumber > 1 ) return undefined; + + // document_meta blocks consist of one or more lines of `Key: Value\n` + if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) { + this.tree.splice( 1, 0, {} ); + } + + var pairs = block.split( /\n/ ); + for ( p in pairs ) { + var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), + key = m[ 1 ].toLowerCase(), + value = m[ 2 ]; + + this.tree[ 1 ][ key ] = value; + } + + // document_meta produces no content! + return []; +} + +Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { + // check if the last line of the block is an meta hash + var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); + if ( !m ) return undefined; + + // process the meta hash + var attr = process_meta_hash( m[ 2 ] ); + + // if we matched ^ then we need to apply meta to the previous block + if ( m[ 1 ] === "" ) { + var node = this.tree[ this.tree.length - 1 ], + hash = extract_attr( node ); + + // if the node is a string (rather than JsonML), bail + if ( typeof node === "string" ) return undefined; + + // create the attribute hash if it doesn't exist + if ( !hash ) { + hash = {}; + node.splice( 1, 0, hash ); + } + + // add the attributes in + for ( a in attr ) { + hash[ a ] = attr[ a ]; + } + + // return nothing so the meta hash is removed + return []; + } + + // pull the meta hash off the block and process what's left + var b = block.replace( /\n.*$/, "" ), + result = this.processBlock( b, [] ); + + // get or make the attributes hash + var hash = extract_attr( result[ 0 ] ); + if ( !hash ) { + hash = {}; + result[ 0 ].splice( 1, 0, hash ); + } + + // attach the attributes to the block + for ( a in attr ) { + hash[ a ] = attr[ a ]; + } + + return result; +} + +Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) { + // one or more terms followed by one or more definitions, in a single block + var tight = /^((?:[^\s:].*\n)+):\s+([^]+)$/, + list = [ "dl" ]; + + // see if we're dealing with a tight or loose block + if ( ( m = block.match( tight ) ) ) { + // pull subsequent tight DL blocks out of `next` + var blocks = [ block ]; + while ( next.length && tight.exec( next[ 0 ] ) ) { + blocks.push( next.shift() ); + } + + for ( var b = 0; b < blocks.length; ++b ) { + var m = blocks[ b ].match( tight ), + terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), + defns = m[ 2 ].split( /\n:\s+/ ); + + // print( uneval( m ) ); + + for ( var i = 0; i < terms.length; ++i ) { + list.push( [ "dt" ].concat(this.processInline(terms[i]))); + } + + for ( var i = 0; i < defns.length; ++i ) { + // run inline processing over the definition + list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); + } + } + } + else { + return undefined; + } + + return [ list ]; +} + +Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { + if ( !out.length ) { + return [ 2, "{:" ]; + } + + // get the preceeding element + var before = out[ out.length - 1 ]; + + if ( typeof before === "string" ) { + return [ 2, "{:" ]; + } + + // match a meta hash + var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); + + // no match, false alarm + if ( !m ) { + return [ 2, "{:" ]; + } + + // attach the attributes to the preceeding element + var meta = process_meta_hash( m[ 1 ] ), + attr = extract_attr( before ); + + if ( !attr ) { + attr = {}; + before.splice( 1, 0, attr ); + } + + for ( var k in meta ) { + attr[ k ] = meta[ k ]; + } + + // cut out the string and replace it with nothing + return [ m[ 0 ].length, "" ]; +} + +Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); +Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); + +var isArray = expose.isArray = function(obj) { + return (obj instanceof Array || typeof obj === "array" || Array.isArray(obj)); +} + +function extract_attr( jsonml ) { + return isArray(jsonml) + && jsonml.length > 1 + && typeof jsonml[ 1 ] === "object" + && !( isArray(jsonml[ 1 ]) ) + ? jsonml[ 1 ] + : undefined; +} + +function process_meta_hash( meta_string ) { + var meta = split_meta_hash( meta_string ), + attr = {}; + + for ( var i = 0; i < meta.length; ++i ) { + // id: #foo + if ( /^#/.test( meta[ i ] ) ) { + attr.id = meta[ i ].substring( 1 ); + } + // class: .foo + else if ( /^\./.test( meta[ i ] ) ) { + // if class already exists, append the new one + if ( attr['class'] ) { + attr['class'] = attr['class'] + meta[ i ].replace( /./, " " ); + } + else { + attr['class'] = meta[ i ].substring( 1 ); + } + } + // attribute: foo=bar + else if ( /=/.test( meta[ i ] ) ) { + var s = meta[ i ].split( /=/ ); + attr[ s[ 0 ] ] = s[ 1 ]; + } + } + + return attr; +} + +function split_meta_hash( meta_string ) { + var meta = meta_string.split( "" ), + parts = [ "" ], + in_quotes = false; + + while ( meta.length ) { + var letter = meta.shift(); + switch ( letter ) { + case " " : + // if we're in a quoted section, keep it + if ( in_quotes ) { + parts[ parts.length - 1 ] += letter; + } + // otherwise make a new part + else { + parts.push( "" ); + } + break; + case "'" : + case '"' : + // reverse the quotes and move straight on + in_quotes = !in_quotes; + break; + case "\\" : + // shift off the next letter to be used straight away. + // it was escaped so we'll keep it whatever it is + letter = meta.shift(); + default : + parts[ parts.length - 1 ] += letter; + break; + } + } + + return parts; +} + +/** + * renderJsonML( jsonml[, options] ) -> String + * - jsonml (Array): JsonML array to render to XML + * - options (Object): options + * + * Converts the given JsonML into well-formed XML. + * + * The options currently understood are: + * + * - root (Boolean): wether or not the root node should be included in the + * output, or just its children. The default `false` is to not include the + * root itself. + */ +expose.renderJsonML = function( jsonml, options ) { + options = options || {}; + // include the root element in the rendered output? + options.root = options.root || false; + + var content = []; + + if ( options.root ) { + content.push( render_tree( jsonml ) ); + } + else { + jsonml.shift(); // get rid of the tag + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { + jsonml.shift(); // get rid of the attributes + } + + while ( jsonml.length ) { + content.push( render_tree( jsonml.shift() ) ); + } + } + + return content.join( "\n\n" ); +} + +function escapeHTML( text ) { + return text.replace( /&/g, "&" ) + .replace( /</g, "<" ) + .replace( />/g, ">" ) + .replace( /"/g, """ ) + .replace( /'/g, "'" ); +} + +function render_tree( jsonml ) { + // basic case + if ( typeof jsonml === "string" ) { + return escapeHTML( jsonml ); + } + + var tag = jsonml.shift(), + attributes = {}, + content = []; + + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { + attributes = jsonml.shift(); + } + + while ( jsonml.length ) { + content.push( arguments.callee( jsonml.shift() ) ); + } + + var tag_attrs = ""; + for ( var a in attributes ) { + tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; + } + + // be careful about adding whitespace here for inline elements + return "<"+ tag + tag_attrs + ">" + content.join( "" ) + "</" + tag + ">"; +} + +function convert_tree_to_html( tree, references, options ) { + options = options || {}; + + // shallow clone + var jsonml = tree.slice( 0 ); + + if (typeof options.preprocessTreeNode === "function") { + jsonml = options.preprocessTreeNode(jsonml, references); + } + + // Clone attributes if they exist + var attrs = extract_attr( jsonml ); + if ( attrs ) { + jsonml[ 1 ] = {}; + for ( var i in attrs ) { + jsonml[ 1 ][ i ] = attrs[ i ]; + } + attrs = jsonml[ 1 ]; + } + + // basic case + if ( typeof jsonml === "string" ) { + return jsonml; + } + + // convert this node + switch ( jsonml[ 0 ] ) { + case "header": + jsonml[ 0 ] = "h" + jsonml[ 1 ].level; + delete jsonml[ 1 ].level; + break; + case "bulletlist": + jsonml[ 0 ] = "ul"; + break; + case "numberlist": + jsonml[ 0 ] = "ol"; + break; + case "listitem": + jsonml[ 0 ] = "li"; + break; + case "para": + jsonml[ 0 ] = "p"; + break; + case "markdown": + jsonml[ 0 ] = "html"; + if ( attrs ) delete attrs.references; + break; + case "code_block": + jsonml[ 0 ] = "pre"; + var i = attrs ? 2 : 1; + var code = [ "code" ]; + code.push.apply( code, jsonml.splice( i ) ); + jsonml[ i ] = code; + break; + case "inlinecode": + jsonml[ 0 ] = "code"; + break; + case "img": + jsonml[ 1 ].src = jsonml[ 1 ].href; + delete jsonml[ 1 ].href; + break; + case "linebreak": + jsonml[0] = "br"; + break; + case "link": + jsonml[ 0 ] = "a"; + break; + case "link_ref": + jsonml[ 0 ] = "a"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.href = ref.href; + if ( ref.title ) { + attrs.title = ref.title; + } + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + } + + // convert all the children + var i = 1; + + // deal with the attribute node, if it exists + if ( attrs ) { + // if there are keys, skip over it + for ( var key in jsonml[ 1 ] ) { + i = 2; + } + // if there aren't, remove it + if ( i === 1 ) { + jsonml.splice( i, 1 ); + } + } + + for ( ; i < jsonml.length; ++i ) { + jsonml[ i ] = arguments.callee( jsonml[ i ], references, options ); + } + + return jsonml; +} + + +// merges adjacent text nodes into a single node +function merge_text_nodes( jsonml ) { + // skip the tag name and attribute hash + var i = extract_attr( jsonml ) ? 2 : 1; + + while ( i < jsonml.length ) { + // if it's a string check the next item too + if ( typeof jsonml[ i ] === "string" ) { + if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { + // merge the second string into the first and remove it + jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; + } + else { + ++i; + } + } + // if it's not a string recurse + else { + arguments.callee( jsonml[ i ] ); + ++i; + } + } +} + +} )( (function() { + if ( typeof exports === "undefined" ) { + window.markdown = {}; + return window.markdown; + } + else { + return exports; + } +} )() ); diff --git a/doc/tutorial/mod.md b/doc/tutorial/mod.md new file mode 100644 index 00000000000..d7a0e6417f3 --- /dev/null +++ b/doc/tutorial/mod.md @@ -0,0 +1,3 @@ +# Modules and crates + +FIXME include name resolution details diff --git a/doc/tutorial/order b/doc/tutorial/order new file mode 100644 index 00000000000..54b7314a2dd --- /dev/null +++ b/doc/tutorial/order @@ -0,0 +1,9 @@ +intro +setup +syntax +control +func +data +args +generic +mod diff --git a/doc/tutorial/setup.md b/doc/tutorial/setup.md new file mode 100644 index 00000000000..ffa3a976629 --- /dev/null +++ b/doc/tutorial/setup.md @@ -0,0 +1,42 @@ +# Getting started + +## Installation + +FIXME Fill this in when the installation package is finished. + +## Compiling your first program + +Rust program files are, by convention, given the extension `.rs`. Say +we have a file `hello.rs` containing this program: + + use std; + fn main() { + std::io::println("hello world!"); + } + +If the Rust compiler was installed successfully, running `rustc +hello.rs` will produce a binary called `hello` (or `hello.exe`). + +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 +this: + + hello.rs:2:4: 2:20 error: unresolved modulename: std + hello.rs:2 std::io::println("hello world!"); + ^~~~~~~~~~~~~~~~ + +The Rust compiler tries to provide useful information when it runs +into an error. + +## Anatomy of a Rust program + +FIXME say something about libs, main, modules, use + +## Editing Rust code + +There are Vim highlighting and indentation scrips in the Rust source +distribution under `src/etc/vim/`. An Emacs mode can be found at +`[https://github.com/marijnh/rust-mode](https://github.com/marijnh/rust-mode)`. + +Other editors are not provided for yet. If you end up writing a Rust +mode for your favorite editor, let us know so that we can link to it. diff --git a/doc/tutorial/syntax.md b/doc/tutorial/syntax.md new file mode 100644 index 00000000000..19e9c4aa8ef --- /dev/null +++ b/doc/tutorial/syntax.md @@ -0,0 +1,207 @@ +# Syntax Basics + +FIXME: mention the module separator `::` somewhere + +## Braces + +Assuming you've programmed in any C-family language (C++, Java, +JavaScript, C#, or PHP), Rust will feel familiar. The main surface +difference to be aware of is that the bodies of `if` statements and of +loops *have* to be wrapped in brackets. Single-statement, bracket-less +bodies are not allowed. + +If the verbosity of that bothers you, consider the fact that this +allows you to omit the parentheses around the condition in `if`, +`while`, and similar constructs. This will save you two characters +every time. As a bonus, you no longer have to spend any mental energy +on deciding whether you need to add braces or not, or on adding them +after the fact when a adding a statement to an `if` branch. + +Accounting for these differences, the surface syntax of Rust +statements and expressions is C-like. Function calls are written +`myfunc(arg1, arg2)`, operators have mostly the same name and +precedence that they have in C, comments look the same, and constructs +like `if` and `while` are available: + + fn main() { + if 1 < 2 { + while false { call_a_function(10 * 4); } + } else if 4 < 3 || 3 < 4 { + // Comments are C++-style too + } else { + /* Multi-line comment syntax */ + } + } + +## Expression syntax + +Though it isn't apparent in most everyday code, there is a fundamental +difference between Rust's syntax and the predecessors in this family +of languages. Almost everything in rust is an expression, even things +that are statements in other languages. This allows for useless things +like this (which passes nil—the void type—to a function): + + a_function(while false {}); + +But also useful things like this: + + let x = if the_stars_align() { 4 } + else if something_else() { 3 } + else { 0 }; + +This piece of code will bind the variable `x` to a value depending on +the conditions. Note the condition bodies, which look like `{ +expression }`. The lack of a semicolon after the last statement in a +braced block gives the whole block the value of that last expression. +If the branches of the `if` had looked like `{ 4; }`, the above +example would simply assign nil (void) to `x`. But without the +semicolon, each branch has a different value, and `x` gets the value +of the branch that was taken. + +This also works for function bodies. This function returns a boolean: + + fn is_four(x: int) -> bool { x == 4 } + +If everything is an expression, you might conclude that you have to +add a terminating semicolon after *every* statement, even ones that +are not traditionally terminated with a semicolon in C (like `while`). +That is not the case, though. Statements that end in a block only need +a semicolon if that block contains a trailing expression. `while` +loops do not allow trailing expressions, and `if` statements tend to +only have a trailing expression when you want to use their value for +something—in which case you'll have embedded it in a bigger statement, +like the `let x = ...` example above. + +## Types + +The `-> bool` in the last example is the way a function's return type +is written. For functions that do not return a meaningful value (these +conceptually return nil in Rust), you can optionally say `-> ()` (`()` +is how nil is written), but usually the return annotation is simply +left off, as in the `fn main() { ... }` examples we've seen earlier. + +Every argument to a function must have its type declared (for example, +`x: int`). Inside the function, type inference will be able to +automatically deduce the type of most locals (generic functions, which +we'll come back to later, will occasionally need additional +annotation). Locals can be written either with or without a type +annotation: + + // The type of this vector will be inferred based on its use. + let x = []; + // Explicitly say this is a vector of integers. + let y: [int] = []; + +The basic types are written like this: + +`()` +: Nil, the type that has only a single value. + +`bool` +: Boolean type.. + +`int` +: A machine-pointer-sized integer. + +`uint` +: A machine-pointer-sized unsigned integer. + +`i8`, `i16`, `i32`, `i64` +: Signed integers with a specific size (in bits). + +`u8`, `u16`, `u32`, `u64` +: Unsigned integers with a specific size. + +`f32`, `f64` +: Floating-point types. + +`float` +: The largest floating-point type efficiently supported on the target machine. + +`char` +: A character is a 32-bit Unicode code point. + +`str` +: String type. A string contains a utf-8 encoded sequence of characters. + +These can be combined in composite types, which will be described in +more detail later on (the `T`s here stand for any other type): + +`[T]` +: Vector type. + +`[mutable T]` +: Mutable vector type. + +`(T1, T2)` +: Tuple type. Any arity above 1 is supported. + +`{fname1: T1, fname2: T2}` +: Record type. + +`fn(arg1: T1, arg2: T2) -> T3` +: Function type. + +`@T`, `~T`, `*T` +: Pointer types. + +`obj { fn method1() }` +: Object type. + +Types can be given names with `type` declarations: + + type monster_size = uint; + +This will provide a synonym, `monster_size`, for unsigned integers. It +will not actually create a new type—`monster_size` and `uint` can be +used interchangeably, and using one where the other is expected is not +a type error. Read about [single-variant tags][svt] in the next +section if you need to create a type name that's not just a synonym. + +[svt]: FIXME + +## Literals + +Integers can be written in decimal (`144`), hexadecimal (`0x90`), and +binary (`0b10010000`) base. Without suffix, an integer literal is +considered to be of type `int`. Add a `u` (`144u`) to make it a `uint` +instead. Literals of the fixed-size integer types can be created by +the literal with the type name (`i8`, `u64`, etc). + +Note that, in Rust, no implicit conversion between integer types +happens. If you are adding one to a variable of type `uint`, you must +type `v += 1u`—saying `+= 1` will give you a type error. + +Floating point numbers are written `0.0`, `1e6`, or `2.1e-4`. Without +suffix, the literal is assumed to be of type `float`. Suffixes `f32` +and `f64` can be used to create literals of a specific type. The +suffix `f` can be used to write `float` literals without a dot or +exponent: `3f`. + +The nil literal is written just like the type: `()`. The keywords +`true` and `false` produce the boolean literals. + +Character literals are written between single quotes, as in `'x'`. You +may put non-ascii characters between single quotes (your source file +should be encoded as utf-8 in that case). Rust understands a number of +character escapes, using the backslash character: + +`\n` +: A newline (unicode character 32). +`\r` +: A carriage return (13). +`\t` +: A tab character (9). +`\\`, `\'`, `\"` +: Simply escapes the following character. +`\xHH`, `\uHHHH`, `\UHHHHHHHH` +: Unicode escapes, where the `H` characters are the hexadecimal digits that form the character code. + +String literals allow the same escape sequences. They are written +between double quotes (`"hello"`). Rust strings may contain newlines. +When a newline is preceded by a backslash, it, and all white space +following it, will not appear in the resulting string literal. + +## Operators + +FIXME recap C-style operators, ?:, explain `as` diff --git a/doc/tutorial/web/style.css b/doc/tutorial/web/style.css new file mode 100644 index 00000000000..a71180027f0 --- /dev/null +++ b/doc/tutorial/web/style.css @@ -0,0 +1,36 @@ +body { + padding: 1em; + margin: 0; + font-family: "Helvetica Neue", Helvetica, sans-serif; +} + +#content { + padding: 1em 6em; + max-width: 50em; +} + +h1 { font-size: 22pt; } +h2 { font-size: 17pt; } +h3 { font-size: 14pt; } + +code { + color: #033; +} + +pre { + margin: 1.1em 12px; + border: 1px solid #CCCCCC; + padding: .4em; + font-size: 120%; +} + +p.head { + font-size: 80%; + font-style: italic; + text-align: right; +} + +a, a:visited, a:link { + text-decoration: none; + color: #00438a; +} |
