1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
// Copyright 2012-2014 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.

//! Machinery for hygienic macros, as described in the MTWT[1] paper.
//!
//! [1] Matthew Flatt, Ryan Culpepper, David Darais, and Robert Bruce Findler.
//! 2012. *Macros that work together: Compile-time bindings, partial expansion,
//! and definition contexts*. J. Funct. Program. 22, 2 (March 2012), 181-216.
//! DOI=10.1017/S0956796812000093 http://dx.doi.org/10.1017/S0956796812000093

pub use self::SyntaxContext_::*;

use ast::{Ident, Mrk, Name, SyntaxContext};

use std::cell::RefCell;
use std::collections::HashMap;

/// The SCTable contains a table of SyntaxContext_'s. It
/// represents a flattened tree structure, to avoid having
/// managed pointers everywhere (that caused an ICE).
/// the mark_memo and rename_memo fields are side-tables
/// that ensure that adding the same mark to the same context
/// gives you back the same context as before. This shouldn't
/// change the semantics--everything here is immutable--but
/// it should cut down on memory use *a lot*; applying a mark
/// to a tree containing 50 identifiers would otherwise generate
/// 50 new contexts
pub struct SCTable {
    table: RefCell<Vec<SyntaxContext_>>,
    mark_memo: RefCell<HashMap<(SyntaxContext,Mrk),SyntaxContext>>,
    rename_memo: RefCell<HashMap<(SyntaxContext,Ident,Name),SyntaxContext>>,
}

#[derive(PartialEq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum SyntaxContext_ {
    EmptyCtxt,
    Mark (Mrk,SyntaxContext),
    /// flattening the name and syntaxcontext into the rename...
    /// HIDDEN INVARIANTS:
    /// 1) the first name in a Rename node
    /// can only be a programmer-supplied name.
    /// 2) Every Rename node with a given Name in the
    /// "to" slot must have the same name and context
    /// in the "from" slot. In essence, they're all
    /// pointers to a single "rename" event node.
    Rename (Ident,Name,SyntaxContext),
    /// actually, IllegalCtxt may not be necessary.
    IllegalCtxt
}

/// A list of ident->name renamings
pub type RenameList = Vec<(Ident, Name)>;

/// Extend a syntax context with a given mark
pub fn apply_mark(m: Mrk, ctxt: SyntaxContext) -> SyntaxContext {
    with_sctable(|table| apply_mark_internal(m, ctxt, table))
}

/// Extend a syntax context with a given mark and sctable (explicit memoization)
fn apply_mark_internal(m: Mrk, ctxt: SyntaxContext, table: &SCTable) -> SyntaxContext {
    let key = (ctxt, m);
    * table.mark_memo.borrow_mut().entry(key).get().unwrap_or_else(
          |vacant_entry|
              vacant_entry.insert(idx_push(&mut *table.table.borrow_mut(), Mark(m, ctxt))))
}

/// Extend a syntax context with a given rename
pub fn apply_rename(id: Ident, to:Name,
                  ctxt: SyntaxContext) -> SyntaxContext {
    with_sctable(|table| apply_rename_internal(id, to, ctxt, table))
}

/// Extend a syntax context with a given rename and sctable (explicit memoization)
fn apply_rename_internal(id: Ident,
                       to: Name,
                       ctxt: SyntaxContext,
                       table: &SCTable) -> SyntaxContext {
    let key = (ctxt, id, to);

    * table.rename_memo.borrow_mut().entry(key).get().unwrap_or_else(
          |vacant_entry|
              vacant_entry.insert(idx_push(&mut *table.table.borrow_mut(), Rename(id, to, ctxt))))
}

/// Apply a list of renamings to a context
// if these rename lists get long, it would make sense
// to consider memoizing this fold. This may come up
// when we add hygiene to item names.
pub fn apply_renames(renames: &RenameList, ctxt: SyntaxContext) -> SyntaxContext {
    renames.iter().fold(ctxt, |ctxt, &(from, to)| {
        apply_rename(from, to, ctxt)
    })
}

/// Fetch the SCTable from TLS, create one if it doesn't yet exist.
pub fn with_sctable<T, F>(op: F) -> T where
    F: FnOnce(&SCTable) -> T,
{
    thread_local!(static SCTABLE_KEY: SCTable = new_sctable_internal());
    SCTABLE_KEY.with(move |slot| op(slot))
}

// Make a fresh syntax context table with EmptyCtxt in slot zero
// and IllegalCtxt in slot one.
fn new_sctable_internal() -> SCTable {
    SCTable {
        table: RefCell::new(vec!(EmptyCtxt, IllegalCtxt)),
        mark_memo: RefCell::new(HashMap::new()),
        rename_memo: RefCell::new(HashMap::new()),
    }
}

/// Print out an SCTable for debugging
pub fn display_sctable(table: &SCTable) {
    error!("SC table:");
    for (idx,val) in table.table.borrow().iter().enumerate() {
        error!("{:4} : {:?}",idx,val);
    }
}

/// Clear the tables from TLD to reclaim memory.
pub fn clear_tables() {
    with_sctable(|table| {
        *table.table.borrow_mut() = Vec::new();
        *table.mark_memo.borrow_mut() = HashMap::new();
        *table.rename_memo.borrow_mut() = HashMap::new();
    });
    with_resolve_table_mut(|table| *table = HashMap::new());
}

/// Reset the tables to their initial state
pub fn reset_tables() {
    with_sctable(|table| {
        *table.table.borrow_mut() = vec!(EmptyCtxt, IllegalCtxt);
        *table.mark_memo.borrow_mut() = HashMap::new();
        *table.rename_memo.borrow_mut() = HashMap::new();
    });
    with_resolve_table_mut(|table| *table = HashMap::new());
}

/// Add a value to the end of a vec, return its index
fn idx_push<T>(vec: &mut Vec<T>, val: T) -> u32 {
    vec.push(val);
    (vec.len() - 1) as u32
}

/// Resolve a syntax object to a name, per MTWT.
pub fn resolve(id: Ident) -> Name {
    with_sctable(|sctable| {
        with_resolve_table_mut(|resolve_table| {
            resolve_internal(id, sctable, resolve_table)
        })
    })
}

type ResolveTable = HashMap<(Name,SyntaxContext),Name>;

// okay, I admit, putting this in TLS is not so nice:
// fetch the SCTable from TLS, create one if it doesn't yet exist.
fn with_resolve_table_mut<T, F>(op: F) -> T where
    F: FnOnce(&mut ResolveTable) -> T,
{
    thread_local!(static RESOLVE_TABLE_KEY: RefCell<ResolveTable> = {
        RefCell::new(HashMap::new())
    });

    RESOLVE_TABLE_KEY.with(move |slot| op(&mut *slot.borrow_mut()))
}

/// Resolve a syntax object to a name, per MTWT.
/// adding memoization to resolve 500+ seconds in resolve for librustc (!)
fn resolve_internal(id: Ident,
                    table: &SCTable,
                    resolve_table: &mut ResolveTable) -> Name {
    let key = (id.name, id.ctxt);

    match resolve_table.get(&key) {
        Some(&name) => return name,
        None => {}
    }

    let resolved = {
        let result = (*table.table.borrow())[id.ctxt as usize];
        match result {
            EmptyCtxt => id.name,
            // ignore marks here:
            Mark(_,subctxt) =>
                resolve_internal(Ident{name:id.name, ctxt: subctxt},
                                 table, resolve_table),
            // do the rename if necessary:
            Rename(Ident{name, ctxt}, toname, subctxt) => {
                let resolvedfrom =
                    resolve_internal(Ident{name:name, ctxt:ctxt},
                                     table, resolve_table);
                let resolvedthis =
                    resolve_internal(Ident{name:id.name, ctxt:subctxt},
                                     table, resolve_table);
                if (resolvedthis == resolvedfrom)
                    && (marksof_internal(ctxt, resolvedthis, table)
                        == marksof_internal(subctxt, resolvedthis, table)) {
                    toname
                } else {
                    resolvedthis
                }
            }
            IllegalCtxt => panic!("expected resolvable context, got IllegalCtxt")
        }
    };
    resolve_table.insert(key, resolved);
    resolved
}

/// Compute the marks associated with a syntax context.
pub fn marksof(ctxt: SyntaxContext, stopname: Name) -> Vec<Mrk> {
    with_sctable(|table| marksof_internal(ctxt, stopname, table))
}

// the internal function for computing marks
// it's not clear to me whether it's better to use a [] mutable
// vector or a cons-list for this.
fn marksof_internal(ctxt: SyntaxContext,
                    stopname: Name,
                    table: &SCTable) -> Vec<Mrk> {
    let mut result = Vec::new();
    let mut loopvar = ctxt;
    loop {
        let table_entry = (*table.table.borrow())[loopvar as usize];
        match table_entry {
            EmptyCtxt => {
                return result;
            },
            Mark(mark, tl) => {
                xor_push(&mut result, mark);
                loopvar = tl;
            },
            Rename(_,name,tl) => {
                // see MTWT for details on the purpose of the stopname.
                // short version: it prevents duplication of effort.
                if name == stopname {
                    return result;
                } else {
                    loopvar = tl;
                }
            }
            IllegalCtxt => panic!("expected resolvable context, got IllegalCtxt")
        }
    }
}

/// Return the outer mark for a context with a mark at the outside.
/// FAILS when outside is not a mark.
pub fn outer_mark(ctxt: SyntaxContext) -> Mrk {
    with_sctable(|sctable| {
        match (*sctable.table.borrow())[ctxt as usize] {
            Mark(mrk, _) => mrk,
            _ => panic!("can't retrieve outer mark when outside is not a mark")
        }
    })
}

/// Push a name... unless it matches the one on top, in which
/// case pop and discard (so two of the same marks cancel)
fn xor_push(marks: &mut Vec<Mrk>, mark: Mrk) {
    if (marks.len() > 0) && (*marks.last().unwrap() == mark) {
        marks.pop().unwrap();
    } else {
        marks.push(mark);
    }
}

#[cfg(test)]
mod tests {
    use self::TestSC::*;
    use ast::{EMPTY_CTXT, Ident, Mrk, Name, SyntaxContext};
    use super::{resolve, xor_push, apply_mark_internal, new_sctable_internal};
    use super::{apply_rename_internal, apply_renames, marksof_internal, resolve_internal};
    use super::{SCTable, EmptyCtxt, Mark, Rename, IllegalCtxt};
    use std::collections::HashMap;

    #[test]
    fn xorpush_test () {
        let mut s = Vec::new();
        xor_push(&mut s, 14);
        assert_eq!(s.clone(), vec!(14));
        xor_push(&mut s, 14);
        assert_eq!(s.clone(), Vec::new());
        xor_push(&mut s, 14);
        assert_eq!(s.clone(), vec!(14));
        xor_push(&mut s, 15);
        assert_eq!(s.clone(), vec!(14, 15));
        xor_push(&mut s, 16);
        assert_eq!(s.clone(), vec!(14, 15, 16));
        xor_push(&mut s, 16);
        assert_eq!(s.clone(), vec!(14, 15));
        xor_push(&mut s, 15);
        assert_eq!(s.clone(), vec!(14));
    }

    fn id(n: u32, s: SyntaxContext) -> Ident {
        Ident {name: Name(n), ctxt: s}
    }

    // because of the SCTable, I now need a tidy way of
    // creating syntax objects. Sigh.
    #[derive(Clone, PartialEq, Debug)]
    enum TestSC {
        M(Mrk),
        R(Ident,Name)
    }

    // unfold a vector of TestSC values into a SCTable,
    // returning the resulting index
    fn unfold_test_sc(tscs : Vec<TestSC> , tail: SyntaxContext, table: &SCTable)
        -> SyntaxContext {
        tscs.iter().rev().fold(tail, |tail : SyntaxContext, tsc : &TestSC|
                  {match *tsc {
                      M(mrk) => apply_mark_internal(mrk,tail,table),
                      R(ident,name) => apply_rename_internal(ident,name,tail,table)}})
    }

    // gather a SyntaxContext back into a vector of TestSCs
    fn refold_test_sc(mut sc: SyntaxContext, table : &SCTable) -> Vec<TestSC> {
        let mut result = Vec::new();
        loop {
            let table = table.table.borrow();
            match (*table)[sc as usize] {
                EmptyCtxt => {return result;},
                Mark(mrk,tail) => {
                    result.push(M(mrk));
                    sc = tail;
                    continue;
                },
                Rename(id,name,tail) => {
                    result.push(R(id,name));
                    sc = tail;
                    continue;
                }
                IllegalCtxt => panic!("expected resolvable context, got IllegalCtxt")
            }
        }
    }

    #[test]
    fn test_unfold_refold(){
        let mut t = new_sctable_internal();

        let test_sc = vec!(M(3),R(id(101,0),Name(14)),M(9));
        assert_eq!(unfold_test_sc(test_sc.clone(),EMPTY_CTXT,&mut t),4);
        {
            let table = t.table.borrow();
            assert!((*table)[2] == Mark(9,0));
            assert!((*table)[3] == Rename(id(101,0),Name(14),2));
            assert!((*table)[4] == Mark(3,3));
        }
        assert_eq!(refold_test_sc(4,&t),test_sc);
    }

    // extend a syntax context with a sequence of marks given
    // in a vector. v[0] will be the outermost mark.
    fn unfold_marks(mrks: Vec<Mrk> , tail: SyntaxContext, table: &SCTable)
                    -> SyntaxContext {
        mrks.iter().rev().fold(tail, |tail:SyntaxContext, mrk:&Mrk|
                   {apply_mark_internal(*mrk,tail,table)})
    }

    #[test] fn unfold_marks_test() {
        let mut t = new_sctable_internal();

        assert_eq!(unfold_marks(vec!(3,7),EMPTY_CTXT,&mut t),3);
        {
            let table = t.table.borrow();
            assert!((*table)[2] == Mark(7,0));
            assert!((*table)[3] == Mark(3,2));
        }
    }

    #[test]
    fn test_marksof () {
        let stopname = Name(242);
        let name1 = Name(243);
        let mut t = new_sctable_internal();
        assert_eq!(marksof_internal (EMPTY_CTXT,stopname,&t),Vec::new());
        // FIXME #5074: ANF'd to dodge nested calls
        { let ans = unfold_marks(vec!(4,98),EMPTY_CTXT,&mut t);
         assert_eq! (marksof_internal (ans,stopname,&t),vec!(4,98));}
        // does xoring work?
        { let ans = unfold_marks(vec!(5,5,16),EMPTY_CTXT,&mut t);
         assert_eq! (marksof_internal (ans,stopname,&t), vec!(16));}
        // does nested xoring work?
        { let ans = unfold_marks(vec!(5,10,10,5,16),EMPTY_CTXT,&mut t);
         assert_eq! (marksof_internal (ans, stopname,&t), vec!(16));}
        // rename where stop doesn't match:
        { let chain = vec!(M(9),
                        R(id(name1.usize() as u32,
                             apply_mark_internal (4, EMPTY_CTXT,&mut t)),
                          Name(100101102)),
                        M(14));
         let ans = unfold_test_sc(chain,EMPTY_CTXT,&mut t);
         assert_eq! (marksof_internal (ans, stopname, &t), vec!(9,14));}
        // rename where stop does match
        { let name1sc = apply_mark_internal(4, EMPTY_CTXT, &mut t);
         let chain = vec!(M(9),
                       R(id(name1.usize() as u32, name1sc),
                         stopname),
                       M(14));
         let ans = unfold_test_sc(chain,EMPTY_CTXT,&mut t);
         assert_eq! (marksof_internal (ans, stopname, &t), vec!(9)); }
    }


    #[test]
    fn resolve_tests () {
        let a = 40;
        let mut t = new_sctable_internal();
        let mut rt = HashMap::new();
        // - ctxt is MT
        assert_eq!(resolve_internal(id(a,EMPTY_CTXT),&mut t, &mut rt),Name(a));
        // - simple ignored marks
        { let sc = unfold_marks(vec!(1,2,3),EMPTY_CTXT,&mut t);
         assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),Name(a));}
        // - orthogonal rename where names don't match
        { let sc = unfold_test_sc(vec!(R(id(50,EMPTY_CTXT),Name(51)),M(12)),EMPTY_CTXT,&mut t);
         assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),Name(a));}
        // - rename where names do match, but marks don't
        { let sc1 = apply_mark_internal(1,EMPTY_CTXT,&mut t);
         let sc = unfold_test_sc(vec!(R(id(a,sc1),Name(50)),
                                   M(1),
                                   M(2)),
                                 EMPTY_CTXT,&mut t);
        assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), Name(a));}
        // - rename where names and marks match
        { let sc1 = unfold_test_sc(vec!(M(1),M(2)),EMPTY_CTXT,&mut t);
         let sc = unfold_test_sc(vec!(R(id(a,sc1),Name(50)),M(1),M(2)),EMPTY_CTXT,&mut t);
         assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), Name(50)); }
        // - rename where names and marks match by literal sharing
        { let sc1 = unfold_test_sc(vec!(M(1),M(2)),EMPTY_CTXT,&mut t);
         let sc = unfold_test_sc(vec!(R(id(a,sc1),Name(50))),sc1,&mut t);
         assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), Name(50)); }
        // - two renames of the same var.. can only happen if you use
        // local-expand to prevent the inner binding from being renamed
        // during the rename-pass caused by the first:
        println!("about to run bad test");
        { let sc = unfold_test_sc(vec!(R(id(a,EMPTY_CTXT),Name(50)),
                                    R(id(a,EMPTY_CTXT),Name(51))),
                                  EMPTY_CTXT,&mut t);
         assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), Name(51)); }
        // the simplest double-rename:
        { let a_to_a50 = apply_rename_internal(id(a,EMPTY_CTXT),Name(50),EMPTY_CTXT,&mut t);
         let a50_to_a51 = apply_rename_internal(id(a,a_to_a50),Name(51),a_to_a50,&mut t);
         assert_eq!(resolve_internal(id(a,a50_to_a51),&mut t, &mut rt),Name(51));
         // mark on the outside doesn't stop rename:
         let sc = apply_mark_internal(9,a50_to_a51,&mut t);
         assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),Name(51));
         // but mark on the inside does:
         let a50_to_a51_b = unfold_test_sc(vec!(R(id(a,a_to_a50),Name(51)),
                                              M(9)),
                                           a_to_a50,
                                           &mut t);
         assert_eq!(resolve_internal(id(a,a50_to_a51_b),&mut t, &mut rt),Name(50));}
    }

    #[test]
    fn mtwt_resolve_test(){
        let a = 40;
        assert_eq!(resolve(id(a,EMPTY_CTXT)),Name(a));
    }


    #[test]
    fn hashing_tests () {
        let mut t = new_sctable_internal();
        assert_eq!(apply_mark_internal(12,EMPTY_CTXT,&mut t),2);
        assert_eq!(apply_mark_internal(13,EMPTY_CTXT,&mut t),3);
        // using the same one again should result in the same index:
        assert_eq!(apply_mark_internal(12,EMPTY_CTXT,&mut t),2);
        // I'm assuming that the rename table will behave the same....
    }

    #[test]
    fn resolve_table_hashing_tests() {
        let mut t = new_sctable_internal();
        let mut rt = HashMap::new();
        assert_eq!(rt.len(),0);
        resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt);
        assert_eq!(rt.len(),1);
        resolve_internal(id(39,EMPTY_CTXT),&mut t, &mut rt);
        assert_eq!(rt.len(),2);
        resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt);
        assert_eq!(rt.len(),2);
    }

    #[test]
    fn new_resolves_test() {
        let renames = vec!((Ident{name:Name(23),ctxt:EMPTY_CTXT},Name(24)),
                           (Ident{name:Name(29),ctxt:EMPTY_CTXT},Name(29)));
        let new_ctxt1 = apply_renames(&renames,EMPTY_CTXT);
        assert_eq!(resolve(Ident{name:Name(23),ctxt:new_ctxt1}),Name(24));
        assert_eq!(resolve(Ident{name:Name(29),ctxt:new_ctxt1}),Name(29));
    }
}