1
use std::cell::Cell;
2
use std::cell::RefCell;
3
use std::cell::UnsafeCell;
4
use std::mem::ManuallyDrop;
5
use std::ops::Deref;
6
use std::ops::DerefMut;
7
use std::sync::Arc;
8
use std::sync::Mutex;
9

            
10
use log::debug;
11

            
12
use merc_pest_consume::Parser;
13
use merc_sharedmutex::RecursiveLock;
14
use merc_sharedmutex::RecursiveLockReadGuard;
15
use merc_unsafety::ProtectionIndex;
16
use merc_unsafety::ProtectionSet;
17
use merc_unsafety::StablePointer;
18
use merc_utilities::MercError;
19
use merc_utilities::debug_trace;
20

            
21
use crate::ATermIndex;
22
use crate::Markable;
23
use crate::Return;
24
use crate::Rule;
25
use crate::Symb;
26
use crate::Symbol;
27
use crate::SymbolRef;
28
use crate::Term;
29
use crate::TermParser;
30
use crate::aterm::ATerm;
31
use crate::aterm::ATermRef;
32
use crate::storage::AGGRESSIVE_GC;
33
use crate::storage::GlobalTermPool;
34
use crate::storage::GlobalTermPoolGuard;
35
use crate::storage::SharedTerm;
36
use crate::storage::SharedTermProtection;
37
use crate::storage::global_aterm_pool::GLOBAL_TERM_POOL;
38

            
39
thread_local! {
40
    /// Thread-specific [ThreadTermPool] that manages protection sets for the current thread.
41
    pub static THREAD_TERM_POOL: RefCell<ThreadTermPool> = RefCell::new(ThreadTermPool::new());
42
}
43

            
44
/// Per-thread term pool managing local protection sets for interaction with the [GlobalTermPool].
45
pub struct ThreadTermPool {
46
    /// Contains all the protection sets for this thread.
47
    protection_sets: Arc<UnsafeCell<SharedTermProtection>>,
48

            
49
    /// A separate protection set for sendable terms, see [crate::ATermSend].
50
    send_term_protection_set: Arc<Mutex<ProtectionSet<ATermIndex>>>,
51

            
52
    /// The number of times terms have been created before garbage collection is triggered.
53
    garbage_collection_counter: Cell<usize>,
54

            
55
    /// A vector of terms that are used to store the arguments of a term for lookup.
56
    tmp_arguments: RefCell<Vec<ATermRef<'static>>>,
57

            
58
    /// A local view for the global term pool.
59
    term_pool: RecursiveLock<GlobalTermPool>,
60

            
61
    /// Copy of the default terms since thread local access is cheaper.
62
    int_symbol: SymbolRef<'static>,
63
    empty_list_symbol: SymbolRef<'static>,
64
    list_symbol: SymbolRef<'static>,
65
}
66

            
67
impl ThreadTermPool {
68
    /// Creates a new thread-local term pool.
69
962
    fn new() -> Self {
70
        // Register protection sets with global pool
71
962
        let term_pool: RecursiveLock<GlobalTermPool> = RecursiveLock::from_mutex(GLOBAL_TERM_POOL.share());
72

            
73
962
        let mut pool = term_pool.write().expect("Lock poisoned!");
74

            
75
962
        let (protection_sets, send_term_protection_set) = pool.register_thread_term_pool();
76
962
        let int_symbol = pool.get_int_symbol().copy();
77
962
        let empty_list_symbol = pool.get_empty_list_symbol().copy();
78
962
        let list_symbol = pool.get_list_symbol().copy();
79
962
        drop(pool);
80

            
81
        // Arbitrary value to trigger garbage collection
82
        Self {
83
962
            protection_sets,
84
962
            send_term_protection_set,
85
962
            garbage_collection_counter: Cell::new(if AGGRESSIVE_GC { 1 } else { 1000 }),
86
962
            tmp_arguments: RefCell::new(Vec::new()),
87
962
            int_symbol,
88
962
            empty_list_symbol,
89
962
            list_symbol,
90
962
            term_pool,
91
        }
92
962
    }
93

            
94
    /// Creates a constant [ATerm] (arity 0) for the given [SymbolRef].
95
3961694
    pub fn create_constant(&self, symbol: &SymbolRef<'_>) -> ATerm {
96
3961694
        assert!(symbol.arity() == 0, "A constant should not have arity > 0");
97

            
98
3961694
        let empty_args: [ATermRef<'_>; 0] = [];
99
3961694
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
100

            
101
3961694
        let (index, inserted) = guard.create_term_array(symbol, &empty_args);
102
3961694
        let result = self.protect_guard(guard, &unsafe { ATermRef::from_index(&index) });
103

            
104
3961694
        if inserted {
105
197020
            // Intentially called after the guard is dropped.
106
197020
            self.decrement_garbage_collection_counter();
107
3764674
        }
108

            
109
3961694
        result
110
3961694
    }
111

            
112
    /// Create a term with the given arguments
113
1675999
    pub fn create_term<'a, 'b, S: Symb<'a, 'b>, T: Term<'a, 'b>>(
114
1675999
        &self,
115
1675999
        symbol: &'b S,
116
1675999
        args: &'b [T],
117
1675999
    ) -> Return<ATermRef<'static>> {
118
        // We cannot perform garbage collection afterwards since the guard is alive in the return.
119
1675999
        self.trigger_garbage_collection();
120

            
121
1675999
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
122
1675999
        let mut arguments = self.tmp_arguments.borrow_mut();
123

            
124
1675999
        arguments.clear();
125
2711682
        for arg in args {
126
2711682
            unsafe {
127
2711682
                arguments.push(ATermRef::from_index(arg.shared()));
128
2711682
            }
129
        }
130

            
131
1675999
        let (index, inserted) = guard.create_term_array(symbol, &arguments);
132
1675999
        let result = self.make_return(index, guard);
133

            
134
1675999
        if inserted {
135
268979
            self.decrement_garbage_collection_counter();
136
1418741
        }
137

            
138
1675999
        result
139
1675999
    }
140

            
141
    /// Create a term with the given index.
142
6438188
    pub fn create_int(&self, value: usize) -> ATerm {
143
6438188
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
144
6438188
        let (index, inserted) = guard.create_int(value);
145
6438188
        let result = self.protect_guard(guard, &unsafe { ATermRef::from_index(&index) });
146

            
147
6438188
        if inserted {
148
5328524
            // Intentially called after the guard is dropped.
149
5328524
            self.decrement_garbage_collection_counter();
150
5328524
        }
151

            
152
6438188
        result
153
6438188
    }
154

            
155
    /// Create a term with the given arguments given by the iterator.
156
96358
    pub fn create_term_iter<'a, 'b, 'c, 'd, S, I, T>(&self, symbol: &'b S, args: I) -> ATerm
157
96358
    where
158
96358
        S: Symb<'a, 'b>,
159
96358
        I: IntoIterator<Item = T>,
160
96358
        T: Term<'c, 'd>,
161
    {
162
96358
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
163
96358
        let mut arguments = self.tmp_arguments.borrow_mut();
164
96358
        arguments.clear();
165
142125
        for arg in args {
166
142125
            unsafe {
167
142125
                arguments.push(ATermRef::from_index(arg.shared()));
168
142125
            }
169
        }
170

            
171
96358
        let (index, inserted) = guard.create_term_array(symbol, &arguments);
172

            
173
96358
        let result = self.protect_guard(guard, &unsafe { ATermRef::from_index(&index) });
174

            
175
96358
        if inserted {
176
41186
            // Intentially called after the guard is dropped.
177
41186
            self.decrement_garbage_collection_counter();
178
55176
        }
179

            
180
96358
        result
181
96358
    }
182

            
183
    /// Create a term with the given arguments given by the iterator that is fallible.
184
210217
    pub fn try_create_term_iter<'a, 'b, 'c, 'd, S, I, T>(&self, symbol: &'b S, args: I) -> Result<ATerm, MercError>
185
210217
    where
186
210217
        S: Symb<'a, 'b>,
187
210217
        I: IntoIterator<Item = Result<T, MercError>>,
188
210217
        T: Term<'c, 'd>,
189
    {
190
210217
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
191
210217
        let mut arguments = self.tmp_arguments.borrow_mut();
192
210217
        arguments.clear();
193
214884
        for arg in args {
194
            unsafe {
195
214884
                arguments.push(ATermRef::from_index(arg?.shared()));
196
            }
197
        }
198

            
199
210217
        let (index, inserted) = guard.create_term_array(symbol, &arguments);
200
210217
        let result = Ok(self.protect_guard(guard, &unsafe { ATermRef::from_index(&index) }));
201

            
202
210217
        if inserted {
203
4357
            // Intentially called after the guard is dropped.
204
4357
            self.decrement_garbage_collection_counter();
205
206754
        }
206

            
207
210217
        result
208
210217
    }
209

            
210
    /// Create a term with the given arguments given by the iterator.
211
4359807
    pub fn create_term_iter_head<'a, 'b, 'c, 'd, 'e, 'f, S, H, I, T>(
212
4359807
        &self,
213
4359807
        symbol: &'b S,
214
4359807
        head: &'d H,
215
4359807
        args: I,
216
4359807
    ) -> ATerm
217
4359807
    where
218
4359807
        S: Symb<'a, 'b>,
219
4359807
        H: Term<'c, 'd>,
220
4359807
        I: IntoIterator<Item = T>,
221
4359807
        T: Term<'e, 'f>,
222
    {
223
4359807
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
224
4359807
        let mut arguments = self.tmp_arguments.borrow_mut();
225
4359807
        arguments.clear();
226
4359807
        unsafe {
227
4359807
            arguments.push(ATermRef::from_index(head.shared()));
228
4359807
        }
229
6872463
        for arg in args {
230
6872463
            unsafe {
231
6872463
                arguments.push(ATermRef::from_index(arg.shared()));
232
6872463
            }
233
        }
234

            
235
4359807
        let (index, inserted) = guard.create_term_array(symbol, &arguments);
236

            
237
4359807
        let result = self.protect_guard(guard, &unsafe { ATermRef::from_index(&index) });
238

            
239
4359807
        if inserted {
240
432944
            // Intentially called after the guard is dropped.
241
432944
            self.decrement_garbage_collection_counter();
242
3926865
        }
243

            
244
4359807
        result
245
4359807
    }
246

            
247
    /// Create a function symbol
248
4981772
    pub fn create_symbol<N: Into<String> + AsRef<str>>(&self, name: N, arity: usize) -> Symbol {
249
4981772
        self.term_pool
250
4981772
            .read_recursive()
251
4981772
            .expect("Lock poisoned!")
252
4981772
            .create_symbol(name, arity, |index| unsafe {
253
4981772
                self.protect_symbol(&SymbolRef::from_index(&index))
254
4981772
            })
255
4981772
    }
256

            
257
    /// Protect the term by adding its index to the protection set
258
566644787
    pub fn protect(&self, term: &ATermRef<'_>) -> ATerm {
259
        // Protect the term by adding its index to the protection set
260
566644787
        let root = self
261
566644787
            .lock_protection_set()
262
566644787
            .term_protection_set
263
566644787
            .protect(term.shared().copy());
264

            
265
        // Return the protected term
266
566644787
        let result = ATerm::from_index(term.shared(), root);
267

            
268
566644787
        debug_trace!(
269
            "Protected term {:?}, root {}, protection set {}",
270
            term,
271
            root,
272
            self.index()
273
        );
274

            
275
566644787
        result
276
566644787
    }
277

            
278
    /// Protect the term by adding its index to the protection set
279
82362654
    pub fn protect_guard(&self, _guard: RecursiveLockReadGuard<'_, GlobalTermPool>, term: &ATermRef<'_>) -> ATerm {
280
        // Protect the term by adding its index to the protection set
281
        // SAFETY: If the global term pool is locked, so we can safely access the protection set.
282
82362654
        let root = unsafe { &mut *self.protection_sets.get() }
283
82362654
            .term_protection_set
284
82362654
            .protect(term.shared().copy());
285

            
286
        // Return the protected term
287
82362654
        let result = ATerm::from_index(term.shared(), root);
288

            
289
82362654
        debug_trace!(
290
            "Protected term {:?}, root {}, protection set {}",
291
            term,
292
            root,
293
            self.index()
294
        );
295

            
296
82362654
        result
297
82362654
    }
298

            
299
    /// Unprotects a term from this thread's protection set.
300
649007441
    pub fn drop(&self, term: &ATerm) {
301
649007441
        self.lock_protection_set().term_protection_set.unprotect(term.root());
302

            
303
649007441
        debug_trace!(
304
            "Unprotected term {:?}, root {}, protection set {}",
305
            term,
306
            term.root(),
307
            self.index()
308
        );
309
649007441
    }
310

            
311
    /// Protects a container in this thread's container protection set.
312
53191857
    pub fn protect_container(&self, container: Arc<dyn Markable + Send + Sync>) -> ProtectionIndex {
313
53191857
        let root = self.lock_protection_set().container_protection_set.protect(container);
314

            
315
53191857
        debug_trace!("Protected container index {}, protection set {}", root, self.index());
316

            
317
53191857
        root
318
53191857
    }
319

            
320
    /// Unprotects a container from this thread's container protection set.
321
53191857
    pub fn drop_container(&self, root: ProtectionIndex) {
322
53191857
        self.lock_protection_set().container_protection_set.unprotect(root);
323

            
324
53191857
        debug_trace!("Unprotected container index {}, protection set {}", root, self.index());
325
53191857
    }
326

            
327
    /// Parse the given string and returns the Term representation.
328
6082
    pub fn from_string(&self, text: &str) -> Result<ATerm, MercError> {
329
6082
        let mut result = TermParser::parse(Rule::TermSpec, text)?;
330
6082
        let root = result.next().unwrap();
331

            
332
6082
        Ok(TermParser::TermSpec(root).unwrap())
333
6082
    }
334

            
335
    /// Protects a symbol from garbage collection.
336
5017692
    pub fn protect_symbol(&self, symbol: &SymbolRef<'_>) -> Symbol {
337
5017692
        let result = unsafe {
338
5017692
            Symbol::from_index(
339
5017692
                symbol.shared(),
340
5017692
                self.lock_protection_set()
341
5017692
                    .symbol_protection_set
342
5017692
                    .protect(symbol.shared().copy()),
343
            )
344
        };
345

            
346
5017692
        debug_trace!(
347
            "Protected symbol {}, root {}, protection set {}",
348
            symbol,
349
            result.root(),
350
            lock.index,
351
        );
352

            
353
5017692
        result
354
5017692
    }
355

            
356
    /// Unprotects a symbol, allowing it to be garbage collected.
357
5002140
    pub fn drop_symbol(&self, symbol: &mut Symbol) {
358
5002140
        self.lock_protection_set()
359
5002140
            .symbol_protection_set
360
5002140
            .unprotect(symbol.root());
361
5002140
    }
362

            
363
    /// Returns the symbol for ATermInt
364
1098737334
    pub fn int_symbol(&self) -> &SymbolRef<'_> {
365
1098737334
        &self.int_symbol
366
1098737334
    }
367

            
368
    /// Returns the symbol for ATermList
369
1919503
    pub fn list_symbol(&self) -> &SymbolRef<'_> {
370
1919503
        &self.list_symbol
371
1919503
    }
372

            
373
    /// Returns the symbol for the empty ATermInt
374
1944313
    pub fn empty_list_symbol(&self) -> &SymbolRef<'_> {
375
1944313
        &self.empty_list_symbol
376
1944313
    }
377

            
378
    /// Enables or disables automatic garbage collection.
379
    pub fn automatic_garbage_collection(&self, enabled: bool) {
380
        let mut guard = self.term_pool.write().expect("Lock poisoned!");
381
        guard.automatic_garbage_collection(enabled);
382
    }
383

            
384
    /// Forces a garbage collection to occur, regardless of the current counter value or whether it is enabled.
385
    pub fn force_collect_garbage(&self) {
386
        let mut guard = self.term_pool.write().expect("Lock poisoned!");
387
        guard.collect_garbage();
388
    }
389

            
390
    /// Perform a garbage collection.
391
388407
    pub fn collect_garbage(&self) {
392
388407
        if !self.term_pool.is_locked() {
393
388407
            // Trigger garbage collection and acquire a new counter value.
394
388407
            let value = self
395
388407
                .term_pool
396
388407
                .write()
397
388407
                .expect("Lock poisoned!")
398
388407
                .trigger_garbage_collection();
399
388407
            self.garbage_collection_counter.set(value);
400
388407
        }
401
388407
    }
402

            
403
    /// Triggers delayed garbage collection if the counter has reached zero.
404
    ///
405
    /// # Safety
406
    ///
407
    /// This function drops the passed guard.
408
446803959
    pub(crate) unsafe fn trigger_delayed_garbage_collection(&self, guard: &mut ManuallyDrop<GlobalTermPoolGuard<'_>>) {
409
446803959
        unsafe {
410
446803959
            ManuallyDrop::drop(guard);
411
446803959
        }
412

            
413
446803959
        debug_assert!(
414
446803959
            guard.read_depth() == 0,
415
            "Cannot trigger garbage collection while holding a read lock"
416
        );
417
446803959
        if self.garbage_collection_counter.get() == 0 {
418
61920
            self.trigger_garbage_collection();
419
446742039
        }
420
446803959
    }
421

            
422
    /// Decrements the garbage collection counter and triggers garbage collection if necessary.
423
13935026
    fn decrement_garbage_collection_counter(&self) {
424
        // If the term was newly inserted, decrease the garbage collection counter and trigger garbage collection if necessary
425
13935026
        self.garbage_collection_counter
426
13935026
            .set(self.garbage_collection_counter.get().saturating_sub(1));
427

            
428
13935026
        self.trigger_garbage_collection();
429
13935026
    }
430

            
431
    /// Triggers garbage collection if the counter has reached zero.
432
18375880
    fn trigger_garbage_collection(&self) {
433
18375880
        if self.garbage_collection_counter.get() == 0 && !self.term_pool.is_locked() {
434
384711
            self.collect_garbage();
435
17991169
        }
436
18375880
    }
437

            
438
    /// Returns a reference to the send term protection set.
439
200001
    pub fn send_term_protection_set(&self) -> &Arc<Mutex<ProtectionSet<ATermIndex>>> {
440
200001
        &self.send_term_protection_set
441
200001
    }
442

            
443
    /// Returns a reference to the global term pool.
444
1077593960
    pub(crate) fn term_pool(&self) -> &RecursiveLock<GlobalTermPool> {
445
1077593960
        &self.term_pool
446
1077593960
    }
447

            
448
    /// Replace the entry in the protection set with the given term.
449
    pub(crate) fn replace(
450
        &self,
451
        _guard: RecursiveLockReadGuard<'_, GlobalTermPool>,
452
        root: ProtectionIndex,
453
        term: StablePointer<SharedTerm>,
454
    ) {
455
        // Protect the term by adding its index to the protection set
456
        // SAFETY: If the global term pool is locked, so we can safely access the protection set.
457
        unsafe { &mut *self.protection_sets.get() }
458
            .term_protection_set
459
            .replace(root, term);
460
    }
461

            
462
    /// Creates a Return for the given index and guard.
463
4378933
    fn make_return(
464
4378933
        &self,
465
4378933
        index: ATermIndex,
466
4378933
        guard: RecursiveLockReadGuard<'_, GlobalTermPool>,
467
4378933
    ) -> Return<ATermRef<'static>> {
468
        // SAFETY: The guard is guaranteed to live as long as the returned term, since it is thread local and Return cannot be sent to other threads.
469
        unsafe {
470
4378933
            Return::new(
471
4378933
                std::mem::transmute::<RecursiveLockReadGuard<'_, _>, RecursiveLockReadGuard<'static, _>>(guard),
472
4378933
                ATermRef::from_index(&index),
473
            )
474
        }
475
4378933
    }
476

            
477
    /// Returns the index of the protection set.
478
962
    fn index(&self) -> usize {
479
962
        self.lock_protection_set().index
480
962
    }
481

            
482
    /// The protection set is locked by the global read-write lock
483
1332056742
    fn lock_protection_set(&self) -> ProtectionSetGuard<'_> {
484
1332056742
        let guard = self.term_pool.read_recursive().expect("Lock poisoned!");
485
1332056742
        let protection_set = unsafe { &mut *self.protection_sets.get() };
486

            
487
1332056742
        ProtectionSetGuard::new(guard, protection_set)
488
1332056742
    }
489
}
490

            
491
impl Drop for ThreadTermPool {
492
962
    fn drop(&mut self) {
493
962
        let mut write = self.term_pool.write().expect("Lock poisoned!");
494

            
495
962
        debug!("{}", write.metrics());
496
962
        write.deregister_thread_pool(self.index());
497

            
498
962
        debug!("{}", unsafe { &mut *self.protection_sets.get() }.metrics());
499
962
        debug!(
500
            "Acquired {} read locks and {} write locks",
501
            self.term_pool.read_recursive_call_count(),
502
            self.term_pool.write_call_count()
503
        )
504
962
    }
505
}
506

            
507
struct ProtectionSetGuard<'a> {
508
    _guard: RecursiveLockReadGuard<'a, GlobalTermPool>,
509
    object: &'a mut SharedTermProtection,
510
}
511

            
512
impl ProtectionSetGuard<'_> {
513
1332056742
    fn new<'a>(
514
1332056742
        guard: RecursiveLockReadGuard<'a, GlobalTermPool>,
515
1332056742
        object: &'a mut SharedTermProtection,
516
1332056742
    ) -> ProtectionSetGuard<'a> {
517
1332056742
        ProtectionSetGuard { _guard: guard, object }
518
1332056742
    }
519
}
520

            
521
impl Deref for ProtectionSetGuard<'_> {
522
    type Target = SharedTermProtection;
523

            
524
968
    fn deref(&self) -> &Self::Target {
525
968
        self.object
526
968
    }
527
}
528

            
529
impl DerefMut for ProtectionSetGuard<'_> {
530
1332055774
    fn deref_mut(&mut self) -> &mut Self::Target {
531
1332055774
        self.object
532
1332055774
    }
533
}
534

            
535
#[cfg(test)]
536
mod tests {
537
    use crate::ATerm;
538
    use crate::Symb;
539
    use crate::Symbol;
540
    use crate::Term;
541
    use crate::storage::THREAD_TERM_POOL;
542

            
543
    use std::thread;
544

            
545
    #[test]
546
1
    fn test_thread_local_protection() {
547
1
        let _ = merc_utilities::test_logger();
548

            
549
1
        thread::scope(|scope| {
550
1
            for _ in 0..3 {
551
3
                scope.spawn(|| {
552
                    // Create and protect some terms
553
3
                    let symbol = Symbol::new("test", 0);
554
3
                    let term = ATerm::constant(&symbol);
555
3
                    let protected = term.protect();
556

            
557
                    // Verify protection
558
3
                    THREAD_TERM_POOL.with_borrow(|tp| {
559
3
                        assert!(
560
3
                            tp.lock_protection_set()
561
3
                                .term_protection_set
562
3
                                .contains_root(protected.root())
563
                        );
564
3
                    });
565

            
566
                    // Unprotect
567
3
                    let root = protected.root();
568
3
                    drop(protected);
569

            
570
3
                    THREAD_TERM_POOL.with_borrow(|tp| {
571
3
                        assert!(!tp.lock_protection_set().term_protection_set.contains_root(root));
572
3
                    });
573
3
                });
574
            }
575
1
        });
576
1
    }
577

            
578
    #[test]
579
1
    fn test_parsing() {
580
1
        let _ = merc_utilities::test_logger();
581

            
582
1
        let t = ATerm::from_string("f(g(a),b)").unwrap();
583

            
584
1
        assert!(t.get_head_symbol().name() == "f");
585
1
        assert!(t.arg(0).get_head_symbol().name() == "g");
586
1
        assert!(t.arg(1).get_head_symbol().name() == "b");
587
1
    }
588

            
589
    #[test]
590
1
    fn test_create_term() {
591
1
        let _ = merc_utilities::test_logger();
592

            
593
1
        let f = Symbol::new("f", 2);
594
1
        let g = Symbol::new("g", 1);
595

            
596
1
        let t = THREAD_TERM_POOL.with_borrow(|tp| {
597
1
            tp.create_term(
598
1
                &f,
599
1
                &[
600
1
                    tp.create_term(&g, &[tp.create_constant(&Symbol::new("a", 0))])
601
1
                        .protect(),
602
1
                    tp.create_constant(&Symbol::new("b", 0)),
603
1
                ],
604
1
            )
605
1
            .protect()
606
1
        });
607

            
608
1
        assert!(t.get_head_symbol().name() == "f");
609
1
        assert!(t.arg(0).get_head_symbol().name() == "g");
610
1
        assert!(t.arg(1).get_head_symbol().name() == "b");
611
1
    }
612
}