This extension can be used to concatenate string literals at compile time. C has
this useful ability when placing string literals lexically next to one another,
but this needs to be handled at the syntax extension level to recursively expand
macros.
The major use case for this is something like:
macro_rules! mylog( ($fmt:expr $($arg:tt)*) => {
error2!(concat!(file!(), ":", line!(), " - ", $fmt) $($arg)*);
})
Where the mylog macro will automatically prepend the filename/line number to the
beginning of every log message.
663 lines
24 KiB
Rust
663 lines
24 KiB
Rust
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
|
|
// file at the top-level directory of this distribution and at
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
use ast;
|
|
use ast::Name;
|
|
use codemap;
|
|
use codemap::{CodeMap, Span, ExpnInfo};
|
|
use diagnostic::span_handler;
|
|
use ext;
|
|
use ext::expand;
|
|
use parse;
|
|
use parse::token;
|
|
use parse::token::{ident_to_str, intern, str_to_ident};
|
|
|
|
use std::hashmap::HashMap;
|
|
|
|
// new-style macro! tt code:
|
|
//
|
|
// MacResult, NormalTT, IdentTT
|
|
//
|
|
// also note that ast::mac used to have a bunch of extraneous cases and
|
|
// is now probably a redundant AST node, can be merged with
|
|
// ast::mac_invoc_tt.
|
|
|
|
pub struct MacroDef {
|
|
name: @str,
|
|
ext: SyntaxExtension
|
|
}
|
|
|
|
pub type ItemDecorator = extern "Rust" fn(@ExtCtxt,
|
|
Span,
|
|
@ast::MetaItem,
|
|
~[@ast::item])
|
|
-> ~[@ast::item];
|
|
|
|
pub struct SyntaxExpanderTT {
|
|
expander: SyntaxExpanderTTExpander,
|
|
span: Option<Span>
|
|
}
|
|
|
|
pub trait SyntaxExpanderTTTrait {
|
|
fn expand(&self,
|
|
ecx: @ExtCtxt,
|
|
span: Span,
|
|
token_tree: &[ast::token_tree],
|
|
context: ast::SyntaxContext)
|
|
-> MacResult;
|
|
}
|
|
|
|
pub type SyntaxExpanderTTFunNoCtxt =
|
|
extern "Rust" fn(ecx: @ExtCtxt,
|
|
span: codemap::Span,
|
|
token_tree: &[ast::token_tree])
|
|
-> MacResult;
|
|
|
|
enum SyntaxExpanderTTExpander {
|
|
SyntaxExpanderTTExpanderWithoutContext(SyntaxExpanderTTFunNoCtxt),
|
|
}
|
|
|
|
impl SyntaxExpanderTTTrait for SyntaxExpanderTT {
|
|
fn expand(&self,
|
|
ecx: @ExtCtxt,
|
|
span: Span,
|
|
token_tree: &[ast::token_tree],
|
|
_: ast::SyntaxContext)
|
|
-> MacResult {
|
|
match self.expander {
|
|
SyntaxExpanderTTExpanderWithoutContext(f) => {
|
|
f(ecx, span, token_tree)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SyntaxExpanderTTItemExpander {
|
|
SyntaxExpanderTTItemExpanderWithContext(SyntaxExpanderTTItemFun),
|
|
SyntaxExpanderTTItemExpanderWithoutContext(SyntaxExpanderTTItemFunNoCtxt),
|
|
}
|
|
|
|
pub struct SyntaxExpanderTTItem {
|
|
expander: SyntaxExpanderTTItemExpander,
|
|
span: Option<Span>
|
|
}
|
|
|
|
pub trait SyntaxExpanderTTItemTrait {
|
|
fn expand(&self,
|
|
cx: @ExtCtxt,
|
|
sp: Span,
|
|
ident: ast::Ident,
|
|
token_tree: ~[ast::token_tree],
|
|
context: ast::SyntaxContext)
|
|
-> MacResult;
|
|
}
|
|
|
|
impl SyntaxExpanderTTItemTrait for SyntaxExpanderTTItem {
|
|
fn expand(&self,
|
|
cx: @ExtCtxt,
|
|
sp: Span,
|
|
ident: ast::Ident,
|
|
token_tree: ~[ast::token_tree],
|
|
context: ast::SyntaxContext)
|
|
-> MacResult {
|
|
match self.expander {
|
|
SyntaxExpanderTTItemExpanderWithContext(fun) => {
|
|
fun(cx, sp, ident, token_tree, context)
|
|
}
|
|
SyntaxExpanderTTItemExpanderWithoutContext(fun) => {
|
|
fun(cx, sp, ident, token_tree)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type SyntaxExpanderTTItemFun = extern "Rust" fn(@ExtCtxt,
|
|
Span,
|
|
ast::Ident,
|
|
~[ast::token_tree],
|
|
ast::SyntaxContext)
|
|
-> MacResult;
|
|
|
|
pub type SyntaxExpanderTTItemFunNoCtxt =
|
|
extern "Rust" fn(@ExtCtxt, Span, ast::Ident, ~[ast::token_tree])
|
|
-> MacResult;
|
|
|
|
pub trait AnyMacro {
|
|
fn make_expr(&self) -> @ast::Expr;
|
|
fn make_item(&self) -> Option<@ast::item>;
|
|
fn make_stmt(&self) -> @ast::Stmt;
|
|
}
|
|
|
|
pub enum MacResult {
|
|
MRExpr(@ast::Expr),
|
|
MRItem(@ast::item),
|
|
MRAny(@AnyMacro),
|
|
MRDef(MacroDef),
|
|
}
|
|
|
|
pub enum SyntaxExtension {
|
|
// #[auto_encode] and such
|
|
ItemDecorator(ItemDecorator),
|
|
|
|
// Token-tree expanders
|
|
NormalTT(@SyntaxExpanderTTTrait, Option<Span>),
|
|
|
|
// An IdentTT is a macro that has an
|
|
// identifier in between the name of the
|
|
// macro and the argument. Currently,
|
|
// the only examples of this is
|
|
// macro_rules!
|
|
|
|
// perhaps macro_rules! will lose its odd special identifier argument,
|
|
// and this can go away also
|
|
IdentTT(@SyntaxExpanderTTItemTrait, Option<Span>),
|
|
}
|
|
|
|
|
|
// The SyntaxEnv is the environment that's threaded through the expansion
|
|
// of macros. It contains bindings for macros, and also a special binding
|
|
// for " block" (not a legal identifier) that maps to a BlockInfo
|
|
pub type SyntaxEnv = @mut MapChain<Name, Transformer>;
|
|
|
|
// Transformer : the codomain of SyntaxEnvs
|
|
|
|
pub enum Transformer {
|
|
// this identifier maps to a syntax extension or macro
|
|
SE(SyntaxExtension),
|
|
// blockinfo : this is ... well, it's simpler than threading
|
|
// another whole data stack-structured data structure through
|
|
// expansion. Basically, there's an invariant that every
|
|
// map must contain a binding for " block".
|
|
BlockInfo(BlockInfo)
|
|
}
|
|
|
|
pub struct BlockInfo {
|
|
// should macros escape from this scope?
|
|
macros_escape : bool,
|
|
// what are the pending renames?
|
|
pending_renames : @mut RenameList
|
|
}
|
|
|
|
// a list of ident->name renamings
|
|
type RenameList = ~[(ast::Ident,Name)];
|
|
|
|
// The base map of methods for expanding syntax extension
|
|
// AST nodes into full ASTs
|
|
pub fn syntax_expander_table() -> SyntaxEnv {
|
|
// utility function to simplify creating NormalTT syntax extensions
|
|
fn builtin_normal_tt_no_ctxt(f: SyntaxExpanderTTFunNoCtxt)
|
|
-> @Transformer {
|
|
@SE(NormalTT(@SyntaxExpanderTT{
|
|
expander: SyntaxExpanderTTExpanderWithoutContext(f),
|
|
span: None,
|
|
} as @SyntaxExpanderTTTrait,
|
|
None))
|
|
}
|
|
// utility function to simplify creating IdentTT syntax extensions
|
|
// that ignore their contexts
|
|
fn builtin_item_tt_no_ctxt(f: SyntaxExpanderTTItemFunNoCtxt) -> @Transformer {
|
|
@SE(IdentTT(@SyntaxExpanderTTItem {
|
|
expander: SyntaxExpanderTTItemExpanderWithoutContext(f),
|
|
span: None,
|
|
} as @SyntaxExpanderTTItemTrait,
|
|
None))
|
|
}
|
|
let mut syntax_expanders = HashMap::new();
|
|
// NB identifier starts with space, and can't conflict with legal idents
|
|
syntax_expanders.insert(intern(&" block"),
|
|
@BlockInfo(BlockInfo{
|
|
macros_escape : false,
|
|
pending_renames : @mut ~[]
|
|
}));
|
|
syntax_expanders.insert(intern(&"macro_rules"),
|
|
@SE(IdentTT(@SyntaxExpanderTTItem {
|
|
expander: SyntaxExpanderTTItemExpanderWithContext(
|
|
ext::tt::macro_rules::add_new_extension),
|
|
span: None,
|
|
} as @SyntaxExpanderTTItemTrait,
|
|
None)));
|
|
syntax_expanders.insert(intern(&"fmt"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::fmt::expand_syntax_ext));
|
|
syntax_expanders.insert(intern(&"format_args"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::format::expand_args));
|
|
syntax_expanders.insert(
|
|
intern(&"auto_encode"),
|
|
@SE(ItemDecorator(ext::auto_encode::expand_auto_encode)));
|
|
syntax_expanders.insert(
|
|
intern(&"auto_decode"),
|
|
@SE(ItemDecorator(ext::auto_encode::expand_auto_decode)));
|
|
syntax_expanders.insert(intern(&"env"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::env::expand_env));
|
|
syntax_expanders.insert(intern(&"option_env"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::env::expand_option_env));
|
|
syntax_expanders.insert(intern("bytes"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::bytes::expand_syntax_ext));
|
|
syntax_expanders.insert(intern("concat_idents"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::concat_idents::expand_syntax_ext));
|
|
syntax_expanders.insert(intern("concat"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::concat::expand_syntax_ext));
|
|
syntax_expanders.insert(intern(&"log_syntax"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::log_syntax::expand_syntax_ext));
|
|
syntax_expanders.insert(intern(&"deriving"),
|
|
@SE(ItemDecorator(
|
|
ext::deriving::expand_meta_deriving)));
|
|
|
|
// Quasi-quoting expanders
|
|
syntax_expanders.insert(intern(&"quote_tokens"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::quote::expand_quote_tokens));
|
|
syntax_expanders.insert(intern(&"quote_expr"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::quote::expand_quote_expr));
|
|
syntax_expanders.insert(intern(&"quote_ty"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::quote::expand_quote_ty));
|
|
syntax_expanders.insert(intern(&"quote_item"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::quote::expand_quote_item));
|
|
syntax_expanders.insert(intern(&"quote_pat"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::quote::expand_quote_pat));
|
|
syntax_expanders.insert(intern(&"quote_stmt"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::quote::expand_quote_stmt));
|
|
|
|
syntax_expanders.insert(intern(&"line"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_line));
|
|
syntax_expanders.insert(intern(&"col"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_col));
|
|
syntax_expanders.insert(intern(&"file"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_file));
|
|
syntax_expanders.insert(intern(&"stringify"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_stringify));
|
|
syntax_expanders.insert(intern(&"include"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_include));
|
|
syntax_expanders.insert(intern(&"include_str"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_include_str));
|
|
syntax_expanders.insert(intern(&"include_bin"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_include_bin));
|
|
syntax_expanders.insert(intern(&"module_path"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::source_util::expand_mod));
|
|
syntax_expanders.insert(intern(&"asm"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::asm::expand_asm));
|
|
syntax_expanders.insert(intern(&"cfg"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::cfg::expand_cfg));
|
|
syntax_expanders.insert(intern(&"trace_macros"),
|
|
builtin_normal_tt_no_ctxt(
|
|
ext::trace_macros::expand_trace_macros));
|
|
MapChain::new(~syntax_expanders)
|
|
}
|
|
|
|
// One of these is made during expansion and incrementally updated as we go;
|
|
// when a macro expansion occurs, the resulting nodes have the backtrace()
|
|
// -> expn_info of their expansion context stored into their span.
|
|
pub struct ExtCtxt {
|
|
parse_sess: @mut parse::ParseSess,
|
|
cfg: ast::CrateConfig,
|
|
backtrace: @mut Option<@ExpnInfo>,
|
|
|
|
// These two @mut's should really not be here,
|
|
// but the self types for CtxtRepr are all wrong
|
|
// and there are bugs in the code for object
|
|
// types that make this hard to get right at the
|
|
// moment. - nmatsakis
|
|
mod_path: @mut ~[ast::Ident],
|
|
trace_mac: @mut bool
|
|
}
|
|
|
|
impl ExtCtxt {
|
|
pub fn new(parse_sess: @mut parse::ParseSess, cfg: ast::CrateConfig)
|
|
-> @ExtCtxt {
|
|
@ExtCtxt {
|
|
parse_sess: parse_sess,
|
|
cfg: cfg,
|
|
backtrace: @mut None,
|
|
mod_path: @mut ~[],
|
|
trace_mac: @mut false
|
|
}
|
|
}
|
|
|
|
pub fn expand_expr(@self, mut e: @ast::Expr) -> @ast::Expr {
|
|
loop {
|
|
match e.node {
|
|
ast::ExprMac(*) => {
|
|
let extsbox = @mut syntax_expander_table();
|
|
let expander = expand::MacroExpander {
|
|
extsbox: extsbox,
|
|
cx: self,
|
|
};
|
|
e = expand::expand_expr(extsbox, self, e, &expander);
|
|
}
|
|
_ => return e
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn codemap(&self) -> @CodeMap { self.parse_sess.cm }
|
|
pub fn parse_sess(&self) -> @mut parse::ParseSess { self.parse_sess }
|
|
pub fn cfg(&self) -> ast::CrateConfig { self.cfg.clone() }
|
|
pub fn call_site(&self) -> Span {
|
|
match *self.backtrace {
|
|
Some(@ExpnInfo {call_site: cs, _}) => cs,
|
|
None => self.bug("missing top span")
|
|
}
|
|
}
|
|
pub fn print_backtrace(&self) { }
|
|
pub fn backtrace(&self) -> Option<@ExpnInfo> { *self.backtrace }
|
|
pub fn mod_push(&self, i: ast::Ident) { self.mod_path.push(i); }
|
|
pub fn mod_pop(&self) { self.mod_path.pop(); }
|
|
pub fn mod_path(&self) -> ~[ast::Ident] { (*self.mod_path).clone() }
|
|
pub fn bt_push(&self, ei: codemap::ExpnInfo) {
|
|
match ei {
|
|
ExpnInfo {call_site: cs, callee: ref callee} => {
|
|
*self.backtrace =
|
|
Some(@ExpnInfo {
|
|
call_site: Span {lo: cs.lo, hi: cs.hi,
|
|
expn_info: *self.backtrace},
|
|
callee: *callee});
|
|
}
|
|
}
|
|
}
|
|
pub fn bt_pop(&self) {
|
|
match *self.backtrace {
|
|
Some(@ExpnInfo {
|
|
call_site: Span {expn_info: prev, _}, _}) => {
|
|
*self.backtrace = prev
|
|
}
|
|
_ => self.bug("tried to pop without a push")
|
|
}
|
|
}
|
|
pub fn span_fatal(&self, sp: Span, msg: &str) -> ! {
|
|
self.print_backtrace();
|
|
self.parse_sess.span_diagnostic.span_fatal(sp, msg);
|
|
}
|
|
pub fn span_err(&self, sp: Span, msg: &str) {
|
|
self.print_backtrace();
|
|
self.parse_sess.span_diagnostic.span_err(sp, msg);
|
|
}
|
|
pub fn span_warn(&self, sp: Span, msg: &str) {
|
|
self.print_backtrace();
|
|
self.parse_sess.span_diagnostic.span_warn(sp, msg);
|
|
}
|
|
pub fn span_unimpl(&self, sp: Span, msg: &str) -> ! {
|
|
self.print_backtrace();
|
|
self.parse_sess.span_diagnostic.span_unimpl(sp, msg);
|
|
}
|
|
pub fn span_bug(&self, sp: Span, msg: &str) -> ! {
|
|
self.print_backtrace();
|
|
self.parse_sess.span_diagnostic.span_bug(sp, msg);
|
|
}
|
|
pub fn bug(&self, msg: &str) -> ! {
|
|
self.print_backtrace();
|
|
self.parse_sess.span_diagnostic.handler().bug(msg);
|
|
}
|
|
pub fn trace_macros(&self) -> bool {
|
|
*self.trace_mac
|
|
}
|
|
pub fn set_trace_macros(&self, x: bool) {
|
|
*self.trace_mac = x
|
|
}
|
|
pub fn str_of(&self, id: ast::Ident) -> @str {
|
|
ident_to_str(&id)
|
|
}
|
|
pub fn ident_of(&self, st: &str) -> ast::Ident {
|
|
str_to_ident(st)
|
|
}
|
|
}
|
|
|
|
pub fn expr_to_str(cx: @ExtCtxt, expr: @ast::Expr, err_msg: &str) -> (@str, ast::StrStyle) {
|
|
match expr.node {
|
|
ast::ExprLit(l) => match l.node {
|
|
ast::lit_str(s, style) => (s, style),
|
|
_ => cx.span_fatal(l.span, err_msg)
|
|
},
|
|
_ => cx.span_fatal(expr.span, err_msg)
|
|
}
|
|
}
|
|
|
|
pub fn check_zero_tts(cx: @ExtCtxt, sp: Span, tts: &[ast::token_tree],
|
|
name: &str) {
|
|
if tts.len() != 0 {
|
|
cx.span_fatal(sp, format!("{} takes no arguments", name));
|
|
}
|
|
}
|
|
|
|
pub fn get_single_str_from_tts(cx: @ExtCtxt,
|
|
sp: Span,
|
|
tts: &[ast::token_tree],
|
|
name: &str)
|
|
-> @str {
|
|
if tts.len() != 1 {
|
|
cx.span_fatal(sp, format!("{} takes 1 argument.", name));
|
|
}
|
|
|
|
match tts[0] {
|
|
ast::tt_tok(_, token::LIT_STR(ident))
|
|
| ast::tt_tok(_, token::LIT_STR_RAW(ident, _)) => cx.str_of(ident),
|
|
_ => cx.span_fatal(sp, format!("{} requires a string.", name)),
|
|
}
|
|
}
|
|
|
|
pub fn get_exprs_from_tts(cx: @ExtCtxt,
|
|
sp: Span,
|
|
tts: &[ast::token_tree]) -> ~[@ast::Expr] {
|
|
let p = parse::new_parser_from_tts(cx.parse_sess(),
|
|
cx.cfg(),
|
|
tts.to_owned());
|
|
let mut es = ~[];
|
|
while *p.token != token::EOF {
|
|
if es.len() != 0 && !p.eat(&token::COMMA) {
|
|
cx.span_fatal(sp, "expected token: `,`");
|
|
}
|
|
es.push(p.parse_expr());
|
|
}
|
|
es
|
|
}
|
|
|
|
// in order to have some notion of scoping for macros,
|
|
// we want to implement the notion of a transformation
|
|
// environment.
|
|
|
|
// This environment maps Names to Transformers.
|
|
// Initially, this includes macro definitions and
|
|
// block directives.
|
|
|
|
|
|
|
|
// Actually, the following implementation is parameterized
|
|
// by both key and value types.
|
|
|
|
//impl question: how to implement it? Initially, the
|
|
// env will contain only macros, so it might be painful
|
|
// to add an empty frame for every context. Let's just
|
|
// get it working, first....
|
|
|
|
// NB! the mutability of the underlying maps means that
|
|
// if expansion is out-of-order, a deeper scope may be
|
|
// able to refer to a macro that was added to an enclosing
|
|
// scope lexically later than the deeper scope.
|
|
|
|
// Note on choice of representation: I've been pushed to
|
|
// use a top-level managed pointer by some difficulties
|
|
// with pushing and popping functionally, and the ownership
|
|
// issues. As a result, the values returned by the table
|
|
// also need to be managed; the &'self ... type that Maps
|
|
// return won't work for things that need to get outside
|
|
// of that managed pointer. The easiest way to do this
|
|
// is just to insist that the values in the tables are
|
|
// managed to begin with.
|
|
|
|
// a transformer env is either a base map or a map on top
|
|
// of another chain.
|
|
pub enum MapChain<K,V> {
|
|
BaseMapChain(~HashMap<K,@V>),
|
|
ConsMapChain(~HashMap<K,@V>,@mut MapChain<K,V>)
|
|
}
|
|
|
|
|
|
// get the map from an env frame
|
|
impl <K: Eq + Hash + IterBytes + 'static, V: 'static> MapChain<K,V>{
|
|
// Constructor. I don't think we need a zero-arg one.
|
|
pub fn new(init: ~HashMap<K,@V>) -> @mut MapChain<K,V> {
|
|
@mut BaseMapChain(init)
|
|
}
|
|
|
|
// add a new frame to the environment (functionally)
|
|
pub fn push_frame (@mut self) -> @mut MapChain<K,V> {
|
|
@mut ConsMapChain(~HashMap::new() ,self)
|
|
}
|
|
|
|
// no need for pop, it'll just be functional.
|
|
|
|
// utility fn...
|
|
|
|
// ugh: can't get this to compile with mut because of the
|
|
// lack of flow sensitivity.
|
|
pub fn get_map<'a>(&'a self) -> &'a HashMap<K,@V> {
|
|
match *self {
|
|
BaseMapChain (~ref map) => map,
|
|
ConsMapChain (~ref map,_) => map
|
|
}
|
|
}
|
|
|
|
// traits just don't work anywhere...?
|
|
//impl Map<Name,SyntaxExtension> for MapChain {
|
|
|
|
pub fn contains_key (&self, key: &K) -> bool {
|
|
match *self {
|
|
BaseMapChain (ref map) => map.contains_key(key),
|
|
ConsMapChain (ref map,ref rest) =>
|
|
(map.contains_key(key)
|
|
|| rest.contains_key(key))
|
|
}
|
|
}
|
|
// should each_key and each_value operate on shadowed
|
|
// names? I think not.
|
|
// delaying implementing this....
|
|
pub fn each_key (&self, _f: &fn (&K)->bool) {
|
|
fail!("unimplemented 2013-02-15T10:01");
|
|
}
|
|
|
|
pub fn each_value (&self, _f: &fn (&V) -> bool) {
|
|
fail!("unimplemented 2013-02-15T10:02");
|
|
}
|
|
|
|
// Returns a copy of the value that the name maps to.
|
|
// Goes down the chain 'til it finds one (or bottom out).
|
|
pub fn find (&self, key: &K) -> Option<@V> {
|
|
match self.get_map().find (key) {
|
|
Some(ref v) => Some(**v),
|
|
None => match *self {
|
|
BaseMapChain (_) => None,
|
|
ConsMapChain (_,ref rest) => rest.find(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn find_in_topmost_frame(&self, key: &K) -> Option<@V> {
|
|
let map = match *self {
|
|
BaseMapChain(ref map) => map,
|
|
ConsMapChain(ref map,_) => map
|
|
};
|
|
// strip one layer of indirection off the pointer.
|
|
map.find(key).map(|r| {*r})
|
|
}
|
|
|
|
// insert the binding into the top-level map
|
|
pub fn insert (&mut self, key: K, ext: @V) -> bool {
|
|
// can't abstract over get_map because of flow sensitivity...
|
|
match *self {
|
|
BaseMapChain (~ref mut map) => map.insert(key, ext),
|
|
ConsMapChain (~ref mut map,_) => map.insert(key,ext)
|
|
}
|
|
}
|
|
// insert the binding into the topmost frame for which the binding
|
|
// associated with 'n' exists and satisfies pred
|
|
// ... there are definitely some opportunities for abstraction
|
|
// here that I'm ignoring. (e.g., manufacturing a predicate on
|
|
// the maps in the chain, and using an abstract "find".
|
|
pub fn insert_into_frame(&mut self, key: K, ext: @V, n: K, pred: &fn(&@V)->bool) {
|
|
match *self {
|
|
BaseMapChain (~ref mut map) => {
|
|
if satisfies_pred(map,&n,pred) {
|
|
map.insert(key,ext);
|
|
} else {
|
|
fail!("expected map chain containing satisfying frame")
|
|
}
|
|
},
|
|
ConsMapChain (~ref mut map, rest) => {
|
|
if satisfies_pred(map,&n,|v|pred(v)) {
|
|
map.insert(key,ext);
|
|
} else {
|
|
rest.insert_into_frame(key,ext,n,pred)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns true if the binding for 'n' satisfies 'pred' in 'map'
|
|
fn satisfies_pred<K : Eq + Hash + IterBytes,V>(map : &mut HashMap<K,V>,
|
|
n: &K,
|
|
pred: &fn(&V)->bool)
|
|
-> bool {
|
|
match map.find(n) {
|
|
Some(ref v) => (pred(*v)),
|
|
None => false
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::MapChain;
|
|
use std::hashmap::HashMap;
|
|
|
|
#[test] fn testenv () {
|
|
let mut a = HashMap::new();
|
|
a.insert (@"abc",@15);
|
|
let m = MapChain::new(~a);
|
|
m.insert (@"def",@16);
|
|
assert_eq!(m.find(&@"abc"),Some(@15));
|
|
assert_eq!(m.find(&@"def"),Some(@16));
|
|
assert_eq!(*(m.find(&@"abc").unwrap()),15);
|
|
assert_eq!(*(m.find(&@"def").unwrap()),16);
|
|
let n = m.push_frame();
|
|
// old bindings are still present:
|
|
assert_eq!(*(n.find(&@"abc").unwrap()),15);
|
|
assert_eq!(*(n.find(&@"def").unwrap()),16);
|
|
n.insert (@"def",@17);
|
|
// n shows the new binding
|
|
assert_eq!(*(n.find(&@"abc").unwrap()),15);
|
|
assert_eq!(*(n.find(&@"def").unwrap()),17);
|
|
// ... but m still has the old ones
|
|
assert_eq!(m.find(&@"abc"),Some(@15));
|
|
assert_eq!(m.find(&@"def"),Some(@16));
|
|
assert_eq!(*(m.find(&@"abc").unwrap()),15);
|
|
assert_eq!(*(m.find(&@"def").unwrap()),16);
|
|
}
|
|
}
|