diff options
| author | Marijn Haverbeke <marijnh@gmail.com> | 2011-07-05 11:48:19 +0200 |
|---|---|---|
| committer | Marijn Haverbeke <marijnh@gmail.com> | 2011-07-05 15:57:21 +0200 |
| commit | 6fd6fdea93fca19f168526943c177f942212cbc6 (patch) | |
| tree | 967778dac7798a33628a4e7992f2d26492e9d71f /src/comp/syntax/ext | |
| parent | c59ebf0f018b748011fc9b23ce0bab3dcfcfe733 (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.rs | 98 | ||||
| -rw-r--r-- | src/comp/syntax/ext/env.rs | 47 | ||||
| -rw-r--r-- | src/comp/syntax/ext/fmt.rs | 372 | ||||
| -rw-r--r-- | src/comp/syntax/ext/simplext.rs | 144 |
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: +// |
