⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tutor10.doc

📁 计算机编译原理教材
💻 DOC
📖 第 1 页 / 共 5 页
字号:





            <data declaration> ::= VAR <var-list>


       Note that since there is only one variable type, there is no need
       to  declare the type.  Later on, for full KISS, we can easily add
       a type description.

       The procedure Prog becomes:


       {--------------------------------------------------------------}
       {  Parse and Translate a Program }

       procedure Prog;
       begin
          Match('p');
          Header;
          TopDecls;
          Main;
          Match('.');
       end;
       {--------------------------------------------------------------}


       Now, add the two new procedures:


       {--------------------------------------------------------------}
       { Process a Data Declaration }

       procedure Decl;
       begin
          Match('v');
          GetChar;
       end;


       {--------------------------------------------------------------}
       { Parse and Translate Global Declarations }

       procedure TopDecls;
       begin
          while Look <> 'b' do
             case Look of
               'v': Decl;
             else Abort('Unrecognized Keyword ''' + Look + '''');
             end;
       end;
       {--------------------------------------------------------------}


       Note that at this point, Decl  is  just  a stub.  It generates no
       code, and it doesn't process a list ... every variable must occur
       in a separate VAR statement.A*2A*
                                     - 8 -

PA2A





       OK,  now  we  can have any  number  of  data  declarations,  each
       starting with a 'v' for VAR,  before  the BEGIN-block.  Try a few
       cases and see what happens.


       DECLARATIONS AND SYMBOLS

       That looks pretty good, but  we're still only generating the null
       program  for  output.    A  real compiler would  issue  assembler
       directives to allocate storage for  the  variables.    It's about
       time we actually produced some code.

       With  a  little  extra  code,  that's  an  easy  thing to do from
       procedure Decl.  Modify it as follows:


       {--------------------------------------------------------------}
       { Parse and Translate a Data Declaration }

       procedure Decl;
       var Name: char;
       begin
          Match('v');
          Alloc(GetName);
       end;
       {--------------------------------------------------------------}


       The procedure Alloc just  issues  a  command  to the assembler to
       allocate storage:


       {--------------------------------------------------------------}
       { Allocate Storage for a Variable }

       procedure Alloc(N: char);
       begin
          WriteLn(N, ':', TAB, 'DC 0');
       end;
       {--------------------------------------------------------------}


       Give  this  one  a  whirl.    Try  an  input  that declares  some
       variables, such as:

            pvxvyvzbe.

       See how the storage is allocated?    Simple, huh?  Note also that
       the entry point, "MAIN," comes out in the right place.

       For the record, a "real" compiler would also have a  symbol table
       to record the variables being used.  Normally,  the  symbol table
       is necessary to record the type  of  each variable.  But since in
       this case  all  variables  have  the  same  type, we don't need aA*2A*
                                     - 9 -

PA2A





       symbol  table  for  that reason.  As it turns out, we're going to
       find a symbol  necessary  even without different types, but let's
       postpone that need until it arises.

       Of course, we haven't really parsed the correct syntax for a data
       declaration, since it involves a variable list.  Our version only
       permits a single variable.  That's easy to fix, too.

       The BNF for <var-list> is


            <var-list> ::= <ident> (, <ident>)*


       Adding this syntax to Decl gives this new version:


       {--------------------------------------------------------------}
       { Parse and Translate a Data Declaration }

       procedure Decl;
       var Name: char;
       begin
          Match('v');
          Alloc(GetName);
          while Look = ',' do begin
             GetChar;
             Alloc(GetName);
          end;
       end;
       {--------------------------------------------------------------}


       OK, now compile this code and give it  a  try.    Try a number of
       lines of VAR declarations, try a list of several variables on one
       line, and try combinations of the two.  Does it work?


       INITIALIZERS

       As long as we're dealing with data declarations, one thing that's
       always  bothered  me  about  Pascal  is  that  it  doesn't  allow
       initializing  data items in the declaration.    That  feature  is
       admittedly sort of a frill, and it  may  be  out  of  place  in a
       language that purports to  be  a minimal language.  But it's also
       SO easy to add that it seems a shame not  to  do  so.    The  BNF
       becomes:


            <var-list> ::= <var> ( <var> )*

            <var> ::= <ident> [ = <integer> ]AB2AB
                                    - 10 -A*2A*

PA2A





       Change Alloc as follows:


       {--------------------------------------------------------------}
       { Allocate Storage for a Variable }

       procedure Alloc(N: char);
       begin
          Write(N, ':', TAB, 'DC ');
          if Look = '=' then begin
             Match('=');
             WriteLn(GetNum);
             end
          else
             WriteLn('0');
       end;
       {--------------------------------------------------------------}


       There you are: an initializer with six added lines of Pascal.

       OK, try this  version  of  TINY  and verify that you can, indeed,
       give the variables initial values.

       By golly, this thing is starting to look  real!    Of  course, it
       still doesn't DO anything, but it looks good, doesn't it?

       Before leaving this section, I should point out  that  we've used
       two versions of function GetNum.  One, the earlier one, returns a
       character value, a single digit.  The other accepts a multi-digit
       integer and returns an integer value.  Either one will work here,
       since WriteLn will handle either type.  But there's no  reason to
       limit ourselves  to  single-digit  values  here,  so  the correct
       version to use is the one that returns an integer.  Here it is:


       {--------------------------------------------------------------}
       { Get a Number }

       function GetNum: integer;
       var Val: integer;
       begin
          Val := 0;
          if not IsDigit(Look) then Expected('Integer');
          while IsDigit(Look) do begin
             Val := 10 * Val + Ord(Look) - Ord('0');
             GetChar;
          end;
          GetNum := Val;
       end;
       {--------------------------------------------------------------}AN2AN
                                    - 11 -A*2A*

PA2A





       As a matter  of  fact,  strictly  speaking  we  should  allow for
       expressions in the data field of the initializer, or at  the very
       least  for  negative  values.  For  now,  let's  just  allow  for
       negative values by changing the code for Alloc as follows:


       {--------------------------------------------------------------}
       { Allocate Storage for a Variable }

       procedure Alloc(N: char);
       begin
          if InTable(N) then Abort('Duplicate Variable Name ' + N);
          ST[N] := 'v';
          Write(N, ':', TAB, 'DC ');
          if Look = '=' then begin
             Match('=');
             If Look = '-' then begin
                Write(Look);
                Match('-');
             end;
             WriteLn(GetNum);
             end
          else
             WriteLn('0');
       end;
       {--------------------------------------------------------------}


       Now  you should be able to  initialize  variables  with  negative
       and/or multi-digit values.


       THE SYMBOL TABLE

       There's one problem  with  the  compiler  as it stands so far: it
       doesn't do anything to record a variable when we declare it.   So
       the compiler is perfectly content to allocate storage for several
       variables with the same name.  You can easily verify this with an
       input like


            pvavavabe.


       Here we've declared the variable A three times.  As you  can see,
       the compiler will  cheerfully  accept  that,  and  generate three
       identical labels.  Not good.

       Later on,  when we start referencing variables, the compiler will
       also let us reference variables  that don't exist.  The assembler
       will  catch  both  of these error conditions, but it doesn't seem
       friendly at all to pass such errors along to the assembler.   The
       compiler should catch such things at the source language level.A62A6
                                    - 12 -A*2A*

PA2A





       So even though we don't need a symbol table to record data types,
       we ought to install  one  just to check for these two conditions.
       Since at this  point  we are still restricted to single-character
       variable names, the symbol table can be trivial.  To  provide for
       it, first add the following  declaration at the beginning of your
       program:


            var ST: array['A'..'Z'] of char;


       and insert the following function:


       {--------------------------------------------------------------}
       { Look for Symbol in Table }

       function InTable(n: char): Boolean;
       begin
          InTable := ST[n] <> ' ';
       end;
       {--------------------------------------------------------------}


       We  also  need  to initialize the  table  to  all  blanks.    The
       following lines in Init will do the job:


       var i: char;
       begin
          for i := 'A' to 'Z' do
             ST[i] := ' ';
          ...


       Finally,  insert  the  following two lines at  the  beginning  of
       Alloc:


          if InTable(N) then Abort('Duplicate Variable Name ' + N);
          ST[N] := 'v';


       That  should  do  it.  The  compiler  will  now  catch  duplicate
       declarations.  Later, we can  also  use  InTable  when generating
       references to the variables.


       EXECUTABLE STATEMENTS

       At this point, we can generate a null program that has  some data
       variables  declared  and  possibly initialized.  But  so  far  we
       haven't arranged to generate the first line of executable code.A62A6
                                    - 13 -A*2A*

PA2A





       Believe  it or not, though, we almost  have  a  usable  language!
       What's missing is the executable code that must go into  the main
       program.  But that code is just assignment statements and control
       statements ... all stuff we have done before.   So  it  shouldn't
       take us long to provide for them, as well.

       The BNF definition given earlier  for the main program included a
       statement block, which we have so far ignored:


            <main> ::= BEGIN <block> END


       For now,  we  can  just  consider  a  block  to  be  a  series of
       assignment statements:


            <block> ::= (Assignment)*


       Let's start things off by adding  a  parser for the block.  We'll
       begin with a stub for the assignment statement:


       {--------------------------------------------------------------}
       { Parse and Translate an Assignment Statement }

       procedure Assignment;
       begin
          GetChar;
       end;


       {--------------------------------------------------------------}
       { Parse and Translate a Block of Statements }

       procedure Block;
       begin
          while Look <> 'e' do
             Assignment;
       end;
       {--------------------------------------------------------------}


       Modify procedure Main to call Block as shown below:


       {--------------------------------------------------------------}
       { Parse and Translate a Main Program }

       procedure Main;
       begin
          Match('b');
          Prolog;A*2A*
                                    - 14 -

PA2A





          Block;
          Match('e');
          Epilog;
       end;
       {--------------------------------------------------------------}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -