]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! Machinery for hygienic macros, as described in the MTWT[1] paper. | |
12 | //! | |
13 | //! [1] Matthew Flatt, Ryan Culpepper, David Darais, and Robert Bruce Findler. | |
14 | //! 2012. *Macros that work together: Compile-time bindings, partial expansion, | |
15 | //! and definition contexts*. J. Funct. Program. 22, 2 (March 2012), 181-216. | |
16 | //! DOI=10.1017/S0956796812000093 http://dx.doi.org/10.1017/S0956796812000093 | |
17 | ||
18 | pub use self::SyntaxContext_::*; | |
19 | ||
20 | use ast::{Ident, Mrk, Name, SyntaxContext}; | |
21 | ||
22 | use std::cell::RefCell; | |
23 | use std::collections::HashMap; | |
24 | ||
25 | /// The SCTable contains a table of SyntaxContext_'s. It | |
26 | /// represents a flattened tree structure, to avoid having | |
27 | /// managed pointers everywhere (that caused an ICE). | |
3157f602 | 28 | /// the `marks` and `renames` fields are side-tables |
1a4d82fc | 29 | /// that ensure that adding the same mark to the same context |
3157f602 XL |
30 | /// gives you back the same context as before. This should cut |
31 | /// down on memory use *a lot*; applying a mark to a tree containing | |
32 | /// 50 identifiers would otherwise generate 50 new contexts. | |
1a4d82fc JJ |
33 | pub struct SCTable { |
34 | table: RefCell<Vec<SyntaxContext_>>, | |
3157f602 XL |
35 | marks: RefCell<HashMap<(SyntaxContext,Mrk),SyntaxContext>>, |
36 | renames: RefCell<HashMap<Name,SyntaxContext>>, | |
1a4d82fc JJ |
37 | } |
38 | ||
c34b1796 | 39 | #[derive(PartialEq, RustcEncodable, RustcDecodable, Hash, Debug, Copy, Clone)] |
1a4d82fc JJ |
40 | pub enum SyntaxContext_ { |
41 | EmptyCtxt, | |
42 | Mark (Mrk,SyntaxContext), | |
3157f602 | 43 | Rename (Name), |
1a4d82fc JJ |
44 | /// actually, IllegalCtxt may not be necessary. |
45 | IllegalCtxt | |
46 | } | |
47 | ||
48 | /// A list of ident->name renamings | |
49 | pub type RenameList = Vec<(Ident, Name)>; | |
50 | ||
51 | /// Extend a syntax context with a given mark | |
52 | pub fn apply_mark(m: Mrk, ctxt: SyntaxContext) -> SyntaxContext { | |
53 | with_sctable(|table| apply_mark_internal(m, ctxt, table)) | |
54 | } | |
55 | ||
56 | /// Extend a syntax context with a given mark and sctable (explicit memoization) | |
57 | fn apply_mark_internal(m: Mrk, ctxt: SyntaxContext, table: &SCTable) -> SyntaxContext { | |
3157f602 XL |
58 | let ctxts = &mut *table.table.borrow_mut(); |
59 | match ctxts[ctxt.0 as usize] { | |
60 | // Applying the same mark twice is a no-op. | |
61 | Mark(outer_mark, prev_ctxt) if outer_mark == m => return prev_ctxt, | |
62 | _ => *table.marks.borrow_mut().entry((ctxt, m)).or_insert_with(|| { | |
63 | SyntaxContext(idx_push(ctxts, Mark(m, ctxt))) | |
64 | }), | |
65 | } | |
1a4d82fc JJ |
66 | } |
67 | ||
68 | /// Extend a syntax context with a given rename | |
3157f602 XL |
69 | pub fn apply_rename(from: Ident, to: Name, ident: Ident) -> Ident { |
70 | with_sctable(|table| apply_rename_internal(from, to, ident, table)) | |
1a4d82fc JJ |
71 | } |
72 | ||
73 | /// Extend a syntax context with a given rename and sctable (explicit memoization) | |
3157f602 XL |
74 | fn apply_rename_internal(from: Ident, to: Name, ident: Ident, table: &SCTable) -> Ident { |
75 | if (ident.name, ident.ctxt) != (from.name, from.ctxt) { | |
76 | return ident; | |
77 | } | |
78 | let ctxt = *table.renames.borrow_mut().entry(to).or_insert_with(|| { | |
79 | SyntaxContext(idx_push(&mut *table.table.borrow_mut(), Rename(to))) | |
80 | }); | |
81 | Ident { ctxt: ctxt, ..ident } | |
1a4d82fc JJ |
82 | } |
83 | ||
84 | /// Apply a list of renamings to a context | |
85 | // if these rename lists get long, it would make sense | |
86 | // to consider memoizing this fold. This may come up | |
87 | // when we add hygiene to item names. | |
3157f602 XL |
88 | pub fn apply_renames(renames: &RenameList, ident: Ident) -> Ident { |
89 | renames.iter().fold(ident, |ident, &(from, to)| { | |
90 | apply_rename(from, to, ident) | |
1a4d82fc JJ |
91 | }) |
92 | } | |
93 | ||
94 | /// Fetch the SCTable from TLS, create one if it doesn't yet exist. | |
95 | pub fn with_sctable<T, F>(op: F) -> T where | |
96 | F: FnOnce(&SCTable) -> T, | |
97 | { | |
98 | thread_local!(static SCTABLE_KEY: SCTable = new_sctable_internal()); | |
99 | SCTABLE_KEY.with(move |slot| op(slot)) | |
100 | } | |
101 | ||
102 | // Make a fresh syntax context table with EmptyCtxt in slot zero | |
103 | // and IllegalCtxt in slot one. | |
104 | fn new_sctable_internal() -> SCTable { | |
105 | SCTable { | |
106 | table: RefCell::new(vec!(EmptyCtxt, IllegalCtxt)), | |
3157f602 XL |
107 | marks: RefCell::new(HashMap::new()), |
108 | renames: RefCell::new(HashMap::new()), | |
1a4d82fc JJ |
109 | } |
110 | } | |
111 | ||
112 | /// Print out an SCTable for debugging | |
113 | pub fn display_sctable(table: &SCTable) { | |
114 | error!("SC table:"); | |
115 | for (idx,val) in table.table.borrow().iter().enumerate() { | |
116 | error!("{:4} : {:?}",idx,val); | |
117 | } | |
118 | } | |
119 | ||
120 | /// Clear the tables from TLD to reclaim memory. | |
121 | pub fn clear_tables() { | |
122 | with_sctable(|table| { | |
123 | *table.table.borrow_mut() = Vec::new(); | |
3157f602 XL |
124 | *table.marks.borrow_mut() = HashMap::new(); |
125 | *table.renames.borrow_mut() = HashMap::new(); | |
1a4d82fc | 126 | }); |
1a4d82fc JJ |
127 | } |
128 | ||
129 | /// Reset the tables to their initial state | |
130 | pub fn reset_tables() { | |
131 | with_sctable(|table| { | |
132 | *table.table.borrow_mut() = vec!(EmptyCtxt, IllegalCtxt); | |
3157f602 XL |
133 | *table.marks.borrow_mut() = HashMap::new(); |
134 | *table.renames.borrow_mut() = HashMap::new(); | |
1a4d82fc | 135 | }); |
1a4d82fc JJ |
136 | } |
137 | ||
138 | /// Add a value to the end of a vec, return its index | |
139 | fn idx_push<T>(vec: &mut Vec<T>, val: T) -> u32 { | |
140 | vec.push(val); | |
141 | (vec.len() - 1) as u32 | |
142 | } | |
143 | ||
144 | /// Resolve a syntax object to a name, per MTWT. | |
145 | pub fn resolve(id: Ident) -> Name { | |
146 | with_sctable(|sctable| { | |
3157f602 | 147 | resolve_internal(id, sctable) |
1a4d82fc JJ |
148 | }) |
149 | } | |
150 | ||
1a4d82fc JJ |
151 | /// Resolve a syntax object to a name, per MTWT. |
152 | /// adding memoization to resolve 500+ seconds in resolve for librustc (!) | |
3157f602 XL |
153 | fn resolve_internal(id: Ident, table: &SCTable) -> Name { |
154 | match table.table.borrow()[id.ctxt.0 as usize] { | |
155 | EmptyCtxt => id.name, | |
156 | // ignore marks here: | |
157 | Mark(_, subctxt) => resolve_internal(Ident::new(id.name, subctxt), table), | |
158 | Rename(name) => name, | |
159 | IllegalCtxt => panic!("expected resolvable context, got IllegalCtxt") | |
1a4d82fc JJ |
160 | } |
161 | } | |
162 | ||
163 | /// Return the outer mark for a context with a mark at the outside. | |
164 | /// FAILS when outside is not a mark. | |
165 | pub fn outer_mark(ctxt: SyntaxContext) -> Mrk { | |
166 | with_sctable(|sctable| { | |
b039eaaf | 167 | match (*sctable.table.borrow())[ctxt.0 as usize] { |
1a4d82fc JJ |
168 | Mark(mrk, _) => mrk, |
169 | _ => panic!("can't retrieve outer mark when outside is not a mark") | |
170 | } | |
171 | }) | |
172 | } | |
173 | ||
1a4d82fc JJ |
174 | #[cfg(test)] |
175 | mod tests { | |
1a4d82fc | 176 | use ast::{EMPTY_CTXT, Ident, Mrk, Name, SyntaxContext}; |
3157f602 XL |
177 | use super::{resolve, apply_mark_internal, new_sctable_internal}; |
178 | use super::{SCTable, Mark}; | |
1a4d82fc JJ |
179 | |
180 | fn id(n: u32, s: SyntaxContext) -> Ident { | |
b039eaaf | 181 | Ident::new(Name(n), s) |
1a4d82fc JJ |
182 | } |
183 | ||
1a4d82fc JJ |
184 | // extend a syntax context with a sequence of marks given |
185 | // in a vector. v[0] will be the outermost mark. | |
186 | fn unfold_marks(mrks: Vec<Mrk> , tail: SyntaxContext, table: &SCTable) | |
187 | -> SyntaxContext { | |
188 | mrks.iter().rev().fold(tail, |tail:SyntaxContext, mrk:&Mrk| | |
189 | {apply_mark_internal(*mrk,tail,table)}) | |
190 | } | |
191 | ||
192 | #[test] fn unfold_marks_test() { | |
193 | let mut t = new_sctable_internal(); | |
194 | ||
b039eaaf | 195 | assert_eq!(unfold_marks(vec!(3,7),EMPTY_CTXT,&mut t),SyntaxContext(3)); |
1a4d82fc JJ |
196 | { |
197 | let table = t.table.borrow(); | |
b039eaaf SL |
198 | assert!((*table)[2] == Mark(7,EMPTY_CTXT)); |
199 | assert!((*table)[3] == Mark(3,SyntaxContext(2))); | |
1a4d82fc JJ |
200 | } |
201 | } | |
202 | ||
1a4d82fc JJ |
203 | #[test] |
204 | fn mtwt_resolve_test(){ | |
205 | let a = 40; | |
206 | assert_eq!(resolve(id(a,EMPTY_CTXT)),Name(a)); | |
207 | } | |
208 | ||
1a4d82fc JJ |
209 | #[test] |
210 | fn hashing_tests () { | |
211 | let mut t = new_sctable_internal(); | |
b039eaaf SL |
212 | assert_eq!(apply_mark_internal(12,EMPTY_CTXT,&mut t),SyntaxContext(2)); |
213 | assert_eq!(apply_mark_internal(13,EMPTY_CTXT,&mut t),SyntaxContext(3)); | |
1a4d82fc | 214 | // using the same one again should result in the same index: |
b039eaaf | 215 | assert_eq!(apply_mark_internal(12,EMPTY_CTXT,&mut t),SyntaxContext(2)); |
1a4d82fc JJ |
216 | // I'm assuming that the rename table will behave the same.... |
217 | } | |
1a4d82fc | 218 | } |