Repository navigation
Expand file tree
/
Copy pathterms.nw
More file actions
4070 lines (3594 loc) · 135 KB
/
Copy pathterms.nw
File metadata and controls
4070 lines (3594 loc) · 135 KB
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
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% -*- mode: Noweb; noweb-code-mode: c++-mode; c-basic-offset: 8; -*-
\section{Terms}
\subsection{Term Representation}
\begin{comment}
We use a standard approach to represent terms.
A term is a graph of nodes, where each node is a term-schema as defined.
One possible optimization is to distinguish between boxed and unboxed
fields \cite[pg. 190]{peyton-jones87}.
For a discussion on term representations, see
\cite[Chap. 10]{peyton-jones87}.
\end{comment}
\begin{comment}
A term schema can be any one of the following: a syntactical variable
(SV), a variable (V), a function symbol (F), a data constructor (D),
an application (APP), an abstraction (ABS), a product (PROD) or a
modal term (MOD).
This information is recorded in {\tt tag}.
\end{comment}
<<term::type defs>>=
enum kind { SV, V, F, D, APP, ABS, PROD, MODAL };
<<term parts>>=
kind tag;
@
\begin{comment}
Syntactic variables, variables, functions and data constructors have names.
For efficiency considerations, we use integers to represent names.
(See Comment \ref{com:symbols and integers} for the mappings.)
Modal terms have indices.
\end{comment}
<<term parts>>=
int cname;
char modality;
type * ptype;
<<term init>>=
cname = -5;
modality = -5;
ptype = NULL;
<<heap term init>>=
ret->cname = -5;
ret->modality = -5;
ret->ptype = NULL;
<<term clone parts>>=
ret->cname = cname;
if (tag == MODAL) ret->modality = modality;
// if (ptype) ret->ptype = ptype->clone();
<<term replace parts>>=
cname = t->cname;
if (t->tag == MODAL) modality = t->modality;
// if (t->ptype) ptype = t->ptype->clone();
@
\begin{comment}
Terms with names are called atomic terms.\index{atomic terms}
Terms that does not have names are called composite
terms.\index{composite terms}
\end{comment}
<<term::function declarations>>=
bool isF() { return (tag == F); }
bool isF(int code) { return (tag == F && cname == code); }
bool isApp() { return (tag == APP); }
bool isD() { return (tag == D); }
bool isD(int code) { return (tag == D && cname == code); }
bool isVar() { return (tag == V); }
bool isVar(int v) { return (tag == V && cname == v); }
bool isAbs() { return (tag == ABS); }
bool isProd() { return (tag == PROD); }
bool isModal() { return (tag == MODAL); }
@ %def isF isApp isD isVar isAbs isProd isModal
\begin{comment}
The parameters {\tt tag} and {\tt kind} does not have default values.
They are initialized in the constructor code with pass-in values.
\end{comment}
\begin{comment}
Application, abstraction and product terms have subterms.
These are captured in the vector {\tt fields}.
\end{comment}
<<term vector parts>>=
// vector<term *> fields;
term * fields[10];
unint fieldsize;
<<term init>>=
fieldsize = 0;
<<heap term init>>=
ret->fieldsize = 0;
<<term::function declarations>>=
term * lc() { /*assert(tag == APP);*/ return fields[0]; }
term * rc() { /*assert(tag == APP);*/ return fields[1]; }
void insert(term * t) {
fields[fieldsize] = t; fieldsize++;
if (fieldsize > 10) assert(false);
}
@ %def lc rc insert
\begin{comment}
Certain basic data constructors like numbers can best be dealt with in
their original machine representations.
(Otherwise, a lot of conversions from and to strings are needed.)
The variable {\tt num} replaces the {\tt cname} field for numbers.
Cloning of {\tt isfloat}, {\tt isint}, {\tt numi} and {\tt numf} is
done in the {\tt clone()} procedure.
We do not have to worry about them here.
\end{comment}
<<term bool parts>>=
bool isfloat, isint;
<<term parts>>=
long long int numi;
double numf;
<<term init>>=
isfloat = false; isint = false;
<<heap term init>>=
ret->isfloat = false; ret->isint = false;
<<term replace parts>>=
if (t->tag == D) { isfloat = t->isfloat; isint = t->isint;
numi = t->numi; numf = t->numf; }
@
\begin{comment}
Sometimes we want to prevent a certain subterm from being modified.
This is done by setting a {\tt freeze} flag.
\end{comment}
<<term bool parts>>=
bool freeze;
@ %def freeze
<<term init>>=
freeze = false;
<<heap term init>>=
ret->freeze = false;
<<term replace parts>>=
freeze = t->freeze;
@
\begin{comment}
A term of the form $(t_1\;(t_2\cdots (t_{n-1} \;t_n)\cdots ))$ can be
visualized to take on the shape of a spine. (Draw it!)
The (leftmost) term $t_1$ is called the tip of the spine.
At different places throughout a computation, we need to access the
leftmost term in a nested application node, and the following two
functions provide this service.
The input {\tt x} to the second function will get assigned the value $n-1$.
We currently perform a (linear) traversal down the spine.
It is possible to make this go faster if necessary.
We cache the results in {\tt spinetip} and {\tt spinelength}.
\end{comment}
<<term parts>>=
term * spinetip;
int spinelength;
int spine_time;
<<term init>>=
spinetip = NULL; spinelength = -1; spine_time = -5;
@
<<heap term init>>=
ret->spinetip = NULL; ret->spinelength = -1; ret->spine_time = -5;
@
\begin{comment}
All these values become obsolete on replacing.
\end{comment}
<<term replace parts>>=
spinetip = NULL; spinelength = -1; spine_time = -5;
@
<<term::function definitions>>=
term * term::spineTip() {
if (spinetip && spinelength > -1 && spine_time ==ltime) return spinetip;
spine_time = ltime;
if (tag != APP) { spinetip = this; spinelength = 1; return spinetip; }
spinelength = 2; spinetip = fields[0];
while (spinetip->isApp())
{ spinetip = spinetip->fields[0]; spinelength++; }
return spinetip;
}
term * term::spineTip(int & numarg) {
if (tag != APP) { numarg = 0; return this; }
numarg = 1; term * p = fields[0];
while (p->isApp()) { p = p->fields[0]; numarg++; }
return p;
}
@ %def spineTip
\begin{comment}
The following function checks whether the current term has the general
form $((f \; t_1) \; t_2)$, where $f$ is given as input.
If {\tt spinetip} has already been computed, we can do things slightly
faster.
\end{comment}
<<term::function definitions>>=
bool term::isFunc2Args() {
if (spinetip && spinelength == 3 && spinetip->isF()) return true;
return (isApp() && lc()->isApp() && lc()->lc()->isF());
}
bool term::isFunc2Args(int f) {
if (spinetip && spinelength == 3 && spinetip->isF(f)) return true;
return (isApp() && lc()->isApp() && lc()->lc()->isF(f));
}
@ %def isFunc2Args
\begin{comment}
This function checks whether a term is a string.
\end{comment}
<<term::function definitions>>=
bool term::isAString() {
return (isApp() && lc()->isApp() && lc()->lc()->isD(iHash)
&& lc()->rc()->isChar());
}
bool term::isChar() {
if (isfloat || isint) return false;
return (tag == D && cname >= 2000 && cname < 3000);
}
bool term::isString() {
if (isfloat || isint) return false;
return (tag == D && strings.find(cname) != strings.end());
}
@ %def isChar isAString isString
\begin{comment}
Constants that are rigid have the same meaning in each possible world.
A term is rigid if every constant in it is rigid.
\end{comment}
<<term::function definitions>>=
bool term::isRigid() {
if (tag == V || tag == D) return true;
if (tag == F) return is_rigid_constant(cname);
if (tag == ABS) return fields[1]->isRigid();
if (tag == MODAL) return fields[0]->isRigid();
assert(tag == PROD || tag == APP);
for (unint i=0; i!=fieldsize; i++)
if (!fields[i]->isRigid()) return false;
return true;
}
@
\begin{comment}
The following function creates a new term having the form
$((f\; t_1) \;t_2)$ where $f$ (given) is a function symbol of arity two.
The arguments $t_1$ and $t_2$ needs to be initialized by the calling
function.
\end{comment}
<<terms.cc::local functions>>=
term * newT2Args(kind k, int f) {
term * ret = new_term(APP);
ret->insert(new_term(APP)); ret->lc()->insert(new_term(k, f));
return ret;
}
@ %def newT2Args
\begin{comment}
The following function initializes the two arguments of a term created
using {\tt newT2Args}.
\end{comment}
<<term::function declarations>>=
void initT2Args(term * t1, term * t2) {
lc()->insert(t1); insert(t2);
}
@ %def initT2Args
\begin{comment}
The following function checks whether two terms are equal to each other.
This is currently only used in debugging code.
\end{comment}
<<term::function definitions>>=
bool term::equal(term * t) {
if (tag != t->tag) return false;
if (cname != t->cname) return false;
if (modality != t->modality) return false;
<<term schema::equal::numbers>>
// unint size1 = fieldsize;
// unint size2 = t->fieldsize;
if (fieldsize != t->fieldsize) return false;
for (unint i=0; i!=fieldsize; i++)
if (fields[i]->equal(t->fields[i]) == false)
return false;
return true;
}
@ %def equal
\begin{comment}
We treat numbers in a slightly peculiar way.
We will equate an integer and a floating-point number (even though the
types do not agree) if they are the same number.
We do this because the internal arithmetic of Escher can add,
subtract, multiply and divide integers and floating-point numbers to
produce another floating-point number.
See Comment \ref{com:arithmetic}.
\end{comment}
<<term schema::equal::numbers>>=
if (isint && t->isint && numi != t->numi) return false;
if (isint && t->isfloat && (double)numi != t->numf) return false;
if (isfloat && t->isint && numf != (double)t->numi) return false;
if (isfloat && t->isfloat && numf != t->numf) return false;
@
\begin{comment}
This is used for marking and printing redexes.
\end{comment}
<<term bool parts>>=
bool redex;
<<term init>>=
redex = false;
@
<<heap term init>>=
ret->redex = false;
@
\begin{comment}
The variable {\tt redex} does not really play a part during cloning and reusing.
\end{comment}
\begin{comment}
A term is printed in the way it is represented.
The redex (if one exists) is marked out using square brackets.
Shared nodes are also marked with their reference count.
\end{comment}
<<term::function definitions>>=
extern const string pve;
void term::print() {
if (getSelector() == SILENT) return;
<<term schema::print strings>>
<<term schema::print lists>>
if (redex) ioprint(" [[[ ");
<<term schema::print if-then-else>>
// if (refcount > 1) ioprint("_s_");
if (cname >= 5000) { ioprint(pve); ioprint(cname - 5000); }
else if (cname > 0) ioprint(getString(cname));
else if (isfloat) ioprint(numf);
else if (isint) ioprint(numi);
else if (isFunc2Args()) {
ioprint("("); lc()->lc()->print(); ioprint(" ");
lc()->rc()->print(); ioprint(" "); rc()->print(); ioprint(")");
} else if (tag == APP && (lc()->isF(iSigma) || lc()->isF(iPi))) {
if (lc()->isF(iSigma)) ioprint("\\exists ");
else ioprint("\\forall ");
rc()->fields[0]->print(); ioprint(".");
rc()->fields[1]->print();
} else if (tag == APP) {
ioprint("("); fields[0]->print(); ioprint(" ");
fields[1]->print(); ioprint(")");
} else if (tag == ABS) {
ioprint("\\"); fields[0]->print();
ioprint("."); fields[1]->print();
} else if (tag == PROD) {
int size = fieldsize;
if (size == 0) { ioprint("()"); return; }
ioprint("(");
for (int i=0; i!=size-1; i++)
{ fields[i]->print(); ioprint(","); }
fields[size-1]->print(); ioprint(")");
} else if (tag == MODAL) {
ioprint("["); ioprint(modality); ioprint("] ");
fields[0]->print();
} else { <<print error handling>> }
if (redex) ioprint(" ]]] ");
}
@
\begin{comment}
(Composite) strings are represented as lists of characters.
Printing them as lists is not good for the eyes.
What we do here is to collect the characters together and print a
string as a string.
\end{comment}
<<term schema::print strings>>=
if (isAString()) {
string temp = ""; temp += getString(lc()->rc()->cname)[1];
term * arg2 = rc();
while (!arg2->isD(iEmptyList)) {
assert(arg2->lc()->rc()->isChar());
temp += getString(arg2->lc()->rc()->cname)[1];
arg2 = arg2->rc();
}
ioprint("\""); ioprint(temp); ioprint("\""); return;
}
@
\begin{comment}
We print a list in the syntactic sugar form.
\end{comment}
<<term schema::print lists>>=
if (isApp() && lc()->isApp() && lc()->lc()->isD(iHash)) {
ioprint("["); lc()->rc()->print();
term * arg2 = rc();
while (arg2->isD(iEmptyList) == false) {
ioprint(", ");
if (arg2->isApp() && arg2->lc()->isApp() &&
arg2->lc()->lc()->isD(iHash))
{ arg2->lc()->rc()->print(); arg2 = arg2->rc(); }
else { arg2->print(); break; }
}
ioprint("]");
return;
}
@
\begin{comment}
We print if-then-else statements in a more human-readable form here.
\end{comment}
<<term schema::print if-then-else>>=
if (isApp() && lc()->cname == iIte) {
ioprint("if "); rc()->fields[0]->print();
ioprint(" then "); rc()->fields[1]->print();
/* ioprint("\n\t");*/ ioprint(" else "); rc()->fields[2]->print();
return;
}
@
<<print error handling>>=
cerr << "Printing untagged term.\ttag = " << tag << endl;
assert(false);
@
\begin{comment}
In vertical printing, we print the current term vertically (with some
indentation).
Miscellaneous information about the individual subterms are also printed.
This is a convenient way to look at sharing and other information
associated with each node.
\end{comment}
<<term::function definitions>>=
void term::printVertical(unint level) {
if (getSelector() == SILENT) return;
<<print white spaces>>
if (cname >= 5000) { ioprint(pve); ioprint(cname-5000); }
else if (cname > 0)
{ ioprint(getString(cname)); <<print extra information>> }
else if (isfloat) { ioprint(numf); <<print extra information>> }
else if (isint) { ioprint(numi); <<print extra information>> }
else if (tag == APP) {
ioprint("("); <<print extra information>>
fields[0]->printVertical(level+1);
fields[1]->printVertical(level+1);
<<print white spaces>> ioprint(")\n");
} else if (tag == ABS) {
ioprint("\\"); fields[0]->print(); ioprint(".");
<<print extra information>>
fields[1]->printVertical(level+1);
} else if (tag == PROD) {
int size = fieldsize;
if (size == 0)
{ ioprint("()"); <<print extra information>> return; }
ioprint("("); <<print extra information>>
for (int i=0; i!=size-1; i++) {
fields[i]->printVertical(level+1); ioprint(",\n"); }
fields[size-1]->printVertical(level+1);
<<print white spaces>> ioprint(")\n");
} else if (tag == MODAL) {
assert(false);
} else { <<print error handling>> }
}
@ %def printVertical
<<print white spaces>>=
for (unint i=0; i!=level; i++) ioprint(" ");
<<print extra information>>=
ioprint("\t\t");
if (refcount > 1) { ioprint("shared"); ioprint(refcount); }
ioprintln();
@
\subsubsection{Constraints for Syntactic Variables}
\begin{comment}\label{com:sv constraints}
We have a (limited) syntax for specifying constraints on what sort of
terms a syntactical variable can range over.
(See the grammar for Escher.)
Four types of constraints are supported at present.
The constraint CVAR forces a syntactical variable to range over
variables only; CCONST forces a syntactical variable to range over
constants only.
The constraint CEQUAL dictates that the value of one syntactical variable
must be equal to the value of one other;
The constraint CNOTEQUAL dictates that the value of one syntactical
variable must not be equal to the value of one other.
For details on how these constraints are implemented, see
Comment \ref{com:redex matching sv}.
\end{comment}
<<term::definitions>>=
#define CVAR 1
#define CCONST 2
#define CEQUAL 3
#define CNOTEQUAL 4
@
<<term::supporting types>>=
struct condition { int tag; int svname; };
<<term parts>>=
condition * cond; // only applies to SV
<<term init>>=
cond = NULL;
<<heap term init>>=
ret->cond = NULL;
<<term clone parts>>=
if (cond) { assert(tag == SV);
ret->cond = new condition;
ret->cond->svname = cond->svname; ret->cond->tag = cond->tag; }
<<term replace parts>>=
if (cond) delete cond;
cond = t->cond;
@
\newpage
\subsection{Memory Management}
\begin{comment}
We look at some memory management issues in this section.
A naive scheme relying on {\tt new} and {\tt delete} is in use at the
moment.
It is not clear to the author whether a separate heap-allocating
scheme would make the system go a whole lot faster.
\end{comment}
\begin{comment}
We put wrappers around {\tt new} and {\tt delete} to collect some
statistics.
The procedure {\tt mem\_report} shows the total number of terms
allocated and subsequently freed.
This is used to check whether there is a memory leak.
\end{comment}
<<term::memory management>>=
extern void makeHeap();
extern term * new_term(kind k);
extern term * new_term(kind k, int code);
extern term * new_term_int(int x);
extern term * new_term_int(long long int x);
extern term * new_term_float(float x);
extern void mem_report();
@
<<terms.cc::local functions>>=
#ifdef DEBUG_MEM
static long int allocated = 0;
static long int freed = 0;
#endif
#define HEAPSIZE 100000
term heap[HEAPSIZE];
term * avail;
void makeHeap() {
// cout << "Sizeof(term) = " << sizeof(term) << endl;
// cout << "Sizeof(char) = " << sizeof(char) << endl;
// cout << "Sizeof(short) = " << sizeof(short) << endl;
// cout << "Sizeof(int) = " << sizeof(int) << endl;
// cout << "Sizeof(bool) = " << sizeof(bool) << endl;
avail = heap;
for (int i=0; i!=HEAPSIZE-1; i++) {
heap[i].next = &(heap[i+1]);
}
heap[HEAPSIZE-1].next = NULL;
}
term * myalloc() {
if (avail == NULL) assert(false);
term * ret = avail; avail = avail->next;
<<heap term init>>
return ret;
}
inline void mydealloc(term * p) { p->next = avail; avail = p; }
@
<<terms.cc::local functions>>=
term * new_term(kind k) {
term * ret = myalloc(); ret->tag = k;
return ret;
}
term * new_term(kind k, int code) {
term * ret = myalloc();
ret->tag = k;
ret->cname = code;
return ret;
}
term * new_term_int(int x) {
term * ret = myalloc();
ret->tag = D;
ret->isint = true; ret->numi = x; return ret;
}
term * new_term_int(long long int x) {
term * ret = myalloc();
ret->tag = D;
ret->isint = true; ret->numi = x; return ret;
}
term * new_term_float(float x) {
term * ret = myalloc();
ret->tag = D;
ret->isfloat = true; ret->numf = x; return ret;
}
@ %def new_term new_term_int new_term_float
<<terms.cc::local functions>>=
inline void delete_term(term * x) { mydealloc(x); }
void mem_report() {
#ifdef DEBUG_MEM
cout << "\n\nReport from Memory Manager:\n";
cout << "\tAllocated " << allocated << endl;
cout << "\tFreed " << freed << endl;
cout << "\tUnaccounted " << allocated - freed << endl << endl;
#endif
} // >>
@ %def delete_term mem_report
\begin{comment}\label{com:cloning of shared nodes}
Cloning of a term with shared nodes will result in an identical term
without shared nodes.
\end{comment}
<<term::function definitions>>=
term * term::clone() {
term * ret;
if (isfloat) ret = new_term_float(numf);
else if (isint) ret = new_term_int(numi);
else if (tag >= SV && tag <= D) ret = new_term(tag, cname);
else ret = new_term(tag);
<<term clone parts>>
int size = fieldsize;
for (int i=0; i!=size; i++) ret->insert(fields[i]->clone());
return ret;
}
@
\begin{comment}
We explicitly free memory instead of relying on destructors.
The function freememory must take node sharing into account.
A term is in use while its reference count is still non-zero.
\end{comment}
<<term::function definitions>>=
/*
void term::freememory() {
refcount--;
<<freememory error checking>>
if (refcount != 0) return;
if (ptype) { delete_type(ptype); }
if (cond) delete cond;
int size = fieldsize;
for (int i=0; i!=size; i++) if (fields[i]) fields[i]->freememory();
fieldsize = 0;
delete_term(this);
}
*/
void term::freememory() {
refcount--;
<<freememory error checking>>
if (refcount != 0) return;
term * p = this;
delete_term(this);
if (p->ptype) delete_type(p->ptype);
if (p->cond) delete p->cond;
int size = p->fieldsize;
for (int i=0; i!=size; i++)
if (p->fields[i]) p->fields[i]->freememory();
p->fieldsize = 0;
}
@
<<freememory error checking>>=
// if (refcount < 0) { setSelector(STDERR); print(); ioprintln();
// ioprint("refcount = "); ioprintln(refcount); }
assert(refcount >= 0);
@
\begin{comment}
This function overwrites the root of the current term with the input term $t$.
We need to do this if the current node is shared (see
\S \ref{subsec:node sharing}) or when the current
term is the root term (with no parent).
The procedure is simple.
The information on the root of $t$ is copied, and all the child nodes
of $t$ are reused.
We first {\tt reuse} the child nodes of {\tt t} because we could be
replacing the current term with its children, in which case {\tt t}
can get deleted before we can reuse its child nodes if we are not
careful.
\end{comment}
<<term::function definitions>>=
void term::replace(term * t) {
tag = t->tag;
<<term replace parts>>
int tsize = t->fieldsize;
for (int i=0; i!=tsize; i++) t->fields[i]->reuse();
int size = fieldsize;
for (int i=0; i!=size; i++) if (fields[i]) fields[i]->freememory();
// fields.resize(tsize);
// copy(t->fields.begin(), t->fields.end(), fields.begin());
fieldsize = t->fieldsize;
for (int i=0; i!=tsize; i++)
fields[i] = t->fields[i];
}
@ %def replace
\newpage
\subsection{Sharing of Nodes}\label{subsec:node sharing}
\begin{comment}
We use reference counting to implement sharing of nodes.
\end{comment}
<<term parts>>=
int refcount;
<<term init>>=
refcount = 1;
@
<<heap term init>>=
ret->refcount = 1;
@
\begin{comment}
A cloned object of a shared term would have {\tt refcount} 1.
Also, after replacing, the term retains its original {\tt refcount} value.
\end{comment}
<<term::function declarations>>=
term * reuse() { refcount++; return this; }
@ %def reuse
<<term::function declarations>>=
bool shared() { return (refcount > 1); }
@ %def shared
\begin{comment}\label{com:on sharing}
A few notes on sharing.
One of the biggest advantages of sharing is that common subexpressions
need only be evaluated once.
Sharing of nodes can, however, interfere with a few basic
operations in Escher.\\
\noindent Firstly, I believe the operation of checking for
free-variable capture, a test we need to do frequently during pattern
matching (see \S \ref{subsec:pattern matching}) and term substitution
(see \S \ref{subsec:term substitution}), cannot be done efficiently if a
variable that occurs both free and bound in a term is shared.\\
% The algorithm described in Comment \ref{com:labelVariables} will not work.
% It is hard to imagine an (easy) labelling scheme that would work.\\
\noindent Second, sharing of nodes is not always safe.
Some statements in the booleans module, especially the ones that
support logic programming (see for example Comment \ref{com:beta reduction}),
can potentially change shared nodes in destructive ways.
The extensive use of such sharing-unfriendly statements in Escher is
the primary reason I gave up on sharing.\\
\noindent In the absence of sharing, the computational saving that can
be obtained from common subexpression evaluation can be achieved using
(intelligent) caching.\index{caching of computation steps}\\
\noindent Having said all that, sharing does have at least one
important use in our interpreter; see Comment \ref{com:collectSharedVars}.
\end{comment}
\begin{comment}
The following function, which is no longer in use, provides a way to
unshare shared nodes using the side effect of the cloning operation
(see Comment \ref{com:cloning of shared nodes}).
Time complexity: the entire term needs to be traversed; nodes that are
not traversed by this function will be traversed by {\tt clone}.
\end{comment}
<<term::function declarations>>=
void unshare(term * parent, unint id);
@
\begin{comment}
We should {\tt assert(parent)} because a shared node, by definition,
have at least two parents.
\end{comment}
<<term::function definitions>>=
void term::unshare(term * parent, unint id) {
if (refcount > 1) {
assert(parent); term * temp = clone();
parent->fields[id]->freememory(); parent->fields[id] = temp;
return; }
int size = fieldsize;
for (int i=0; i!=size; i++) fields[i]->unshare(this, i);
}
@
% \newpage
\subsection{Free and Bound Variables}
\begin{comment}
One must be careful when dealing with free and bound variables.
This is something that is not difficult to get right, but
incredibly easy to get wrong!
So please pay some attention.
\end{comment}
\begin{definition}
An occurrence of a variable $x$ in a term is {\em bound} if it occurs
within a subterm of the form $\lambda x.t$.
\end{definition}
\begin{definition}
An occurrence of a variable in a term is {\em free} if it is not a
bound occurrence.
\end{definition}
\begin{fact}
A variable is free in a term iff it has a free occurrence.
\end{fact}
\begin{comment}
The following function returns all the free variables inside a term.
It is assumed that we have called {\tt labelVariables} on the term to
initialize all the labels and binding labels.
\end{comment}
\begin{comment}\label{com:freevars computed}
Computed free variables are cached in the array {\tt frvars}.
The flag {\tt freevars\_computed} tells us whether {\tt frvars} has
been initialized.
An array instead of a set is used to store the free variables.
This means free variables with multiple occurrences will be recorded
multiple times.
We need to record multiple occurrences;
see Comment~\ref{com:equation conditions}.
Further, using an array is faster than using a set.
\end{comment}
<<term bool parts>>=
bool freevars_computed;
<<term parts>>=
int time_computed;
<<term vector parts>>=
int frvars[20];
int frvarsize;
<<term init>>=
frvarsize = 0;
freevars_computed = false;
time_computed = -5;
@
<<heap term init>>=
ret->frvarsize = 0;
ret->freevars_computed = false;
ret->time_computed = -5;
@
\begin{comment}
These values become obsolete on replacing.
\end{comment}
<<term replace parts>>=
frvarsize = 0;
freevars_computed = false;
time_computed = -5;
<<term::function definitions>>=
void term::getFreeVars() {
if (freevars_computed && time_computed == ltime) return;
frvarsize = 0;
freevars_computed = true; time_computed = ltime;
if (tag == D || tag == F) return;
if (tag == V) { frvars[0] = cname; frvarsize = 1; return; }
if (tag == ABS) {
fields[1]->getFreeVars();
for (int i=0; i!=fields[1]->frvarsize; i++) {
if (fields[1]->frvars[i] == fields[0]->cname) continue;
frvars[frvarsize] = fields[1]->frvars[i];
frvarsize++;
}
assert(frvarsize <= 20);
return;
}
for (unint i=0; i!=fieldsize; i++) {
fields[i]->getFreeVars();
for (int j=0; j!=fields[i]->frvarsize; j++) {
if (j > 0 && fields[i]->frvars[j] == fields[i]->frvars[j-1])
continue;
frvars[frvarsize] = fields[i]->frvars[j];
frvarsize++;
}
assert(frvarsize <= 20);
}
return;
}
@ %def getFreeVars
\begin{comment}\label{com:labelStaticBoundVars}
For terms that stay unchanged throughout the whole computation
(e.g. program statements), freeness checking of variables can be done
(slightly) more efficiently by flagging each bound variable in the term
directly up front.
This is achieved using the following function {\tt labelStaticBoundVars()}.\\
% \noindent At present, we only call this function on the head of
% program statements.
\end{comment}
\begin{comment}
We first look at the {\tt free} parameter.
To ensure safe use, the {\tt free} parameter is only valid if the
{\tt validfree} parameter is true.
(The function {\tt labelStaticBoundVars} is responsible for setting this
latter parameter.
Its value will get set to {\tt false} during cloning and replacing.)
\end{comment}
<<term bool parts>>=
bool free;
bool validfree;
<<term init>>=
validfree = false;
@
<<heap term init>>=
ret->validfree = false;
@
\begin{comment}
If the whole term $t$ on which {\tt labelBoundVars} is called is to be
cloned, then the existing value of the {\tt free} parameter would
remain correct.
However, if only a subterm $t_1$ of $t$ is to be cloned, then
some variables that are bound in $t$ can become free in $t_1$.
Variables that are free in $t$ would remain free in $t_1$ though.
However, if $t$ (respectively $t_1$) is then subsequently substituted into
another term (using the mechanism of syntactical variables), then free
variables in $t$ (respectively $t_1$) can become bound.
For all these reasons, we will not attempt to recycle values of
{\tt free} parameters during cloning and replacing.
\end{comment}
<<term clone parts>>=
ret->validfree = false;
@