-
Notifications
You must be signed in to change notification settings - Fork 1
/
tutor13.txt
2416 lines (1743 loc) · 72.1 KB
/
tutor13.txt
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
LET'S BUILD A COMPILER!
By
Jack W. Crenshaw, Ph.D.
27 August 1989
Part XIII: PROCEDURES
*****************************************************************
* *
* COPYRIGHT NOTICE *
* *
* Copyright (C) 1989 Jack W. Crenshaw. All rights reserved. *
* *
*****************************************************************
INTRODUCTION
At last we get to the good part!
At this point we've studied almost all the basic features of
compilers and parsing. We have learned how to translate
arithmetic expressions, Boolean expressions, control constructs,
data declarations, and I/O statements. We have defined a
language, TINY 1.3, that embodies all these features, and we have
written a rudimentary compiler that can translate them. By
adding some file I/O we could indeed have a working compiler that
could produce executable object files from programs written in
TINY. With such a compiler, we could write simple programs that
could read integer data, perform calculations with it, and output
the results.
That's nice, but what we have is still only a toy language. We
can't read or write even a single character of text, and we still
don't have procedures.
It's the features to be discussed in the next couple of
installments that separate the men from the toys, so to speak.
"Real" languages have more than one data type, and they support
procedure calls. More than any others, it's these two features
that give a language much of its character and personality. Once
we have provided for them, our languages, TINY and its
successors, will cease to become toys and will take on the
character of real languages, suitable for serious programming
jobs.
For several installments now, I've been promising you sessions on
these two important subjects. Each time, other issues came up
that required me to digress and deal with them. Finally, we've
been able to put all those issues to rest and can get on with the
mainstream of things. In this installment, I'll cover
procedures. Next time, we'll talk about the basic data types.
ONE LAST DIGRESSION
This has been an extraordinarily difficult installment for me to
write. The reason has nothing to do with the subject itself ...
I've known what I wanted to say for some time, and in fact I
presented most of this at Software Development '89, back in
February. It has more to do with the approach. Let me explain.
When I first began this series, I told you that we would use
several "tricks" to make things easy, and to let us learn the
concepts without getting too bogged down in the details. Among
these tricks was the idea of looking at individual pieces of a
compiler at a time, i.e. performing experiments using the Cradle
as a base. When we studied expressions, for example, we dealt
with only that part of compiler theory. When we studied control
structures, we wrote a different program, still based on the
Cradle, to do that part. We only incorporated these concepts into
a complete language fairly recently. These techniques have served
us very well indeed, and led us to the development of a compiler
for TINY version 1.3.
When I first began this session, I tried to build upon what we
had already done, and just add the new features to the existing
compiler. That turned out to be a little awkward and tricky ...
much too much to suit me.
I finally figured out why. In this series of experiments, I had
abandoned the very useful techniques that had allowed us to get
here, and without meaning to I had switched over into a new
method of working, that involved incremental changes to the full
TINY compiler.
You need to understand that what we are doing here is a little
unique. There have been a number of articles, such as the Small
C articles by Cain and Hendrix, that presented finished compilers
for one language or another. This is different. In this series
of tutorials, you are watching me design and implement both a
language and a compiler, in real time.
In the experiments that I've been doing in preparation for this
article, I was trying to inject the changes into the TINY
compiler in such a way that, at every step, we still had a real,
working compiler. In other words, I was attempting an
incremental enhancement of the language and its compiler, while
at the same time explaining to you what I was doing.
That's a tough act to pull off! I finally realized that it was
dumb to try. Having gotten this far using the idea of small
experiments based on single-character tokens and simple,
special-purpose programs, I had abandoned them in favor of
working with the full compiler. It wasn't working.
So we're going to go back to our roots, so to speak. In this
installment and the next, I'll be using single-character tokens
again as we study the concepts of procedures, unfettered by the
other baggage that we have accumulated in the previous sessions.
As a matter of fact, I won't even attempt, at the end of this
session, to merge the constructs into the TINY compiler. We'll
save that for later.
After all this time, you don't need more buildup than that, so
let's waste no more time and dive right in.
THE BASICS
All modern CPU's provide direct support for procedure calls, and
the 68000 is no exception. For the 68000, the call is a BSR
(PC-relative version) or JSR, and the return is RTS. All we have
to do is to arrange for the compiler to issue these commands at
the proper place.
Actually, there are really THREE things we have to address. One
of them is the call/return mechanism. The second is the
mechanism for DEFINING the procedure in the first place. And,
finally, there is the issue of passing parameters to the called
procedure. None of these things are really very difficult, and
we can of course borrow heavily on what people have done in other
languages ... there's no need to reinvent the wheel here. Of the
three issues, that of parameter passing will occupy most of our
attention, simply because there are so many options available.
A BASIS FOR EXPERIMENTS
As always, we will need some software to serve as a basis for
what we are doing. We don't need the full TINY compiler, but we
do need enough of a program so that some of the other constructs
are present. Specifically, we need at least to be able to handle
statements of some sort, and data declarations.
The program shown below is that basis. It's a vestigial form of
TINY, with single-character tokens. It has data declarations,
but only in their simplest form ... no lists or initializers. It
has assignment statements, but only of the kind
<ident> = <ident>
In other words, the only legal expression is a single variable
name. There are no control constructs ... the only legal
statement is the assignment.
Most of the program is just the standard Cradle routines. I've
shown the whole thing here, just to make sure we're all starting
from the same point:
{--------------------------------------------------------------}
program Calls;
{--------------------------------------------------------------}
{ Constant Declarations }
const TAB = ^I;
CR = ^M;
LF = ^J;
{--------------------------------------------------------------}
{ Variable Declarations }
var Look: char; { Lookahead Character }
var ST: Array['A'..'Z'] of char;
{--------------------------------------------------------------}
{ Read New Character From Input Stream }
procedure GetChar;
begin
Read(Look);
end;
{--------------------------------------------------------------}
{ Report an Error }
procedure Error(s: string);
begin
WriteLn;
WriteLn(^G, 'Error: ', s, '.');
end;
{--------------------------------------------------------------}
{ Report Error and Halt }
procedure Abort(s: string);
begin
Error(s);
Halt;
end;
{--------------------------------------------------------------}
{ Report What Was Expected }
procedure Expected(s: string);
begin
Abort(s + ' Expected');
end;
{--------------------------------------------------------------}
{ Report an Undefined Identifier }
procedure Undefined(n: string);
begin
Abort('Undefined Identifier ' + n);
end;
{--------------------------------------------------------------}
{ Report an Duplicate Identifier }
procedure Duplicate(n: string);
begin
Abort('Duplicate Identifier ' + n);
end;
{--------------------------------------------------------------}
{ Get Type of Symbol }
function TypeOf(n: char): char;
begin
TypeOf := ST[n];
end;
{--------------------------------------------------------------}
{ Look for Symbol in Table }
function InTable(n: char): Boolean;
begin
InTable := ST[n] <> ' ';
end;
{--------------------------------------------------------------}
{ Add a New Symbol to Table }
procedure AddEntry(Name, T: char);
begin
if Intable(Name) then Duplicate(Name);
ST[Name] := T;
end;
{--------------------------------------------------------------}
{ Check an Entry to Make Sure It's a Variable }
procedure CheckVar(Name: char);
begin
if not InTable(Name) then Undefined(Name);
if TypeOf(Name) <> 'v' then Abort(Name + ' is not a
variable');
end;
{--------------------------------------------------------------}
{ Recognize an Alpha Character }
function IsAlpha(c: char): boolean;
begin
IsAlpha := upcase(c) in ['A'..'Z'];
end;
{--------------------------------------------------------------}
{ Recognize a Decimal Digit }
function IsDigit(c: char): boolean;
begin
IsDigit := c in ['0'..'9'];
end;
{--------------------------------------------------------------}
{ Recognize an AlphaNumeric Character }
function IsAlNum(c: char): boolean;
begin
IsAlNum := IsAlpha(c) or IsDigit(c);
end;
{--------------------------------------------------------------}
{ Recognize an Addop }
function IsAddop(c: char): boolean;
begin
IsAddop := c in ['+', '-'];
end;
{--------------------------------------------------------------}
{ Recognize a Mulop }
function IsMulop(c: char): boolean;
begin
IsMulop := c in ['*', '/'];
end;
{--------------------------------------------------------------}
{ Recognize a Boolean Orop }
function IsOrop(c: char): boolean;
begin
IsOrop := c in ['|', '~'];
end;
{--------------------------------------------------------------}
{ Recognize a Relop }
function IsRelop(c: char): boolean;
begin
IsRelop := c in ['=', '#', '<', '>'];
end;
{--------------------------------------------------------------}
{ Recognize White Space }
function IsWhite(c: char): boolean;
begin
IsWhite := c in [' ', TAB];
end;
{--------------------------------------------------------------}
{ Skip Over Leading White Space }
procedure SkipWhite;
begin
while IsWhite(Look) do
GetChar;
end;
{--------------------------------------------------------------}
{ Skip Over an End-of-Line }
procedure Fin;
begin
if Look = CR then begin
GetChar;
if Look = LF then
GetChar;
end;
end;
{--------------------------------------------------------------}
{ Match a Specific Input Character }
procedure Match(x: char);
begin
if Look = x then GetChar
else Expected('''' + x + '''');
SkipWhite;
end;
{--------------------------------------------------------------}
{ Get an Identifier }
function GetName: char;
begin
if not IsAlpha(Look) then Expected('Name');
GetName := UpCase(Look);
GetChar;
SkipWhite;
end;
{--------------------------------------------------------------}
{ Get a Number }
function GetNum: char;
begin
if not IsDigit(Look) then Expected('Integer');
GetNum := Look;
GetChar;
SkipWhite;
end;
{--------------------------------------------------------------}
{ Output a String with Tab }
procedure Emit(s: string);
begin
Write(TAB, s);
end;
{--------------------------------------------------------------}
{ Output a String with Tab and CRLF }
procedure EmitLn(s: string);
begin
Emit(s);
WriteLn;
end;
{--------------------------------------------------------------}
{ Post a Label To Output }
procedure PostLabel(L: string);
begin
WriteLn(L, ':');
end;
{--------------------------------------------------------------}
{ Load a Variable to the Primary Register }
procedure LoadVar(Name: char);
begin
CheckVar(Name);
EmitLn('MOVE ' + Name + '(PC),D0');
end;
{--------------------------------------------------------------}
{ Store the Primary Register }
procedure StoreVar(Name: char);
begin
CheckVar(Name);
EmitLn('LEA ' + Name + '(PC),A0');
EmitLn('MOVE D0,(A0)')
end;
{--------------------------------------------------------------}
{ Initialize }
procedure Init;
var i: char;
begin
GetChar;
SkipWhite;
for i := 'A' to 'Z' do
ST[i] := ' ';
end;
{--------------------------------------------------------------}
{ Parse and Translate an Expression }
{ Vestigial Version }
procedure Expression;
begin
LoadVar(GetName);
end;
{--------------------------------------------------------------}
{ Parse and Translate an Assignment Statement }
procedure Assignment;
var Name: char;
begin
Name := GetName;
Match('=');
Expression;
StoreVar(Name);
end;
{--------------------------------------------------------------}
{ Parse and Translate a Block of Statements }
procedure DoBlock;
begin
while not(Look in ['e']) do begin
Assignment;
Fin;
end;
end;
{--------------------------------------------------------------}
{ Parse and Translate a Begin-Block }
procedure BeginBlock;
begin
Match('b');
Fin;
DoBlock;
Match('e');
Fin;
end;
{--------------------------------------------------------------}
{ Allocate Storage for a Variable }
procedure Alloc(N: char);
begin
if InTable(N) then Duplicate(N);
ST[N] := 'v';
WriteLn(N, ':', TAB, 'DC 0');
end;
{--------------------------------------------------------------}
{ Parse and Translate a Data Declaration }
procedure Decl;
var Name: char;
begin
Match('v');
Alloc(GetName);
end;
{--------------------------------------------------------------}
{ Parse and Translate Global Declarations }
procedure TopDecls;
begin
while Look <> 'b' do begin
case Look of
'v': Decl;
else Abort('Unrecognized Keyword ' + Look);
end;
Fin;
end;
end;
{--------------------------------------------------------------}
{ Main Program }
begin
Init;
TopDecls;
BeginBlock;
end.
{--------------------------------------------------------------}
Note that we DO have a symbol table, and there is logic to check
a variable name to make sure it's a legal one. It's also worth
noting that I have included the code you've seen before to
provide for white space and newlines. Finally, note that the
main program is delimited, as usual, by BEGIN-END brackets.
Once you've copied the program to Turbo, the first step is to
compile it and make sure it works. Give it a few declarations,
and then a begin-block. Try something like:
va (for VAR A)
vb (for VAR B)
vc (for VAR C)
b (for BEGIN)
a=b
b=c
e. (for END.)
As usual, you should also make some deliberate errors, and verify
that the program catches them correctly.
DECLARING A PROCEDURE
If you're satisfied that our little program works, then it's time
to deal with the procedures. Since we haven't talked about
parameters yet, we'll begin by considering only procedures that
have no parameter lists.
As a start, let's consider a simple program with a procedure, and
think about the code we'd like to see generated for it:
PROGRAM FOO;
.
.
PROCEDURE BAR; BAR:
BEGIN .
. .
. .
END; RTS
BEGIN { MAIN PROGRAM } MAIN:
. .
. .
FOO; BSR BAR
. .
. .
END. END MAIN
Here I've shown the high-order language constructs on the left,
and the desired assembler code on the right. The first thing to
notice is that we certainly don't have much code to generate
here! For the great bulk of both the procedure and the main
program, our existing constructs take care of the code to be
generated.
The key to dealing with the body of the procedure is to recognize
that although a procedure may be quite long, declaring it is
really no different than declaring a variable. It's just one
more kind of declaration. We can write the BNF:
<declaration> ::= <data decl> | <procedure>
This means that it should be easy to modify TopDecl to deal with
procedures. What about the syntax of a procedure? Well, here's
a suggested syntax, which is essentially that of Pascal:
<procedure> ::= PROCEDURE <ident> <begin-block>
There is practically no code generation required, other than that
generated within the begin-block. We need only emit a label at
the beginning of the procedure, and an RTS at the end.
Here's the required code:
{--------------------------------------------------------------}
{ Parse and Translate a Procedure Declaration }
procedure DoProc;
var N: char;
begin
Match('p');
N := GetName;
Fin;
if InTable(N) then Duplicate(N);
ST[N] := 'p';
PostLabel(N);
BeginBlock;
Return;
end;
{--------------------------------------------------------------}
Note that I've added a new code generation routine, Return, which
merely emits an RTS instruction. The creation of that routine is
"left as an exercise for the student."
To finish this version, add the following line within the Case
statement in DoBlock:
'p': DoProc;
I should mention that this structure for declarations, and the
BNF that drives it, differs from standard Pascal. In the Jensen
& Wirth definition of Pascal, variable declarations, in fact ALL
kinds of declarations, must appear in a specific sequence, i.e.
labels, constants, types, variables, procedures, and main
program. To follow such a scheme, we should separate the two
declarations, and have code in the main program something like
DoVars;
DoProcs;
DoMain;
However, most implementations of Pascal, including Turbo, don't
require that order and let you freely mix up the various
declarations, as long as you still don't try to refer to
something before it's declared. Although it may be more
aesthetically pleasing to declare all the global variables at the
top of the program, it certainly doesn't do any HARM to allow
them to be sprinkled around. In fact, it may do some GOOD, in
the sense that it gives you the opportunity to do a little
rudimentary information hiding. Variables that should be
accessed only by the main program, for example, can be declared
just before it and will thus be inaccessible by the procedures.
OK, try this new version out. Note that we can declare as many
procedures as we choose (as long as we don't run out of single-
character names!), and the labels and RTS's all come out in the
right places.
It's worth noting here that I do _NOT_ allow for nested
procedures. In TINY, all procedures must be declared at the
global level, the same as in C. There has been quite a
discussion about this point in the Computer Language Forum of
CompuServe. It turns out that there is a significant penalty in
complexity that must be paid for the luxury of nested procedures.
What's more, this penalty gets paid at RUN TIME, because extra
code must be added and executed every time a procedure is called.
I also happen to believe that nesting is not a good idea, simply
on the grounds that I have seen too many abuses of the feature.
Before going on to the next step, it's also worth noting that the
"main program" as it stands is incomplete, since it doesn't have
the label and END statement. Let's fix that little oversight:
{--------------------------------------------------------------}
{ Parse and Translate a Main Program }
procedure DoMain;
begin
Match('b');
Fin;
Prolog;
DoBlock;
Epilog;
end;
{--------------------------------------------------------------}
.
.
.
{--------------------------------------------------------------}
{ Main Program }
begin
Init;
TopDecls;
DoMain;
end.
{--------------------------------------------------------------}
Note that DoProc and DoMain are not quite symmetrical. DoProc
uses a call to BeginBlock, whereas DoMain cannot. That's because
a procedure is signaled by the keyword PROCEDURE (abbreviated by
a 'p' here), while the main program gets no keyword other than
the BEGIN itself.
And _THAT_ brings up an interesting question: WHY?
If we look at the structure of C programs, we find that all
functions are treated just alike, except that the main program
happens to be identified by its name, "main." Since C functions
can appear in any order, the main program can also be anywhere in
the compilation unit.
In Pascal, on the other hand, all variables and procedures must
be declared before they're used, which means that there is no
point putting anything after the main program ... it could never
be accessed. The "main program" is not identified at all, other
than being that part of the code that comes after the global
BEGIN. In other words, if it ain't anything else, it must be the
main program.
This causes no small amount of confusion for beginning
programmers, and for big Pascal programs sometimes it's difficult
to find the beginning of the main program at all. This leads to
conventions such as identifying it in comments:
BEGIN { of MAIN }
This has always seemed to me to be a bit of a kludge. The
question comes up: Why should the main program be treated so
much differently than a procedure? In fact, now that we've
recognized that procedure declarations are just that ... part of
the global declarations ... isn't the main program just one more
declaration, also?
The answer is yes, and by treating it that way, we can simplify
the code and make it considerably more orthogonal. I propose
that we use an explicit keyword, PROGRAM, to identify the main
program (Note that this means that we can't start the file with
it, as in Pascal). In this case, our BNF becomes:
<declaration> ::= <data decl> | <procedure> | <main program>
<procedure> ::= PROCEDURE <ident> <begin-block>
<main program> ::= PROGRAM <ident> <begin-block>
The code also looks much better, at least in the sense that
DoMain and DoProc look more alike:
{--------------------------------------------------------------}
{ Parse and Translate a Main Program }
procedure DoMain;
var N: char;
begin
Match('P');
N := GetName;
Fin;
if InTable(N) then Duplicate(N);
Prolog;
BeginBlock;
end;
{--------------------------------------------------------------}
.
.
.
{--------------------------------------------------------------}
{ Parse and Translate Global Declarations }
procedure TopDecls;
begin
while Look <> '.' do begin
case Look of
'v': Decl;
'p': DoProc;
'P': DoMain;
else Abort('Unrecognized Keyword ' + Look);
end;
Fin;
end;
end;
{--------------------------------------------------------------}
{ Main Program }
begin
Init;
TopDecls;
Epilog;
end.
{--------------------------------------------------------------}
Since the declaration of the main program is now within the loop
of TopDecl, that does present some difficulties. How do we
ensure that it's the last thing in the file? And how do we ever
exit from the loop? My answer for the second question, as you
can see, was to bring back our old friend the period. Once the
parser sees that, we're done.
To answer the first question: it depends on how far we're
willing to go to protect the programmer from dumb mistakes. In
the code that I've shown, there's nothing to keep the programmer
from adding code after the main program ... even another main
program. The code will just not be accessible. However, we
COULD access it via a FORWARD statement, which we'll be providing
later. As a matter of fact, many assembler language programmers
like to use the area just after the program to declare large,
uninitialized data blocks, so there may indeed be some value in
not requiring the main program to be last. We'll leave it as it
is.
If we decide that we should give the programmer a little more
help than that, it's pretty easy to add some logic to kick us out
of the loop once the main program has been processed. Or we
could at least flag an error if someone tries to include two
mains.
CALLING THE PROCEDURE
If you're satisfied that things are working, let's address the
second half of the equation ... the call.
Consider the BNF for a procedure call:
<proc_call> ::= <identifier>
for an assignment statement, on the other hand, the BNF is:
<assignment> ::= <identifier> '=' <expression>
At this point we seem to have a problem. The two BNF statements
both begin on the right-hand side with the token <identifier>.
How are we supposed to know, when we see the identifier, whether
we have a procedure call or an assignment statement? This looks
like a case where our parser ceases being predictive, and indeed
that's exactly the case. However, it turns out to be an easy
problem to fix, since all we have to do is to look at the type of
the identifier, as recorded in the symbol table. As we've
discovered before, a minor local violation of the predictive
parsing rule can be easily handled as a special case.
Here's how to do it:
{--------------------------------------------------------------}
{ Parse and Translate an Assignment Statement }
procedure Assignment(Name: char);
begin
Match('=');
Expression;
StoreVar(Name);
end;
{--------------------------------------------------------------}
{ Decide if a Statement is an Assignment or Procedure Call }
procedure AssignOrProc;
var Name: char;
begin
Name := GetName;
case TypeOf(Name) of
' ': Undefined(Name);
'v': Assignment(Name);
'p': CallProc(Name);
else Abort('Identifier ' + Name +
' Cannot Be Used Here');
end;
end;
{--------------------------------------------------------------}
{ Parse and Translate a Block of Statements }
procedure DoBlock;
begin
while not(Look in ['e']) do begin
AssignOrProc;
Fin;
end;
end;
{--------------------------------------------------------------}
As you can see, procedure Block now calls AssignOrProc instead of
Assignment. The function of this new procedure is to simply read
the identifier, determine its type, and then call whichever
procedure is appropriate for that type. Since the name has
already been read, we must pass it to the two procedures, and