home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / TUTORPAS.ARC / TUTOR9.DOC < prev   
Encoding:
Text File  |  1989-05-21  |  31.9 KB  |  946 lines

  1. O
  2. PA A
  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.                             LET'S BUILD A COMPILER!
  30.  
  31.                                        By
  32.  
  33.                             Jack W. Crenshaw, Ph.D.
  34.  
  35.                                  16 April 1989
  36.  
  37.  
  38.                               Part IX: A TOP VIEW
  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. PA A
  70.  
  71.  
  72.  
  73.  
  74.  
  75.        *****************************************************************
  76.        *                                                               *
  77.        *                        COPYRIGHT NOTICE                       *
  78.        *                                                               *
  79.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  80.        *                                                               *
  81.        *****************************************************************
  82.  
  83.  
  84.        INTRODUCTION
  85.  
  86.        In  the  previous  installments,  we  have  learned  many of  the
  87.        techniques required to  build  a full-blown compiler.  We've done
  88.        both  assignment   statements   (with   Boolean   and  arithmetic
  89.        expressions),  relational operators, and control constructs.   We
  90.        still haven't  addressed procedure or function calls, but even so
  91.        we  could  conceivably construct a  mini-language  without  them.
  92.        I've  always  thought  it would be fun to see just  how  small  a
  93.        language  one  could  build  that  would still be useful.   We're
  94.        ALMOST in a position to do that now.  The  problem  is: though we
  95.        know  how  to  parse and translate the constructs, we still don't
  96.        know quite how to put them all together into a language.
  97.  
  98.        In those earlier installments, the  development  of  our programs
  99.        had  a decidedly bottom-up flavor.  In  the  case  of  expression
  100.        parsing,  for  example,  we  began  with  the  very lowest  level
  101.        constructs, the individual constants  and  variables,  and worked
  102.        our way up to more complex expressions.
  103.  
  104.        Most people regard  the  top-down design approach as being better
  105.        than  the  bottom-up  one.  I do too,  but  the  way  we  did  it
  106.        certainly seemed natural enough for the kinds of  things  we were
  107.        parsing.
  108.  
  109.        You mustn't get  the  idea, though, that the incremental approach
  110.        that  we've  been  using  in  all these tutorials  is  inherently
  111.        bottom-up.  In  this  installment  I'd  like to show you that the
  112.        approach can work just as well when applied from the top down ...
  113.        maybe better.  We'll consider languages such as C and Pascal, and
  114.        see how complete compilers can be built starting from the top.
  115.  
  116.        In the next installment, we'll  apply the same technique to build
  117.        a  complete  translator  for a subset of the KISS language, which
  118.        I'll be  calling  TINY.    But one of my goals for this series is
  119.        that you will  not only be able to see how a compiler for TINY or
  120.        KISS  works,  but  that you will also be able to design and build
  121.        compilers for your own languages.  The C and Pascal examples will
  122.        help.    One  thing I'd like you  to  see  is  that  the  natural
  123.        structure of the compiler depends very much on the language being
  124.        translated, so the simplicity and  ease  of  construction  of the
  125.        compiler  depends  very  much  on  letting the language  set  the
  126.        program structure.ABAB
  127.                                      - 2 -A*A*
  128.  
  129. PA A
  130.  
  131.  
  132.  
  133.  
  134.  
  135.        It's  a bit much to produce a full C or Pascal compiler here, and
  136.        we won't try.   But we can flesh out the top levels far enough so
  137.        that you can see how it goes.
  138.  
  139.        Let's get started.
  140.  
  141.  
  142.        THE TOP LEVEL
  143.  
  144.        One of the biggest  mistakes  people make in a top-down design is
  145.        failing  to start at the true top.  They think they know what the
  146.        overall structure of the  design  should be, so they go ahead and
  147.        write it down.
  148.  
  149.        Whenever  I  start a new design, I always like to do  it  at  the
  150.        absolute beginning.   In  program design language (PDL), this top
  151.        level looks something like:
  152.  
  153.  
  154.             begin
  155.                solve the problem
  156.             end
  157.  
  158.  
  159.        OK, I grant  you that this doesn't give much of a hint as to what
  160.        the next level is, but I  like  to  write it down anyway, just to
  161.        give me that warm feeling that I am indeed starting at the top.
  162.  
  163.        For our problem, the overall function of a compiler is to compile
  164.        a complete program.  Any definition of the  language,  written in
  165.        BNF,  begins here.  What does the top level BNF look like?  Well,
  166.        that depends quite a bit on the language to be translated.  Let's
  167.        take a look at Pascal.
  168.  
  169.  
  170.        THE STRUCTURE OF PASCAL
  171.  
  172.        Most  texts  for  Pascal  include  a   BNF   or  "railroad-track"
  173.        definition of the language.  Here are the first few lines of one:
  174.  
  175.  
  176.             <program> ::= <program-header> <block> '.'
  177.  
  178.             <program-header> ::= PROGRAM <ident>
  179.  
  180.             <block> ::= <declarations> <statements>
  181.  
  182.  
  183.        We can write recognizers  to  deal  with  each of these elements,
  184.        just as we've done before.  For each one, we'll use  our familiar
  185.        single-character tokens to represent the input, then flesh things
  186.        out a little at a time.    Let's begin with the first recognizer:
  187.        the program itself.A6A6
  188.                                      - 3 -A*A*
  189.  
  190. PA A
  191.  
  192.  
  193.  
  194.  
  195.  
  196.        To translate this, we'll  start  with a fresh copy of the Cradle.
  197.        Since we're back to single-character  names, we'll just use a 'p'
  198.        to stand for 'PROGRAM.'
  199.  
  200.        To a fresh copy of the cradle, add the following code, and insert
  201.        a call to it from the main program:
  202.  
  203.  
  204.        {--------------------------------------------------------------}
  205.        { Parse and Translate A Program }
  206.  
  207.        procedure Prog;
  208.        var  Name: char;
  209.        begin
  210.           Match('p');            { Handles program header part }
  211.           Name := GetName;
  212.           Prolog(Name);
  213.           Match('.');
  214.           Epilog(Name);
  215.        end;
  216.        {--------------------------------------------------------------}
  217.  
  218.  
  219.        The procedures  Prolog and Epilog perform whatever is required to
  220.        let the program interface with the operating system,  so  that it
  221.        can execute as a program.  Needless to  say,  this  part  will be
  222.        VERY OS-dependent.  Remember, I've been emitting code for a 68000
  223.        running under the OS I use, which is SK*DOS.   I  realize most of
  224.        you are using PC's  and  would rather see something else, but I'm
  225.        in this thing too deep to change now!
  226.  
  227.        Anyhow, SK*DOS is a  particularly  easy OS to interface to.  Here
  228.        is the code for Prolog and Epilog:
  229.  
  230.  
  231.        {--------------------------------------------------------------}
  232.        { Write the Prolog }
  233.  
  234.        procedure Prolog;
  235.        begin
  236.           EmitLn('WARMST EQU $A01E');
  237.        end;
  238.  
  239.  
  240.        {--------------------------------------------------------------}
  241.        { Write the Epilog }
  242.  
  243.        procedure Epilog(Name: char);
  244.        begin
  245.           EmitLn('DC WARMST');
  246.           EmitLn('END ' + Name);
  247.        end;
  248.        {--------------------------------------------------------------}A6A6
  249.                                      - 4 -A*A*
  250.  
  251. PA A
  252.  
  253.  
  254.  
  255.  
  256.  
  257.        As usual, add  this  code  and  try  out the "compiler."  At this
  258.        point, there is only one legal input:
  259.  
  260.  
  261.             px.   (where x is any single letter, the program name)
  262.  
  263.  
  264.        Well,  as  usual  our first effort is rather unimpressive, but by
  265.        now  I'm sure you know that things  will  get  more  interesting.
  266.        There is one important thing to  note:   THE OUTPUT IS A WORKING,
  267.        COMPLETE, AND EXECUTABLE PROGRAM (at least after it's assembled).
  268.  
  269.        This  is  very  important.  The  nice  feature  of  the  top-down
  270.        approach is that at any stage you can  compile  a  subset  of the
  271.        complete language and get  a  program that will run on the target
  272.        machine.    From here on, then, we  need  only  add  features  by
  273.        fleshing out the language constructs.  It's all  very  similar to
  274.        what we've been doing all along, except that we're approaching it
  275.        from the other end.
  276.  
  277.  
  278.        FLESHING IT OUT
  279.  
  280.        To flesh out  the  compiler,  we  only have to deal with language
  281.        features  one by one.  I like to start with a stub procedure that
  282.        does  nothing, then add detail in  incremental  fashion.    Let's
  283.        begin  by  processing  a block, in accordance with its PDL above.
  284.        We can do this in two stages.  First, add the null procedure:
  285.  
  286.  
  287.        {--------------------------------------------------------------}
  288.        { Parse and Translate a Pascal Block }
  289.  
  290.        procedure DoBlock(Name: char);
  291.        begin
  292.        end;
  293.        {--------------------------------------------------------------}
  294.  
  295.  
  296.        and modify Prog to read:
  297.  
  298.  
  299.        {--------------------------------------------------------------}
  300.        { Parse and Translate A Program }
  301.  
  302.        procedure Prog;
  303.        var  Name: char;
  304.        begin
  305.           Match('p');
  306.           Name := GetName;
  307.           Prolog;
  308.           DoBlock(Name);
  309.           Match('.');
  310.           Epilog(Name);A*A*
  311.                                      - 5 -
  312.  
  313. PA A
  314.  
  315.  
  316.  
  317.  
  318.  
  319.        end;
  320.        {--------------------------------------------------------------}
  321.  
  322.  
  323.        That certainly  shouldn't change the behavior of the program, and
  324.        it doesn't.  But now the  definition  of Prog is complete, and we
  325.        can proceed to flesh out DoBlock.  That's done right from its BNF
  326.        definition:
  327.  
  328.  
  329.        {--------------------------------------------------------------}
  330.        { Parse and Translate a Pascal Block }
  331.  
  332.        procedure DoBlock(Name: char);
  333.        begin
  334.           Declarations;
  335.           PostLabel(Name);
  336.           Statements;
  337.        end;
  338.        {--------------------------------------------------------------}
  339.  
  340.  
  341.        The  procedure  PostLabel  was  defined  in  the  installment  on
  342.        branches.  Copy it into your cradle.
  343.  
  344.        I probably need to  explain  the  reason  for inserting the label
  345.        where I have.  It has to do with the operation of SK*DOS.  Unlike
  346.        some OS's,  SK*DOS allows the entry point to the main  program to
  347.        be  anywhere in the program.  All you have to do is to give  that
  348.        point a name.  The call  to  PostLabel puts that name just before
  349.        the first executable statement  in  the  main  program.  How does
  350.        SK*DOS know which of the many labels is the entry point, you ask?
  351.        It's the one that matches the END statement  at  the  end  of the
  352.        program.
  353.  
  354.        OK,  now  we  need  stubs  for  the  procedures Declarations  and
  355.        Statements.  Make them null procedures as we did before.
  356.  
  357.        Does the program  still run the same?  Then we can move on to the
  358.        next stage.
  359.  
  360.  
  361.        DECLARATIONS
  362.  
  363.        The BNF for Pascal declarations is:
  364.  
  365.  
  366.             <declarations> ::= ( <label list>    |
  367.                                  <constant list> |
  368.                                  <type list>     |
  369.                                  <variable list> |
  370.                                  <procedure>     |
  371.                                  <function>         )*A6A6
  372.                                      - 6 -A*A*
  373.  
  374. PA A
  375.  
  376.  
  377.  
  378.  
  379.  
  380.        (Note  that  I'm  using the more liberal definition used by Turbo
  381.        Pascal.  In the standard Pascal definition, each  of  these parts
  382.        must be in a specific order relative to the rest.)
  383.  
  384.        As  usual,  let's  let a single character represent each of these
  385.        declaration types.  The new form of Declarations is:
  386.  
  387.  
  388.        {--------------------------------------------------------------}
  389.        { Parse and Translate the Declaration Part }
  390.  
  391.        procedure Declarations;
  392.        begin
  393.           while Look in ['l', 'c', 't', 'v', 'p', 'f'] do
  394.              case Look of
  395.               'l': Labels;
  396.               'c': Constants;
  397.               't': Types;
  398.               'v': Variables;
  399.               'p': DoProcedure;
  400.               'f': DoFunction;
  401.              end;
  402.        end;
  403.        {--------------------------------------------------------------}
  404.  
  405.  
  406.        Of course, we need stub  procedures for each of these declaration
  407.        types.  This time,  they  can't  quite  be null procedures, since
  408.        otherwise we'll end up with an infinite While loop.  At  the very
  409.        least, each recognizer must  eat  the  character that invokes it.
  410.        Insert the following procedures:
  411.  
  412.  
  413.        {--------------------------------------------------------------}
  414.        { Process Label Statement }
  415.  
  416.        procedure Labels;
  417.        begin
  418.           Match('l');
  419.        end;
  420.  
  421.  
  422.        {--------------------------------------------------------------}
  423.        { Process Const Statement }
  424.  
  425.        procedure Constants;
  426.        begin
  427.           Match('c');
  428.        end;
  429.  
  430.  
  431.        {--------------------------------------------------------------}
  432.        { Process Type Statement }A6A6
  433.                                      - 7 -A*A*
  434.  
  435. PA A
  436.  
  437.  
  438.  
  439.  
  440.  
  441.        procedure Types;
  442.        begin
  443.           Match('t');
  444.        end;
  445.  
  446.  
  447.        {--------------------------------------------------------------}
  448.        { Process Var Statement }
  449.  
  450.        procedure Variables;
  451.        begin
  452.           Match('v');
  453.        end;
  454.  
  455.  
  456.        {--------------------------------------------------------------}
  457.        { Process Procedure Definition }
  458.  
  459.        procedure DoProcedure;
  460.        begin
  461.           Match('p');
  462.        end;
  463.  
  464.  
  465.        {--------------------------------------------------------------}
  466.        { Process Function Definition }
  467.  
  468.        procedure DoFunction;
  469.        begin
  470.           Match('f');
  471.        end;
  472.        {--------------------------------------------------------------}
  473.  
  474.  
  475.        Now try out the  compiler  with a few representative inputs.  You
  476.        can  mix  the  declarations any way you like, as long as the last
  477.        character  in  the  program is'.' to  indicate  the  end  of  the
  478.        program.  Of course,  none  of  the declarations actually declare
  479.        anything, so you don't need  (and can't use) any characters other
  480.        than those standing for the keywords.
  481.  
  482.        We can flesh out the statement  part  in  a similar way.  The BNF
  483.        for it is:
  484.  
  485.  
  486.             <statements> ::= <compound statement>
  487.  
  488.             <compound statement> ::= BEGIN <statement>
  489.                                           (';' <statement>) END
  490.  
  491.  
  492.        Note that statements can  begin  with  any identifier except END.
  493.        So the first stub form of procedure Statements is:A6A6
  494.                                      - 8 -A*A*
  495.  
  496. PA A
  497.  
  498.  
  499.  
  500.  
  501.  
  502.        {--------------------------------------------------------------}
  503.        { Parse and Translate the Statement Part }
  504.  
  505.        procedure Statements;
  506.        begin
  507.           Match('b');
  508.           while Look <> 'e' do
  509.              GetChar;
  510.           Match('e');
  511.        end;
  512.        {--------------------------------------------------------------}
  513.  
  514.  
  515.        At  this  point  the  compiler   will   accept   any   number  of
  516.        declarations, followed by the  BEGIN  block  of the main program.
  517.        This  block  itself  can contain any characters at all (except an
  518.        END), but it must be present.
  519.  
  520.        The simplest form of input is now
  521.  
  522.             'pxbe.'
  523.  
  524.        Try  it.    Also  try  some  combinations  of  this.   Make  some
  525.        deliberate errors and see what happens.
  526.  
  527.        At this point you should be beginning to see the drill.  We begin
  528.        with a stub translator to process a program, then  we  flesh  out
  529.        each procedure in turn,  based  upon its BNF definition.  Just as
  530.        the lower-level BNF definitions add detail and elaborate upon the
  531.        higher-level ones, the lower-level  recognizers  will  parse more
  532.        detail  of  the  input  program.    When  the  last stub has been
  533.        expanded,  the  compiler  will  be  complete.    That's  top-down
  534.        design/implementation in its purest form.
  535.  
  536.        You might note that even though we've been adding procedures, the
  537.        output of the program hasn't changed.  That's as  it  should  be.
  538.        At these  top  levels  there  is  no  emitted code required.  The
  539.        recognizers are  functioning as just that: recognizers.  They are
  540.        accepting input sentences, catching bad ones, and channeling good
  541.        input to the right places, so  they  are  doing their job.  If we
  542.        were to pursue this a bit longer, code would start to appear.
  543.  
  544.        The  next  step  in our expansion should  probably  be  procedure
  545.        Statements.  The Pascal definition is:
  546.  
  547.  
  548.            <statement> ::= <simple statement> | <structured statement>
  549.  
  550.            <simple statement> ::= <assignment> | <procedure call> | null
  551.  
  552.            <structured statement> ::= <compound statement> |
  553.                                       <if statement>       |
  554.                                       <case statement>     |
  555.                                       <while statement>    |A*A*
  556.                                      - 9 -
  557.  
  558. PA A
  559.  
  560.  
  561.  
  562.  
  563.  
  564.                                       <repeat statement>   |
  565.                                       <for statement>      |
  566.                                       <with statement>
  567.  
  568.  
  569.        These  are  starting  to look familiar.  As a matter of fact, you
  570.        have already gone  through  the process of parsing and generating
  571.        code for both assignment statements and control structures.  This
  572.        is where the top level meets our bottom-up  approach  of previous
  573.        sessions.  The constructs will be a little  different  from those
  574.        we've  been  using  for KISS, but the differences are nothing you
  575.        can't handle.
  576.  
  577.        I  think  you can get the picture now as to the  procedure.    We
  578.        begin with a complete BNF  description of the language.  Starting
  579.        at  the  top  level, we code  up  the  recognizer  for  that  BNF
  580.        statement, using stubs  for  the next-level recognizers.  Then we
  581.        flesh those lower-level statements out one by one.
  582.  
  583.        As it happens, the definition of Pascal is  very  compatible with
  584.        the  use of BNF, and BNF descriptions  of  the  language  abound.
  585.        Armed  with  such   a   description,  you  will  find  it  fairly
  586.        straightforward to continue the process we've begun.
  587.  
  588.        You  might  have  a go at fleshing a few of these constructs out,
  589.        just  to get a feel for it.  I don't expect you  to  be  able  to
  590.        complete a Pascal compiler  here  ...  there  are too many things
  591.        such  as  procedures  and types that we haven't addressed yet ...
  592.        but  it  might  be helpful to try some of the more familiar ones.
  593.        It will do  you  good  to  see executable programs coming out the
  594.        other end.
  595.  
  596.        If I'm going to address those issues that we haven't covered yet,
  597.        I'd rather  do  it  in  the context of KISS.  We're not trying to
  598.        build a complete Pascal  compiler  just yet, so I'm going to stop
  599.        the expansion of Pascal here.    Let's  take  a  look  at  a very
  600.        different language.
  601.  
  602.  
  603.        THE STRUCTURE OF C
  604.  
  605.        The C language is quite another matter, as you'll see.   Texts on
  606.        C  rarely  include  a BNF definition of  the  language.  Probably
  607.        that's because the language is quite hard to write BNF for.
  608.  
  609.        One reason I'm showing you these structures now is so that  I can
  610.        impress upon you these two facts:
  611.  
  612.         (1) The definition of  the  language drives the structure of the
  613.             compiler.  What works for one language may be a disaster for
  614.             another.    It's  a very bad idea to try to  force  a  given
  615.             structure upon the compiler.  Rather, you should let the BNF
  616.             drive the structure, as we have done here.A6A6
  617.                                     - 10 -A*A*
  618.  
  619. PA A
  620.  
  621.  
  622.  
  623.  
  624.  
  625.         (2) A language that is hard to write BNF for  will  probably  be
  626.             hard  to  write  a compiler for, as well.  C  is  a  popular
  627.             language,  and  it  has  a  reputation  for  letting you  do
  628.             virtually  anything that is possible to  do.    Despite  the
  629.             success of Small C, C is _NOT_ an easy language to parse.
  630.  
  631.  
  632.        A C program has  less  structure than its Pascal counterpart.  At
  633.        the top level, everything in C is a static declaration, either of
  634.        data or of a function.  We can capture this thought like this:
  635.  
  636.  
  637.             <program> ::= ( <global declaration> )*
  638.  
  639.             <global declaration> ::= <data declaration>  |
  640.                                      <function>
  641.  
  642.        In Small C, functions  can  only have the default type int, which
  643.        is not declared.  This makes  the  input easy to parse: the first
  644.        token is either "int," "char," or the name  of  a  function.   In
  645.        Small  C, the preprocessor commands are  also  processed  by  the
  646.        compiler proper, so the syntax becomes:
  647.  
  648.  
  649.             <global declaration> ::= '#' <preprocessor command>  |
  650.                                      'int' <data list>           |
  651.                                      'char' <data list>          |
  652.                                      <ident> <function body>     |
  653.  
  654.  
  655.        Although we're really more interested in full C  here,  I'll show
  656.        you the  code corresponding to this top-level structure for Small
  657.        C.
  658.  
  659.  
  660.        {--------------------------------------------------------------}
  661.        { Parse and Translate A Program }
  662.  
  663.        procedure Prog;
  664.        begin
  665.           while Look <> ^Z do begin
  666.              case Look of
  667.               '#': PreProc;
  668.               'i': IntDecl;
  669.               'c': CharDecl;
  670.              else DoFunction(Int);
  671.              end;
  672.           end;
  673.        end;
  674.        {--------------------------------------------------------------}
  675.  
  676.        Note that I've had to use a ^Z to indicate the end of the source.
  677.        C has no keyword such as END or the '.' to otherwise indicate the
  678.        end.A*A*
  679.                                     - 11 -
  680.  
  681. PA A
  682.  
  683.  
  684.  
  685.  
  686.  
  687.        With full C,  things  aren't  even  this easy.  The problem comes
  688.        about because in full C, functions can also have types.   So when
  689.        the compiler sees a  keyword  like  "int,"  it still doesn't know
  690.        whether to expect a  data  declaration  or a function definition.
  691.        Things get more  complicated  since  the  next token may not be a
  692.        name  ... it may start with an '*' or '(', or combinations of the
  693.        two.
  694.  
  695.        More specifically, the BNF for full C begins with:
  696.  
  697.  
  698.             <program> ::= ( <top-level decl> )*
  699.  
  700.             <top-level decl> ::= <function def> | <data decl>
  701.  
  702.             <data decl> ::= [<class>] <type> <decl-list>
  703.  
  704.             <function def> ::= [<class>] [<type>] <function decl>
  705.  
  706.  
  707.        You  can  now  see the problem:   The  first  two  parts  of  the
  708.        declarations for data and functions can be the same.   Because of
  709.        the  ambiguity  in  the grammar as  written  above,  it's  not  a
  710.        suitable  grammar  for  a  recursive-descent  parser.     Can  we
  711.        transform it into one that is suitable?  Yes, with a little work.
  712.        Suppose we write it this way:
  713.  
  714.  
  715.             <top-level decl> ::= [<class>] <decl>
  716.  
  717.             <decl> ::= <type> <typed decl> | <function decl>
  718.  
  719.             <typed decl> ::= <data list> | <function decl>
  720.  
  721.  
  722.        We  can  build  a  parsing  routine  for  the   class   and  type
  723.        definitions, and have them store away their findings  and  go on,
  724.        without their ever having to "know" whether a function or  a data
  725.        declaration is being processed.
  726.  
  727.        To begin, key in the following version of the main program:
  728.  
  729.  
  730.        {--------------------------------------------------------------}
  731.        { Main Program }
  732.  
  733.        begin
  734.           Init;
  735.           while Look <> ^Z do begin
  736.              GetClass;
  737.              GetType;
  738.              TopDecl;
  739.           end;
  740.        end.A*A*
  741.                                     - 12 -
  742.  
  743. PA A
  744.  
  745.  
  746.  
  747.  
  748.  
  749.        {--------------------------------------------------------------}
  750.  
  751.  
  752.        For the first round, just make the three procedures stubs that do
  753.        nothing _BUT_ call GetChar.
  754.  
  755.        Does this program work?  Well, it would be hard put NOT to, since
  756.        we're not really asking it to do anything.  It's been said that a
  757.        C compiler will accept virtually any input without choking.  It's
  758.        certainly true of THIS  compiler,  since in effect all it does is
  759.        to eat input characters until it finds a ^Z.
  760.  
  761.        Next, let's make  GetClass  do something worthwhile.  Declare the
  762.        global variable
  763.  
  764.  
  765.             var Class: char;
  766.  
  767.  
  768.        and change GetClass to do the following:
  769.  
  770.  
  771.        {--------------------------------------------------------------}
  772.        {  Get a Storage Class Specifier }
  773.  
  774.        Procedure GetClass;
  775.        begin
  776.           if Look in ['a', 'x', 's'] then begin
  777.              Class := Look;
  778.              GetChar;
  779.              end
  780.           else Class := 'a';
  781.        end;
  782.        {--------------------------------------------------------------}
  783.  
  784.  
  785.        Here, I've used three  single  characters  to represent the three
  786.        storage classes "auto," "extern,"  and  "static."   These are not
  787.        the only three possible classes ... there are also "register" and
  788.        "typedef," but this should  give  you the picture.  Note that the
  789.        default class is "auto."
  790.  
  791.        We  can  do  a  similar  thing  for  types.   Enter the following
  792.        procedure next:
  793.  
  794.  
  795.        {--------------------------------------------------------------}
  796.        {  Get a Type Specifier }
  797.  
  798.        procedure GetType;
  799.        begin
  800.           Typ := ' ';
  801.           if Look = 'u' then begin
  802.              Sign := 'u';A*A*
  803.                                     - 13 -
  804.  
  805. PA A
  806.  
  807.  
  808.  
  809.  
  810.  
  811.              Typ := 'i';
  812.              GetChar;
  813.              end
  814.           else Sign := 's';
  815.           if Look in ['i', 'l', 'c'] then begin
  816.              Typ := Look;
  817.              GetChar;
  818.           end;
  819.        end;
  820.        {--------------------------------------------------------------}
  821.  
  822.        Note that you must add two more global variables, Sign and Typ.
  823.  
  824.        With these two procedures in place, the compiler will process the
  825.        class and type definitions and store away their findings.  We can
  826.        now process the rest of the declaration.
  827.  
  828.        We  are by no means out of the woods yet, because there are still
  829.        many complexities just in the definition of the  type,  before we
  830.        even get to the actual data or function names.  Let's pretend for
  831.        the moment that we have passed all those gates, and that the next
  832.        thing in the  input stream is a name.  If the name is followed by
  833.        a left paren, we have a function declaration.  If not, we have at
  834.        least one data item,  and  possibly a list, each element of which
  835.        can have an initializer.
  836.  
  837.        Insert the following version of TopDecl:
  838.  
  839.  
  840.        {--------------------------------------------------------------}
  841.        { Process a Top-Level Declaration }
  842.  
  843.        procedure TopDecl;
  844.        var Name: char;
  845.        begin
  846.           Name := Getname;
  847.           if Look = '(' then
  848.              DoFunc(Name)
  849.           else
  850.              DoData(Name);
  851.        end;
  852.        {--------------------------------------------------------------}
  853.  
  854.  
  855.        (Note that, since we have already read the name, we must  pass it
  856.        along to the appropriate routine.)
  857.  
  858.        Finally, add the two procedures DoFunc and DoData:
  859.  
  860.  
  861.        {--------------------------------------------------------------}
  862.        { Process a Function Definition }
  863.  
  864.        procedure DoFunc(n: char);A*A*
  865.                                     - 14 -
  866.  
  867. PA A
  868.  
  869.  
  870.  
  871.  
  872.  
  873.        begin
  874.           Match('(');
  875.           Match(')');
  876.           Match('{');
  877.           Match('}');
  878.           if Typ = ' ' then Typ := 'i';
  879.           Writeln(Class, Sign, Typ, ' function ', n);
  880.        end;
  881.  
  882.        {--------------------------------------------------------------}
  883.        { Process a Data Declaration }
  884.  
  885.        procedure DoData(n: char);
  886.        begin
  887.           if Typ = ' ' then Expected('Type declaration');
  888.           Writeln(Class, Sign, Typ, ' data ', n);
  889.           while Look = ',' do begin
  890.              Match(',');
  891.              n := GetName;
  892.              WriteLn(Class, Sign, Typ, ' data ', n);
  893.           end;
  894.           Match(';');
  895.        end;
  896.        {--------------------------------------------------------------}
  897.  
  898.  
  899.        Since  we're  still  a long way from producing executable code, I
  900.        decided to just have these two routines tell us what they found.
  901.  
  902.        OK, give this program a try.    For data declarations, it's OK to
  903.        give a list separated by commas.  We  can't  process initializers
  904.        as yet.  We also can't process argument lists for  the functions,
  905.        but the "(){}" characters should be there.
  906.  
  907.        We're still a _VERY_ long way from having a C compiler,  but what
  908.        we have is starting to process the right kinds of inputs,  and is
  909.        recognizing both good  and  bad  inputs.    In  the  process, the
  910.        natural structure of the compiler is starting to take form.
  911.  
  912.        Can we continue this until we have something that acts  more like
  913.        a compiler. Of course we can.  Should we?  That's another matter.
  914.        I don't know about you, but I'm beginning to get dizzy, and we've
  915.        still  got  a  long  way  to  go  to  even  get  past   the  data
  916.        declarations.
  917.  
  918.        At  this  point,  I think you can see how the  structure  of  the
  919.        compiler evolves from the language  definition.    The structures
  920.        we've seen for our  two  examples, Pascal and C, are as different
  921.        as night and day.  Pascal was designed at least partly to be easy
  922.        to parse, and that's  reflected  in the compiler.  In general, in
  923.        Pascal there is more structure and we have a better idea  of what
  924.        kinds of constructs to expect at any point.  In  C,  on the other
  925.        hand,  the  program  is  essentially  a  list   of  declarations,
  926.        terminated only by the end of file.A*A*
  927.                                     - 15 -
  928.  
  929. PA A
  930.  
  931.  
  932.  
  933.  
  934.  
  935.        We  could  pursue  both  of  these structures much  farther,  but
  936.        remember that our purpose here is  not  to  build a Pascal or a C
  937.        compiler, but rather to study compilers in general.  For those of
  938.        you  who DO want to deal with Pascal or C, I hope I've given  you
  939.        enough of a start so that you can  take  it  from  here (although
  940.        you'll soon need some of the stuff we still haven't  covered yet,
  941.        such as typing and procedure calls).    For the rest of you, stay
  942.        with me through the next installment.  There, I'll be leading you
  943.        through the development of a complete compiler for TINY, a subset
  944.        of KISS.
  945.  
  946.        See you then.
  947.  
  948.  
  949.        *****************************************************************
  950.        *                                                               *
  951.        *                        COPYRIGHT NOTICE                       *
  952.        *                                                               *
  953.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  954.        *                                                               *
  955.        *****************************************************************AIAI
  956.  
  957.  
  958.  
  959.  
  960.  
  961.                                     - 16 -A*A*
  962. @