about summary refs log tree commit diff
path: root/src/comp/syntax/ext
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/ext
parentc59ebf0f018b748011fc9b23ce0bab3dcfcfe733 (diff)
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/ext')
-rw-r--r--src/comp/syntax/ext/base.rs98
-rw-r--r--src/comp/syntax/ext/env.rs47
-rw-r--r--src/comp/syntax/ext/fmt.rs372
-rw-r--r--src/comp/syntax/ext/simplext.rs144
4 files changed, 661 insertions, 0 deletions
diff --git a/src/comp/syntax/ext/base.rs b/src/comp/syntax/ext/base.rs
new file mode 100644
index 00000000000..719e96f5fe6
--- /dev/null
+++ b/src/comp/syntax/ext/base.rs
@@ -0,0 +1,98 @@
+import std::vec;
+import std::option;
+import std::map::hashmap;
+import parse::parser::parse_sess;
+import codemap::span;
+import syntax::_std::new_str_hash;
+import codemap;
+
+type syntax_expander = 
+    fn(&ext_ctxt, span, &vec[@ast::expr], option::t[str]) -> @ast::expr;
+type macro_definer = fn(&ext_ctxt, span, &vec[@ast::expr],
+                        option::t[str]) -> tup(str, syntax_extension);
+
+tag syntax_extension {
+    normal(syntax_expander);
+    macro_defining(macro_definer);
+}
+
+// A temporary hard-coded map of methods for expanding syntax extension
+// AST nodes into full ASTs
+fn syntax_expander_table() -> hashmap[str, syntax_extension] {
+    auto syntax_expanders = new_str_hash[syntax_extension]();
+    syntax_expanders.insert("fmt", normal(ext::fmt::expand_syntax_ext));
+    syntax_expanders.insert("env", normal(ext::env::expand_syntax_ext));
+    syntax_expanders.insert("macro",    
+                            macro_defining(ext::simplext::add_new_extension));
+    ret syntax_expanders;
+}
+
+type span_msg_fn = fn(span, str) -> !  ;
+
+type next_id_fn = fn() -> ast::node_id ;
+
+
+// Provides a limited set of services necessary for syntax extensions
+// to do their thing
+type ext_ctxt =
+    rec(span_msg_fn span_fatal,
+        span_msg_fn span_unimpl,
+        next_id_fn next_id);
+
+fn mk_ctxt(&parse_sess sess) -> ext_ctxt {
+    fn ext_span_fatal_(&codemap::codemap cm, span sp, str msg) -> ! {
+        codemap::emit_error(option::some(sp), msg, cm);
+        fail;
+    }
+    auto ext_span_fatal = bind ext_span_fatal_(sess.cm, _, _);
+    fn ext_span_unimpl_(&codemap::codemap cm, span sp, str msg) -> ! {
+        codemap::emit_error(option::some(sp), "unimplemented " + msg, cm);
+        fail;
+    }
+    auto ext_span_unimpl = bind ext_span_unimpl_(sess.cm, _, _);
+    auto ext_next_id = bind parse::parser::next_node_id(sess);
+    ret rec(span_fatal=ext_span_fatal,
+            span_unimpl=ext_span_unimpl,
+            next_id=ext_next_id);
+}
+
+fn expr_to_str(&ext_ctxt cx, @ast::expr expr, str error) -> str {
+    alt (expr.node) {
+        case (ast::expr_lit(?l)) {
+            alt (l.node) {
+                case (ast::lit_str(?s, _)) { ret s; }
+                case (_) { cx.span_fatal(l.span, error); }
+            }
+        }
+        case (_) { cx.span_fatal(expr.span, error); }
+    }
+}
+
+fn expr_to_ident(&ext_ctxt cx, @ast::expr expr, str error) -> ast::ident {
+    alt(expr.node) {
+        case (ast::expr_path(?p)) {
+            if (vec::len(p.node.types) > 0u 
+                || vec::len(p.node.idents) != 1u) {
+                cx.span_fatal(expr.span, error);
+            } else {
+                ret p.node.idents.(0);
+            }
+        }
+        case (_) {
+            cx.span_fatal(expr.span, error);
+        }
+    }
+}
+
+
+
+//
+// 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/ext/env.rs b/src/comp/syntax/ext/env.rs
new file mode 100644
index 00000000000..7b12c045898
--- /dev/null
+++ b/src/comp/syntax/ext/env.rs
@@ -0,0 +1,47 @@
+
+
+/*
+ * The compiler code necessary to support the #env extension.  Eventually this
+ * should all get sucked into either the compiler syntax extension plugin
+ * interface.
+ */
+import std::str;
+import std::vec;
+import std::option;
+import std::generic_os;
+import base::*;
+export expand_syntax_ext;
+
+fn expand_syntax_ext(&ext_ctxt cx, codemap::span sp, &vec[@ast::expr] args,
+                     option::t[str] body) -> @ast::expr {
+    if (vec::len[@ast::expr](args) != 1u) {
+        cx.span_fatal(sp, "malformed #env call");
+    }
+    // FIXME: if this was more thorough it would manufacture an
+    // option::t[str] rather than just an maybe-empty string.
+
+    auto var = expr_to_str(cx, args.(0), "#env requires a string");
+    alt (generic_os::getenv(var)) {
+        case (option::none) { ret make_new_str(cx, sp, ""); }
+        case (option::some(?s)) { ret make_new_str(cx, sp, s); }
+    }
+}
+
+fn make_new_lit(&ext_ctxt cx, codemap::span sp, ast::lit_ lit) -> @ast::expr {
+    auto sp_lit = @rec(node=lit, span=sp);
+    ret @rec(id=cx.next_id(), node=ast::expr_lit(sp_lit), span=sp);
+}
+
+fn make_new_str(&ext_ctxt cx, codemap::span sp, str s) -> @ast::expr {
+    ret make_new_lit(cx, sp, ast::lit_str(s, ast::sk_rc));
+}
+//
+// 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/ext/fmt.rs b/src/comp/syntax/ext/fmt.rs
new file mode 100644
index 00000000000..1ea2d694374
--- /dev/null
+++ b/src/comp/syntax/ext/fmt.rs
@@ -0,0 +1,372 @@
+
+
+/*
+ * The compiler code necessary to support the #fmt extension.  Eventually this
+ * should all get sucked into either the standard library extfmt module or the
+ * compiler syntax extension plugin interface.
+ */
+import std::str;
+import std::vec;
+import std::option;
+import std::option::none;
+import std::option::some;
+import std::extfmt::ct::*;
+import base::*;
+import codemap::span;
+export expand_syntax_ext;
+
+fn expand_syntax_ext(&ext_ctxt cx, span sp, &vec[@ast::expr] args,
+                     option::t[str] body) -> @ast::expr {
+    if (vec::len[@ast::expr](args) == 0u) {
+        cx.span_fatal(sp, "#fmt requires a format string");
+    }
+    auto fmt = expr_to_str(cx, args.(0), "first argument to #fmt must be a "
+                           + "string literal.");
+    auto fmtspan = args.(0).span;
+    log "Format string:";
+    log fmt;
+    fn parse_fmt_err_(&ext_ctxt cx, span sp, str msg) -> ! {
+        cx.span_fatal(sp, msg);
+    }
+    auto parse_fmt_err = bind parse_fmt_err_(cx, fmtspan, _);
+    auto pieces = parse_fmt_string(fmt, parse_fmt_err);
+    ret pieces_to_expr(cx, sp, pieces, args);
+}
+
+// FIXME: A lot of these functions for producing expressions can probably
+// be factored out in common with other code that builds expressions.
+// FIXME: Cleanup the naming of these functions
+fn pieces_to_expr(&ext_ctxt cx, span sp, vec[piece] pieces,
+                  vec[@ast::expr] args) -> @ast::expr {
+    fn make_new_lit(&ext_ctxt cx, span sp, ast::lit_ lit) ->
+       @ast::expr {
+        auto sp_lit = @rec(node=lit, span=sp);
+        ret @rec(id=cx.next_id(), node=ast::expr_lit(sp_lit), span=sp);
+    }
+    fn make_new_str(&ext_ctxt cx, span sp, str s) -> @ast::expr {
+        auto lit = ast::lit_str(s, ast::sk_rc);
+        ret make_new_lit(cx, sp, lit);
+    }
+    fn make_new_int(&ext_ctxt cx, span sp, int i) -> @ast::expr {
+        auto lit = ast::lit_int(i);
+        ret make_new_lit(cx, sp, lit);
+    }
+    fn make_new_uint(&ext_ctxt cx, span sp, uint u) -> @ast::expr {
+        auto lit = ast::lit_uint(u);
+        ret make_new_lit(cx, sp, lit);
+    }
+    fn make_add_expr(&ext_ctxt cx, span sp, @ast::expr lhs,
+                     @ast::expr rhs) -> @ast::expr {
+        auto binexpr = ast::expr_binary(ast::add, lhs, rhs);
+        ret @rec(id=cx.next_id(), node=binexpr, span=sp);
+    }
+    fn make_path_expr(&ext_ctxt cx, span sp, vec[ast::ident] idents)
+       -> @ast::expr {
+        let vec[@ast::ty] types = [];
+        auto path = rec(idents=idents, types=types);
+        auto sp_path = rec(node=path, span=sp);
+        auto pathexpr = ast::expr_path(sp_path);
+        ret @rec(id=cx.next_id(), node=pathexpr, span=sp);
+    }
+    fn make_vec_expr(&ext_ctxt cx, span sp, vec[@ast::expr] exprs) ->
+       @ast::expr {
+        auto vecexpr = ast::expr_vec(exprs, ast::imm, ast::sk_rc);
+        ret @rec(id=cx.next_id(), node=vecexpr, span=sp);
+    }
+    fn make_call(&ext_ctxt cx, span sp, vec[ast::ident] fn_path,
+                 vec[@ast::expr] args) -> @ast::expr {
+        auto pathexpr = make_path_expr(cx, sp, fn_path);
+        auto callexpr = ast::expr_call(pathexpr, args);
+        ret @rec(id=cx.next_id(), node=callexpr, span=sp);
+    }
+    fn make_rec_expr(&ext_ctxt cx, span sp,
+                     vec[tup(ast::ident, @ast::expr)] fields) -> @ast::expr {
+        let vec[ast::field] astfields = [];
+        for (tup(ast::ident, @ast::expr) field in fields) {
+            auto ident = field._0;
+            auto val = field._1;
+            auto astfield =
+                rec(node=rec(mut=ast::imm, ident=ident, expr=val), span=sp);
+            astfields += [astfield];
+        }
+        auto recexpr = ast::expr_rec(astfields, option::none[@ast::expr]);
+        ret @rec(id=cx.next_id(), node=recexpr, span=sp);
+    }
+    fn make_path_vec(str ident) -> vec[str] {
+        // FIXME: #fmt can't currently be used from within std
+        // because we're explicitly referencing the 'std' crate here
+
+        ret ["std", "extfmt", "rt", ident];
+    }
+    fn make_rt_path_expr(&ext_ctxt cx, span sp, str ident) ->
+       @ast::expr {
+        auto path = make_path_vec(ident);
+        ret make_path_expr(cx, sp, path);
+    }
+    // Produces an AST expression that represents a RT::conv record,
+    // which tells the RT::conv* functions how to perform the conversion
+
+    fn make_rt_conv_expr(&ext_ctxt cx, span sp, &conv cnv) ->
+       @ast::expr {
+        fn make_flags(&ext_ctxt cx, span sp, vec[flag] flags) ->
+           @ast::expr {
+            let vec[@ast::expr] flagexprs = [];
+            for (flag f in flags) {
+                auto fstr;
+                alt (f) {
+                    case (flag_left_justify) { fstr = "flag_left_justify"; }
+                    case (flag_left_zero_pad) { fstr = "flag_left_zero_pad"; }
+                    case (flag_space_for_sign) {
+                        fstr = "flag_space_for_sign";
+                    }
+                    case (flag_sign_always) { fstr = "flag_sign_always"; }
+                    case (flag_alternate) { fstr = "flag_alternate"; }
+                }
+                flagexprs += [make_rt_path_expr(cx, sp, fstr)];
+            }
+            // FIXME: 0-length vectors can't have their type inferred
+            // through the rec that these flags are a member of, so
+            // this is a hack placeholder flag
+
+            if (vec::len[@ast::expr](flagexprs) == 0u) {
+                flagexprs += [make_rt_path_expr(cx, sp, "flag_none")];
+            }
+            ret make_vec_expr(cx, sp, flagexprs);
+        }
+        fn make_count(&ext_ctxt cx, span sp, &count cnt) ->
+           @ast::expr {
+            alt (cnt) {
+                case (count_implied) {
+                    ret make_rt_path_expr(cx, sp, "count_implied");
+                }
+                case (count_is(?c)) {
+                    auto count_lit = make_new_int(cx, sp, c);
+                    auto count_is_path = make_path_vec("count_is");
+                    auto count_is_args = [count_lit];
+                    ret make_call(cx, sp, count_is_path, count_is_args);
+                }
+                case (_) {
+                    cx.span_unimpl(sp, "unimplemented #fmt conversion");
+                }
+            }
+        }
+        fn make_ty(&ext_ctxt cx, span sp, &ty t) -> @ast::expr {
+            auto rt_type;
+            alt (t) {
+                case (ty_hex(?c)) {
+                    alt (c) {
+                        case (case_upper) { rt_type = "ty_hex_upper"; }
+                        case (case_lower) { rt_type = "ty_hex_lower"; }
+                    }
+                }
+                case (ty_bits) { rt_type = "ty_bits"; }
+                case (ty_octal) { rt_type = "ty_octal"; }
+                case (_) { rt_type = "ty_default"; }
+            }
+            ret make_rt_path_expr(cx, sp, rt_type);
+        }
+        fn make_conv_rec(&ext_ctxt cx, span sp, @ast::expr flags_expr,
+                         @ast::expr width_expr, @ast::expr precision_expr,
+                         @ast::expr ty_expr) -> @ast::expr {
+            ret make_rec_expr(cx, sp,
+                              [tup("flags", flags_expr),
+                               tup("width", width_expr),
+                               tup("precision", precision_expr),
+                               tup("ty", ty_expr)]);
+        }
+        auto rt_conv_flags = make_flags(cx, sp, cnv.flags);
+        auto rt_conv_width = make_count(cx, sp, cnv.width);
+        auto rt_conv_precision = make_count(cx, sp, cnv.precision);
+        auto rt_conv_ty = make_ty(cx, sp, cnv.ty);
+        ret make_conv_rec(cx, sp, rt_conv_flags, rt_conv_width,
+                          rt_conv_precision, rt_conv_ty);
+    }
+    fn make_conv_call(&ext_ctxt cx, span sp, str conv_type, &conv cnv,
+                      @ast::expr arg) -> @ast::expr {
+        auto fname = "conv_" + conv_type;
+        auto path = make_path_vec(fname);
+        auto cnv_expr = make_rt_conv_expr(cx, sp, cnv);
+        auto args = [cnv_expr, arg];
+        ret make_call(cx, arg.span, path, args);
+    }
+    fn make_new_conv(&ext_ctxt cx, span sp, conv cnv, @ast::expr arg)
+       -> @ast::expr {
+        // FIXME: Extract all this validation into extfmt::ct
+
+        fn is_signed_type(conv cnv) -> bool {
+            alt (cnv.ty) {
+                case (ty_int(?s)) {
+                    alt (s) {
+                        case (signed) { ret true; }
+                        case (unsigned) { ret false; }
+                    }
+                }
+                case (_) { ret false; }
+            }
+        }
+        auto unsupported = "conversion not supported in #fmt string";
+        alt (cnv.param) {
+            case (option::none) { }
+            case (_) { cx.span_unimpl(sp, unsupported); }
+        }
+        for (flag f in cnv.flags) {
+            alt (f) {
+                case (flag_left_justify) { }
+                case (flag_sign_always) {
+                    if (!is_signed_type(cnv)) {
+                        cx.span_fatal(sp,
+                                    "+ flag only valid in " +
+                                        "signed #fmt conversion");
+                    }
+                }
+                case (flag_space_for_sign) {
+                    if (!is_signed_type(cnv)) {
+                        cx.span_fatal(sp,
+                                    "space flag only valid in " +
+                                        "signed #fmt conversions");
+                    }
+                }
+                case (flag_left_zero_pad) { }
+                case (_) { cx.span_unimpl(sp, unsupported); }
+            }
+        }
+        alt (cnv.width) {
+            case (count_implied) { }
+            case (count_is(_)) { }
+            case (_) { cx.span_unimpl(sp, unsupported); }
+        }
+        alt (cnv.precision) {
+            case (count_implied) { }
+            case (count_is(_)) { }
+            case (_) { cx.span_unimpl(sp, unsupported); }
+        }
+        alt (cnv.ty) {
+            case (ty_str) {
+                ret make_conv_call(cx, arg.span, "str", cnv, arg);
+            }
+            case (ty_int(?sign)) {
+                alt (sign) {
+                    case (signed) {
+                        ret make_conv_call(cx, arg.span, "int", cnv, arg);
+                    }
+                    case (unsigned) {
+                        ret make_conv_call(cx, arg.span, "uint", cnv, arg);
+                    }
+                }
+            }
+            case (ty_bool) {
+                ret make_conv_call(cx, arg.span, "bool", cnv, arg);
+            }
+            case (ty_char) {
+                ret make_conv_call(cx, arg.span, "char", cnv, arg);
+            }
+            case (ty_hex(_)) {
+                ret make_conv_call(cx, arg.span, "uint", cnv, arg);
+            }
+            case (ty_bits) {
+                ret make_conv_call(cx, arg.span, "uint", cnv, arg);
+            }
+            case (ty_octal) {
+                ret make_conv_call(cx, arg.span, "uint", cnv, arg);
+            }
+            case (_) { cx.span_unimpl(sp, unsupported); }
+        }
+    }
+    fn log_conv(conv c) {
+        alt (c.param) {
+            case (some(?p)) { log "param: " + std::int::to_str(p, 10u); }
+            case (_) { log "param: none"; }
+        }
+        for (flag f in c.flags) {
+            alt (f) {
+                case (flag_left_justify) { log "flag: left justify"; }
+                case (flag_left_zero_pad) { log "flag: left zero pad"; }
+                case (flag_space_for_sign) { log "flag: left space pad"; }
+                case (flag_sign_always) { log "flag: sign always"; }
+                case (flag_alternate) { log "flag: alternate"; }
+            }
+        }
+        alt (c.width) {
+            case (count_is(?i)) {
+                log "width: count is " + std::int::to_str(i, 10u);
+            }
+            case (count_is_param(?i)) {
+                log "width: count is param " + std::int::to_str(i, 10u);
+            }
+            case (count_is_next_param) { log "width: count is next param"; }
+            case (count_implied) { log "width: count is implied"; }
+        }
+        alt (c.precision) {
+            case (count_is(?i)) {
+                log "prec: count is " + std::int::to_str(i, 10u);
+            }
+            case (count_is_param(?i)) {
+                log "prec: count is param " + std::int::to_str(i, 10u);
+            }
+            case (count_is_next_param) { log "prec: count is next param"; }
+            case (count_implied) { log "prec: count is implied"; }
+        }
+        alt (c.ty) {
+            case (ty_bool) { log "type: bool"; }
+            case (ty_str) { log "type: str"; }
+            case (ty_char) { log "type: char"; }
+            case (ty_int(?s)) {
+                alt (s) {
+                    case (signed) { log "type: signed"; }
+                    case (unsigned) { log "type: unsigned"; }
+                }
+            }
+            case (ty_bits) { log "type: bits"; }
+            case (ty_hex(?cs)) {
+                alt (cs) {
+                    case (case_upper) { log "type: uhex"; }
+                    case (case_lower) { log "type: lhex"; }
+                }
+            }
+            case (ty_octal) { log "type: octal"; }
+        }
+    }
+    auto fmt_sp = args.(0).span;
+    auto n = 0u;
+    auto tmp_expr = make_new_str(cx, sp, "");
+    auto nargs = vec::len[@ast::expr](args);
+    for (piece pc in pieces) {
+        alt (pc) {
+            case (piece_string(?s)) {
+                auto s_expr = make_new_str(cx, fmt_sp, s);
+                tmp_expr = make_add_expr(cx, fmt_sp, tmp_expr, s_expr);
+            }
+            case (piece_conv(?conv)) {
+                n += 1u;
+                if (n >= nargs) {
+                    cx.span_fatal(sp,
+                                "not enough arguments to #fmt " +
+                                    "for the given format string");
+                }
+                log "Building conversion:";
+                log_conv(conv);
+                auto arg_expr = args.(n);
+                auto c_expr = make_new_conv(cx, fmt_sp, conv, arg_expr);
+                tmp_expr = make_add_expr(cx, fmt_sp, tmp_expr, c_expr);
+            }
+        }
+    }
+    auto expected_nargs = n + 1u; // n conversions + the fmt string
+
+    if (expected_nargs < nargs) {
+        cx.span_fatal(sp,
+                    #fmt("too many arguments to #fmt. found %u, expected %u",
+                         nargs, expected_nargs));
+    }
+    ret tmp_expr;
+}
+//
+// 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/ext/simplext.rs b/src/comp/syntax/ext/simplext.rs
new file mode 100644
index 00000000000..e214524f0b7
--- /dev/null
+++ b/src/comp/syntax/ext/simplext.rs
@@ -0,0 +1,144 @@
+use std;
+
+import codemap::span;
+import std::vec;
+import std::option;
+import vec::map;
+import vec::len;
+import option::some;
+import option::none;
+
+import base::syntax_extension;
+import base::ext_ctxt;
+import base::normal;
+import base::expr_to_str;
+import base::expr_to_ident;
+
+import fold::*;
+import ast::ident;
+import ast::path_;
+import ast::expr_path;
+
+export add_new_extension;
+
+
+//temporary, until 'position' shows up in the snapshot
+fn position[T](&T x, &vec[T] v) -> option::t[uint] {
+    let uint i = 0u;
+    while (i < len(v)) {
+        if (x == v.(i)) { ret some[uint](i); }
+        i += 1u;
+    }
+    ret none[uint];
+}
+
+// substitute, in a position that's required to be an ident
+fn subst_ident(&ext_ctxt cx, &vec[@ast::expr] args, 
+               @vec[ident] param_names, &ident i, ast_fold fld) -> ident {
+    alt (position(i, *param_names)) {
+        case (some[uint](?idx)) {
+            ret expr_to_ident(cx, args.(idx), 
+                              "This argument is expanded as an "
+                              + "identifier; it must be one.");
+        }
+        case (none[uint]) {
+            ret i;
+        }
+    }
+}
+
+fn subst_path(&ext_ctxt cx, &vec[@ast::expr] args, 
+              @vec[ident] param_names, &path_ p, ast_fold fld) -> path_ {
+    // Don't substitute into qualified names.
+    if (len(p.types) > 0u || len(p.idents) != 1u) { ret p; }
+    alt (position(p.idents.(0), *param_names)) {
+        case (some[uint](?idx)) {
+            alt (args.(idx).node) {
+                case (expr_path(?new_path)) {
+                    ret new_path.node;
+                }
+                case (_) {
+                    cx.span_fatal(args.(idx).span,
+                                "This argument is expanded as a path; "
+                                + "it must be one.");
+                }
+            }
+        }
+        case (none[uint]) { ret p; }
+    }
+}
+
+
+fn subst_expr(&ext_ctxt cx, &vec[@ast::expr] args, @vec[ident] param_names, 
+              &ast::expr_ e, ast_fold fld, 
+              fn(&ast::expr_, ast_fold) -> ast::expr_ orig) -> ast::expr_ {
+    ret alt(e) {
+        case (expr_path(?p)){
+            // Don't substitute into qualified names.
+            if (len(p.node.types) > 0u || len(p.node.idents) != 1u) { e }
+            alt (position(p.node.idents.(0), *param_names)) {
+                case (some[uint](?idx)) {
+                    args.(idx).node
+                }
+                case (none[uint]) { e }
+            }
+        }
+        case (_) { orig(e,fld) }
+    }
+}
+
+
+fn add_new_extension(&ext_ctxt cx, span sp, &vec[@ast::expr] args,
+                     option::t[str] body) -> tup(str, syntax_extension) {
+    if (len(args) < 2u) {
+        cx.span_fatal(sp, "malformed extension description");
+    }
+
+    fn generic_extension(&ext_ctxt cx, span sp, &vec[@ast::expr] args,
+                         option::t[str] body, @vec[ident] param_names,
+                         @ast::expr dest_form) -> @ast::expr {
+        if (len(args) != len(*param_names)) {
+            cx.span_fatal(sp, #fmt("extension expects %u arguments, got %u",
+                                 len(*param_names), len(args)));
+        }
+
+        auto afp = default_ast_fold();
+        auto f_pre = 
+            rec(fold_ident = bind subst_ident(cx, args, param_names, _, _),
+                fold_path = bind subst_path(cx, args, param_names, _, _),
+                fold_expr = bind subst_expr(cx, args, param_names, _, _,
+                                            afp.fold_expr)
+                with *afp);
+        auto f = make_fold(f_pre);
+        auto result = f.fold_expr(dest_form);
+        dummy_out(f); //temporary: kill circular reference
+        ret result;
+        
+    }
+
+    let vec[ident] param_names = vec::empty[ident]();
+    let uint idx = 1u;
+    while(1u+idx < len(args)) {
+        param_names +=
+            [expr_to_ident(cx, args.(idx),
+                           "this parameter name must be an identifier.")];
+        idx += 1u;
+    }
+
+    ret tup(expr_to_str(cx, args.(0), "first arg must be a literal string."),
+            normal(bind generic_extension(_,_,_,_,@param_names,
+                                          args.(len(args)-1u))));
+}
+
+
+
+//
+// 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:
+//