about summary refs log tree commit diff
path: root/src/comp/syntax/parse
diff options
context:
space:
mode:
authorMarijn Haverbeke <marijnh@gmail.com>2011-07-05 11:48:19 +0200
committerMarijn Haverbeke <marijnh@gmail.com>2011-07-05 15:57:21 +0200
commit6fd6fdea93fca19f168526943c177f942212cbc6 (patch)
tree967778dac7798a33628a4e7992f2d26492e9d71f /src/comp/syntax/parse
parentc59ebf0f018b748011fc9b23ce0bab3dcfcfe733 (diff)
downloadrust-6fd6fdea93fca19f168526943c177f942212cbc6.tar.gz
rust-6fd6fdea93fca19f168526943c177f942212cbc6.zip
Move everything syntax-related to syntax/, break deps on rest of compiler
src/comp/syntax is currently just a sub-module of rustc, but it will,
in the near future, be its own crate. This includes:

 - The AST data structure
 - The parser
 - The pretty-printer
 - Visit, walk, and fold
 - The syntax extension system
 - Some utility stuff that should be in the stdlib*

*) Stdlib extensions currently require a snapshot before they can be
   used, and the win build is very broken right now. This is temporary
   and will be cleaned up when one of those problems goes away.

A lot of code was moved by this patch, mostly towards a more organized
layout. Some package paths did get longer, and I guess the new layout
will take some getting used to. Sorry about that!

Please try not to re-introduce any dependencies in syntax/ on any of
the other src/comp/ subdirs.
Diffstat (limited to 'src/comp/syntax/parse')
-rw-r--r--src/comp/syntax/parse/eval.rs124
-rw-r--r--src/comp/syntax/parse/lexer.rs758
-rw-r--r--src/comp/syntax/parse/parser.rs2468
-rw-r--r--src/comp/syntax/parse/token.rs202
4 files changed, 3552 insertions, 0 deletions
diff --git a/src/comp/syntax/parse/eval.rs b/src/comp/syntax/parse/eval.rs
new file mode 100644
index 00000000000..89f6ac536c3
--- /dev/null
+++ b/src/comp/syntax/parse/eval.rs
@@ -0,0 +1,124 @@
+
+import std::vec;
+import std::str;
+import std::option;
+import std::option::some;
+import std::option::none;
+import syntax::ast;
+import syntax::parse::token;
+import syntax::parse::parser::parser;
+import syntax::parse::parser::new_parser;
+import syntax::parse::parser::parse_inner_attrs_and_next;
+import syntax::parse::parser::parse_mod_items;
+
+export eval_crate_directives_to_mod;
+export mode_parse;
+
+tag eval_mode { mode_depend; mode_parse; }
+
+type ctx =
+    @rec(parser p,
+         eval_mode mode,
+         mutable vec[str] deps,
+         parser::parse_sess sess,
+         mutable uint chpos,
+         ast::crate_cfg cfg);
+
+fn eval_crate_directives(ctx cx, vec[@ast::crate_directive] cdirs,
+                         str prefix, &mutable vec[@ast::view_item] view_items,
+                         &mutable vec[@ast::item] items) {
+    for (@ast::crate_directive sub_cdir in cdirs) {
+        eval_crate_directive(cx, sub_cdir, prefix, view_items, items);
+    }
+}
+
+fn eval_crate_directives_to_mod(ctx cx,
+                                vec[@ast::crate_directive] cdirs, str prefix)
+   -> ast::_mod {
+    let vec[@ast::view_item] view_items = [];
+    let vec[@ast::item] items = [];
+    eval_crate_directives(cx, cdirs, prefix, view_items, items);
+    ret rec(view_items=view_items, items=items);
+}
+
+fn eval_crate_directive_block(ctx cx, &ast::block blk, str prefix,
+                              &mutable vec[@ast::view_item] view_items,
+                              &mutable vec[@ast::item] items) {
+    for (@ast::stmt s in blk.node.stmts) {
+        alt (s.node) {
+            case (ast::stmt_crate_directive(?cdir)) {
+                eval_crate_directive(cx, cdir, prefix, view_items, items);
+            }
+            case (_) {
+                codemap::emit_warning
+                   (some(s.span), "unsupported stmt in crate-directive block",
+                    cx.sess.cm);
+            }
+        }
+    }
+}
+
+fn eval_crate_directive(ctx cx, @ast::crate_directive cdir, str prefix,
+                        &mutable vec[@ast::view_item] view_items,
+                        &mutable vec[@ast::item] items) {
+    alt (cdir.node) {
+        case (ast::cdir_src_mod(?id, ?file_opt, ?attrs)) {
+            auto file_path = id + ".rs";
+            alt (file_opt) {
+                case (some(?f)) { file_path = f; }
+                case (none) { }
+            }
+            auto full_path = if (std::fs::path_is_absolute(file_path)) {
+                file_path
+            } else {
+                prefix + std::fs::path_sep() + file_path
+            };
+            if (cx.mode == mode_depend) { cx.deps += [full_path]; ret; }
+            auto p0 =
+                new_parser(cx.sess, cx.cfg, full_path, cx.chpos);
+            auto inner_attrs = parse_inner_attrs_and_next(p0);
+            auto mod_attrs = attrs + inner_attrs._0;
+            auto first_item_outer_attrs = inner_attrs._1;
+            auto m0 = parse_mod_items(p0, token::EOF, first_item_outer_attrs);
+
+            auto i = syntax::parse::parser::mk_item
+                (p0, cdir.span.lo, cdir.span.hi, id, ast::item_mod(m0),
+                 mod_attrs);
+            // Thread defids and chpos through the parsers
+            cx.chpos = p0.get_chpos();
+            vec::push[@ast::item](items, i);
+        }
+        case (ast::cdir_dir_mod(?id, ?dir_opt, ?cdirs, ?attrs)) {
+            auto path = id;
+            alt (dir_opt) { case (some(?d)) { path = d; } case (none) { } }
+            auto full_path = if (std::fs::path_is_absolute(path)) {
+                path
+            } else {
+                prefix + std::fs::path_sep() + path
+            };
+            auto m0 = eval_crate_directives_to_mod(cx, cdirs, full_path);
+            auto i = @rec(ident=id,
+                          attrs=attrs,
+                          id=cx.sess.next_id,
+                          node=ast::item_mod(m0),
+                          span=cdir.span);
+            cx.sess.next_id += 1;
+            vec::push[@ast::item](items, i);
+        }
+        case (ast::cdir_view_item(?vi)) {
+            vec::push[@ast::view_item](view_items, vi);
+        }
+        case (ast::cdir_syntax(?pth)) { }
+        case (ast::cdir_auth(?pth, ?eff)) { }
+    }
+}
+//
+// Local Variables:
+// mode: rust
+// fill-column: 78;
+// indent-tabs-mode: nil
+// c-basic-offset: 4
+// buffer-file-coding-system: utf-8-unix
+// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
+// End:
+//
diff --git a/src/comp/syntax/parse/lexer.rs b/src/comp/syntax/parse/lexer.rs
new file mode 100644
index 00000000000..ffc83bcbbea
--- /dev/null
+++ b/src/comp/syntax/parse/lexer.rs
@@ -0,0 +1,758 @@
+
+import std::io;
+import std::str;
+import std::vec;
+import std::int;
+import std::map;
+import std::map::hashmap;
+import std::option;
+import std::option::some;
+import std::option::none;
+import util::interner;
+import util::interner::intern;
+import codemap;
+
+type reader =
+    obj {
+        fn is_eof() -> bool ;
+        fn curr() -> char ;
+        fn next() -> char ;
+        fn init() ;
+        fn bump() ;
+        fn mark() ;
+        fn get_mark_chpos() -> uint ;
+        fn get_mark_str() -> str ;
+        fn get_interner() -> @interner::interner[str] ;
+        fn get_chpos() -> uint ;
+        fn get_col() -> uint ;
+        fn get_filemap() -> codemap::filemap ;
+        fn err(str) ;
+    };
+
+fn new_reader(&codemap::codemap cm, io::reader rdr, codemap::filemap filemap,
+              @interner::interner[str] itr) -> reader {
+    obj reader(codemap::codemap cm,
+               str file,
+               uint len,
+               mutable uint col,
+               mutable uint pos,
+               mutable char ch,
+               mutable uint mark_chpos,
+               mutable uint chpos,
+               mutable vec[str] strs,
+               codemap::filemap fm,
+               @interner::interner[str] itr) {
+        fn is_eof() -> bool { ret ch == -1 as char; }
+        fn mark() { mark_chpos = chpos; }
+        fn get_mark_str() -> str { ret str::slice(file, mark_chpos, chpos); }
+        fn get_mark_chpos() -> uint { ret mark_chpos; }
+        fn get_chpos() -> uint { ret chpos; }
+        fn curr() -> char { ret ch; }
+        fn next() -> char {
+            if (pos < len) {
+                ret str::char_at(file, pos);
+            } else { ret -1 as char; }
+        }
+        fn init() {
+            if (pos < len) {
+                auto next = str::char_range_at(file, pos);
+                pos = next._1;
+                ch = next._0;
+            }
+        }
+        fn bump() {
+            if (pos < len) {
+                col += 1u;
+                chpos += 1u;
+                if (ch == '\n') { codemap::next_line(fm, chpos); col = 0u; }
+                auto next = str::char_range_at(file, pos);
+                pos = next._1;
+                ch = next._0;
+            } else { ch = -1 as char; }
+        }
+        fn get_interner() -> @interner::interner[str] { ret itr; }
+        fn get_col() -> uint { ret col; }
+        fn get_filemap() -> codemap::filemap { ret fm; }
+        fn err(str m) {
+            codemap::emit_error(some(rec(lo=chpos, hi=chpos)), m, cm);
+        }
+    }
+    auto file = str::unsafe_from_bytes(rdr.read_whole_stream());
+    let vec[str] strs = [];
+    auto rd =
+        reader(cm, file, str::byte_len(file), 0u, 0u, -1 as char,
+               filemap.start_pos, filemap.start_pos, strs, filemap, itr);
+    rd.init();
+    ret rd;
+}
+
+fn dec_digit_val(char c) -> int { ret (c as int) - ('0' as int); }
+
+fn hex_digit_val(char c) -> int {
+    if (in_range(c, '0', '9')) { ret (c as int) - ('0' as int); }
+    if (in_range(c, 'a', 'f')) { ret (c as int) - ('a' as int) + 10; }
+    if (in_range(c, 'A', 'F')) { ret (c as int) - ('A' as int) + 10; }
+    fail;
+}
+
+fn bin_digit_value(char c) -> int { if (c == '0') { ret 0; } ret 1; }
+
+fn is_whitespace(char c) -> bool {
+    ret c == ' ' || c == '\t' || c == '\r' || c == '\n';
+}
+
+fn may_begin_ident(char c) -> bool { ret is_alpha(c) || c == '_'; }
+
+fn in_range(char c, char lo, char hi) -> bool { ret lo <= c && c <= hi; }
+
+fn is_alpha(char c) -> bool {
+    ret in_range(c, 'a', 'z') || in_range(c, 'A', 'Z');
+}
+
+fn is_dec_digit(char c) -> bool { ret in_range(c, '0', '9'); }
+
+fn is_alnum(char c) -> bool { ret is_alpha(c) || is_dec_digit(c); }
+
+fn is_hex_digit(char c) -> bool {
+    ret in_range(c, '0', '9') || in_range(c, 'a', 'f') ||
+            in_range(c, 'A', 'F');
+}
+
+fn is_bin_digit(char c) -> bool { ret c == '0' || c == '1'; }
+
+fn consume_whitespace_and_comments(&reader rdr) {
+    while (is_whitespace(rdr.curr())) { rdr.bump(); }
+    be consume_any_line_comment(rdr);
+}
+
+fn consume_any_line_comment(&reader rdr) {
+    if (rdr.curr() == '/') {
+        alt (rdr.next()) {
+            case ('/') {
+                while (rdr.curr() != '\n' && !rdr.is_eof()) { rdr.bump(); }
+                // Restart whitespace munch.
+
+                be consume_whitespace_and_comments(rdr);
+            }
+            case ('*') {
+                rdr.bump();
+                rdr.bump();
+                be consume_block_comment(rdr);
+            }
+            case (_) { ret; }
+        }
+    }
+}
+
+fn consume_block_comment(&reader rdr) {
+    let int level = 1;
+    while (level > 0) {
+        if (rdr.is_eof()) { rdr.err("unterminated block comment"); fail; }
+        if (rdr.curr() == '/' && rdr.next() == '*') {
+            rdr.bump();
+            rdr.bump();
+            level += 1;
+        } else {
+            if (rdr.curr() == '*' && rdr.next() == '/') {
+                rdr.bump();
+                rdr.bump();
+                level -= 1;
+            } else { rdr.bump(); }
+        }
+    }
+    // restart whitespace munch.
+
+    be consume_whitespace_and_comments(rdr);
+}
+
+fn digits_to_string(str s) -> int {
+    let int accum_int = 0;
+    for (u8 c in s) {
+        accum_int *= 10;
+        accum_int += dec_digit_val(c as char);
+    }
+    ret accum_int;
+}
+
+fn scan_exponent(&reader rdr) -> option::t[str] {
+    auto c = rdr.curr();
+    auto rslt = "";
+    if (c == 'e' || c == 'E') {
+        rslt += str::from_bytes([c as u8]);
+        rdr.bump();
+        c = rdr.curr();
+        if (c == '-' || c == '+') {
+            rslt += str::from_bytes([c as u8]);
+            rdr.bump();
+        }
+        auto exponent = scan_dec_digits(rdr);
+        if (str::byte_len(exponent) > 0u) {
+            ret some(rslt + exponent);
+        } else { rdr.err("scan_exponent: bad fp literal"); fail; }
+    } else { ret none[str]; }
+}
+
+fn scan_dec_digits(&reader rdr) -> str {
+    auto c = rdr.curr();
+    let str rslt = "";
+    while (is_dec_digit(c) || c == '_') {
+        if (c != '_') { rslt += str::from_bytes([c as u8]); }
+        rdr.bump();
+        c = rdr.curr();
+    }
+    ret rslt;
+}
+
+fn scan_number(char c, &reader rdr) -> token::token {
+    auto accum_int = 0;
+    let str dec_str = "";
+    let bool is_dec_integer = false;
+    auto n = rdr.next();
+    if (c == '0' && n == 'x') {
+        rdr.bump();
+        rdr.bump();
+        c = rdr.curr();
+        while (is_hex_digit(c) || c == '_') {
+            if (c != '_') { accum_int *= 16; accum_int += hex_digit_val(c); }
+            rdr.bump();
+            c = rdr.curr();
+        }
+    } else if (c == '0' && n == 'b') {
+        rdr.bump();
+        rdr.bump();
+        c = rdr.curr();
+        while (is_bin_digit(c) || c == '_') {
+            if (c != '_') { accum_int *= 2; accum_int += bin_digit_value(c); }
+            rdr.bump();
+            c = rdr.curr();
+        }
+    } else { dec_str = scan_dec_digits(rdr); is_dec_integer = true; }
+    if (is_dec_integer) { accum_int = digits_to_string(dec_str); }
+    c = rdr.curr();
+    n = rdr.next();
+    if (c == 'u' || c == 'i') {
+        let bool signed = c == 'i';
+        rdr.bump();
+        c = rdr.curr();
+        if (c == '8') {
+            rdr.bump();
+            if (signed) {
+                ret token::LIT_MACH_INT(ast::ty_i8, accum_int);
+            } else { ret token::LIT_MACH_INT(ast::ty_u8, accum_int); }
+        }
+        n = rdr.next();
+        if (c == '1' && n == '6') {
+            rdr.bump();
+            rdr.bump();
+            if (signed) {
+                ret token::LIT_MACH_INT(ast::ty_i16, accum_int);
+            } else { ret token::LIT_MACH_INT(ast::ty_u16, accum_int); }
+        }
+        if (c == '3' && n == '2') {
+            rdr.bump();
+            rdr.bump();
+            if (signed) {
+                ret token::LIT_MACH_INT(ast::ty_i32, accum_int);
+            } else { ret token::LIT_MACH_INT(ast::ty_u32, accum_int); }
+        }
+        if (c == '6' && n == '4') {
+            rdr.bump();
+            rdr.bump();
+            if (signed) {
+                ret token::LIT_MACH_INT(ast::ty_i64, accum_int);
+            } else { ret token::LIT_MACH_INT(ast::ty_u64, accum_int); }
+        }
+        if (signed) {
+            ret token::LIT_INT(accum_int);
+        } else {
+            // FIXME: should cast in the target bit-width.
+
+            ret token::LIT_UINT(accum_int as uint);
+        }
+    }
+    c = rdr.curr();
+    if (c == '.') {
+        // Parse a floating-point number.
+
+        rdr.bump();
+        auto dec_part = scan_dec_digits(rdr);
+        auto float_str = dec_str + "." + dec_part;
+        c = rdr.curr();
+        auto exponent_str = scan_exponent(rdr);
+        alt (exponent_str) {
+            case (some(?s)) { float_str += s; }
+            case (none) { }
+        }
+        c = rdr.curr();
+        if (c == 'f') {
+            rdr.bump();
+            c = rdr.curr();
+            n = rdr.next();
+            if (c == '3' && n == '2') {
+                rdr.bump();
+                rdr.bump();
+                ret token::LIT_MACH_FLOAT(ast::ty_f32,
+                                          intern(*rdr.get_interner(),
+                                                 float_str));
+            } else if (c == '6' && n == '4') {
+                rdr.bump();
+                rdr.bump();
+                ret token::LIT_MACH_FLOAT(ast::ty_f64,
+                                          intern(*rdr.get_interner(),
+                                                 float_str));
+                /* FIXME: if this is out of range for either a 32-bit or
+                   64-bit float, it won't be noticed till the back-end */
+
+            }
+        } else {
+            ret token::LIT_FLOAT(interner::intern[str](*rdr.get_interner(),
+                                                       float_str));
+        }
+    }
+    auto maybe_exponent = scan_exponent(rdr);
+    alt (maybe_exponent) {
+        case (some(?s)) {
+            ret token::LIT_FLOAT(interner::intern[str](*rdr.get_interner(),
+                                                       dec_str + s));
+        }
+        case (none) { ret token::LIT_INT(accum_int); }
+    }
+}
+
+fn scan_numeric_escape(&reader rdr, uint n_hex_digits) -> char {
+    auto accum_int = 0;
+    while (n_hex_digits != 0u) {
+        auto n = rdr.curr();
+        rdr.bump();
+        if (!is_hex_digit(n)) {
+            rdr.err(#fmt("illegal numeric character escape: %d", n as int));
+            fail;
+        }
+        accum_int *= 16;
+        accum_int += hex_digit_val(n);
+        n_hex_digits -= 1u;
+    }
+    ret accum_int as char;
+}
+
+fn next_token(&reader rdr) -> token::token {
+    auto accum_str = "";
+    consume_whitespace_and_comments(rdr);
+    if (rdr.is_eof()) { ret token::EOF; }
+    rdr.mark();
+    auto c = rdr.curr();
+    if (is_alpha(c) || c == '_') {
+        while (is_alnum(c) || c == '_') {
+            str::push_char(accum_str, c);
+            rdr.bump();
+            c = rdr.curr();
+        }
+        if (str::eq(accum_str, "_")) { ret token::UNDERSCORE; }
+        auto is_mod_name = c == ':' && rdr.next() == ':';
+        ret token::IDENT(interner::intern[str](*rdr.get_interner(),
+                                               accum_str), is_mod_name);
+    }
+    if (is_dec_digit(c)) { ret scan_number(c, rdr); }
+    fn binop(&reader rdr, token::binop op) -> token::token {
+        rdr.bump();
+        if (rdr.curr() == '=') {
+            rdr.bump();
+            ret token::BINOPEQ(op);
+        } else { ret token::BINOP(op); }
+    }
+    alt (c) {
+        case (
+             // One-byte tokens.
+             '?') {
+            rdr.bump();
+            ret token::QUES;
+        }
+        case (';') { rdr.bump(); ret token::SEMI; }
+        case (',') { rdr.bump(); ret token::COMMA; }
+        case ('.') { rdr.bump(); ret token::DOT; }
+        case ('(') { rdr.bump(); ret token::LPAREN; }
+        case (')') { rdr.bump(); ret token::RPAREN; }
+        case ('{') { rdr.bump(); ret token::LBRACE; }
+        case ('}') { rdr.bump(); ret token::RBRACE; }
+        case ('[') { rdr.bump(); ret token::LBRACKET; }
+        case (']') { rdr.bump(); ret token::RBRACKET; }
+        case ('@') { rdr.bump(); ret token::AT; }
+        case ('#') { rdr.bump(); ret token::POUND; }
+        case ('~') { rdr.bump(); ret token::TILDE; }
+        case (':') {
+            rdr.bump();
+            if (rdr.curr() == ':') {
+                rdr.bump();
+                ret token::MOD_SEP;
+            } else { ret token::COLON; }
+        }
+        case (
+             // Multi-byte tokens.
+             '=') {
+            rdr.bump();
+            if (rdr.curr() == '=') {
+                rdr.bump();
+                ret token::EQEQ;
+            } else { ret token::EQ; }
+        }
+        case ('!') {
+            rdr.bump();
+            if (rdr.curr() == '=') {
+                rdr.bump();
+                ret token::NE;
+            } else { ret token::NOT; }
+        }
+        case ('<') {
+            rdr.bump();
+            alt (rdr.curr()) {
+                case ('=') { rdr.bump(); ret token::LE; }
+                case ('<') { ret binop(rdr, token::LSL); }
+                case ('|') { rdr.bump(); ret token::SEND; }
+                case ('-') {
+                    rdr.bump();
+                    alt (rdr.curr()) {
+                        case ('>') { rdr.bump(); ret token::DARROW; }
+                        case (_) { ret token::LARROW; }
+                    }
+                }
+                case (_) { ret token::LT; }
+            }
+        }
+        case ('>') {
+            rdr.bump();
+            alt (rdr.curr()) {
+                case ('=') { rdr.bump(); ret token::GE; }
+                case ('>') {
+                    if (rdr.next() == '>') {
+                        rdr.bump();
+                        ret binop(rdr, token::ASR);
+                    } else { ret binop(rdr, token::LSR); }
+                }
+                case (_) { ret token::GT; }
+            }
+        }
+        case ('\'') {
+            rdr.bump();
+            auto c2 = rdr.curr();
+            rdr.bump();
+            if (c2 == '\\') {
+                auto escaped = rdr.curr();
+                rdr.bump();
+                alt (escaped) {
+                    case ('n') { c2 = '\n'; }
+                    case ('r') { c2 = '\r'; }
+                    case ('t') { c2 = '\t'; }
+                    case ('\\') { c2 = '\\'; }
+                    case ('\'') { c2 = '\''; }
+                    case ('x') { c2 = scan_numeric_escape(rdr, 2u); }
+                    case ('u') { c2 = scan_numeric_escape(rdr, 4u); }
+                    case ('U') { c2 = scan_numeric_escape(rdr, 8u); }
+                    case (?c2) {
+                        rdr.err(#fmt("unknown character escape: %d",
+                                     c2 as int));
+                        fail;
+                    }
+                }
+            }
+            if (rdr.curr() != '\'') {
+                rdr.err("unterminated character constant");
+                fail;
+            }
+            rdr.bump(); // advance curr past token
+
+            ret token::LIT_CHAR(c2);
+        }
+        case ('"') {
+            rdr.bump();
+            while (rdr.curr() != '"') {
+                auto ch = rdr.curr();
+                rdr.bump();
+                alt (ch) {
+                    case ('\\') {
+                        auto escaped = rdr.curr();
+                        rdr.bump();
+                        alt (escaped) {
+                            case ('n') {
+                                str::push_byte(accum_str, '\n' as u8);
+                            }
+                            case ('r') {
+                                str::push_byte(accum_str, '\r' as u8);
+                            }
+                            case ('t') {
+                                str::push_byte(accum_str, '\t' as u8);
+                            }
+                            case ('\\') {
+                                str::push_byte(accum_str, '\\' as u8);
+                            }
+                            case ('"') {
+                                str::push_byte(accum_str, '"' as u8);
+                            }
+                            case ('\n') { consume_whitespace(rdr); }
+                            case ('x') {
+                                str::push_char(accum_str,
+                                               scan_numeric_escape(rdr, 2u));
+                            }
+                            case ('u') {
+                                str::push_char(accum_str,
+                                               scan_numeric_escape(rdr, 4u));
+                            }
+                            case ('U') {
+                                str::push_char(accum_str,
+                                               scan_numeric_escape(rdr, 8u));
+                            }
+                            case (?c2) {
+                                rdr.err(#fmt("unknown string escape: %d",
+                                             c2 as int));
+                                fail;
+                            }
+                        }
+                    }
+                    case (_) { str::push_char(accum_str, ch); }
+                }
+            }
+            rdr.bump();
+            ret token::LIT_STR(interner::intern[str](*rdr.get_interner(),
+                                                     accum_str));
+        }
+        case ('-') {
+            if (rdr.next() == '>') {
+                rdr.bump();
+                rdr.bump();
+                ret token::RARROW;
+            } else { ret binop(rdr, token::MINUS); }
+        }
+        case ('&') {
+            if (rdr.next() == '&') {
+                rdr.bump();
+                rdr.bump();
+                ret token::ANDAND;
+            } else { ret binop(rdr, token::AND); }
+        }
+        case ('|') {
+            alt (rdr.next()) {
+                case ('|') { rdr.bump(); rdr.bump(); ret token::OROR; }
+                case ('>') { rdr.bump(); rdr.bump(); ret token::RECV; }
+                case (_) { ret binop(rdr, token::OR); }
+            }
+        }
+        case ('+') { ret binop(rdr, token::PLUS); }
+        case ('*') { ret binop(rdr, token::STAR); }
+        case ('/') { ret binop(rdr, token::SLASH); }
+        case ('^') { ret binop(rdr, token::CARET); }
+        case ('%') { ret binop(rdr, token::PERCENT); }
+        case (?c) {
+            rdr.err(#fmt("unkown start of token: %d", c as int));
+            fail;
+        }
+    }
+    fail;
+}
+
+tag cmnt_style {
+    isolated; // No code on either side of each line of the comment
+
+    trailing; // Code exists to the left of the comment
+
+    mixed; // Code before /* foo */ and after the comment
+
+    blank_line; // Just a manual blank linke "\n\n", for layout
+
+}
+
+type cmnt = rec(cmnt_style style, vec[str] lines, uint pos);
+
+fn read_to_eol(&reader rdr) -> str {
+    auto val = "";
+    while (rdr.curr() != '\n' && !rdr.is_eof()) {
+        str::push_char(val, rdr.curr());
+        rdr.bump();
+    }
+    ret val;
+}
+
+fn read_one_line_comment(&reader rdr) -> str {
+    auto val = read_to_eol(rdr);
+    assert (val.(0) == '/' as u8 && val.(1) == '/' as u8);
+    ret val;
+}
+
+fn consume_whitespace(&reader rdr) {
+    while (is_whitespace(rdr.curr()) && !rdr.is_eof()) { rdr.bump(); }
+}
+
+fn consume_non_eol_whitespace(&reader rdr) {
+    while (is_whitespace(rdr.curr()) && rdr.curr() != '\n' && !rdr.is_eof()) {
+        rdr.bump();
+    }
+}
+
+fn consume_whitespace_counting_blank_lines(&reader rdr,
+                                           &mutable vec[cmnt] comments) {
+    while (is_whitespace(rdr.curr()) && !rdr.is_eof()) {
+        if (rdr.curr() == '\n' && rdr.next() == '\n') {
+            log ">>> blank-line comment";
+            let vec[str] v = [];
+            comments += [rec(style=blank_line, lines=v,
+                             pos=rdr.get_chpos())];
+        }
+        rdr.bump();
+    }
+}
+
+fn read_line_comments(&reader rdr, bool code_to_the_left) -> cmnt {
+    log ">>> line comments";
+    auto p = rdr.get_chpos();
+    let vec[str] lines = [];
+    while (rdr.curr() == '/' && rdr.next() == '/') {
+        auto line = read_one_line_comment(rdr);
+        log line;
+        lines += [line];
+        consume_non_eol_whitespace(rdr);
+    }
+    log "<<< line comments";
+    ret rec(style=if (code_to_the_left) { trailing } else { isolated },
+            lines=lines,
+            pos=p);
+}
+
+fn all_whitespace(&str s, uint begin, uint end) -> bool {
+    let uint i = begin;
+    while (i != end) {
+        if (!is_whitespace(s.(i) as char)) { ret false; }
+        i += 1u;
+    }
+    ret true;
+}
+
+fn trim_whitespace_prefix_and_push_line(&mutable vec[str] lines, &str s,
+                                        uint col) {
+    auto s1;
+    if (all_whitespace(s, 0u, col)) {
+        if (col < str::byte_len(s)) {
+            s1 = str::slice(s, col, str::byte_len(s));
+        } else { s1 = ""; }
+    } else { s1 = s; }
+    log "pushing line: " + s1;
+    lines += [s1];
+}
+
+fn read_block_comment(&reader rdr, bool code_to_the_left) -> cmnt {
+    log ">>> block comment";
+    auto p = rdr.get_chpos();
+    let vec[str] lines = [];
+    let uint col = rdr.get_col();
+    rdr.bump();
+    rdr.bump();
+    auto curr_line = "/*";
+    let int level = 1;
+    while (level > 0) {
+        log #fmt("=== block comment level %d", level);
+        if (rdr.is_eof()) { rdr.err("unterminated block comment"); fail; }
+        if (rdr.curr() == '\n') {
+            trim_whitespace_prefix_and_push_line(lines, curr_line, col);
+            curr_line = "";
+            rdr.bump();
+        } else {
+            str::push_char(curr_line, rdr.curr());
+            if (rdr.curr() == '/' && rdr.next() == '*') {
+                rdr.bump();
+                rdr.bump();
+                curr_line += "*";
+                level += 1;
+            } else {
+                if (rdr.curr() == '*' && rdr.next() == '/') {
+                    rdr.bump();
+                    rdr.bump();
+                    curr_line += "/";
+                    level -= 1;
+                } else { rdr.bump(); }
+            }
+        }
+    }
+    if (str::byte_len(curr_line) != 0u) {
+        trim_whitespace_prefix_and_push_line(lines, curr_line, col);
+    }
+    auto style = if (code_to_the_left) { trailing } else { isolated };
+    consume_non_eol_whitespace(rdr);
+    if (!rdr.is_eof() && rdr.curr() != '\n' && vec::len(lines) == 1u) {
+        style = mixed;
+    }
+    log "<<< block comment";
+    ret rec(style=style, lines=lines, pos=p);
+}
+
+fn peeking_at_comment(&reader rdr) -> bool {
+    ret rdr.curr() == '/' && rdr.next() == '/' ||
+            rdr.curr() == '/' && rdr.next() == '*';
+}
+
+fn consume_comment(&reader rdr, bool code_to_the_left,
+                   &mutable vec[cmnt] comments) {
+    log ">>> consume comment";
+    if (rdr.curr() == '/' && rdr.next() == '/') {
+        vec::push[cmnt](comments, read_line_comments(rdr, code_to_the_left));
+    } else if (rdr.curr() == '/' && rdr.next() == '*') {
+        vec::push[cmnt](comments, read_block_comment(rdr, code_to_the_left));
+    } else { fail; }
+    log "<<< consume comment";
+}
+
+fn is_lit(&token::token t) -> bool {
+    ret alt (t) {
+            case (token::LIT_INT(_)) { true }
+            case (token::LIT_UINT(_)) { true }
+            case (token::LIT_MACH_INT(_, _)) { true }
+            case (token::LIT_FLOAT(_)) { true }
+            case (token::LIT_MACH_FLOAT(_, _)) { true }
+            case (token::LIT_STR(_)) { true }
+            case (token::LIT_CHAR(_)) { true }
+            case (token::LIT_BOOL(_)) { true }
+            case (_) { false }
+        }
+}
+
+type lit = rec(str lit, uint pos);
+
+fn gather_comments_and_literals(&codemap::codemap cm, str path) ->
+   rec(vec[cmnt] cmnts, vec[lit] lits) {
+    auto srdr = io::file_reader(path);
+    auto itr = @interner::mk[str](str::hash, str::eq);
+    auto rdr = new_reader(cm, srdr, codemap::new_filemap(path, 0u), itr);
+    let vec[cmnt] comments = [];
+    let vec[lit] literals = [];
+    let bool first_read = true;
+    while (!rdr.is_eof()) {
+        while (true) {
+            auto code_to_the_left = !first_read;
+            consume_non_eol_whitespace(rdr);
+            if (rdr.curr() == '\n') {
+                code_to_the_left = false;
+                consume_whitespace_counting_blank_lines(rdr, comments);
+            }
+            while (peeking_at_comment(rdr)) {
+                consume_comment(rdr, code_to_the_left, comments);
+                consume_whitespace_counting_blank_lines(rdr, comments);
+            }
+            break;
+        }
+        auto tok = next_token(rdr);
+        if (is_lit(tok)) {
+            vec::push[lit](literals,
+                           rec(lit=rdr.get_mark_str(),
+                               pos=rdr.get_mark_chpos()));
+        }
+        log "tok: " + token::to_str(rdr, tok);
+        first_read = false;
+    }
+    ret rec(cmnts=comments, lits=literals);
+}
+//
+// Local Variables:
+// mode: rust
+// fill-column: 78;
+// indent-tabs-mode: nil
+// c-basic-offset: 4
+// buffer-file-coding-system: utf-8-unix
+// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
+// End:
+//
diff --git a/src/comp/syntax/parse/parser.rs b/src/comp/syntax/parse/parser.rs
new file mode 100644
index 00000000000..48213c1f1ac
--- /dev/null
+++ b/src/comp/syntax/parse/parser.rs
@@ -0,0 +1,2468 @@
+
+import std::io;
+import std::vec;
+import std::str;
+import std::option;
+import std::option::some;
+import std::option::none;
+import std::either;
+import std::either::left;
+import std::either::right;
+import std::map::hashmap;
+import token::can_begin_expr;
+import ex=ext::base;
+import codemap::span;
+import _std::new_str_hash;
+import util::interner;
+
+tag restriction { UNRESTRICTED; RESTRICT_NO_CALL_EXPRS; }
+
+tag file_type { CRATE_FILE; SOURCE_FILE; }
+
+tag ty_or_bang { a_ty(@ast::ty); a_bang; }
+
+type parse_sess = @rec(codemap::codemap cm,
+                       mutable ast::node_id next_id);
+
+fn next_node_id(&parse_sess sess) -> ast::node_id {
+    auto rv = sess.next_id;
+    sess.next_id += 1;
+    ret rv;
+}
+
+type parser =
+    obj {
+        fn peek() -> token::token ;
+        fn bump() ;
+        fn fatal(str) -> !  ;
+        fn warn(str);
+        fn restrict(restriction) ;
+        fn get_restriction() -> restriction ;
+        fn get_file_type() -> file_type ;
+        fn get_cfg() -> ast::crate_cfg;
+        fn get_span() -> span ;
+        fn get_lo_pos() -> uint ;
+        fn get_hi_pos() -> uint ;
+        fn get_last_lo_pos() -> uint ;
+        fn get_prec_table() -> vec[op_spec] ;
+        fn get_str(token::str_num) -> str ;
+        fn get_reader() -> lexer::reader ;
+        fn get_filemap() -> codemap::filemap ;
+        fn get_bad_expr_words() -> hashmap[str, ()] ;
+        fn get_syntax_expanders() -> hashmap[str, ex::syntax_extension] ;
+        fn get_chpos() -> uint ;
+        fn get_id() -> ast::node_id ;
+        fn get_sess() -> parse_sess;
+    };
+
+fn new_parser(parse_sess sess, ast::crate_cfg cfg,
+              str path, uint pos) -> parser {
+    obj stdio_parser(parse_sess sess,
+                     ast::crate_cfg cfg,
+                     file_type ftype,
+                     mutable token::token tok,
+                     mutable uint lo,
+                     mutable uint hi,
+                     mutable uint last_lo,
+                     mutable restriction restr,
+                     lexer::reader rdr,
+                     vec[op_spec] precs,
+                     hashmap[str, ()] bad_words,
+                     hashmap[str, ex::syntax_extension] syntax_expanders) {
+        fn peek() -> token::token { ret tok; }
+        fn bump() {
+            // log rdr.get_filename()
+            //   + ":" + common::istr(lo.line as int);
+
+            last_lo = lo;
+            tok = lexer::next_token(rdr);
+            lo = rdr.get_mark_chpos();
+            hi = rdr.get_chpos();
+        }
+        fn fatal(str m) -> ! {
+            codemap::emit_error(some(self.get_span()), m, sess.cm);
+            fail;
+        }
+        fn warn(str m) {
+            codemap::emit_warning(some(self.get_span()), m, sess.cm);
+        }
+        fn restrict(restriction r) { restr = r; }
+        fn get_restriction() -> restriction { ret restr; }
+        fn get_span() -> span { ret rec(lo=lo, hi=hi); }
+        fn get_lo_pos() -> uint { ret lo; }
+        fn get_hi_pos() -> uint { ret hi; }
+        fn get_last_lo_pos() -> uint { ret last_lo; }
+        fn get_file_type() -> file_type { ret ftype; }
+        fn get_cfg() -> ast::crate_cfg { ret cfg; }
+        fn get_prec_table() -> vec[op_spec] { ret precs; }
+        fn get_str(token::str_num i) -> str {
+            ret interner::get(*rdr.get_interner(), i);
+        }
+        fn get_reader() -> lexer::reader { ret rdr; }
+        fn get_filemap() -> codemap::filemap { ret rdr.get_filemap(); }
+        fn get_bad_expr_words() -> hashmap[str, ()] { ret bad_words; }
+        fn get_syntax_expanders() -> hashmap[str, ex::syntax_extension] {
+            ret syntax_expanders;
+        }
+        fn get_chpos() -> uint { ret rdr.get_chpos(); }
+        fn get_id() -> ast::node_id { ret next_node_id(sess); }
+        fn get_sess() -> parse_sess { ret sess; }
+    }
+
+    auto ftype = SOURCE_FILE;
+    if (str::ends_with(path, ".rc")) { ftype = CRATE_FILE; }
+    auto srdr = io::file_reader(path);
+    auto filemap = codemap::new_filemap(path, pos);
+    vec::push(sess.cm.files, filemap);
+    auto itr = @interner::mk(str::hash, str::eq);
+    auto rdr = lexer::new_reader(sess.cm, srdr, filemap, itr);
+    // Make sure npos points at first actual token:
+
+    lexer::consume_whitespace_and_comments(rdr);
+    auto npos = rdr.get_chpos();
+    ret stdio_parser(sess, cfg, ftype, lexer::next_token(rdr),
+                     npos, npos, npos, UNRESTRICTED, rdr,
+                     prec_table(), bad_expr_word_table(),
+                     ex::syntax_expander_table());
+}
+
+// These are the words that shouldn't be allowed as value identifiers,
+// because, if used at the start of a line, they will cause the line to be
+// interpreted as a specific kind of statement, which would be confusing.
+fn bad_expr_word_table() -> hashmap[str, ()] {
+    auto words = new_str_hash();
+    words.insert("mod", ());
+    words.insert("if", ());
+    words.insert("else", ());
+    words.insert("while", ());
+    words.insert("do", ());
+    words.insert("alt", ());
+    words.insert("for", ());
+    words.insert("break", ());
+    words.insert("cont", ());
+    words.insert("put", ());
+    words.insert("ret", ());
+    words.insert("be", ());
+    words.insert("fail", ());
+    words.insert("type", ());
+    words.insert("resource", ());
+    words.insert("check", ());
+    words.insert("assert", ());
+    words.insert("claim", ());
+    words.insert("prove", ());
+    words.insert("state", ());
+    words.insert("gc", ());
+    words.insert("native", ());
+    words.insert("auto", ());
+    words.insert("fn", ());
+    words.insert("pred", ());
+    words.insert("iter", ());
+    words.insert("import", ());
+    words.insert("export", ());
+    words.insert("let", ());
+    words.insert("const", ());
+    words.insert("log", ());
+    words.insert("log_err", ());
+    words.insert("tag", ());
+    words.insert("obj", ());
+    ret words;
+}
+
+fn unexpected(&parser p, token::token t) -> ! {
+    let str s = "unexpected token: ";
+    s += token::to_str(p.get_reader(), t);
+    p.fatal(s);
+}
+
+fn expect(&parser p, token::token t) {
+    if (p.peek() == t) {
+        p.bump();
+    } else {
+        let str s = "expecting ";
+        s += token::to_str(p.get_reader(), t);
+        s += ", found ";
+        s += token::to_str(p.get_reader(), p.peek());
+        p.fatal(s);
+    }
+}
+
+fn spanned[T](uint lo, uint hi, &T node) -> ast::spanned[T] {
+    ret rec(node=node, span=rec(lo=lo, hi=hi));
+}
+
+fn parse_ident(&parser p) -> ast::ident {
+    alt (p.peek()) {
+        case (token::IDENT(?i, _)) { p.bump(); ret p.get_str(i); }
+        case (_) { p.fatal("expecting ident"); fail; }
+    }
+}
+
+fn parse_value_ident(&parser p) -> ast::ident {
+    check_bad_word(p);
+    ret parse_ident(p);
+}
+
+fn is_word(&parser p, &str word) -> bool {
+    ret alt (p.peek()) {
+            case (token::IDENT(?sid, false)) { str::eq(word, p.get_str(sid)) }
+            case (_) { false }
+        };
+}
+
+fn eat_word(&parser p, &str word) -> bool {
+    alt (p.peek()) {
+        case (token::IDENT(?sid, false)) {
+            if (str::eq(word, p.get_str(sid))) {
+                p.bump();
+                ret true;
+            } else { ret false; }
+        }
+        case (_) { ret false; }
+    }
+}
+
+fn expect_word(&parser p, &str word) {
+    if (!eat_word(p, word)) {
+        p.fatal("expecting " + word + ", found " +
+                  token::to_str(p.get_reader(), p.peek()));
+    }
+}
+
+fn check_bad_word(&parser p) {
+    alt (p.peek()) {
+        case (token::IDENT(?sid, false)) {
+            auto w = p.get_str(sid);
+            if (p.get_bad_expr_words().contains_key(w)) {
+                p.fatal("found " + w + " in expression position");
+            }
+        }
+        case (_) { }
+    }
+}
+
+fn parse_ty_fn(ast::proto proto, &parser p, uint lo) -> ast::ty_ {
+    fn parse_fn_input_ty(&parser p) -> ast::ty_arg {
+        auto lo = p.get_lo_pos();
+        auto mode = ast::val;
+        if (p.peek() == token::BINOP(token::AND)) {
+            p.bump();
+            mode = ast::alias(eat_word(p, "mutable"));
+        }
+        auto t = parse_ty(p);
+        alt (p.peek()) {
+            case (token::IDENT(_, _)) { p.bump();/* ignore param name */ }
+            case (_) {/* no param name present */ }
+        }
+        ret spanned(lo, t.span.hi, rec(mode=mode, ty=t));
+    }
+    auto lo = p.get_lo_pos();
+    auto inputs =
+        parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                  parse_fn_input_ty, p);
+    auto constrs = parse_constrs([], p);
+    let @ast::ty output;
+    auto cf = ast::return;
+    if (p.peek() == token::RARROW) {
+        p.bump();
+        auto tmp = parse_ty_or_bang(p);
+        alt (tmp) {
+            case (a_ty(?t)) { output = t; }
+            case (a_bang) {
+                output = @spanned(lo, inputs.span.hi, ast::ty_bot);
+                cf = ast::noreturn;
+            }
+        }
+    } else { output = @spanned(lo, inputs.span.hi, ast::ty_nil); }
+    ret ast::ty_fn(proto, inputs.node, output, cf, constrs.node);
+}
+
+fn parse_proto(&parser p) -> ast::proto {
+    if (eat_word(p, "iter")) {
+        ret ast::proto_iter;
+    } else if (eat_word(p, "fn")) {
+        ret ast::proto_fn;
+    } else if (eat_word(p, "pred")) {
+        ret ast::proto_fn;
+    } else { unexpected(p, p.peek()); }
+}
+
+fn parse_ty_obj(&parser p, &mutable uint hi) -> ast::ty_ {
+    fn parse_method_sig(&parser p) -> ast::ty_method {
+        auto flo = p.get_lo_pos();
+        let ast::proto proto = parse_proto(p);
+        auto ident = parse_value_ident(p);
+        auto f = parse_ty_fn(proto, p, flo);
+        expect(p, token::SEMI);
+        alt (f) {
+            case (ast::ty_fn(?proto, ?inputs, ?output, ?cf, ?constrs)) {
+                ret spanned(flo, output.span.hi,
+                            rec(proto=proto,
+                                ident=ident,
+                                inputs=inputs,
+                                output=output,
+                                cf=cf,
+                                constrs=constrs));
+            }
+        }
+        fail;
+    }
+    auto f = parse_method_sig;
+    auto meths = parse_seq(token::LBRACE, token::RBRACE, none, f, p);
+    hi = meths.span.hi;
+    ret ast::ty_obj(meths.node);
+}
+
+fn parse_mt(&parser p) -> ast::mt {
+    auto mut = parse_mutability(p);
+    auto t = parse_ty(p);
+    ret rec(ty=t, mut=mut);
+}
+
+fn parse_ty_field(&parser p) -> ast::ty_field {
+    auto lo = p.get_lo_pos();
+    auto mt = parse_mt(p);
+    auto id = parse_ident(p);
+    ret spanned(lo, mt.ty.span.hi, rec(ident=id, mt=mt));
+}
+
+
+// if i is the jth ident in args, return j
+// otherwise, fail
+fn ident_index(&parser p, &vec[ast::arg] args, &ast::ident i) -> uint {
+    auto j = 0u;
+    for (ast::arg a in args) { if (a.ident == i) { ret j; } j += 1u; }
+    p.fatal("Unbound variable " + i + " in constraint arg");
+}
+
+fn parse_constr_arg(vec[ast::arg] args, &parser p) -> @ast::constr_arg {
+    auto sp = p.get_span();
+    auto carg = ast::carg_base;
+    if (p.peek() == token::BINOP(token::STAR)) {
+        p.bump();
+    } else {
+        let ast::ident i = parse_value_ident(p);
+        carg = ast::carg_ident(ident_index(p, args, i));
+    }
+    ret @rec(node=carg, span=sp);
+}
+
+fn parse_ty_constr(&vec[ast::arg] fn_args, &parser p) -> @ast::constr {
+    auto lo = p.get_lo_pos();
+    auto path = parse_path(p);
+    auto pf = bind parse_constr_arg(fn_args, _);
+    let rec(vec[@ast::constr_arg] node, span span) args =
+        parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA), pf, p);
+    // FIXME fix the def_id
+
+    ret @spanned(lo, args.span.hi,
+                 rec(path=path, args=args.node, id=p.get_id()));
+}
+
+
+// Use the args list to translate each bound variable 
+// mentioned in a constraint to an arg index.
+// Seems weird to do this in the parser, but I'm not sure how else to.
+fn parse_constrs(&vec[ast::arg] args, &parser p) ->
+    ast::spanned[vec[@ast::constr]] {
+    auto lo = p.get_lo_pos();
+    auto hi = p.get_hi_pos();
+    let vec[@ast::constr] constrs = [];
+    if (p.peek() == token::COLON) {
+        p.bump();
+        while (true) {
+            auto constr = parse_ty_constr(args, p);
+            hi = constr.span.hi;
+            vec::push(constrs, constr);
+            if (p.peek() == token::COMMA) { p.bump(); } else { break; }
+        }
+    }
+    ret spanned(lo, hi, constrs);
+}
+
+fn parse_ty_constrs(@ast::ty t, &parser p) -> @ast::ty {
+    if (p.peek() == token::COLON) {
+        auto constrs = parse_constrs([], p);
+        ret @spanned(t.span.lo, constrs.span.hi,
+                     ast::ty_constr(t, constrs.node));
+    }
+    ret t;
+}
+
+fn parse_ty_postfix(@ast::ty orig_t, &parser p) -> @ast::ty {
+    auto lo = p.get_lo_pos();
+    if (p.peek() == token::LBRACKET) {
+        p.bump();
+
+        auto mut;
+        if (eat_word(p, "mutable")) {
+            if (p.peek() == token::QUES) {
+                p.bump();
+                mut = ast::maybe_mut;
+            } else {
+                mut = ast::mut;
+            }
+        } else {
+            mut = ast::imm;
+        }
+
+        if (mut == ast::imm && p.peek() != token::RBRACKET) {
+            // This is explicit type parameter instantiation.
+            auto seq = parse_seq_to_end(token::RBRACKET, some(token::COMMA),
+                                        parse_ty, p);
+            alt (orig_t.node) {
+                case (ast::ty_path(?pth, ?ann)) {
+                    auto hi = p.get_hi_pos();
+                    ret @spanned(lo, hi,
+                                 ast::ty_path(spanned(lo, hi,
+                                              rec(idents=pth.node.idents,
+                                                  types=seq)),
+                                              ann));
+                }
+                case (_) {
+                    p.fatal("type parameter instantiation only allowed for " +
+                          "paths");
+                }
+            }
+        }
+
+        expect(p, token::RBRACKET);
+        auto hi = p.get_hi_pos();
+        auto t = ast::ty_ivec(rec(ty=orig_t, mut=mut));
+        ret parse_ty_postfix(@spanned(lo, hi, t), p);
+    }
+    ret parse_ty_constrs(orig_t, p);
+}
+
+fn parse_ty_or_bang(&parser p) -> ty_or_bang {
+    alt (p.peek()) {
+        case (token::NOT) { p.bump(); ret a_bang; }
+        case (_) { ret a_ty(parse_ty(p)); }
+    }
+}
+
+fn parse_ty(&parser p) -> @ast::ty {
+    auto lo = p.get_lo_pos();
+    auto hi = lo;
+    let ast::ty_ t;
+    // FIXME: do something with this
+
+    parse_layer(p);
+    if (eat_word(p, "bool")) {
+        t = ast::ty_bool;
+    } else if (eat_word(p, "int")) {
+        t = ast::ty_int;
+    } else if (eat_word(p, "uint")) {
+        t = ast::ty_uint;
+    } else if (eat_word(p, "float")) {
+        t = ast::ty_float;
+    } else if (eat_word(p, "str")) {
+        t = ast::ty_str;
+    } else if (eat_word(p, "istr")) {
+        t = ast::ty_istr;
+    } else if (eat_word(p, "char")) {
+        t = ast::ty_char;
+    } else if (eat_word(p, "task")) {
+        t = ast::ty_task;
+    } else if (eat_word(p, "i8")) {
+        t = ast::ty_machine(ast::ty_i8);
+    } else if (eat_word(p, "i16")) {
+        t = ast::ty_machine(ast::ty_i16);
+    } else if (eat_word(p, "i32")) {
+        t = ast::ty_machine(ast::ty_i32);
+    } else if (eat_word(p, "i64")) {
+        t = ast::ty_machine(ast::ty_i64);
+    } else if (eat_word(p, "u8")) {
+        t = ast::ty_machine(ast::ty_u8);
+    } else if (eat_word(p, "u16")) {
+        t = ast::ty_machine(ast::ty_u16);
+    } else if (eat_word(p, "u32")) {
+        t = ast::ty_machine(ast::ty_u32);
+    } else if (eat_word(p, "u64")) {
+        t = ast::ty_machine(ast::ty_u64);
+    } else if (eat_word(p, "f32")) {
+        t = ast::ty_machine(ast::ty_f32);
+    } else if (eat_word(p, "f64")) {
+        t = ast::ty_machine(ast::ty_f64);
+    } else if (p.peek() == token::LPAREN) {
+        p.bump();
+        alt (p.peek()) {
+            case (token::RPAREN) {
+                hi = p.get_hi_pos();
+                p.bump();
+                t = ast::ty_nil;
+            }
+            case (_) {
+                t = parse_ty(p).node;
+                hi = p.get_hi_pos();
+                expect(p, token::RPAREN);
+            }
+        }
+    } else if (p.peek() == token::AT) {
+        p.bump();
+        auto mt = parse_mt(p);
+        hi = mt.ty.span.hi;
+        t = ast::ty_box(mt);
+    } else if (p.peek() == token::BINOP(token::STAR)) {
+        p.bump();
+        auto mt = parse_mt(p);
+        hi = mt.ty.span.hi;
+        t = ast::ty_ptr(mt);
+    } else if (eat_word(p, "vec")) {
+        expect(p, token::LBRACKET);
+        t = ast::ty_vec(parse_mt(p));
+        hi = p.get_hi_pos();
+        expect(p, token::RBRACKET);
+    } else if (eat_word(p, "tup")) {
+        auto elems =
+            parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                      parse_mt, p);
+        hi = elems.span.hi;
+        t = ast::ty_tup(elems.node);
+    } else if (eat_word(p, "rec")) {
+        auto elems =
+            parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                      parse_ty_field, p);
+        hi = elems.span.hi;
+        t = ast::ty_rec(elems.node);
+    } else if (eat_word(p, "fn")) {
+        auto flo = p.get_last_lo_pos();
+        t = parse_ty_fn(ast::proto_fn, p, flo);
+        alt (t) { case (ast::ty_fn(_, _, ?out, _, _)) { hi = out.span.hi; } }
+    } else if (eat_word(p, "iter")) {
+        auto flo = p.get_last_lo_pos();
+        t = parse_ty_fn(ast::proto_iter, p, flo);
+        alt (t) { case (ast::ty_fn(_, _, ?out, _, _)) { hi = out.span.hi; } }
+    } else if (eat_word(p, "obj")) {
+        t = parse_ty_obj(p, hi);
+    } else if (eat_word(p, "port")) {
+        expect(p, token::LBRACKET);
+        t = ast::ty_port(parse_ty(p));
+        hi = p.get_hi_pos();
+        expect(p, token::RBRACKET);
+    } else if (eat_word(p, "chan")) {
+        expect(p, token::LBRACKET);
+        t = ast::ty_chan(parse_ty(p));
+        hi = p.get_hi_pos();
+        expect(p, token::RBRACKET);
+    } else if (eat_word(p, "mutable")) {
+        p.warn("ignoring deprecated 'mutable' type constructor");
+        auto typ = parse_ty(p);
+        t = typ.node;
+        hi = typ.span.hi;
+    } else if (is_ident(p.peek())) {
+        auto path = parse_path(p);
+        t = ast::ty_path(path, p.get_id());
+        hi = path.span.hi;
+    } else { p.fatal("expecting type"); t = ast::ty_nil; fail; }
+    ret parse_ty_postfix(@spanned(lo, hi, t), p);
+}
+
+fn parse_arg(&parser p) -> ast::arg {
+    let ast::mode m = ast::val;
+    if (p.peek() == token::BINOP(token::AND)) {
+        p.bump();
+        m = ast::alias(eat_word(p, "mutable"));
+    }
+    let @ast::ty t = parse_ty(p);
+    let ast::ident i = parse_value_ident(p);
+    ret rec(mode=m, ty=t, ident=i, id=p.get_id());
+}
+
+fn parse_seq_to_end[T](token::token ket, option::t[token::token] sep,
+                       fn(&parser) -> T  f, &parser p) -> vec[T] {
+    let bool first = true;
+    let vec[T] v = [];
+    while (p.peek() != ket) {
+        alt (sep) {
+            case (some(?t)) {
+                if (first) { first = false; } else { expect(p, t); }
+            }
+            case (_) { }
+        }
+        v += [f(p)];
+    }
+    expect(p, ket);
+    ret v;
+}
+
+fn parse_seq[T](token::token bra, token::token ket,
+                option::t[token::token] sep, fn(&parser) -> T  f, &parser p)
+   -> ast::spanned[vec[T]] {
+    auto lo = p.get_lo_pos();
+    expect(p, bra);
+    auto result = parse_seq_to_end[T](ket, sep, f, p);
+    auto hi = p.get_hi_pos();
+    ret spanned(lo, hi, result);
+}
+
+fn parse_lit(&parser p) -> ast::lit {
+    auto sp = p.get_span();
+    let ast::lit_ lit = ast::lit_nil;
+    if (eat_word(p, "true")) {
+        lit = ast::lit_bool(true);
+    } else if (eat_word(p, "false")) {
+        lit = ast::lit_bool(false);
+    } else {
+        alt (p.peek()) {
+            case (token::LIT_INT(?i)) { p.bump(); lit = ast::lit_int(i); }
+            case (token::LIT_UINT(?u)) { p.bump(); lit = ast::lit_uint(u); }
+            case (token::LIT_FLOAT(?s)) {
+                p.bump();
+                lit = ast::lit_float(p.get_str(s));
+            }
+            case (token::LIT_MACH_INT(?tm, ?i)) {
+                p.bump();
+                lit = ast::lit_mach_int(tm, i);
+            }
+            case (token::LIT_MACH_FLOAT(?tm, ?s)) {
+                p.bump();
+                lit = ast::lit_mach_float(tm, p.get_str(s));
+            }
+            case (token::LIT_CHAR(?c)) { p.bump(); lit = ast::lit_char(c); }
+            case (token::LIT_STR(?s)) {
+                p.bump();
+                lit = ast::lit_str(p.get_str(s), ast::sk_rc);
+            }
+            case (?t) { unexpected(p, t); }
+        }
+    }
+    ret rec(node=lit, span=sp);
+}
+
+fn is_ident(token::token t) -> bool {
+    alt (t) { case (token::IDENT(_, _)) { ret true; } case (_) { } }
+    ret false;
+}
+
+fn parse_path(&parser p) -> ast::path {
+    auto lo = p.get_lo_pos();
+    auto hi = lo;
+    let vec[ast::ident] ids = [];
+    while (true) {
+        alt (p.peek()) {
+            case (token::IDENT(?i, _)) {
+                hi = p.get_hi_pos();
+                ids += [p.get_str(i)];
+                p.bump();
+                if (p.peek() == token::MOD_SEP) { p.bump(); } else { break; }
+            }
+            case (_) { break; }
+        }
+    }
+    hi = p.get_hi_pos();
+    ret spanned(lo, hi, rec(idents=ids, types=[]));
+}
+
+fn parse_path_and_ty_param_substs(&parser p) -> ast::path {
+    auto lo = p.get_lo_pos();
+    auto path = parse_path(p);
+    if (p.peek() == token::LBRACKET) {
+        auto seq = parse_seq(token::LBRACKET, token::RBRACKET,
+                             some(token::COMMA), parse_ty, p);
+        auto hi = p.get_hi_pos();
+        path = spanned(lo, hi, rec(idents=path.node.idents, types=seq.node));
+    }
+    ret path;
+}
+
+fn parse_mutability(&parser p) -> ast::mutability {
+    if (eat_word(p, "mutable")) {
+        if (p.peek() == token::QUES) { p.bump(); ret ast::maybe_mut; }
+        ret ast::mut;
+    }
+    ret ast::imm;
+}
+
+fn parse_field(&parser p) -> ast::field {
+    auto lo = p.get_lo_pos();
+    auto m = parse_mutability(p);
+    auto i = parse_ident(p);
+    expect(p, token::EQ);
+    auto e = parse_expr(p);
+    ret spanned(lo, e.span.hi, rec(mut=m, ident=i, expr=e));
+}
+
+fn mk_expr(&parser p, uint lo, uint hi, &ast::expr_ node) -> @ast::expr {
+    ret @rec(id=p.get_id(),
+             node=node,
+             span=rec(lo=lo, hi=hi));
+}
+
+fn parse_bottom_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_lo_pos();
+    auto hi = p.get_hi_pos();
+    // FIXME: can only remove this sort of thing when both typestate and
+    // alt-exhaustive-match checking are co-operating.
+
+    auto lit = @spanned(lo, hi, ast::lit_nil);
+    let ast::expr_ ex = ast::expr_lit(lit);
+    if (p.peek() == token::LPAREN) {
+        p.bump();
+        alt (p.peek()) {
+            case (token::RPAREN) {
+                hi = p.get_hi_pos();
+                p.bump();
+                auto lit = @spanned(lo, hi, ast::lit_nil);
+                ret mk_expr(p, lo, hi, ast::expr_lit(lit));
+            }
+            case (_) {/* fall through */ }
+        }
+        auto e = parse_expr(p);
+        hi = p.get_hi_pos();
+        expect(p, token::RPAREN);
+        ret mk_expr(p, lo, hi, e.node);
+    } else if (p.peek() == token::LBRACE) {
+        auto blk = parse_block(p);
+        ret mk_expr(p, blk.span.lo, blk.span.hi, ast::expr_block(blk));
+    } else if (eat_word(p, "if")) {
+        ret parse_if_expr(p);
+    } else if (eat_word(p, "for")) {
+        ret parse_for_expr(p);
+    } else if (eat_word(p, "while")) {
+        ret parse_while_expr(p);
+    } else if (eat_word(p, "do")) {
+        ret parse_do_while_expr(p);
+    } else if (eat_word(p, "alt")) {
+        ret parse_alt_expr(p);
+    } else if (eat_word(p, "spawn")) {
+        ret parse_spawn_expr(p);
+    } else if (eat_word(p, "fn")) {
+        ret parse_fn_expr(p);
+    } else if (eat_word(p, "tup")) {
+        fn parse_elt(&parser p) -> ast::elt {
+            auto m = parse_mutability(p);
+            auto e = parse_expr(p);
+            ret rec(mut=m, expr=e);
+        }
+        auto es =
+            parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                      parse_elt, p);
+        hi = es.span.hi;
+        ex = ast::expr_tup(es.node);
+    } else if (p.peek() == token::LBRACKET) {
+        p.bump();
+        auto mut = parse_mutability(p);
+        auto es =
+            parse_seq_to_end(token::RBRACKET, some(token::COMMA), parse_expr,
+                             p);
+        ex = ast::expr_vec(es, mut, ast::sk_rc);
+    } else if (p.peek() == token::TILDE) {
+        p.bump();
+        alt (p.peek()) {
+            case (token::LBRACKET) { // unique array (temporary)
+
+                p.bump();
+                auto mut = parse_mutability(p);
+                auto es =
+                    parse_seq_to_end(token::RBRACKET, some(token::COMMA),
+                                     parse_expr, p);
+                ex = ast::expr_vec(es, mut, ast::sk_unique);
+            }
+            case (token::LIT_STR(?s)) {
+                p.bump();
+                auto lit =
+                    @rec(node=ast::lit_str(p.get_str(s), ast::sk_unique),
+                         span=p.get_span());
+                ex = ast::expr_lit(lit);
+            }
+            case (_) {
+                p.fatal("unimplemented: unique pointer creation");
+            }
+        }
+    } else if (eat_word(p, "obj")) {
+        // Anonymous object
+
+        // FIXME: Can anonymous objects have ty params?
+        auto ty_params = parse_ty_params(p);
+
+        // Only make people type () if they're actually adding new fields
+        let option::t[vec[ast::anon_obj_field]] fields = none;
+        if (p.peek() == token::LPAREN) {
+            p.bump();
+            fields =
+                some(parse_seq_to_end(token::RPAREN, some(token::COMMA),
+                                      parse_anon_obj_field, p));
+        }
+        let vec[@ast::method] meths = [];
+        let option::t[@ast::expr] with_obj = none;
+        expect(p, token::LBRACE);
+        while (p.peek() != token::RBRACE) {
+            if (eat_word(p, "with")) {
+                with_obj = some(parse_expr(p));
+            } else { vec::push(meths, parse_method(p)); }
+        }
+        hi = p.get_hi_pos();
+        expect(p, token::RBRACE);
+        // fields and methods may be *additional* or *overriding* fields
+        // and methods if there's a with_obj, or they may be the *only*
+        // fields and methods if there's no with_obj.
+
+        // We don't need to pull ".node" out of fields because it's not a
+        // "spanned".
+        let ast::anon_obj ob =
+            rec(fields=fields, methods=meths, with_obj=with_obj);
+        auto odid = rec(ty=p.get_id(), ctor=p.get_id());
+        ex = ast::expr_anon_obj(ob, ty_params, odid);
+    } else if (eat_word(p, "rec")) {
+        expect(p, token::LPAREN);
+        auto fields = [parse_field(p)];
+        auto more = true;
+        auto base = none;
+        while (more) {
+            if (p.peek() == token::RPAREN) {
+                hi = p.get_hi_pos();
+                p.bump();
+                more = false;
+            } else if (eat_word(p, "with")) {
+                base = some(parse_expr(p));
+                hi = p.get_hi_pos();
+                expect(p, token::RPAREN);
+                more = false;
+            } else if (p.peek() == token::COMMA) {
+                p.bump();
+                fields += [parse_field(p)];
+            } else { unexpected(p, p.peek()); }
+        }
+        ex = ast::expr_rec(fields, base);
+    } else if (eat_word(p, "bind")) {
+        auto e = parse_expr_res(p, RESTRICT_NO_CALL_EXPRS);
+        fn parse_expr_opt(&parser p) -> option::t[@ast::expr] {
+            alt (p.peek()) {
+                case (token::UNDERSCORE) { p.bump(); ret none; }
+                case (_) { ret some(parse_expr(p)); }
+            }
+        }
+        auto es =
+            parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                      parse_expr_opt, p);
+        hi = es.span.hi;
+        ex = ast::expr_bind(e, es.node);
+    } else if (p.peek() == token::POUND) {
+        auto ex_ext = parse_syntax_ext(p);
+        lo = ex_ext.span.lo;
+        ex = ex_ext.node;
+    } else if (eat_word(p, "fail")) {
+        if (can_begin_expr(p.peek())) {
+            auto e = parse_expr(p);
+            hi = e.span.hi;
+            ex = ast::expr_fail(some(e));
+        }
+        else {
+            ex = ast::expr_fail(none);
+        }
+    } else if (eat_word(p, "log")) {
+        auto e = parse_expr(p);
+        ex = ast::expr_log(1, e);
+    } else if (eat_word(p, "log_err")) {
+        auto e = parse_expr(p);
+        ex = ast::expr_log(0, e);
+    } else if (eat_word(p, "assert")) {
+        auto e = parse_expr(p);
+        ex = ast::expr_assert(e);
+    } else if (eat_word(p, "check")) {
+        /* Should be a predicate (pure boolean function) applied to 
+           arguments that are all either slot variables or literals.
+           but the typechecker enforces that. */
+
+        auto e = parse_expr(p);
+        ex = ast::expr_check(ast::checked, e);
+    } else if (eat_word(p, "claim")) {
+        /* Same rules as check, except that if check-claims
+         is enabled (a command-line flag), then the parser turns
+        claims into check */
+        
+        auto e = parse_expr(p);
+        ex = ast::expr_check(ast::unchecked, e);
+    } else if (eat_word(p, "ret")) {
+        alt (p.peek()) {
+            case (token::SEMI) { ex = ast::expr_ret(none); }
+            // Handle ret as the block result expression
+            case (token::RBRACE) { ex = ast::expr_ret(none); }
+            case (_) {
+                auto e = parse_expr(p);
+                hi = e.span.hi;
+                ex = ast::expr_ret(some(e));
+            }
+        }
+    } else if (eat_word(p, "break")) {
+        ex = ast::expr_break;
+    } else if (eat_word(p, "cont")) {
+        ex = ast::expr_cont;
+    } else if (eat_word(p, "put")) {
+        alt (p.peek()) {
+            case (token::SEMI) { ex = ast::expr_put(none); }
+            case (_) {
+                auto e = parse_expr(p);
+                hi = e.span.hi;
+                ex = ast::expr_put(some(e));
+            }
+        }
+    } else if (eat_word(p, "be")) {
+        auto e = parse_expr(p);
+
+        // FIXME: Is this the right place for this check?
+        if (/*check*/ast::is_call_expr(e)) {
+            hi = e.span.hi;
+            ex = ast::expr_be(e);
+        } else { p.fatal("Non-call expression in tail call"); }
+    } else if (eat_word(p, "port")) {
+        auto ty = none;
+        if(token::LBRACKET == p.peek()) {
+            expect(p, token::LBRACKET);
+            ty = some(parse_ty(p));
+            expect(p, token::RBRACKET);
+        }
+        expect(p, token::LPAREN);
+        expect(p, token::RPAREN);
+        hi = p.get_hi_pos();
+        ex = ast::expr_port(ty);
+    } else if (eat_word(p, "chan")) {
+        expect(p, token::LPAREN);
+        auto e = parse_expr(p);
+        hi = e.span.hi;
+        expect(p, token::RPAREN);
+        ex = ast::expr_chan(e);
+    } else if (eat_word(p, "self")) {
+        log "parsing a self-call...";
+        expect(p, token::DOT);
+        // The rest is a call expression.
+
+        let @ast::expr f = parse_self_method(p);
+        auto es =
+            parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                      parse_expr, p);
+        hi = es.span.hi;
+        ex = ast::expr_call(f, es.node);
+    } else if (is_ident(p.peek()) && !is_word(p, "true") &&
+               !is_word(p, "false")) {
+        check_bad_word(p);
+        auto pth = parse_path_and_ty_param_substs(p);
+        hi = pth.span.hi;
+        ex = ast::expr_path(pth);
+    } else {
+        auto lit = parse_lit(p);
+        hi = lit.span.hi;
+        ex = ast::expr_lit(@lit);
+    }
+    ret mk_expr(p, lo, hi, ex);
+}
+
+fn parse_syntax_ext(&parser p) -> @ast::expr {
+    auto lo = p.get_lo_pos();
+    expect(p, token::POUND);
+    ret parse_syntax_ext_naked(p, lo);
+}
+
+fn parse_syntax_ext_naked(&parser p, uint lo) -> @ast::expr {
+    auto pth = parse_path(p);
+    if (vec::len(pth.node.idents) == 0u) {
+        p.fatal("expected a syntax expander name");
+    }
+    auto es = parse_seq(token::LPAREN, token::RPAREN,
+                        some(token::COMMA), parse_expr, p);
+    auto hi = es.span.hi;
+    auto ext_span = rec(lo=lo, hi=hi);
+    auto ex = expand_syntax_ext(p, ext_span, pth, es.node, none);
+    ret mk_expr(p, lo, hi, ex);
+}
+
+/*
+ * FIXME: This is a crude approximation of the syntax-extension system,
+ * for purposes of prototyping and/or hard-wiring any extensions we
+ * wish to use while bootstrapping. The eventual aim is to permit
+ * loading rust crates to process extensions.
+ */
+fn expand_syntax_ext(&parser p, span sp, &ast::path path,
+                     vec[@ast::expr] args, option::t[str] body) ->
+   ast::expr_ {
+    assert (vec::len(path.node.idents) > 0u);
+    auto extname = path.node.idents.(0);
+    alt (p.get_syntax_expanders().find(extname)) {
+        case (none) { p.fatal("unknown syntax expander: '" + extname + "'"); }
+        case (some(ex::normal(?ext))) {
+            auto ext_cx = ex::mk_ctxt(p.get_sess());
+            ret ast::expr_ext(path, args, body, ext(ext_cx, sp, args, body));
+        }
+        // because we have expansion inside parsing, new macros are only
+        // visible further down the file
+        case (some(ex::macro_defining(?ext))) {
+            auto ext_cx = ex::mk_ctxt(p.get_sess());
+            auto name_and_extension = ext(ext_cx, sp, args, body);
+            p.get_syntax_expanders().insert(name_and_extension._0,
+                                            name_and_extension._1);
+            ret ast::expr_tup(vec::empty[ast::elt]());
+        }
+    }
+}
+
+fn parse_self_method(&parser p) -> @ast::expr {
+    auto sp = p.get_span();
+    let ast::ident f_name = parse_ident(p);
+    ret mk_expr(p, sp.lo, sp.hi, ast::expr_self_method(f_name));
+}
+
+fn parse_dot_or_call_expr(&parser p) -> @ast::expr {
+    ret parse_dot_or_call_expr_with(p, parse_bottom_expr(p));
+}
+
+fn parse_dot_or_call_expr_with(&parser p, @ast::expr e) -> @ast::expr {
+    auto lo = e.span.lo;
+    auto hi = e.span.hi;
+    while (true) {
+        alt (p.peek()) {
+            case (token::LPAREN) {
+                if (p.get_restriction() == RESTRICT_NO_CALL_EXPRS) {
+                    ret e;
+                } else {
+                    // Call expr.
+
+                    auto es =
+                        parse_seq(token::LPAREN, token::RPAREN,
+                                  some(token::COMMA), parse_expr, p);
+                    hi = es.span.hi;
+                    e = mk_expr(p, lo, hi, ast::expr_call(e, es.node));
+                }
+            }
+            case (token::DOT) {
+                p.bump();
+                alt (p.peek()) {
+                    case (token::IDENT(?i, _)) {
+                        hi = p.get_hi_pos();
+                        p.bump();
+                        e = mk_expr(p, lo, hi,
+                                    ast::expr_field(e, p.get_str(i)));
+                    }
+                    case (token::LPAREN) {
+                        p.bump();
+                        auto ix = parse_expr(p);
+                        hi = ix.span.hi;
+                        expect(p, token::RPAREN);
+                        e = mk_expr(p, lo, hi, ast::expr_index(e, ix));
+                    }
+                    case (?t) { unexpected(p, t); }
+                }
+            }
+            case (_) { ret e; }
+        }
+    }
+    ret e;
+}
+
+fn parse_prefix_expr(&parser p) -> @ast::expr {
+    if (eat_word(p, "mutable")) {
+        p.warn("ignoring deprecated 'mutable' prefix operator");
+    }
+    auto lo = p.get_lo_pos();
+    auto hi = p.get_hi_pos();
+    // FIXME: can only remove this sort of thing when both typestate and
+    // alt-exhaustive-match checking are co-operating.
+
+    auto lit = @spanned(lo, lo, ast::lit_nil);
+    let ast::expr_ ex = ast::expr_lit(lit);
+    alt (p.peek()) {
+        case (token::NOT) {
+            p.bump();
+            auto e = parse_prefix_expr(p);
+            hi = e.span.hi;
+            ex = ast::expr_unary(ast::not, e);
+        }
+        case (token::BINOP(?b)) {
+            alt (b) {
+                case (token::MINUS) {
+                    p.bump();
+                    auto e = parse_prefix_expr(p);
+                    hi = e.span.hi;
+                    ex = ast::expr_unary(ast::neg, e);
+                }
+                case (token::STAR) {
+                    p.bump();
+                    auto e = parse_prefix_expr(p);
+                    hi = e.span.hi;
+                    ex = ast::expr_unary(ast::deref, e);
+                }
+                case (_) { ret parse_dot_or_call_expr(p); }
+            }
+        }
+        case (token::AT) {
+            p.bump();
+            auto m = parse_mutability(p);
+            auto e = parse_prefix_expr(p);
+            hi = e.span.hi;
+            ex = ast::expr_unary(ast::box(m), e);
+        }
+        case (_) { ret parse_dot_or_call_expr(p); }
+    }
+    ret mk_expr(p, lo, hi, ex);
+}
+
+fn parse_ternary(&parser p) -> @ast::expr {
+    auto cond_expr = parse_binops(p);
+    if (p.peek() == token::QUES) {
+        p.bump();
+        auto then_expr = parse_expr(p);
+        expect(p, token::COLON);
+        auto else_expr = parse_expr(p);
+        ret mk_expr(p, cond_expr.span.lo, else_expr.span.hi,
+                    ast::expr_ternary(cond_expr, then_expr, else_expr));
+    } else {
+        ret cond_expr;
+    }
+}
+
+type op_spec = rec(token::token tok, ast::binop op, int prec);
+
+
+// FIXME make this a const, don't store it in parser state
+fn prec_table() -> vec[op_spec] {
+    ret [rec(tok=token::BINOP(token::STAR), op=ast::mul, prec=11),
+         rec(tok=token::BINOP(token::SLASH), op=ast::div, prec=11),
+         rec(tok=token::BINOP(token::PERCENT), op=ast::rem, prec=11),
+         rec(tok=token::BINOP(token::PLUS), op=ast::add, prec=10),
+         rec(tok=token::BINOP(token::MINUS), op=ast::sub, prec=10),
+         rec(tok=token::BINOP(token::LSL), op=ast::lsl, prec=9),
+         rec(tok=token::BINOP(token::LSR), op=ast::lsr, prec=9),
+         rec(tok=token::BINOP(token::ASR), op=ast::asr, prec=9),
+         rec(tok=token::BINOP(token::AND), op=ast::bitand, prec=8),
+         rec(tok=token::BINOP(token::CARET), op=ast::bitxor, prec=6),
+         rec(tok=token::BINOP(token::OR), op=ast::bitor, prec=6),
+         // 'as' sits between here with 5
+         rec(tok=token::LT, op=ast::lt, prec=4),
+         rec(tok=token::LE, op=ast::le, prec=4),
+         rec(tok=token::GE, op=ast::ge, prec=4),
+         rec(tok=token::GT, op=ast::gt, prec=4),
+         rec(tok=token::EQEQ, op=ast::eq, prec=3),
+         rec(tok=token::NE, op=ast::ne, prec=3),
+         rec(tok=token::ANDAND, op=ast::and, prec=2),
+         rec(tok=token::OROR, op=ast::or, prec=1)];
+}
+
+fn parse_binops(&parser p) -> @ast::expr {
+    ret parse_more_binops(p, parse_prefix_expr(p), 0);
+}
+
+const int unop_prec = 100;
+
+const int as_prec = 5;
+const int ternary_prec = 0;
+
+fn parse_more_binops(&parser p, @ast::expr lhs, int min_prec) -> @ast::expr {
+    auto peeked = p.peek();
+    for (op_spec cur in p.get_prec_table()) {
+        if (cur.prec > min_prec && cur.tok == peeked) {
+            p.bump();
+            auto rhs = parse_more_binops(p, parse_prefix_expr(p), cur.prec);
+            auto bin = mk_expr(p, lhs.span.lo, rhs.span.hi,
+                               ast::expr_binary(cur.op, lhs, rhs));
+            ret parse_more_binops(p, bin, min_prec);
+        }
+    }
+    if (as_prec > min_prec && eat_word(p, "as")) {
+        auto rhs = parse_ty(p);
+        auto _as = mk_expr(p, lhs.span.lo, rhs.span.hi,
+                           ast::expr_cast(lhs, rhs));
+        ret parse_more_binops(p, _as, min_prec);
+    }
+    ret lhs;
+}
+
+fn parse_assign_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_lo_pos();
+    auto lhs = parse_ternary(p);
+    alt (p.peek()) {
+        case (token::EQ) {
+            p.bump();
+            auto rhs = parse_expr(p);
+            ret mk_expr(p, lo, rhs.span.hi, ast::expr_assign(lhs, rhs));
+        }
+        case (token::BINOPEQ(?op)) {
+            p.bump();
+            auto rhs = parse_expr(p);
+            auto aop = ast::add;
+            alt (op) {
+                case (token::PLUS) { aop = ast::add; }
+                case (token::MINUS) { aop = ast::sub; }
+                case (token::STAR) { aop = ast::mul; }
+                case (token::SLASH) { aop = ast::div; }
+                case (token::PERCENT) { aop = ast::rem; }
+                case (token::CARET) { aop = ast::bitxor; }
+                case (token::AND) { aop = ast::bitand; }
+                case (token::OR) { aop = ast::bitor; }
+                case (token::LSL) { aop = ast::lsl; }
+                case (token::LSR) { aop = ast::lsr; }
+                case (token::ASR) { aop = ast::asr; }
+            }
+            ret mk_expr(p, lo, rhs.span.hi,
+                        ast::expr_assign_op(aop, lhs, rhs));
+        }
+        case (token::LARROW) {
+            p.bump();
+            auto rhs = parse_expr(p);
+            ret mk_expr(p, lo, rhs.span.hi, ast::expr_move(lhs, rhs));
+        }
+        case (token::SEND) {
+            p.bump();
+            auto rhs = parse_expr(p);
+            ret mk_expr(p, lo, rhs.span.hi, ast::expr_send(lhs, rhs));
+        }
+        case (token::RECV) {
+            p.bump();
+            auto rhs = parse_expr(p);
+            ret mk_expr(p, lo, rhs.span.hi, ast::expr_recv(lhs, rhs));
+        }
+        case (token::DARROW) {
+            p.bump();
+            auto rhs = parse_expr(p);
+            ret mk_expr(p, lo, rhs.span.hi, ast::expr_swap(lhs, rhs));
+        }
+        case (_) {/* fall through */ }
+    }
+    ret lhs;
+}
+
+fn parse_if_expr_1(&parser p) -> tup(@ast::expr,
+                                     ast::block, option::t[@ast::expr],
+                                     uint, uint) {
+    auto lo = p.get_last_lo_pos();
+    expect(p, token::LPAREN);
+    auto cond = parse_expr(p);
+    expect(p, token::RPAREN);
+    auto thn = parse_block(p);
+    let option::t[@ast::expr] els = none;
+    auto hi = thn.span.hi;
+    if (eat_word(p, "else")) {
+        auto elexpr = parse_else_expr(p);
+        els = some(elexpr);
+        hi = elexpr.span.hi;
+    }
+    ret tup(cond, thn, els, lo, hi);
+}
+
+fn parse_if_expr(&parser p) -> @ast::expr {
+    if (eat_word(p, "check")) {
+            auto q = parse_if_expr_1(p);
+            ret mk_expr(p, q._3, q._4, ast::expr_if_check(q._0, q._1, q._2));
+    }
+    else {
+        auto q = parse_if_expr_1(p);
+        ret mk_expr(p, q._3, q._4, ast::expr_if(q._0, q._1, q._2));
+    }
+}
+
+fn parse_fn_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_last_lo_pos();
+    auto decl = parse_fn_decl(p, ast::impure_fn);
+    auto body = parse_block(p);
+    auto _fn = rec(decl=decl, proto=ast::proto_fn, body=body);
+    ret mk_expr(p, lo, body.span.hi, ast::expr_fn(_fn));
+}
+
+fn parse_else_expr(&parser p) -> @ast::expr {
+    if (eat_word(p, "if")) {
+        ret parse_if_expr(p);
+    } else {
+        auto blk = parse_block(p);
+        ret mk_expr(p, blk.span.lo, blk.span.hi, ast::expr_block(blk));
+    }
+}
+
+fn parse_head_local(&parser p) -> @ast::local {
+    if (is_word(p, "auto")) {
+        ret parse_auto_local(p);
+    } else { 
+        ret parse_typed_local(p);
+    }
+}
+
+fn parse_for_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_last_lo_pos();
+    auto is_each = eat_word(p, "each");
+    expect(p, token::LPAREN);
+    auto decl = parse_head_local(p);
+    expect_word(p, "in");
+    auto seq = parse_expr(p);
+    expect(p, token::RPAREN);
+    auto body = parse_block(p);
+    auto hi = body.span.hi;
+    if (is_each) {
+        ret mk_expr(p, lo, hi, ast::expr_for_each(decl, seq, body));
+    } else {
+        ret mk_expr(p, lo, hi, ast::expr_for(decl, seq, body));
+    }
+}
+
+fn parse_while_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_last_lo_pos();
+    expect(p, token::LPAREN);
+    auto cond = parse_expr(p);
+    expect(p, token::RPAREN);
+    auto body = parse_block(p);
+    auto hi = body.span.hi;
+    ret mk_expr(p, lo, hi, ast::expr_while(cond, body));
+}
+
+fn parse_do_while_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_last_lo_pos();
+    auto body = parse_block(p);
+    expect_word(p, "while");
+    expect(p, token::LPAREN);
+    auto cond = parse_expr(p);
+    expect(p, token::RPAREN);
+    auto hi = cond.span.hi;
+    ret mk_expr(p, lo, hi, ast::expr_do_while(body, cond));
+}
+
+fn parse_alt_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_last_lo_pos();
+    expect(p, token::LPAREN);
+    auto discriminant = parse_expr(p);
+    expect(p, token::RPAREN);
+    expect(p, token::LBRACE);
+    let vec[ast::arm] arms = [];
+    while (p.peek() != token::RBRACE) {
+        if (eat_word(p, "case")) {
+            expect(p, token::LPAREN);
+            auto pat = parse_pat(p);
+            expect(p, token::RPAREN);
+            auto block = parse_block(p);
+            arms += [rec(pat=pat, block=block)];
+        } else if (p.peek() == token::RBRACE) {
+            /* empty */
+
+        } else {
+            p.fatal("expected 'case' or '}' when parsing 'alt' statement " +
+                      "but found " + token::to_str(p.get_reader(), p.peek()));
+        }
+    }
+    auto hi = p.get_hi_pos();
+    p.bump();
+    ret mk_expr(p, lo, hi, ast::expr_alt(discriminant, arms));
+}
+
+fn parse_spawn_expr(&parser p) -> @ast::expr {
+    auto lo = p.get_last_lo_pos();
+    // FIXME: Parse domain and name
+    // FIXME: why no full expr?
+
+    auto fn_expr = parse_bottom_expr(p);
+    auto es =
+        parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                  parse_expr, p);
+    auto hi = es.span.hi;
+    ret mk_expr(p, lo, hi, ast::expr_spawn
+                (ast::dom_implicit, option::none, fn_expr, es.node));
+}
+
+fn parse_expr(&parser p) -> @ast::expr {
+    ret parse_expr_res(p, UNRESTRICTED);
+}
+
+fn parse_expr_res(&parser p, restriction r) -> @ast::expr {
+    auto old = p.get_restriction();
+    p.restrict(r);
+    auto e = parse_assign_expr(p);
+    p.restrict(old);
+    ret e;
+}
+
+fn parse_initializer(&parser p) -> option::t[ast::initializer] {
+    alt (p.peek()) {
+        case (token::EQ) {
+            p.bump();
+            ret some(rec(op=ast::init_assign, expr=parse_expr(p)));
+        }
+        case (token::LARROW) {
+            p.bump();
+            ret some(rec(op=ast::init_move, expr=parse_expr(p)));
+        }
+        case (
+             // Now that the the channel is the first argument to receive,
+             // combining it with an initializer doesn't really make sense.
+             // case (token::RECV) {
+             //     p.bump();
+             //     ret some(rec(op = ast::init_recv,
+             //                  expr = parse_expr(p)));
+             // }
+             _) {
+            ret none;
+        }
+    }
+}
+
+fn parse_pat(&parser p) -> @ast::pat {
+    auto lo = p.get_lo_pos();
+    auto hi = p.get_hi_pos();
+    auto pat;
+    alt (p.peek()) {
+        case (token::UNDERSCORE) {
+            p.bump();
+            pat = ast::pat_wild;
+        }
+        case (token::QUES) {
+            p.bump();
+            alt (p.peek()) {
+                case (token::IDENT(?id, _)) {
+                    hi = p.get_hi_pos();
+                    p.bump();
+                    pat = ast::pat_bind(p.get_str(id));
+                }
+                case (?tok) {
+                    p.fatal("expected identifier after '?' in pattern but " +
+                              "found " + token::to_str(p.get_reader(), tok));
+                    fail;
+                }
+            }
+        }
+        case (?tok) {
+            if (!is_ident(tok) || is_word(p, "true") || is_word(p, "false")) {
+                auto lit = parse_lit(p);
+                hi = lit.span.hi;
+                pat = ast::pat_lit(@lit);
+            } else {
+                auto tag_path = parse_path_and_ty_param_substs(p);
+                hi = tag_path.span.hi;
+                let vec[@ast::pat] args;
+                alt (p.peek()) {
+                    case (token::LPAREN) {
+                        auto f = parse_pat;
+                        auto a =
+                            parse_seq(token::LPAREN, token::RPAREN,
+                                      some(token::COMMA), f, p);
+                        args = a.node;
+                        hi = a.span.hi;
+                    }
+                    case (_) { args = []; }
+                }
+                pat = ast::pat_tag(tag_path, args);
+            }
+        }
+    }
+    ret @rec(id=p.get_id(), node=pat, span=rec(lo=lo, hi=hi));
+}
+
+fn parse_local_full(&option::t[@ast::ty] tyopt, &parser p)
+    -> @ast::local {
+    auto lo = p.get_lo_pos();
+    auto ident = parse_value_ident(p);
+    auto init = parse_initializer(p);
+    ret @spanned(lo, p.get_hi_pos(),
+                 rec(ty=tyopt,
+                     infer=false,
+                     ident=ident,
+                     init=init,
+                     id=p.get_id()));
+}
+
+fn parse_typed_local(&parser p) -> @ast::local {
+    auto ty = parse_ty(p);
+    ret parse_local_full(some(ty), p);
+}
+
+fn parse_auto_local(&parser p) -> @ast::local {
+    ret parse_local_full(none, p);
+}
+
+fn parse_let(&parser p) -> @ast::decl {
+    auto lo = p.get_last_lo_pos();
+    auto local = parse_typed_local(p);
+    ret @spanned(lo, p.get_hi_pos(), ast::decl_local(local));
+}
+
+fn parse_auto(&parser p) -> @ast::decl {
+    auto lo = p.get_last_lo_pos();
+    auto local = parse_auto_local(p);
+    ret @spanned(lo, p.get_hi_pos(), ast::decl_local(local));
+}
+
+fn parse_stmt(&parser p) -> @ast::stmt {
+    if (p.get_file_type() == SOURCE_FILE) {
+        ret parse_source_stmt(p);
+    } else { ret parse_crate_stmt(p); }
+}
+
+fn parse_crate_stmt(&parser p) -> @ast::stmt {
+    auto cdir = parse_crate_directive(p, []);
+    ret @spanned(cdir.span.lo, cdir.span.hi,
+                 ast::stmt_crate_directive(@cdir));
+}
+
+fn parse_source_stmt(&parser p) -> @ast::stmt {
+    auto lo = p.get_lo_pos();
+    if (eat_word(p, "let")) {
+        auto decl = parse_let(p);
+        ret @spanned(lo, decl.span.hi, ast::stmt_decl(decl, p.get_id()));
+    } else if (eat_word(p, "auto")) {
+        auto decl = parse_auto(p);
+        ret @spanned(lo, decl.span.hi, ast::stmt_decl(decl, p.get_id()));
+    } else {
+
+        auto item_attrs;
+        alt (parse_outer_attrs_or_ext(p)) {
+            case (none) {
+                item_attrs = [];
+            }
+            case (some(left(?attrs))) {
+                item_attrs = attrs;
+            }
+            case (some(right(?ext))) {
+                ret @spanned(lo, ext.span.hi,
+                             ast::stmt_expr(ext, p.get_id()));
+            }
+        }
+
+        auto maybe_item = parse_item(p, item_attrs);
+
+        // If we have attributes then we should have an item
+        if (vec::len(item_attrs) > 0u) {
+            alt (maybe_item) {
+                case (got_item(_)) { /* fallthrough */ }
+                case (_) {
+                    ret p.fatal("expected item");
+                }
+            }
+        }
+
+        alt (maybe_item) {
+            case (got_item(?i)) {
+                auto hi = i.span.hi;
+                auto decl = @spanned(lo, hi, ast::decl_item(i));
+                ret @spanned(lo, hi, ast::stmt_decl(decl, p.get_id()));
+            }
+            case (fn_no_item) { // parse_item will have already skipped "fn"
+
+                auto e = parse_fn_expr(p);
+                e = parse_dot_or_call_expr_with(p, e);
+                ret @spanned(lo, e.span.hi, ast::stmt_expr(e, p.get_id()));
+            }
+            case (no_item) {
+                // Remainder are line-expr stmts.
+
+                auto e = parse_expr(p);
+                ret @spanned(lo, e.span.hi, ast::stmt_expr(e, p.get_id()));
+            }
+        }
+    }
+    p.fatal("expected statement");
+    fail;
+}
+
+fn stmt_to_expr(@ast::stmt stmt) -> option::t[@ast::expr] {
+    ret alt (stmt.node) {
+            case (ast::stmt_expr(?e, _)) { some(e) }
+            case (_) { none }
+        };
+}
+
+fn stmt_ends_with_semi(&ast::stmt stmt) -> bool {
+    alt (stmt.node) {
+        case (ast::stmt_decl(?d, _)) {
+            ret alt (d.node) {
+                case (ast::decl_local(_)) { true }
+                case (ast::decl_item(_)) { false }
+            }
+        }
+        case (ast::stmt_expr(?e, _)) {
+            ret alt (e.node) {
+                case (ast::expr_vec(_, _, _)) { true }
+                case (ast::expr_tup(_)) { true }
+                case (ast::expr_rec(_, _)) { true }
+                case (ast::expr_call(_, _)) { true }
+                case (ast::expr_self_method(_)) { false }
+                case (ast::expr_bind(_, _)) { true }
+                case (ast::expr_spawn(_, _, _, _)) { true }
+                case (ast::expr_binary(_, _, _)) { true }
+                case (ast::expr_unary(_, _)) { true }
+                case (ast::expr_lit(_)) { true }
+                case (ast::expr_cast(_, _)) { true }
+                case (ast::expr_if(_, _, _)) { false }
+                case (ast::expr_ternary(_, _, _)) { true }
+                case (ast::expr_for(_, _, _)) { false }
+                case (ast::expr_for_each(_, _, _)) { false }
+                case (ast::expr_while(_, _)) { false }
+                case (ast::expr_do_while(_, _)) { false }
+                case (ast::expr_alt(_, _)) { false }
+                case (ast::expr_fn(_)) { false }
+                case (ast::expr_block(_)) { false }
+                case (ast::expr_move(_, _)) { true }
+                case (ast::expr_assign(_, _)) { true }
+                case (ast::expr_swap(_, _)) { true }
+                case (ast::expr_assign_op(_, _, _)) { true }
+                case (ast::expr_send(_, _)) { true }
+                case (ast::expr_recv(_, _)) { true }
+                case (ast::expr_field(_, _)) { true }
+                case (ast::expr_index(_, _)) { true }
+                case (ast::expr_path(_)) { true }
+                case (ast::expr_ext(_, _, _, _)) { true }
+                case (ast::expr_fail(_)) { true }
+                case (ast::expr_break) { true }
+                case (ast::expr_cont) { true }
+                case (ast::expr_ret(_)) { true }
+                case (ast::expr_put(_)) { true }
+                case (ast::expr_be(_)) { true }
+                case (ast::expr_log(_, _)) { true }
+                case (ast::expr_check(_, _)) { true }
+                case (ast::expr_if_check(_, _, _)) { false }
+                case (ast::expr_port(_)) { true }
+                case (ast::expr_chan(_)) { true }
+                case (ast::expr_anon_obj(_,_,_)) { false }
+                case (ast::expr_assert(_)) { true }
+            }
+        }
+        // We should not be calling this on a cdir.
+        case (ast::stmt_crate_directive(?cdir)) {
+            fail;
+        }
+    }
+}
+
+fn parse_block(&parser p) -> ast::block {
+    auto lo = p.get_lo_pos();
+    let vec[@ast::stmt] stmts = [];
+    let option::t[@ast::expr] expr = none;
+    expect(p, token::LBRACE);
+    while (p.peek() != token::RBRACE) {
+        alt (p.peek()) {
+            case (token::SEMI) {
+                p.bump(); // empty
+            }
+            case (_) {
+                auto stmt = parse_stmt(p);
+                alt (stmt_to_expr(stmt)) {
+                    case (some(?e)) {
+                        alt (p.peek()) {
+                            case (token::SEMI) { p.bump(); stmts += [stmt]; }
+                            case (token::RBRACE) { expr = some(e); }
+                            case (?t) {
+                                if (stmt_ends_with_semi(*stmt)) {
+                                    p.fatal("expected ';' or '}' after " +
+                                              "expression but found " +
+                                              token::to_str(p.get_reader(),
+                                                            t));
+                                    fail;
+                                }
+                                stmts += [stmt];
+                            }
+                        }
+                    }
+                    case (none) {
+                        // Not an expression statement.
+                        stmts += [stmt];
+
+                        if (p.get_file_type() == SOURCE_FILE
+                            && stmt_ends_with_semi(*stmt)) {
+                            expect(p, token::SEMI);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    auto hi = p.get_hi_pos();
+    p.bump();
+    auto bloc = rec(stmts=stmts, expr=expr, id=p.get_id());
+    ret spanned(lo, hi, bloc);
+}
+
+fn parse_ty_param(&parser p) -> ast::ty_param { ret parse_ident(p); }
+
+fn parse_ty_params(&parser p) -> vec[ast::ty_param] {
+    let vec[ast::ty_param] ty_params = [];
+    if (p.peek() == token::LBRACKET) {
+        ty_params =
+            parse_seq(token::LBRACKET, token::RBRACKET, some(token::COMMA),
+                      parse_ty_param, p).node;
+    }
+    ret ty_params;
+}
+
+fn parse_fn_decl(&parser p, ast::purity purity) -> ast::fn_decl {
+    let ast::spanned[vec[ast::arg]] inputs =
+        parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA), parse_arg,
+                  p);
+    let ty_or_bang rslt;
+    auto constrs = parse_constrs(inputs.node, p).node;
+    if (p.peek() == token::RARROW) {
+        p.bump();
+        rslt = parse_ty_or_bang(p);
+    } else {
+        rslt = a_ty(@spanned(inputs.span.lo, inputs.span.hi, ast::ty_nil));
+    }
+    alt (rslt) {
+        case (a_ty(?t)) {
+            ret rec(inputs=inputs.node,
+                    output=t,
+                    purity=purity,
+                    cf=ast::return,
+                    constraints=constrs);
+        }
+        case (a_bang) {
+            ret rec(inputs=inputs.node,
+                    output=@spanned(p.get_lo_pos(), p.get_hi_pos(),
+                                    ast::ty_bot),
+                    purity=purity,
+                    cf=ast::noreturn,
+                    constraints=constrs);
+        }
+    }
+}
+
+fn parse_fn(&parser p, ast::proto proto, ast::purity purity) -> ast::_fn {
+    auto decl = parse_fn_decl(p, purity);
+    auto body = parse_block(p);
+    ret rec(decl=decl, proto=proto, body=body);
+}
+
+fn parse_fn_header(&parser p) -> tup(ast::ident, vec[ast::ty_param]) {
+    auto id = parse_value_ident(p);
+    auto ty_params = parse_ty_params(p);
+    ret tup(id, ty_params);
+}
+
+fn mk_item(&parser p, uint lo, uint hi, &ast::ident ident, &ast::item_ node,
+           &vec[ast::attribute] attrs) -> @ast::item {
+    ret @rec(ident=ident,
+             attrs=attrs,
+             id=p.get_id(),
+             node=node,
+             span=rec(lo=lo, hi=hi));
+}
+
+fn parse_item_fn_or_iter(&parser p, ast::purity purity, ast::proto proto,
+                         vec[ast::attribute] attrs) -> @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto t = parse_fn_header(p);
+    auto f = parse_fn(p, proto, purity);
+    ret mk_item(p, lo, f.body.span.hi, t._0, ast::item_fn(f, t._1), attrs);
+}
+
+fn parse_obj_field(&parser p) -> ast::obj_field {
+    auto mut = parse_mutability(p);
+    auto ty = parse_ty(p);
+    auto ident = parse_value_ident(p);
+    ret rec(mut=mut, ty=ty, ident=ident, id=p.get_id());
+}
+
+fn parse_anon_obj_field(&parser p) -> ast::anon_obj_field {
+    auto mut = parse_mutability(p);
+    auto ty = parse_ty(p);
+    auto ident = parse_value_ident(p);
+    expect(p, token::EQ);
+    auto expr = parse_expr(p);
+    ret rec(mut=mut, ty=ty, expr=expr, ident=ident, id=p.get_id());
+}
+
+fn parse_method(&parser p) -> @ast::method {
+    auto lo = p.get_lo_pos();
+    auto proto = parse_proto(p);
+    auto ident = parse_value_ident(p);
+    auto f = parse_fn(p, proto, ast::impure_fn);
+    auto meth = rec(ident=ident, meth=f, id=p.get_id());
+    ret @spanned(lo, f.body.span.hi, meth);
+}
+
+fn parse_dtor(&parser p) -> @ast::method {
+    auto lo = p.get_last_lo_pos();
+    let ast::block b = parse_block(p);
+    let vec[ast::arg] inputs = [];
+    let @ast::ty output = @spanned(lo, lo, ast::ty_nil);
+    let ast::fn_decl d =
+        rec(inputs=inputs,
+            output=output,
+            purity=ast::impure_fn,
+            cf=ast::return,
+
+            // I guess dtors can't have constraints? 
+            constraints=[]);
+    let ast::_fn f = rec(decl=d, proto=ast::proto_fn, body=b);
+    let ast::method_ m =
+        rec(ident="drop", meth=f, id=p.get_id());
+    ret @spanned(lo, f.body.span.hi, m);
+}
+
+fn parse_item_obj(&parser p, ast::layer lyr, vec[ast::attribute] attrs) ->
+   @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto ident = parse_value_ident(p);
+    auto ty_params = parse_ty_params(p);
+    let ast::spanned[vec[ast::obj_field]] fields =
+        parse_seq[ast::obj_field](token::LPAREN, token::RPAREN,
+                                  some(token::COMMA), parse_obj_field, p);
+    let vec[@ast::method] meths = [];
+    let option::t[@ast::method] dtor = none;
+    expect(p, token::LBRACE);
+    while (p.peek() != token::RBRACE) {
+        if (eat_word(p, "drop")) {
+            dtor = some(parse_dtor(p));
+        } else { vec::push(meths, parse_method(p)); }
+    }
+    auto hi = p.get_hi_pos();
+    expect(p, token::RBRACE);
+    let ast::_obj ob = rec(fields=fields.node, methods=meths, dtor=dtor);
+    ret mk_item(p, lo, hi, ident, ast::item_obj(ob, ty_params,
+                                                p.get_id()), attrs);
+}
+
+fn parse_item_res(&parser p, ast::layer lyr, vec[ast::attribute] attrs) ->
+   @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto ident = parse_value_ident(p);
+    auto ty_params = parse_ty_params(p);
+    expect(p, token::LPAREN);
+    auto t = parse_ty(p);
+    auto arg_ident = parse_value_ident(p);
+    expect(p, token::RPAREN);
+    auto dtor = parse_block(p);
+    auto decl = rec(inputs=[rec(mode=ast::alias(false), ty=t, ident=arg_ident,
+                                id=p.get_id())],
+                    output=@spanned(lo, lo, ast::ty_nil),
+                    purity=ast::impure_fn,
+                    cf=ast::return,
+                    constraints=[]);
+    auto f = rec(decl=decl, proto=ast::proto_fn, body=dtor);
+    ret mk_item(p, lo, dtor.span.hi, ident,
+                ast::item_res(f, p.get_id(), ty_params, p.get_id()), attrs);
+}
+
+fn parse_mod_items(&parser p, token::token term,
+                   vec[ast::attribute] first_item_attrs) -> ast::_mod {
+    auto view_items = if (vec::len(first_item_attrs) == 0u) {
+        parse_view(p)
+    } else {
+        // Shouldn't be any view items since we've already parsed an item attr
+        []
+    };
+    let vec[@ast::item] items = [];
+    auto initial_attrs = first_item_attrs;
+    while (p.peek() != term) {
+        auto attrs = initial_attrs + parse_outer_attributes(p);
+        initial_attrs = [];
+        alt (parse_item(p, attrs)) {
+            case (got_item(?i)) { vec::push(items, i); }
+            case (_) {
+                p.fatal("expected item but found " +
+                          token::to_str(p.get_reader(), p.peek()));
+            }
+        }
+    }
+    ret rec(view_items=view_items, items=items);
+}
+
+fn parse_item_const(&parser p, vec[ast::attribute] attrs) -> @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto ty = parse_ty(p);
+    auto id = parse_value_ident(p);
+    expect(p, token::EQ);
+    auto e = parse_expr(p);
+    auto hi = p.get_hi_pos();
+    expect(p, token::SEMI);
+    ret mk_item(p, lo, hi, id, ast::item_const(ty, e), attrs);
+}
+
+fn parse_item_mod(&parser p, vec[ast::attribute] attrs) -> @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto id = parse_ident(p);
+    expect(p, token::LBRACE);
+    auto inner_attrs = parse_inner_attrs_and_next(p);
+    auto first_item_outer_attrs = inner_attrs._1;
+    auto m = parse_mod_items(p, token::RBRACE,
+                             first_item_outer_attrs);
+    auto hi = p.get_hi_pos();
+    expect(p, token::RBRACE);
+    ret mk_item(p, lo, hi, id, ast::item_mod(m), attrs + inner_attrs._0);
+}
+
+fn parse_item_native_type(&parser p) -> @ast::native_item {
+    auto t = parse_type_decl(p);
+    auto hi = p.get_hi_pos();
+    expect(p, token::SEMI);
+    ret @rec(ident=t._1,
+             node=ast::native_item_ty,
+             id=p.get_id(),
+             span=rec(lo=t._0, hi=hi));
+}
+
+fn parse_item_native_fn(&parser p) -> @ast::native_item {
+    auto lo = p.get_last_lo_pos();
+    auto t = parse_fn_header(p);
+    auto decl = parse_fn_decl(p, ast::impure_fn);
+    auto link_name = none;
+    if (p.peek() == token::EQ) {
+        p.bump();
+        link_name = some(parse_str(p));
+    }
+    auto hi = p.get_hi_pos();
+    expect(p, token::SEMI);
+    ret @rec(ident=t._0,
+             node=ast::native_item_fn(link_name, decl, t._1),
+             id=p.get_id(),
+             span=rec(lo=lo, hi=hi));
+}
+
+fn parse_native_item(&parser p) -> @ast::native_item {
+    parse_layer(p);
+    if (eat_word(p, "type")) {
+        ret parse_item_native_type(p);
+    } else if (eat_word(p, "fn")) {
+        ret parse_item_native_fn(p);
+    } else { unexpected(p, p.peek()); fail; }
+}
+
+fn parse_native_mod_items(&parser p, &str native_name, ast::native_abi abi) ->
+   ast::native_mod {
+    let vec[@ast::native_item] items = [];
+    auto view_items = parse_native_view(p);
+    while (p.peek() != token::RBRACE) { items += [parse_native_item(p)]; }
+    ret rec(native_name=native_name,
+            abi=abi,
+            view_items=view_items,
+            items=items);
+}
+
+fn parse_item_native_mod(&parser p, vec[ast::attribute] attrs) -> @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto abi = ast::native_abi_cdecl;
+    if (!is_word(p, "mod")) {
+        auto t = parse_str(p);
+        if (str::eq(t, "cdecl")) {
+        } else if (str::eq(t, "rust")) {
+            abi = ast::native_abi_rust;
+        } else if (str::eq(t, "llvm")) {
+            abi = ast::native_abi_llvm;
+        } else if (str::eq(t, "rust-intrinsic")) {
+            abi = ast::native_abi_rust_intrinsic;
+        } else { p.fatal("unsupported abi: " + t); fail; }
+    }
+    expect_word(p, "mod");
+    auto id = parse_ident(p);
+    auto native_name;
+    if (p.peek() == token::EQ) {
+        expect(p, token::EQ);
+        native_name = parse_str(p);
+    } else {
+        native_name = id;
+    }
+    expect(p, token::LBRACE);
+    auto m = parse_native_mod_items(p, native_name, abi);
+    auto hi = p.get_hi_pos();
+    expect(p, token::RBRACE);
+    ret mk_item(p, lo, hi, id, ast::item_native_mod(m), attrs);
+}
+
+fn parse_type_decl(&parser p) -> tup(uint, ast::ident) {
+    auto lo = p.get_last_lo_pos();
+    auto id = parse_ident(p);
+    ret tup(lo, id);
+}
+
+fn parse_item_type(&parser p, vec[ast::attribute] attrs) -> @ast::item {
+    auto t = parse_type_decl(p);
+    auto tps = parse_ty_params(p);
+    expect(p, token::EQ);
+    auto ty = parse_ty(p);
+    auto hi = p.get_hi_pos();
+    expect(p, token::SEMI);
+    ret mk_item(p, t._0, hi, t._1, ast::item_ty(ty, tps), attrs);
+}
+
+fn parse_item_tag(&parser p, vec[ast::attribute] attrs) -> @ast::item {
+    auto lo = p.get_last_lo_pos();
+    auto id = parse_ident(p);
+    auto ty_params = parse_ty_params(p);
+    let vec[ast::variant] variants = [];
+    // Newtype syntax
+    if (p.peek() == token::EQ) {
+        if (p.get_bad_expr_words().contains_key(id)) {
+            p.fatal("found " + id + " in tag constructor position");
+        }
+        p.bump();
+        auto ty = parse_ty(p);
+        expect(p, token::SEMI);
+        auto variant = spanned(ty.span.lo, ty.span.hi,
+                               rec(name=id,
+                                   args=[rec(ty=ty, id=p.get_id())],
+                                   id=p.get_id()));
+        ret mk_item(p, lo, ty.span.hi, id,
+                    ast::item_tag([variant], ty_params), attrs);
+    }
+    expect(p, token::LBRACE);
+    while (p.peek() != token::RBRACE) {
+        auto tok = p.peek();
+        alt (tok) {
+            case (token::IDENT(?name, _)) {
+                check_bad_word(p);
+                auto vlo = p.get_lo_pos();
+                p.bump();
+                let vec[ast::variant_arg] args = [];
+                alt (p.peek()) {
+                    case (token::LPAREN) {
+                        auto arg_tys =
+                            parse_seq(token::LPAREN, token::RPAREN,
+                                      some(token::COMMA), parse_ty, p);
+                        for (@ast::ty ty in arg_tys.node) {
+                            args += [rec(ty=ty, id=p.get_id())];
+                        }
+                    }
+                    case (_) {/* empty */ }
+                }
+                auto vhi = p.get_hi_pos();
+                expect(p, token::SEMI);
+                p.get_id();
+                auto vr =
+                    rec(name=p.get_str(name),
+                        args=args,
+                        id=p.get_id());
+                variants += [spanned(vlo, vhi, vr)];
+            }
+            case (token::RBRACE) {/* empty */ }
+            case (_) {
+                p.fatal("expected name of variant or '}' but found " +
+                          token::to_str(p.get_reader(), tok));
+            }
+        }
+    }
+    auto hi = p.get_hi_pos();
+    p.bump();
+    ret mk_item(p, lo, hi, id, ast::item_tag(variants, ty_params), attrs);
+}
+
+fn parse_layer(&parser p) -> ast::layer {
+    if (eat_word(p, "state")) {
+        ret ast::layer_state;
+    } else if (eat_word(p, "gc")) {
+        ret ast::layer_gc;
+    } else { ret ast::layer_value; }
+    fail;
+}
+
+fn parse_auth(&parser p) -> ast::_auth {
+    if (eat_word(p, "unsafe")) {
+        ret ast::auth_unsafe;
+    } else { unexpected(p, p.peek()); }
+    fail;
+}
+
+tag parsed_item { got_item(@ast::item); no_item; fn_no_item; }
+
+fn parse_item(&parser p, vec[ast::attribute] attrs) -> parsed_item {
+    if (eat_word(p, "const")) {
+        ret got_item(parse_item_const(p, attrs));
+    } else if (eat_word(p, "fn")) {
+        // This is an anonymous function
+
+        if (p.peek() == token::LPAREN) { ret fn_no_item; }
+        ret got_item(parse_item_fn_or_iter(p, ast::impure_fn, ast::proto_fn,
+                                           attrs));
+    } else if (eat_word(p, "pred")) {
+        ret got_item(parse_item_fn_or_iter(p, ast::pure_fn, ast::proto_fn,
+                                           attrs));
+    } else if (eat_word(p, "iter")) {
+        ret got_item(parse_item_fn_or_iter(p, ast::impure_fn, ast::proto_iter,
+                                           attrs));
+    } else if (eat_word(p, "mod")) {
+        ret got_item(parse_item_mod(p, attrs));
+    } else if (eat_word(p, "native")) {
+        ret got_item(parse_item_native_mod(p, attrs));
+    }
+    auto lyr = parse_layer(p);
+    if (eat_word(p, "type")) {
+        ret got_item(parse_item_type(p, attrs));
+    } else if (eat_word(p, "tag")) {
+        ret got_item(parse_item_tag(p, attrs));
+    } else if (eat_word(p, "obj")) {
+        ret got_item(parse_item_obj(p, lyr, attrs));
+    } else if (eat_word(p, "resource")) {
+        ret got_item(parse_item_res(p, lyr, attrs));
+    } else { ret no_item; }
+}
+
+// A type to distingush between the parsing of item attributes or syntax
+// extensions, which both begin with token.POUND
+type attr_or_ext = option::t[either::t[vec[ast::attribute],
+                                       @ast::expr]];
+
+fn parse_outer_attrs_or_ext(&parser p) -> attr_or_ext {
+    if (p.peek() == token::POUND) {
+        auto lo = p.get_lo_pos();
+        p.bump();
+        if (p.peek() == token::LBRACKET) {
+            auto first_attr = parse_attribute_naked(p, ast::attr_outer, lo);
+            ret some(left([first_attr] + parse_outer_attributes(p)));
+        } else {
+            ret some(right(parse_syntax_ext_naked(p, lo)));
+        }
+    } else {
+        ret none;
+    }
+}
+
+// Parse attributes that appear before an item
+fn parse_outer_attributes(&parser p) -> vec[ast::attribute] {
+    let vec[ast::attribute] attrs = [];
+    while (p.peek() == token::POUND) {
+        attrs += [parse_attribute(p, ast::attr_outer)];
+    }
+    ret attrs;
+}
+
+fn parse_attribute(&parser p, ast::attr_style style) -> ast::attribute {
+    auto lo = p.get_lo_pos();
+    expect(p, token::POUND);
+    ret parse_attribute_naked(p, style, lo);
+}
+
+fn parse_attribute_naked(&parser p, ast::attr_style style,
+                         uint lo) -> ast::attribute {
+    expect(p, token::LBRACKET);
+    auto meta_item = parse_meta_item(p);
+    expect(p, token::RBRACKET);
+    auto hi = p.get_hi_pos();
+    ret spanned(lo, hi, rec(style=style, value=*meta_item));
+}
+
+// Parse attributes that appear after the opening of an item, each terminated
+// by a semicolon. In addition to a vector of inner attributes, this function
+// also returns a vector that may contain the first outer attribute of the
+// next item (since we can't know whether the attribute is an inner attribute
+// of the containing item or an outer attribute of the first contained item
+// until we see the semi).
+fn parse_inner_attrs_and_next(&parser p) -> tup(vec[ast::attribute],
+                                                vec[ast::attribute]) {
+    let vec[ast::attribute] inner_attrs = [];
+    let vec[ast::attribute] next_outer_attrs = [];
+    while (p.peek() == token::POUND) {
+        auto attr = parse_attribute(p, ast::attr_inner);
+        if (p.peek() == token::SEMI) {
+            p.bump();
+            inner_attrs += [attr];
+        } else {
+            // It's not really an inner attribute
+            auto outer_attr = spanned(attr.span.lo,
+                                      attr.span.hi,
+                                      rec(style=ast::attr_outer,
+                                          value=attr.node.value));
+            next_outer_attrs += [outer_attr];
+            break;
+        }
+    }
+    ret tup(inner_attrs, next_outer_attrs);
+}
+
+fn parse_meta_item(&parser p) -> @ast::meta_item {
+    auto lo = p.get_lo_pos();
+    auto ident = parse_ident(p);
+    alt (p.peek()) {
+        case (token::EQ) {
+            p.bump();
+            alt (p.peek()) {
+                case (token::LIT_STR(?s)) {
+                    p.bump();
+                    auto value = p.get_str(s);
+                    auto hi = p.get_hi_pos();
+                    ret @spanned(lo, hi, ast::meta_name_value(ident, value));
+                }
+                case (_) {
+                    p.fatal("Metadata items must be string literals");
+                }
+            }
+        }
+        case (token::LPAREN) {
+            auto inner_items = parse_meta_seq(p);
+            auto hi = p.get_hi_pos();
+            ret @spanned(lo, hi, ast::meta_list(ident, inner_items));
+        }
+        case (_) {
+            auto hi = p.get_hi_pos();
+            ret @spanned(lo, hi, ast::meta_word(ident));
+        }
+    }
+}
+
+fn parse_meta_seq(&parser p) -> vec[@ast::meta_item] {
+    ret parse_seq(token::LPAREN, token::RPAREN, some(token::COMMA),
+                  parse_meta_item, p).node;
+}
+
+fn parse_optional_meta(&parser p) -> vec[@ast::meta_item] {
+    alt (p.peek()) {
+        case (token::LPAREN) { ret parse_meta_seq(p); }
+        case (_) { let vec[@ast::meta_item] v = []; ret v; }
+    }
+}
+
+fn parse_use(&parser p) -> @ast::view_item {
+    auto lo = p.get_last_lo_pos();
+    auto ident = parse_ident(p);
+    auto metadata = parse_optional_meta(p);
+    auto hi = p.get_hi_pos();
+    expect(p, token::SEMI);
+    auto use_decl =
+        ast::view_item_use(ident, metadata, p.get_id());
+    ret @spanned(lo, hi, use_decl);
+}
+
+fn parse_rest_import_name(&parser p, ast::ident first,
+                          option::t[ast::ident] def_ident) ->
+   @ast::view_item {
+    auto lo = p.get_lo_pos();
+    let vec[ast::ident] identifiers = [first];
+    let bool glob = false;
+    while (true) {
+        alt (p.peek()) {
+            case (token::SEMI) { p.bump(); break; }
+            case (token::MOD_SEP) {
+                if (glob) { p.fatal("cannot path into a glob"); }
+                p.bump();
+            }
+            case (_) { p.fatal("expecting '::' or ';'"); }
+        }
+        alt (p.peek()) {
+            case (token::IDENT(_, _)) { identifiers += [parse_ident(p)]; }
+            case (
+                 //the lexer can't tell the different kinds of stars apart ) :
+                 token::BINOP(token::STAR)) {
+                glob = true;
+                p.bump();
+            }
+            case (_) { p.fatal("expecting an identifier, or '*'"); }
+        }
+    }
+    auto hi = p.get_hi_pos();
+    auto import_decl;
+    alt (def_ident) {
+        case (some(?i)) {
+            if (glob) { p.fatal("globbed imports can't be renamed"); }
+            import_decl =
+                ast::view_item_import(i, identifiers, p.get_id());
+        }
+        case (_) {
+            if (glob) {
+                import_decl =
+                    ast::view_item_import_glob(identifiers, p.get_id());
+            } else {
+                auto len = vec::len(identifiers);
+                import_decl =
+                    ast::view_item_import(identifiers.(len - 1u), identifiers,
+                                          p.get_id());
+            }
+        }
+    }
+    ret @spanned(lo, hi, import_decl);
+}
+
+fn parse_full_import_name(&parser p, ast::ident def_ident) ->
+   @ast::view_item {
+    alt (p.peek()) {
+        case (token::IDENT(?i, _)) {
+            p.bump();
+            ret parse_rest_import_name(p, p.get_str(i), some(def_ident));
+        }
+        case (_) { p.fatal("expecting an identifier"); }
+    }
+    fail;
+}
+
+fn parse_import(&parser p) -> @ast::view_item {
+    alt (p.peek()) {
+        case (token::IDENT(?i, _)) {
+            p.bump();
+            alt (p.peek()) {
+                case (token::EQ) {
+                    p.bump();
+                    ret parse_full_import_name(p, p.get_str(i));
+                }
+                case (_) {
+                    ret parse_rest_import_name(p, p.get_str(i), none);
+                }
+            }
+        }
+        case (_) { p.fatal("expecting an identifier"); }
+    }
+    fail;
+}
+
+fn parse_export(&parser p) -> @ast::view_item {
+    auto lo = p.get_last_lo_pos();
+    auto id = parse_ident(p);
+    auto hi = p.get_hi_pos();
+    expect(p, token::SEMI);
+    ret @spanned(lo, hi, ast::view_item_export(id, p.get_id()));
+}
+
+fn parse_view_item(&parser p) -> @ast::view_item {
+    if (eat_word(p, "use")) {
+        ret parse_use(p);
+    } else if (eat_word(p, "import")) {
+        ret parse_import(p);
+    } else if (eat_word(p, "export")) { ret parse_export(p); } else { fail; }
+}
+
+fn is_view_item(&parser p) -> bool {
+    alt (p.peek()) {
+        case (token::IDENT(?sid, false)) {
+            auto st = p.get_str(sid);
+            ret str::eq(st, "use") || str::eq(st, "import") ||
+                    str::eq(st, "export");
+        }
+        case (_) { ret false; }
+    }
+    ret false;
+}
+
+fn parse_view(&parser p) -> vec[@ast::view_item] {
+    let vec[@ast::view_item] items = [];
+    while (is_view_item(p)) { items += [parse_view_item(p)]; }
+    ret items;
+}
+
+fn parse_native_view(&parser p) -> vec[@ast::view_item] {
+    let vec[@ast::view_item] items = [];
+    while (is_view_item(p)) { items += [parse_view_item(p)]; }
+    ret items;
+}
+
+fn parse_crate_from_source_file(&str input, &ast::crate_cfg cfg,
+                                &codemap::codemap cm) -> @ast::crate {
+    auto sess = @rec(cm=cm, mutable next_id=0);
+    auto p = new_parser(sess, cfg, input, 0u);
+    auto lo = p.get_lo_pos();
+    auto crate_attrs = parse_inner_attrs_and_next(p);
+    auto first_item_outer_attrs = crate_attrs._1;
+    auto m = parse_mod_items(p, token::EOF,
+                             first_item_outer_attrs);
+    let vec[@ast::crate_directive] cdirs = [];
+    ret @spanned(lo, p.get_lo_pos(), rec(directives=cdirs,
+                                         module=m,
+                                         attrs=crate_attrs._0,
+                                         config=p.get_cfg()));
+}
+
+fn parse_str(&parser p) -> ast::ident {
+    alt (p.peek()) {
+        case (token::LIT_STR(?s)) {
+            p.bump();
+            ret p.get_str(s);
+        }
+        case (_) { fail; }
+    }
+}
+
+// Logic for parsing crate files (.rc)
+//
+// Each crate file is a sequence of directives.
+//
+// Each directive imperatively extends its environment with 0 or more items.
+fn parse_crate_directive(&parser p, vec[ast::attribute] first_outer_attr)
+    -> ast::crate_directive {
+
+    // Collect the next attributes
+    auto outer_attrs = first_outer_attr
+        + parse_outer_attributes(p);
+    // In a crate file outer attributes are only going to apply to mods
+    auto expect_mod = vec::len(outer_attrs) > 0u;
+
+    auto lo = p.get_lo_pos();
+    if (expect_mod || is_word(p, "mod")) {
+        expect_word(p, "mod");
+        auto id = parse_ident(p);
+        auto file_opt =
+            alt (p.peek()) {
+                case (token::EQ) {
+                    p.bump();
+                    some(parse_str(p))
+                }
+                case (_) { none }
+            };
+        alt (p.peek()) {
+            case (
+                 // mod x = "foo.rs";
+                 token::SEMI) {
+                auto hi = p.get_hi_pos();
+                p.bump();
+                ret spanned(lo, hi, ast::cdir_src_mod(id, file_opt,
+                                                      outer_attrs));
+            }
+            case (
+                 // mod x = "foo_dir" { ...directives... }
+                 token::LBRACE) {
+                p.bump();
+                auto inner_attrs = parse_inner_attrs_and_next(p);
+                auto mod_attrs = outer_attrs + inner_attrs._0;
+                auto next_outer_attr = inner_attrs._1;
+                auto cdirs = parse_crate_directives(p, token::RBRACE,
+                                                    next_outer_attr);
+                auto hi = p.get_hi_pos();
+                expect(p, token::RBRACE);
+                ret spanned(lo, hi, ast::cdir_dir_mod(id, file_opt, cdirs,
+                                                      mod_attrs));
+            }
+            case (?t) { unexpected(p, t); }
+        }
+    } else if (eat_word(p, "auth")) {
+        auto n = parse_path(p);
+        expect(p, token::EQ);
+        auto a = parse_auth(p);
+        auto hi = p.get_hi_pos();
+        expect(p, token::SEMI);
+        ret spanned(lo, hi, ast::cdir_auth(n, a));
+    } else if (is_view_item(p)) {
+        auto vi = parse_view_item(p);
+        ret spanned(lo, vi.span.hi, ast::cdir_view_item(vi));
+    } else {
+        ret p.fatal("expected crate directive");
+    }
+}
+
+fn parse_crate_directives(&parser p, token::token term,
+                          vec[ast::attribute] first_outer_attr) ->
+   vec[@ast::crate_directive] {
+
+    // This is pretty ugly. If we have an outer attribute then we can't accept
+    // seeing the terminator next, so if we do see it then fail the same way
+    // parse_crate_directive would
+    if (vec::len(first_outer_attr) > 0u && p.peek() == term) {
+        expect_word(p, "mod");
+    }
+
+    let vec[@ast::crate_directive] cdirs = [];
+    while (p.peek() != term) {
+        auto cdir = @parse_crate_directive(p, first_outer_attr);
+        vec::push(cdirs, cdir);
+    }
+    ret cdirs;
+}
+
+fn parse_crate_from_crate_file(&str input, &ast::crate_cfg cfg,
+                               &codemap::codemap cm) -> @ast::crate {
+    auto sess = @rec(cm=cm, mutable next_id=0);
+    auto p = new_parser(sess, cfg, input, 0u);
+    auto lo = p.get_lo_pos();
+    auto prefix = std::fs::dirname(p.get_filemap().name);
+    auto leading_attrs = parse_inner_attrs_and_next(p);
+    auto crate_attrs = leading_attrs._0;
+    auto first_cdir_attr = leading_attrs._1;
+    auto cdirs = parse_crate_directives(p, token::EOF, first_cdir_attr);
+    let vec[str] deps = [];
+    auto cx = @rec(p=p,
+                   mode=eval::mode_parse,
+                   mutable deps=deps,
+                   sess=sess,
+                   mutable chpos=p.get_chpos(),
+                   cfg = p.get_cfg());
+    auto m =
+        eval::eval_crate_directives_to_mod(cx, cdirs, prefix);
+    auto hi = p.get_hi_pos();
+    expect(p, token::EOF);
+    ret @spanned(lo, hi, rec(directives=cdirs,
+                             module=m,
+                             attrs=crate_attrs,
+                             config=p.get_cfg()));
+}
+//
+// Local Variables:
+// mode: rust
+// fill-column: 78;
+// indent-tabs-mode: nil
+// c-basic-offset: 4
+// buffer-file-coding-system: utf-8-unix
+// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
+// End:
+//
diff --git a/src/comp/syntax/parse/token.rs b/src/comp/syntax/parse/token.rs
new file mode 100644
index 00000000000..b8ea07d50db
--- /dev/null
+++ b/src/comp/syntax/parse/token.rs
@@ -0,0 +1,202 @@
+
+import ast::ty_mach;
+import ast::ty_mach_to_str;
+import _std::new_str_hash;
+import util::interner;
+import std::int;
+import std::uint;
+import std::str;
+
+type str_num = uint;
+
+tag binop {
+    PLUS;
+    MINUS;
+    STAR;
+    SLASH;
+    PERCENT;
+    CARET;
+    AND;
+    OR;
+    LSL;
+    LSR;
+    ASR;
+}
+
+tag token {
+
+    /* Expression-operator symbols. */
+    EQ;
+    LT;
+    LE;
+    EQEQ;
+    NE;
+    GE;
+    GT;
+    ANDAND;
+    OROR;
+    NOT;
+    TILDE;
+    BINOP(binop);
+    BINOPEQ(binop);
+
+    /* Structural symbols */
+    AT;
+    DOT;
+    COMMA;
+    SEMI;
+    COLON;
+    MOD_SEP;
+    QUES;
+    RARROW;
+    SEND;
+    RECV;
+    LARROW;
+    DARROW;
+    LPAREN;
+    RPAREN;
+    LBRACKET;
+    RBRACKET;
+    LBRACE;
+    RBRACE;
+    POUND;
+
+    /* Literals */
+    LIT_INT(int);
+    LIT_UINT(uint);
+    LIT_MACH_INT(ty_mach, int);
+    LIT_FLOAT(str_num);
+    LIT_MACH_FLOAT(ty_mach, str_num);
+    LIT_STR(str_num);
+    LIT_CHAR(char);
+    LIT_BOOL(bool);
+
+    /* Name components */
+    IDENT(str_num, bool);
+    IDX(int);
+    UNDERSCORE;
+    BRACEQUOTE(str_num);
+    EOF;
+}
+
+fn binop_to_str(binop o) -> str {
+    alt (o) {
+        case (PLUS) { ret "+"; }
+        case (MINUS) { ret "-"; }
+        case (STAR) { ret "*"; }
+        case (SLASH) { ret "/"; }
+        case (PERCENT) { ret "%"; }
+        case (CARET) { ret "^"; }
+        case (AND) { ret "&"; }
+        case (OR) { ret "|"; }
+        case (LSL) { ret "<<"; }
+        case (LSR) { ret ">>"; }
+        case (ASR) { ret ">>>"; }
+    }
+}
+
+fn to_str(lexer::reader r, token t) -> str {
+    alt (t) {
+        case (EQ) { ret "="; }
+        case (LT) { ret "<"; }
+        case (LE) { ret "<="; }
+        case (EQEQ) { ret "=="; }
+        case (NE) { ret "!="; }
+        case (GE) { ret ">="; }
+        case (GT) { ret ">"; }
+        case (NOT) { ret "!"; }
+        case (TILDE) { ret "~"; }
+        case (OROR) { ret "||"; }
+        case (ANDAND) { ret "&&"; }
+        case (BINOP(?op)) { ret binop_to_str(op); }
+        case (BINOPEQ(?op)) { ret binop_to_str(op) + "="; }
+        case (
+             /* Structural symbols */
+             AT) {
+            ret "@";
+        }
+        case (DOT) { ret "."; }
+        case (COMMA) { ret ","; }
+        case (SEMI) { ret ";"; }
+        case (COLON) { ret ":"; }
+        case (MOD_SEP) { ret "::"; }
+        case (QUES) { ret "?"; }
+        case (RARROW) { ret "->"; }
+        case (SEND) { ret "<|"; }
+        case (RECV) { ret "|>"; }
+        case (LARROW) { ret "<-"; }
+        case (DARROW) { ret "<->"; }
+        case (LPAREN) { ret "("; }
+        case (RPAREN) { ret ")"; }
+        case (LBRACKET) { ret "["; }
+        case (RBRACKET) { ret "]"; }
+        case (LBRACE) { ret "{"; }
+        case (RBRACE) { ret "}"; }
+        case (POUND) { ret "#"; }
+        case (
+             /* Literals */
+             LIT_INT(?i)) {
+            ret int::to_str(i, 10u);
+        }
+        case (LIT_UINT(?u)) { ret uint::to_str(u, 10u); }
+        case (LIT_MACH_INT(?tm, ?i)) {
+            ret int::to_str(i, 10u) + "_" + ty_mach_to_str(tm);
+        }
+        case (LIT_MACH_FLOAT(?tm, ?s)) {
+            ret interner::get[str](*r.get_interner(), s) + "_" +
+                    ty_mach_to_str(tm);
+        }
+        case (LIT_FLOAT(?s)) { ret interner::get[str](*r.get_interner(), s); }
+        case (LIT_STR(?s)) {
+            // FIXME: escape.
+
+            ret "\"" + interner::get[str](*r.get_interner(), s) + "\"";
+        }
+        case (LIT_CHAR(?c)) {
+            // FIXME: escape.
+
+            auto tmp = "'";
+            str::push_char(tmp, c);
+            str::push_byte(tmp, '\'' as u8);
+            ret tmp;
+        }
+        case (LIT_BOOL(?b)) { if (b) { ret "true"; } else { ret "false"; } }
+        case (
+             /* Name components */
+             IDENT(?s, _)) {
+            ret interner::get[str](*r.get_interner(), s);
+        }
+        case (IDX(?i)) { ret "_" + int::to_str(i, 10u); }
+        case (UNDERSCORE) { ret "_"; }
+        case (BRACEQUOTE(_)) { ret "<bracequote>"; }
+        case (EOF) { ret "<eof>"; }
+    }
+}
+
+
+pred can_begin_expr(token t) -> bool {
+    alt (t) {
+        case (LPAREN) { true }
+        case (LBRACE) { true }
+        case (IDENT(_,_)) { true }
+        case (UNDERSCORE) { true }
+        case (TILDE) { true }
+        case (LIT_INT(_)) { true }
+        case (LIT_UINT(_)) { true }
+        case (LIT_MACH_INT(_,_)) { true }
+        case (LIT_FLOAT(_)) { true }
+        case (LIT_MACH_FLOAT(_,_)) { true }
+        case (LIT_STR(_)) { true }
+        case (LIT_CHAR(_)) { true }
+        case (POUND) { true }
+        case (AT) { true }
+        case (_) { false }
+    }
+}
+// Local Variables:
+// fill-column: 78;
+// indent-tabs-mode: nil
+// c-basic-offset: 4
+// buffer-file-coding-system: utf-8-unix
+// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
+// End: